From b4d147b07ebc976adcb34498cf0b667714e61bc5 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Wed, 11 Feb 2026 19:23:25 -0800 Subject: [PATCH] Add text rendering for Stream Deck buttons with toggle demo --- cmd/decktest/main.go | 63 +++++++++++++++++++++++++++++++++--------- lib/streamdeck/text.go | 45 ++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 lib/streamdeck/text.go diff --git a/cmd/decktest/main.go b/cmd/decktest/main.go index 5329c50..2d43810 100644 --- a/cmd/decktest/main.go +++ b/cmd/decktest/main.go @@ -3,7 +3,6 @@ package main import ( "fmt" "image/color" - "math/rand/v2" "os" "os/signal" "syscall" @@ -11,6 +10,42 @@ import ( "qrun/lib/streamdeck" ) +var palette = []color.RGBA{ + {220, 50, 50, 255}, + {50, 180, 50, 255}, + {50, 100, 220, 255}, + {220, 160, 30, 255}, + {180, 50, 180, 255}, + {50, 180, 180, 255}, + {220, 120, 50, 255}, + {100, 100, 200, 255}, +} + +func labelForKey(key int) string { + row := key / streamdeck.KeyCols() + col := key % streamdeck.KeyCols() + switch row { + case 0: + return fmt.Sprintf("Ch %d\nSelect", col+1) + case 1: + return fmt.Sprintf("Ch %d\nMute", col+1) + case 2: + return fmt.Sprintf("Ch %d\nSolo", col+1) + case 3: + return fmt.Sprintf("Ch %d\nRec", col+1) + } + return fmt.Sprintf("Key %d", key) +} + +func drawKey(dev *streamdeck.Device, key int, bg color.RGBA) { + dim := color.RGBA{bg.R / 3, bg.G / 3, bg.B / 3, 255} + dev.SetKeyText(key, dim, color.White, labelForKey(key)) +} + +func drawKeyActive(dev *streamdeck.Device, key int, bg color.RGBA) { + dev.SetKeyText(key, bg, color.White, labelForKey(key)) +} + func main() { dev, err := streamdeck.Open() if err != nil { @@ -24,12 +59,12 @@ func main() { dev.SetBrightness(80) for i := 0; i < streamdeck.KeyCount(); i++ { - r := uint8(rand.IntN(256)) - g := uint8(rand.IntN(256)) - b := uint8(rand.IntN(256)) - dev.SetKeyColor(i, color.RGBA{r, g, b, 255}) + col := i % streamdeck.KeyCols() + drawKey(dev, i, palette[col]) } + active := make([]bool, streamdeck.KeyCount()) + keys := make(chan streamdeck.KeyEvent, 64) go func() { if err := dev.ReadKeys(keys); err != nil { @@ -43,15 +78,17 @@ func main() { for { select { case ev := <-keys: - if ev.Pressed { - fmt.Printf("Key %d pressed\n", ev.Key) - r := uint8(rand.IntN(256)) - g := uint8(rand.IntN(256)) - b := uint8(rand.IntN(256)) - dev.SetKeyColor(ev.Key, color.RGBA{r, g, b, 255}) - } else { - fmt.Printf("Key %d released\n", ev.Key) + if !ev.Pressed { + continue } + col := ev.Key % streamdeck.KeyCols() + active[ev.Key] = !active[ev.Key] + if active[ev.Key] { + drawKeyActive(dev, ev.Key, palette[col]) + } else { + drawKey(dev, ev.Key, palette[col]) + } + fmt.Printf("Key %d toggled %v\n", ev.Key, active[ev.Key]) case <-sig: fmt.Println() return diff --git a/lib/streamdeck/text.go b/lib/streamdeck/text.go new file mode 100644 index 0000000..0b5db67 --- /dev/null +++ b/lib/streamdeck/text.go @@ -0,0 +1,45 @@ +package streamdeck + +import ( + "image" + "image/color" + "image/draw" + "strings" + + "golang.org/x/image/font" + "golang.org/x/image/font/basicfont" + "golang.org/x/image/math/fixed" +) + +func TextImage(bg color.Color, fg color.Color, lines ...string) image.Image { + img := image.NewRGBA(image.Rect(0, 0, keySize, keySize)) + draw.Draw(img, img.Bounds(), &image.Uniform{bg}, image.Point{}, draw.Src) + + face := basicfont.Face7x13 + metrics := face.Metrics() + lineHeight := metrics.Height.Ceil() + + totalHeight := lineHeight * len(lines) + startY := (keySize-totalHeight)/2 + metrics.Ascent.Ceil() + + for i, line := range lines { + width := font.MeasureString(face, line).Ceil() + x := (keySize - width) / 2 + y := startY + i*lineHeight + + d := &font.Drawer{ + Dst: img, + Src: &image.Uniform{fg}, + Face: face, + Dot: fixed.P(x, y), + } + d.DrawString(line) + } + + return img +} + +func (d *Device) SetKeyText(key int, bg color.Color, fg color.Color, text string) error { + lines := strings.Split(text, "\n") + return d.SetKeyImage(key, TextImage(bg, fg, lines...)) +}