127 lines
3.8 KiB
Go
127 lines
3.8 KiB
Go
// grid.go — Renders the 8x4 Stream Deck key grid in the terminal.
|
||
//
|
||
// Uses lipgloss for styling: free slots are green with [index], occupied slots
|
||
// are dimmed with a short label (function name, icon name, or command prefix).
|
||
//
|
||
// ## Layout
|
||
//
|
||
// The grid mirrors the physical Stream Deck XL layout:
|
||
//
|
||
// 0 1 2 3 4 5 6 7
|
||
// 8 9 10 11 12 13 14 15
|
||
// 16 17 18 19 20 21 22 23
|
||
// 24 25 26 27 28 29 30 31
|
||
//
|
||
// ## Expanding
|
||
//
|
||
// To support non-XL models (e.g. MK.2 with 15 keys in a 5x3 grid), make
|
||
// gridCols and gridRows parameters instead of constants, and derive them
|
||
// from the device config's product_id. The device model → key count mapping
|
||
// is in internal/device/streamdeck.go.
|
||
//
|
||
// To add richer labels (e.g. showing params or the icon filename alongside
|
||
// the function name), modify keyLabel(). Keep labels under 8 chars to fit
|
||
// the cell width.
|
||
|
||
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
"git.i0t.app/lwoodard/streamdeck-go/internal/config"
|
||
"github.com/charmbracelet/lipgloss"
|
||
)
|
||
|
||
// Grid dimensions — hardcoded for Stream Deck XL (8 columns × 4 rows = 32 keys).
|
||
const (
|
||
gridCols = 8
|
||
gridRows = 4
|
||
gridKeys = gridCols * gridRows
|
||
)
|
||
|
||
// Styles for the grid cells. Color numbers are ANSI 256-color palette indices:
|
||
//
|
||
// 10 = bright green (free slots — draws the eye)
|
||
// 8 = dark gray (occupied slots — visually recedes)
|
||
// 12 = bright blue (title)
|
||
var (
|
||
freeStyle = lipgloss.NewStyle().Width(9).Height(2).Align(lipgloss.Center, lipgloss.Center).Bold(true).Foreground(lipgloss.Color("10"))
|
||
occupiedStyle = lipgloss.NewStyle().Width(9).Height(2).Align(lipgloss.Center, lipgloss.Center).Foreground(lipgloss.Color("8"))
|
||
borderStyle = lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(lipgloss.Color("8"))
|
||
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("12"))
|
||
)
|
||
|
||
// renderGrid builds a string containing the full 8x4 key grid with a legend.
|
||
// Free slots show [index] in green; occupied slots show a short label in gray.
|
||
// The grid is printed to stdout before the key-picking form.
|
||
func renderGrid(keys map[int]config.KeyConfig) string {
|
||
var b strings.Builder
|
||
|
||
b.WriteString(titleStyle.Render(" Stream Deck XL — 8×4 grid"))
|
||
b.WriteString("\n\n")
|
||
|
||
for row := 0; row < gridRows; row++ {
|
||
cells := make([]string, gridCols)
|
||
for col := 0; col < gridCols; col++ {
|
||
idx := row*gridCols + col
|
||
keyCfg, occupied := keys[idx]
|
||
|
||
if occupied {
|
||
label := keyLabel(keyCfg)
|
||
cells[col] = borderStyle.Render(occupiedStyle.Render(label))
|
||
} else {
|
||
cells[col] = borderStyle.Render(freeStyle.Render(fmt.Sprintf("[%d]", idx)))
|
||
}
|
||
}
|
||
// JoinHorizontal places cells side by side, aligned at the top.
|
||
b.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, cells...))
|
||
b.WriteString("\n")
|
||
}
|
||
|
||
// Legend explaining the color coding.
|
||
b.WriteString("\n")
|
||
b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("10")).Render("[n]"))
|
||
b.WriteString(" = free ")
|
||
b.WriteString(lipgloss.NewStyle().Foreground(lipgloss.Color("8")).Render("dim"))
|
||
b.WriteString(" = occupied\n")
|
||
|
||
return b.String()
|
||
}
|
||
|
||
// keyLabel returns a short display label for an occupied key (max 8 chars).
|
||
//
|
||
// Priority order:
|
||
// 1. Module function name (if module-based key)
|
||
// 2. Icon filename without extension (if static key)
|
||
// 3. Command prefix (fallback)
|
||
// 4. "???" (shouldn't happen with valid config)
|
||
func keyLabel(k config.KeyConfig) string {
|
||
if k.Module != "" {
|
||
label := k.Function
|
||
if len(label) > 8 {
|
||
label = label[:8]
|
||
}
|
||
return label
|
||
}
|
||
if k.Icon != "" {
|
||
label := k.Icon
|
||
// Strip file extension for brevity (e.g. "firefox.png" → "firefox").
|
||
if dot := strings.LastIndex(label, "."); dot > 0 {
|
||
label = label[:dot]
|
||
}
|
||
if len(label) > 8 {
|
||
label = label[:8]
|
||
}
|
||
return label
|
||
}
|
||
if k.Command != "" {
|
||
label := k.Command
|
||
if len(label) > 8 {
|
||
label = label[:8]
|
||
}
|
||
return label
|
||
}
|
||
return "???"
|
||
}
|