Files

127 lines
3.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 "???"
}