1
0
streamdeck-lets-go/render.go
Maksim Totmin b075b72eea fix: keep active page on config save, add Cyrillic preview support, fix grid sizing
- daemon.go: save/restore active page on config reload instead of
  always activating default_page (fixes page switch on save)
- render.go: replace basicfont.Face7x13 with parseDisplayFace() for
  unicode/Cyrillic support in edit key preview
- web.go: add SSE endpoint (GET /api/events) for real-time page_changed
  events and POST /api/activate-page API
- app.js: persist active page via localStorage, listen for SSE
  page_changed events
- styles.css, index.html: fix grid sizing (width 100%, minmax(0, 1fr),
  aspect-ratio on key buttons, max-width 525px)
2026-06-17 10:48:31 +07:00

151 lines
3.5 KiB
Go

package main
import (
"image"
"image/color"
"image/draw"
"github.com/disintegration/gift"
"golang.org/x/image/font"
"golang.org/x/image/font/basicfont"
"golang.org/x/image/math/fixed"
"streamdeck-lets-go/internal/config"
)
func RenderKeyToImage(k *config.KeyConfig, keySize int) image.Image {
if k == nil {
return blankImage(keySize, color.RGBA{0, 0, 0, 255})
}
if k.Icon == "" && k.Label == "" {
if k.Background != "" {
if bg, err := parseHexColor(k.Background); err == nil {
return blankImage(keySize, bg)
}
}
return blankImage(keySize, color.RGBA{64, 64, 64, 255})
}
faScale := 0.55
if k.IconScale != nil {
faScale = *k.IconScale
}
if k.Icon != "" {
img, err := loadImage(k.Icon, keySize, faScale)
if err != nil {
img = blankImage(keySize, color.RGBA{64, 64, 64, 255})
}
if k.Background != "" {
if bg, err := parseHexColor(k.Background); err == nil {
img = applyBackground(img, bg)
}
}
if k.Label != "" {
fontSize := 10.0
if k.FontSize != nil {
fontSize = *k.FontSize
}
return composeImageWithLabel(img, k.Label, keySize, fontSize)
}
g := gift.New(gift.Resize(keySize, keySize, gift.LanczosResampling))
rgba := image.NewRGBA(image.Rect(0, 0, keySize, keySize))
g.Draw(rgba, img)
return rgba
}
if k.Background != "" {
if bg, err := parseHexColor(k.Background); err == nil {
if k.Label != "" {
fontSize := 12.0
if k.FontSize != nil {
fontSize = *k.FontSize
}
return renderUnicodeText(k.Label, fontSize, keySize, bg, color.White)
}
return blankImage(keySize, bg)
}
}
fontSize := 12.0
if k.FontSize != nil {
fontSize = *k.FontSize
}
return renderTextImage(k.Label, keySize, fontSize)
}
func blankImage(size int, c color.Color) image.Image {
img := image.NewRGBA(image.Rect(0, 0, size, size))
draw.Draw(img, img.Bounds(), &image.Uniform{c}, image.Point{}, draw.Src)
return img
}
func composeImageWithLabel(src image.Image, text string, keySize int, fontSize float64) image.Image {
g := gift.New(gift.Resize(keySize, keySize, gift.LanczosResampling))
rgba := image.NewRGBA(image.Rect(0, 0, keySize, keySize))
g.Draw(rgba, src)
barHeight := 20
if keySize < 72 {
barHeight = 18
}
barRect := image.Rect(0, keySize-barHeight, keySize, keySize)
draw.Draw(rgba, barRect, &image.Uniform{color.RGBA{0, 0, 0, 180}}, image.Point{}, draw.Over)
if text != "" {
face, err := parseDisplayFace(fontSize)
if err != nil {
face = basicfont.Face7x13
} else {
defer face.Close()
}
textW := font.MeasureString(face, text).Ceil()
posX := (keySize - textW) / 2
if posX < 2 {
posX = 2
}
posY := keySize - barHeight/2 + face.Metrics().Height.Ceil()/2
if posY >= keySize {
posY = keySize - 2
}
d := &font.Drawer{
Dst: rgba,
Src: image.NewUniform(color.White),
Face: face,
Dot: fixed.P(posX, posY),
}
d.DrawString(text)
}
return rgba
}
func renderTextImage(text string, keySize int, fontSize float64) image.Image {
rgba := blankImage(keySize, color.RGBA{0, 0, 0, 255}).(*image.RGBA)
if text != "" {
face, err := parseDisplayFace(fontSize)
if err != nil {
face = basicfont.Face7x13
} else {
defer face.Close()
}
textW := font.MeasureString(face, text).Ceil()
posX := (keySize - textW) / 2
if posX < 2 {
posX = 2
}
posY := keySize/2 + face.Metrics().Height.Ceil()/2
d := &font.Drawer{
Dst: rgba,
Src: image.NewUniform(color.White),
Face: face,
Dot: fixed.P(posX, posY),
}
d.DrawString(text)
}
return rgba
}