v0.2.0 16 colors, default colors, /dev/tty, bracketed paste, themed form controls

This commit is contained in:
lixen committed 2026-10-09 14:15:46 -04:00
1 parent 070131c7fd
commit 2b5d127e84
32 files changed
+1447 -424

No files matched your search

+89 -54
View File
@@ -2,65 +2,100 @@ package tui
import (
"github.com/lixenwraith/color"
"github.com/lixenwraith/terminal"
)
// Theme defines semantic colors for TUI components
// Theme styles the widgets that take one. Each role is a Style whose Attr may
// carry color bits (a palette index, the terminal's own color), so one theme
// per color tier, mono included, draws every widget alike. A role with a zero
// Bg and no Bg bits draws on whatever is beneath it.
type Theme struct {
Bg color.RGB
Fg color.RGB
FocusBg color.RGB
CursorBg color.RGB
Selected color.RGB
Unselected color.RGB
Partial color.RGB
Error color.RGB
Warning color.RGB
Border color.RGB
HeaderBg color.RGB
HeaderFg color.RGB
StatusFg color.RGB
HintFg color.RGB
InputBg color.RGB
DirFg color.RGB
FileFg color.RGB
SymbolFg color.RGB
SyntaxComment color.RGB
SyntaxString color.RGB
SyntaxKeyword color.RGB
SyntaxType color.RGB
SyntaxNumber color.RGB
SyntaxSymbol color.RGB
Text Style // values and body text; its Bg is the panel's
Muted Style // labels, hints, placeholders and defaults, help lines
Accent Style // marks: focus, the chosen option, a required field
Selected Style // the cursor row of a list
Input Style // the background of the field being edited
Cursor Style // the text cursor
Error Style // errors
Border Style // boxes and rules
Glyphs Glyphs
}
// DefaultTheme provides reasonable defaults
// Glyphs are the marks widgets draw; a selection always changes a glyph, so
// it reads without color. A text console's font takes GlyphsCP437, a
// terminal outside UTF-8 GlyphsASCII.
type Glyphs struct {
Focus rune // beside the focused control
Pointer rune // the cursor row of a list
Folded rune // a closed group
Unfolded rune // an open group
On, Off rune // a toggle
Required rune // after a required field's label
}
var (
GlyphsUnicode = Glyphs{Focus: '▌', Pointer: '▸', Folded: '▸', Unfolded: '▾', On: '●', Off: '○', Required: '*'}
GlyphsCP437 = Glyphs{Focus: '▌', Pointer: '►', Folded: '►', Unfolded: '▼', On: '•', Off: '○', Required: '*'}
GlyphsASCII = Glyphs{Focus: '>', Pointer: '>', Folded: '+', Unfolded: '-', On: '*', Off: '-', Required: '*'}
)
// DefaultTheme is a dark theme for true color and 256-color terminals
var DefaultTheme = Theme{
Bg: color.RGB{R: 20, G: 20, B: 30},
Fg: color.RGB{R: 200, G: 200, B: 200},
FocusBg: color.RGB{R: 30, G: 35, B: 45},
CursorBg: color.RGB{R: 50, G: 50, B: 70},
Selected: color.RGB{R: 80, G: 200, B: 80},
Unselected: color.RGB{R: 100, G: 100, B: 100},
Partial: color.RGB{R: 80, G: 160, B: 220},
Error: color.RGB{R: 255, G: 80, B: 80},
Warning: color.RGB{R: 255, G: 80, B: 80},
Border: color.RGB{R: 60, G: 80, B: 100},
HeaderBg: color.RGB{R: 40, G: 60, B: 90},
HeaderFg: color.RGB{R: 255, G: 255, B: 255},
StatusFg: color.RGB{R: 140, G: 140, B: 140},
HintFg: color.RGB{R: 100, G: 180, B: 200},
InputBg: color.RGB{R: 30, G: 30, B: 50},
DirFg: color.RGB{R: 130, G: 170, B: 220},
FileFg: color.RGB{R: 200, G: 200, B: 200},
SymbolFg: color.RGB{R: 180, G: 220, B: 220},
SyntaxComment: color.RGB{R: 100, G: 110, B: 120},
SyntaxString: color.RGB{R: 180, G: 220, B: 140},
SyntaxKeyword: color.RGB{R: 180, G: 140, B: 220},
SyntaxType: color.RGB{R: 80, G: 200, B: 200},
SyntaxNumber: color.RGB{R: 220, G: 180, B: 120},
SyntaxSymbol: color.RGB{R: 220, G: 180, B: 80},
Text: Style{Fg: color.RGB{R: 200, G: 200, B: 200}, Bg: color.RGB{R: 20, G: 20, B: 30}},
Muted: Style{Fg: color.RGB{R: 120, G: 125, B: 140}},
Accent: Style{Fg: color.RGB{R: 80, G: 160, B: 220}, Attr: terminal.AttrBold},
Selected: Style{Bg: color.RGB{R: 40, G: 45, B: 65}},
Input: Style{Bg: color.RGB{R: 30, G: 30, B: 50}},
Cursor: Style{Fg: color.RGB{R: 20, G: 20, B: 30}, Bg: color.RGB{R: 200, G: 200, B: 200}},
Error: Style{Fg: color.RGB{R: 255, G: 80, B: 80}},
Border: Style{Fg: color.RGB{R: 60, G: 80, B: 100}},
Glyphs: GlyphsUnicode,
}
// MonoTheme draws with attributes alone in the terminal's own colors: the
// theme for ColorModeNone, and one that reads on any terminal
var MonoTheme = Theme{
Text: Style{Attr: own | terminal.AttrBgDefault},
Muted: Style{Attr: own | terminal.AttrDim},
Accent: Style{Attr: own | terminal.AttrBold},
Selected: Style{Attr: own | terminal.AttrReverse},
Input: Style{Attr: own | terminal.AttrUnderline},
Cursor: Style{Attr: own | terminal.AttrReverse},
Error: Style{Attr: own | terminal.AttrBold | terminal.AttrUnderline},
Border: Style{Attr: own},
Glyphs: GlyphsUnicode,
}
// Theme16 uses the ANSI 16 colors alone, in the terminal's own shades, on
// its own background: the theme for ColorMode16, a text console's
var Theme16 = Theme{
Text: Style{Attr: own | terminal.AttrBgDefault},
Muted: ansi(color.ANSIBrightBlack, 0),
Accent: ansi(color.ANSICyan, terminal.AttrBold),
Selected: Style{Attr: own | terminal.AttrReverse},
Input: Style{Attr: own | terminal.AttrUnderline},
Cursor: Style{Attr: own | terminal.AttrReverse},
Error: ansi(color.ANSIRed, terminal.AttrBold),
Border: Style{Attr: own},
Glyphs: GlyphsUnicode,
}
// ThemeFor returns the theme for a color mode: DefaultTheme with 256 colors
// or more, Theme16 with 16, MonoTheme with none
func ThemeFor(mode terminal.ColorMode) Theme {
switch mode {
case terminal.ColorMode16:
return Theme16
case terminal.ColorModeNone:
return MonoTheme
}
return DefaultTheme
}
// own is the terminal's own foreground
const own = terminal.AttrFgDefault
// ansi is a foreground of the ANSI 16, in the terminal's shade of it
func ansi(i uint8, attr terminal.Attr) Style {
return Style{Fg: color.RGB{R: i}, Attr: terminal.AttrFg256 | attr}
}