adding init program

This commit is contained in:
lwoodard
2026-04-13 12:45:41 -06:00
parent 639a08a808
commit 91b7028382
12 changed files with 1035 additions and 8 deletions

126
cmd/streamdeck-init/grid.go Normal file
View File

@@ -0,0 +1,126 @@
// 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"
"github.com/WoodardDigital/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 "???"
}