diff --git a/cmd/decktest/main.go b/cmd/decktest/main.go index 2d43810..523442e 100644 --- a/cmd/decktest/main.go +++ b/cmd/decktest/main.go @@ -10,6 +10,13 @@ import ( "qrun/lib/streamdeck" ) +var keyLabels = []string{ + "1", "2", "3", "4", "5", "6", "7", "8", + "A", "B", "C", "D", "E", "F", "G", "H", + "I", "J", "K", "L", "M", "N", "O", "P", + "Q", "R", "S", "T", "U", "V", "W", "X", +} + var palette = []color.RGBA{ {220, 50, 50, 255}, {50, 180, 50, 255}, @@ -21,29 +28,13 @@ var palette = []color.RGBA{ {100, 100, 200, 255}, } -func labelForKey(key int) string { - row := key / streamdeck.KeyCols() +func drawKey(dev *streamdeck.Device, key int, active bool) { 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) + bg := palette[col] + if !active { + bg = color.RGBA{bg.R / 3, bg.G / 3, bg.B / 3, 255} } - 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)) + dev.SetKeyText(key, bg, color.White, keyLabels[key]) } func main() { @@ -59,8 +50,7 @@ func main() { dev.SetBrightness(80) for i := 0; i < streamdeck.KeyCount(); i++ { - col := i % streamdeck.KeyCols() - drawKey(dev, i, palette[col]) + drawKey(dev, i, false) } active := make([]bool, streamdeck.KeyCount()) @@ -81,14 +71,9 @@ func main() { 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]) + drawKey(dev, ev.Key, active[ev.Key]) + fmt.Printf("Key %s toggled %v\n", keyLabels[ev.Key], active[ev.Key]) case <-sig: fmt.Println() return diff --git a/go.mod b/go.mod index faa1f9f..b759def 100644 --- a/go.mod +++ b/go.mod @@ -8,4 +8,7 @@ require ( rafaelmartins.com/p/usbhid v0.0.0-20250616003425-c818f1cb579e ) -require github.com/ebitengine/purego v0.8.4 // indirect +require ( + github.com/ebitengine/purego v0.8.4 // indirect + golang.org/x/text v0.28.0 // indirect +) diff --git a/go.sum b/go.sum index b7f3ea7..4d73f21 100644 --- a/go.sum +++ b/go.sum @@ -4,5 +4,7 @@ gitlab.com/gomidi/midi/v2 v2.3.22 h1:4Q20o6q4BDo7i/KGvnwASeytOlrPI7MwsS7F2hA7fOM gitlab.com/gomidi/midi/v2 v2.3.22/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw= golang.org/x/image v0.30.0 h1:jD5RhkmVAnjqaCUXfbGBrn3lpxbknfN9w2UhHHU+5B4= golang.org/x/image v0.30.0/go.mod h1:SAEUTxCCMWSrJcCy/4HwavEsfZZJlYxeHLc6tTiAe/c= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= rafaelmartins.com/p/usbhid v0.0.0-20250616003425-c818f1cb579e h1:Xlg01Rbs6PVG1yOvNEmMjI+edsmua23REsPO+tyhOyU= rafaelmartins.com/p/usbhid v0.0.0-20250616003425-c818f1cb579e/go.mod h1:focKssvBxJwZE6GrEZipSBZsUwsFkcc0ECSq/In1Kww= diff --git a/lib/streamdeck/fonts/AtkinsonHyperlegible-Bold.ttf b/lib/streamdeck/fonts/AtkinsonHyperlegible-Bold.ttf new file mode 100644 index 0000000..ca3f613 Binary files /dev/null and b/lib/streamdeck/fonts/AtkinsonHyperlegible-Bold.ttf differ diff --git a/lib/streamdeck/fonts/AtkinsonHyperlegible-Regular.ttf b/lib/streamdeck/fonts/AtkinsonHyperlegible-Regular.ttf new file mode 100644 index 0000000..b3cc420 Binary files /dev/null and b/lib/streamdeck/fonts/AtkinsonHyperlegible-Regular.ttf differ diff --git a/lib/streamdeck/fonts/AtkinsonHyperlegibleMono-Bold.ttf b/lib/streamdeck/fonts/AtkinsonHyperlegibleMono-Bold.ttf new file mode 100644 index 0000000..0cd144b Binary files /dev/null and b/lib/streamdeck/fonts/AtkinsonHyperlegibleMono-Bold.ttf differ diff --git a/lib/streamdeck/fonts/AtkinsonHyperlegibleMono-Medium.ttf b/lib/streamdeck/fonts/AtkinsonHyperlegibleMono-Medium.ttf new file mode 100644 index 0000000..9fe029e Binary files /dev/null and b/lib/streamdeck/fonts/AtkinsonHyperlegibleMono-Medium.ttf differ diff --git a/lib/streamdeck/fonts/AtkinsonHyperlegibleMono-Regular.ttf b/lib/streamdeck/fonts/AtkinsonHyperlegibleMono-Regular.ttf new file mode 100644 index 0000000..a55ce8b Binary files /dev/null and b/lib/streamdeck/fonts/AtkinsonHyperlegibleMono-Regular.ttf differ diff --git a/lib/streamdeck/text.go b/lib/streamdeck/text.go index 0b5db67..b1424a7 100644 --- a/lib/streamdeck/text.go +++ b/lib/streamdeck/text.go @@ -1,41 +1,94 @@ package streamdeck import ( + "embed" "image" "image/color" "image/draw" + "log" "strings" "golang.org/x/image/font" - "golang.org/x/image/font/basicfont" + "golang.org/x/image/font/opentype" "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) +//go:embed fonts/*.ttf +var fontFS embed.FS - face := basicfont.Face7x13 +var ( + MonoRegular font.Face + MonoMedium font.Face + MonoBold font.Face + Regular font.Face + Bold font.Face +) + +func init() { + MonoRegular = loadFace("fonts/AtkinsonHyperlegibleMono-Regular.ttf", 72) + MonoMedium = loadFace("fonts/AtkinsonHyperlegibleMono-Medium.ttf", 72) + MonoBold = loadFace("fonts/AtkinsonHyperlegibleMono-Bold.ttf", 72) + Regular = loadFace("fonts/AtkinsonHyperlegible-Regular.ttf", 16) + Bold = loadFace("fonts/AtkinsonHyperlegible-Bold.ttf", 16) +} + +func loadFace(path string, size float64) font.Face { + data, err := fontFS.ReadFile(path) + if err != nil { + log.Fatalf("streamdeck: read font %s: %v", path, err) + } + f, err := opentype.Parse(data) + if err != nil { + log.Fatalf("streamdeck: parse font %s: %v", path, err) + } + face, err := opentype.NewFace(f, &opentype.FaceOptions{ + Size: size, + DPI: 72, + Hinting: font.HintingFull, + }) + if err != nil { + log.Fatalf("streamdeck: create face %s: %v", path, err) + } + return face +} + +func DrawText(img *image.RGBA, face font.Face, fg color.Color, lines ...string) { metrics := face.Metrics() lineHeight := metrics.Height.Ceil() + bounds := img.Bounds() + w := bounds.Dx() + h := bounds.Dy() totalHeight := lineHeight * len(lines) - startY := (keySize-totalHeight)/2 + metrics.Ascent.Ceil() + startY := (h-totalHeight)/2 + metrics.Ascent.Ceil() for i, line := range lines { width := font.MeasureString(face, line).Ceil() - x := (keySize - width) / 2 + x := (w - width) / 2 y := startY + i*lineHeight d := &font.Drawer{ Dst: img, Src: &image.Uniform{fg}, Face: face, - Dot: fixed.P(x, y), + Dot: fixed.P(bounds.Min.X+x, bounds.Min.Y+y), } d.DrawString(line) } +} +func TextImage(bg color.Color, fg color.Color, lines ...string) image.Image { + return TextImageWithFace(MonoMedium, bg, fg, lines...) +} + +func BoldTextImage(bg color.Color, fg color.Color, lines ...string) image.Image { + return TextImageWithFace(MonoBold, bg, fg, lines...) +} + +func TextImageWithFace(face font.Face, 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) + DrawText(img, face, fg, lines...) return img } @@ -43,3 +96,8 @@ func (d *Device) SetKeyText(key int, bg color.Color, fg color.Color, text string lines := strings.Split(text, "\n") return d.SetKeyImage(key, TextImage(bg, fg, lines...)) } + +func (d *Device) SetKeyBoldText(key int, bg color.Color, fg color.Color, text string) error { + lines := strings.Split(text, "\n") + return d.SetKeyImage(key, BoldTextImage(bg, fg, lines...)) +}