v0.1.1 true color extracted to a standalone package, lut256 lazy build
This commit is contained in:
+1
-1
@@ -200,7 +200,7 @@ Pure logic, no rendering — usable independently:
|
||||
|
||||
- Width calculations count runes, not terminal columns; East Asian wide
|
||||
characters and combining marks are not width-aware.
|
||||
- Zero-value `terminal.RGB` in style fields generally means "inherit"
|
||||
- Zero-value `color.RGB` in style fields generally means "inherit"
|
||||
(widget default or row background) — check specific widget docs.
|
||||
- Mouse hit testing: `TabBar` returns `[]TabBounds`; other widgets require
|
||||
application-side geometry from the regions used.
|
||||
|
||||
+15
-13
@@ -1,6 +1,7 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
@@ -36,7 +37,7 @@ const (
|
||||
// --- Box Rendering ---
|
||||
|
||||
// Box draws border around region edge
|
||||
func (r Region) Box(line LineType, fg terminal.RGB) {
|
||||
func (r Region) Box(line LineType, fg color.RGB) {
|
||||
if r.W < 2 || r.H < 2 {
|
||||
return
|
||||
}
|
||||
@@ -45,7 +46,7 @@ func (r Region) Box(line LineType, fg terminal.RGB) {
|
||||
}
|
||||
|
||||
chars := boxChars[line]
|
||||
bg := terminal.RGB{} // Transparent (use existing bg)
|
||||
bg := color.RGB{} // Transparent (use existing bg)
|
||||
|
||||
// Corners
|
||||
r.Cell(0, 0, chars[boxTL], fg, bg, terminal.AttrNone)
|
||||
@@ -67,7 +68,7 @@ func (r Region) Box(line LineType, fg terminal.RGB) {
|
||||
}
|
||||
|
||||
// BoxFilled draws border and fills interior with background
|
||||
func (r Region) BoxFilled(line LineType, fg, bg terminal.RGB) {
|
||||
func (r Region) BoxFilled(line LineType, fg, bg color.RGB) {
|
||||
// Fill interior first
|
||||
for y := 1; y < r.H-1; y++ {
|
||||
for x := 1; x < r.W-1; x++ {
|
||||
@@ -81,7 +82,7 @@ func (r Region) BoxFilled(line LineType, fg, bg terminal.RGB) {
|
||||
// --- Line rendering ---
|
||||
|
||||
// HLine draws horizontal line across region width at row y
|
||||
func (r Region) HLine(y int, line LineType, fg terminal.RGB) {
|
||||
func (r Region) HLine(y int, line LineType, fg color.RGB) {
|
||||
if y < 0 || y >= r.H {
|
||||
return
|
||||
}
|
||||
@@ -90,12 +91,12 @@ func (r Region) HLine(y int, line LineType, fg terminal.RGB) {
|
||||
}
|
||||
ch := boxChars[line][boxH]
|
||||
for x := 0; x < r.W; x++ {
|
||||
r.Cell(x, y, ch, fg, terminal.RGB{}, terminal.AttrNone)
|
||||
r.Cell(x, y, ch, fg, color.RGB{}, terminal.AttrNone)
|
||||
}
|
||||
}
|
||||
|
||||
// VLine draws vertical line across region height at column x
|
||||
func (r Region) VLine(x int, line LineType, fg terminal.RGB) {
|
||||
func (r Region) VLine(x int, line LineType, fg color.RGB) {
|
||||
if x < 0 || x >= r.W {
|
||||
return
|
||||
}
|
||||
@@ -104,12 +105,12 @@ func (r Region) VLine(x int, line LineType, fg terminal.RGB) {
|
||||
}
|
||||
ch := boxChars[line][boxV]
|
||||
for y := 0; y < r.H; y++ {
|
||||
r.Cell(x, y, ch, fg, terminal.RGB{}, terminal.AttrNone)
|
||||
r.Cell(x, y, ch, fg, color.RGB{}, terminal.AttrNone)
|
||||
}
|
||||
}
|
||||
|
||||
// Divider draws horizontal line with optional centered label
|
||||
func (r Region) Divider(y int, label string, line LineType, fg terminal.RGB) {
|
||||
func (r Region) Divider(y int, label string, line LineType, fg color.RGB) {
|
||||
if y < 0 || y >= r.H {
|
||||
return
|
||||
}
|
||||
@@ -121,7 +122,7 @@ func (r Region) Divider(y int, label string, line LineType, fg terminal.RGB) {
|
||||
|
||||
// Fill with horizontal line
|
||||
for x := 0; x < r.W; x++ {
|
||||
r.Cell(x, y, hChar, fg, terminal.RGB{}, terminal.AttrNone)
|
||||
r.Cell(x, y, hChar, fg, color.RGB{}, terminal.AttrNone)
|
||||
}
|
||||
|
||||
// Center label if provided
|
||||
@@ -134,7 +135,7 @@ func (r Region) Divider(y int, label string, line LineType, fg terminal.RGB) {
|
||||
}
|
||||
startX := (r.W - textLen) / 2
|
||||
for i, ch := range text {
|
||||
r.Cell(startX+i, y, ch, fg, terminal.RGB{}, terminal.AttrBold)
|
||||
r.Cell(startX+i, y, ch, fg, color.RGB{}, terminal.AttrBold)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +143,7 @@ func (r Region) Divider(y int, label string, line LineType, fg terminal.RGB) {
|
||||
// --- Card rendering ---
|
||||
|
||||
// Card draws titled border and returns inner content region
|
||||
func (r Region) Card(title string, line LineType, fg terminal.RGB) Region {
|
||||
func (r Region) Card(title string, line LineType, fg color.RGB) Region {
|
||||
r.Box(line, fg)
|
||||
|
||||
if title != "" && r.W > 4 {
|
||||
@@ -152,8 +153,9 @@ func (r Region) Card(title string, line LineType, fg terminal.RGB) Region {
|
||||
displayTitle = Truncate(displayTitle, maxTitleLen)
|
||||
}
|
||||
titleX := (r.W - RuneLen(displayTitle) - 2) / 2
|
||||
r.Text(titleX, 0, " "+displayTitle+" ", fg, terminal.RGB{}, terminal.AttrBold)
|
||||
r.Text(titleX, 0, " "+displayTitle+" ", fg, color.RGB{}, terminal.AttrBold)
|
||||
}
|
||||
|
||||
return r.Inset(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-15
@@ -1,6 +1,9 @@
|
||||
package tui
|
||||
|
||||
import "github.com/lixenwraith/terminal"
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// Button defines a single button in a button bar
|
||||
type Button struct {
|
||||
@@ -18,27 +21,27 @@ type ButtonBarOpts struct {
|
||||
|
||||
// ButtonStyle defines button bar colors
|
||||
type ButtonStyle struct {
|
||||
LabelFg terminal.RGB
|
||||
LabelBg terminal.RGB
|
||||
KeyFg terminal.RGB
|
||||
FocusFg terminal.RGB
|
||||
FocusBg terminal.RGB
|
||||
Bg terminal.RGB
|
||||
LabelFg color.RGB
|
||||
LabelBg color.RGB
|
||||
KeyFg color.RGB
|
||||
FocusFg color.RGB
|
||||
FocusBg color.RGB
|
||||
Bg color.RGB
|
||||
}
|
||||
|
||||
// DefaultButtonStyle returns default button colors with dark background
|
||||
func DefaultButtonStyle() ButtonStyle {
|
||||
return DefaultButtonStyleFrom(terminal.RGB{R: 25, G: 25, B: 35})
|
||||
return DefaultButtonStyleFrom(color.RGB{R: 25, G: 25, B: 35})
|
||||
}
|
||||
|
||||
// DefaultButtonStyleFrom returns default button colors using the given background
|
||||
func DefaultButtonStyleFrom(bg terminal.RGB) ButtonStyle {
|
||||
func DefaultButtonStyleFrom(bg color.RGB) ButtonStyle {
|
||||
return ButtonStyle{
|
||||
LabelFg: terminal.RGB{R: 200, G: 200, B: 200},
|
||||
LabelBg: terminal.RGB{R: 50, G: 50, B: 60},
|
||||
KeyFg: terminal.RGB{R: 130, G: 130, B: 150},
|
||||
FocusFg: terminal.RGB{R: 255, G: 255, B: 255},
|
||||
FocusBg: terminal.RGB{R: 60, G: 80, B: 120},
|
||||
LabelFg: color.RGB{R: 200, G: 200, B: 200},
|
||||
LabelBg: color.RGB{R: 50, G: 50, B: 60},
|
||||
KeyFg: color.RGB{R: 130, G: 130, B: 150},
|
||||
FocusFg: color.RGB{R: 255, G: 255, B: 255},
|
||||
FocusBg: color.RGB{R: 60, G: 80, B: 120},
|
||||
Bg: bg,
|
||||
}
|
||||
}
|
||||
@@ -124,4 +127,5 @@ func (r Region) ButtonBar(y int, buttons []Button, opts ButtonBarOpts) {
|
||||
x += gap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-5
@@ -1,6 +1,7 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
@@ -15,7 +16,7 @@ const (
|
||||
)
|
||||
|
||||
// Checkbox draws a checkbox indicator
|
||||
func (r Region) Checkbox(x, y int, state CheckState, fg terminal.RGB) {
|
||||
func (r Region) Checkbox(x, y int, state CheckState, fg color.RGB) {
|
||||
if x < 0 || x+2 >= r.W || y < 0 || y >= r.H {
|
||||
return
|
||||
}
|
||||
@@ -30,7 +31,8 @@ func (r Region) Checkbox(x, y int, state CheckState, fg terminal.RGB) {
|
||||
case CheckPlus:
|
||||
ch = '+'
|
||||
}
|
||||
r.Cell(x, y, '[', fg, terminal.RGB{}, terminal.AttrNone)
|
||||
r.Cell(x+1, y, ch, fg, terminal.RGB{}, terminal.AttrNone)
|
||||
r.Cell(x+2, y, ']', fg, terminal.RGB{}, terminal.AttrNone)
|
||||
}
|
||||
r.Cell(x, y, '[', fg, color.RGB{}, terminal.AttrNone)
|
||||
r.Cell(x+1, y, ch, fg, color.RGB{}, terminal.AttrNone)
|
||||
r.Cell(x+2, y, ']', fg, color.RGB{}, terminal.AttrNone)
|
||||
}
|
||||
|
||||
|
||||
+26
-22
@@ -1,6 +1,9 @@
|
||||
package tui
|
||||
|
||||
import "github.com/lixenwraith/terminal"
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// ConfirmResult represents dialog outcome
|
||||
type ConfirmResult uint8
|
||||
@@ -98,31 +101,31 @@ type ConfirmOpts struct {
|
||||
|
||||
// ConfirmStyle defines dialog colors
|
||||
type ConfirmStyle struct {
|
||||
BorderFg terminal.RGB
|
||||
TitleFg terminal.RGB
|
||||
MessageFg terminal.RGB
|
||||
Bg terminal.RGB
|
||||
ButtonFg terminal.RGB
|
||||
ButtonBg terminal.RGB
|
||||
ButtonFocusFg terminal.RGB
|
||||
ButtonFocusBg terminal.RGB
|
||||
DestructiveFg terminal.RGB
|
||||
DestructiveBg terminal.RGB
|
||||
BorderFg color.RGB
|
||||
TitleFg color.RGB
|
||||
MessageFg color.RGB
|
||||
Bg color.RGB
|
||||
ButtonFg color.RGB
|
||||
ButtonBg color.RGB
|
||||
ButtonFocusFg color.RGB
|
||||
ButtonFocusBg color.RGB
|
||||
DestructiveFg color.RGB
|
||||
DestructiveBg color.RGB
|
||||
}
|
||||
|
||||
// DefaultConfirmStyle returns default dialog colors
|
||||
func DefaultConfirmStyle() ConfirmStyle {
|
||||
return ConfirmStyle{
|
||||
BorderFg: terminal.RGB{R: 100, G: 100, B: 120},
|
||||
TitleFg: terminal.RGB{R: 255, G: 255, B: 255},
|
||||
MessageFg: terminal.RGB{R: 200, G: 200, B: 200},
|
||||
Bg: terminal.RGB{R: 30, G: 30, B: 40},
|
||||
ButtonFg: terminal.RGB{R: 180, G: 180, B: 180},
|
||||
ButtonBg: terminal.RGB{R: 50, G: 50, B: 60},
|
||||
ButtonFocusFg: terminal.RGB{R: 255, G: 255, B: 255},
|
||||
ButtonFocusBg: terminal.RGB{R: 60, G: 80, B: 120},
|
||||
DestructiveFg: terminal.RGB{R: 255, G: 255, B: 255},
|
||||
DestructiveBg: terminal.RGB{R: 180, G: 60, B: 60},
|
||||
BorderFg: color.RGB{R: 100, G: 100, B: 120},
|
||||
TitleFg: color.RGB{R: 255, G: 255, B: 255},
|
||||
MessageFg: color.RGB{R: 200, G: 200, B: 200},
|
||||
Bg: color.RGB{R: 30, G: 30, B: 40},
|
||||
ButtonFg: color.RGB{R: 180, G: 180, B: 180},
|
||||
ButtonBg: color.RGB{R: 50, G: 50, B: 60},
|
||||
ButtonFocusFg: color.RGB{R: 255, G: 255, B: 255},
|
||||
ButtonFocusBg: color.RGB{R: 60, G: 80, B: 120},
|
||||
DestructiveFg: color.RGB{R: 255, G: 255, B: 255},
|
||||
DestructiveBg: color.RGB{R: 180, G: 60, B: 60},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,4 +324,5 @@ func (r Region) AlertDialog(opts AlertOpts) Region {
|
||||
}
|
||||
|
||||
return content.Sub(0, 0, content.W, buttonY-1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-17
@@ -1,6 +1,7 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
@@ -16,27 +17,27 @@ type EditorOpts struct {
|
||||
|
||||
// EditorStyle defines editor colors
|
||||
type EditorStyle struct {
|
||||
TextFg terminal.RGB
|
||||
TextBg terminal.RGB
|
||||
CursorFg terminal.RGB
|
||||
CursorBg terminal.RGB
|
||||
LineNumFg terminal.RGB
|
||||
LineNumBg terminal.RGB
|
||||
CurrentLineBg terminal.RGB
|
||||
BorderFg terminal.RGB
|
||||
TextFg color.RGB
|
||||
TextBg color.RGB
|
||||
CursorFg color.RGB
|
||||
CursorBg color.RGB
|
||||
LineNumFg color.RGB
|
||||
LineNumBg color.RGB
|
||||
CurrentLineBg color.RGB
|
||||
BorderFg color.RGB
|
||||
}
|
||||
|
||||
// DefaultEditorStyle returns default colors
|
||||
func DefaultEditorStyle() EditorStyle {
|
||||
return EditorStyle{
|
||||
TextFg: terminal.RGB{R: 220, G: 220, B: 220},
|
||||
TextBg: terminal.RGB{R: 25, G: 25, B: 35},
|
||||
CursorFg: terminal.RGB{R: 0, G: 0, B: 0},
|
||||
CursorBg: terminal.RGB{R: 200, G: 200, B: 200},
|
||||
LineNumFg: terminal.RGB{R: 100, G: 100, B: 120},
|
||||
LineNumBg: terminal.RGB{R: 30, G: 30, B: 40},
|
||||
CurrentLineBg: terminal.RGB{R: 35, G: 35, B: 50},
|
||||
BorderFg: terminal.RGB{R: 80, G: 80, B: 100},
|
||||
TextFg: color.RGB{R: 220, G: 220, B: 220},
|
||||
TextBg: color.RGB{R: 25, G: 25, B: 35},
|
||||
CursorFg: color.RGB{R: 0, G: 0, B: 0},
|
||||
CursorBg: color.RGB{R: 200, G: 200, B: 200},
|
||||
LineNumFg: color.RGB{R: 100, G: 100, B: 120},
|
||||
LineNumBg: color.RGB{R: 30, G: 30, B: 40},
|
||||
CurrentLineBg: color.RGB{R: 35, G: 35, B: 50},
|
||||
BorderFg: color.RGB{R: 80, G: 80, B: 100},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,4 +195,5 @@ func formatLineNum(num, width int) string {
|
||||
s = " " + s
|
||||
}
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-16
@@ -1,6 +1,9 @@
|
||||
package tui
|
||||
|
||||
import "github.com/lixenwraith/terminal"
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// FormField pairs a label with an editable text field
|
||||
type FormField struct {
|
||||
@@ -103,25 +106,25 @@ type FormOpts struct {
|
||||
|
||||
// FormStyle defines form colors
|
||||
type FormStyle struct {
|
||||
LabelFg terminal.RGB
|
||||
FieldFg terminal.RGB
|
||||
FieldBg terminal.RGB
|
||||
FocusBg terminal.RGB
|
||||
CursorFg terminal.RGB
|
||||
CursorBg terminal.RGB
|
||||
Bg terminal.RGB
|
||||
LabelFg color.RGB
|
||||
FieldFg color.RGB
|
||||
FieldBg color.RGB
|
||||
FocusBg color.RGB
|
||||
CursorFg color.RGB
|
||||
CursorBg color.RGB
|
||||
Bg color.RGB
|
||||
}
|
||||
|
||||
// DefaultFormStyle returns default form colors
|
||||
func DefaultFormStyle() FormStyle {
|
||||
return FormStyle{
|
||||
LabelFg: terminal.RGB{R: 150, G: 150, B: 180},
|
||||
FieldFg: terminal.RGB{R: 220, G: 220, B: 220},
|
||||
FieldBg: terminal.RGB{R: 35, G: 35, B: 45},
|
||||
FocusBg: terminal.RGB{R: 45, G: 45, B: 60},
|
||||
CursorFg: terminal.RGB{R: 0, G: 0, B: 0},
|
||||
CursorBg: terminal.RGB{R: 200, G: 200, B: 200},
|
||||
Bg: terminal.RGB{R: 25, G: 25, B: 35},
|
||||
LabelFg: color.RGB{R: 150, G: 150, B: 180},
|
||||
FieldFg: color.RGB{R: 220, G: 220, B: 220},
|
||||
FieldBg: color.RGB{R: 35, G: 35, B: 45},
|
||||
FocusBg: color.RGB{R: 45, G: 45, B: 60},
|
||||
CursorFg: color.RGB{R: 0, G: 0, B: 0},
|
||||
CursorBg: color.RGB{R: 200, G: 200, B: 200},
|
||||
Bg: color.RGB{R: 25, G: 25, B: 35},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,4 +228,5 @@ func (r Region) Form(state *FormState, opts FormOpts) int {
|
||||
}
|
||||
|
||||
return y
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-5
@@ -1,18 +1,19 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// InputOpts configures single-line input field
|
||||
type InputOpts struct {
|
||||
Label string
|
||||
LabelFg terminal.RGB
|
||||
LabelFg color.RGB
|
||||
Text string
|
||||
Cursor int // Cursor position in text (rune index)
|
||||
CursorBg terminal.RGB
|
||||
TextFg terminal.RGB
|
||||
Bg terminal.RGB
|
||||
CursorBg color.RGB
|
||||
TextFg color.RGB
|
||||
Bg color.RGB
|
||||
}
|
||||
|
||||
// Input renders labeled text input field on row y, handling cursor display and horizontal scrolling
|
||||
@@ -75,4 +76,5 @@ func (r Region) Input(y int, opts InputOpts) {
|
||||
if cursor == len(runes) && cursor-scroll < inputW {
|
||||
r.Cell(x+cursor-scroll, y, ' ', opts.TextFg, opts.CursorBg, terminal.AttrNone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-9
@@ -1,22 +1,25 @@
|
||||
package tui
|
||||
|
||||
import "github.com/lixenwraith/terminal"
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// ListItem represents a single row in a scrollable list
|
||||
type ListItem struct {
|
||||
Indent int // Left padding in cells
|
||||
Icon rune // Expand indicator or bullet, 0 = none
|
||||
IconFg terminal.RGB
|
||||
IconFg color.RGB
|
||||
Check CheckState // CheckNone to skip checkbox
|
||||
CheckFg terminal.RGB
|
||||
CheckFg color.RGB
|
||||
Text string
|
||||
TextStyle Style
|
||||
}
|
||||
|
||||
// ListOpts configures list rendering
|
||||
type ListOpts struct {
|
||||
CursorBg terminal.RGB
|
||||
DefaultBg terminal.RGB
|
||||
CursorBg color.RGB
|
||||
DefaultBg color.RGB
|
||||
IconWidth int // Width reserved for icon, default 2
|
||||
}
|
||||
|
||||
@@ -49,7 +52,7 @@ func (r Region) List(items []ListItem, cursor, scroll int, opts ListOpts) int {
|
||||
|
||||
// Clear row
|
||||
for x := 0; x < r.W; x++ {
|
||||
r.Cell(x, y, ' ', terminal.RGB{}, bg, terminal.AttrNone)
|
||||
r.Cell(x, y, ' ', color.RGB{}, bg, terminal.AttrNone)
|
||||
}
|
||||
|
||||
x := item.Indent
|
||||
@@ -61,7 +64,7 @@ func (r Region) List(items []ListItem, cursor, scroll int, opts ListOpts) int {
|
||||
x += iconW
|
||||
|
||||
// Checkbox
|
||||
if item.Check != CheckNone || item.CheckFg != (terminal.RGB{}) {
|
||||
if item.Check != CheckNone || item.CheckFg != (color.RGB{}) {
|
||||
if x+3 <= r.W {
|
||||
var ch rune
|
||||
switch item.Check {
|
||||
@@ -83,7 +86,7 @@ func (r Region) List(items []ListItem, cursor, scroll int, opts ListOpts) int {
|
||||
|
||||
// Text
|
||||
textStyle := item.TextStyle
|
||||
if textStyle.Bg == (terminal.RGB{}) {
|
||||
if textStyle.Bg == (color.RGB{}) {
|
||||
textStyle.Bg = bg
|
||||
}
|
||||
text := item.Text
|
||||
@@ -96,4 +99,5 @@ func (r Region) List(items []ListItem, cursor, scroll int, opts ListOpts) int {
|
||||
}
|
||||
|
||||
return rendered
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-5
@@ -1,6 +1,7 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
@@ -9,10 +10,10 @@ type ModalOpts struct {
|
||||
Title string
|
||||
Hint string // Right-aligned hint text
|
||||
Border LineType
|
||||
BorderFg terminal.RGB
|
||||
TitleFg terminal.RGB
|
||||
HintFg terminal.RGB
|
||||
Bg terminal.RGB
|
||||
BorderFg color.RGB
|
||||
TitleFg color.RGB
|
||||
HintFg color.RGB
|
||||
Bg color.RGB
|
||||
}
|
||||
|
||||
// Modal fills region with background, draws border with title/hint, returns content region
|
||||
@@ -63,4 +64,5 @@ func (r Region) Modal(opts ModalOpts) Region {
|
||||
|
||||
// Return content region
|
||||
return r.Sub(1, 1, r.W-2, r.H-2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+28
-24
@@ -1,6 +1,9 @@
|
||||
package tui
|
||||
|
||||
import "github.com/lixenwraith/terminal"
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// OverlayStyle specifies overlay appearance
|
||||
type OverlayStyle uint8
|
||||
@@ -17,10 +20,10 @@ type OverlayOpts struct {
|
||||
Style OverlayStyle
|
||||
Title string
|
||||
Border LineType
|
||||
Bg terminal.RGB
|
||||
Fg terminal.RGB // Border and title color
|
||||
TitleBg terminal.RGB // Title bar background, zero = same as Fg
|
||||
TitleFg terminal.RGB // Title text color, zero = same as Bg
|
||||
Bg color.RGB
|
||||
Fg color.RGB // Border and title color
|
||||
TitleBg color.RGB // Title bar background, zero = same as Fg
|
||||
TitleFg color.RGB // Title text color, zero = same as Bg
|
||||
|
||||
// Modal/Floating positioning (ignored for Fullscreen)
|
||||
Width int // 0 = 80% of region
|
||||
@@ -28,7 +31,7 @@ type OverlayOpts struct {
|
||||
X, Y int // Offset from center, 0 = centered
|
||||
|
||||
// Shadow for Floating style
|
||||
ShadowColor terminal.RGB
|
||||
ShadowColor color.RGB
|
||||
}
|
||||
|
||||
// DefaultOverlayOpts returns sensible defaults for modal overlay
|
||||
@@ -37,10 +40,10 @@ func DefaultOverlayOpts(title string) OverlayOpts {
|
||||
Style: OverlayModal,
|
||||
Title: title,
|
||||
Border: LineDouble,
|
||||
Bg: terminal.RGB{R: 25, G: 25, B: 35},
|
||||
Fg: terminal.RGB{R: 100, G: 140, B: 180},
|
||||
TitleBg: terminal.RGB{R: 40, G: 60, B: 90},
|
||||
TitleFg: terminal.RGB{R: 255, G: 255, B: 255},
|
||||
Bg: color.RGB{R: 25, G: 25, B: 35},
|
||||
Fg: color.RGB{R: 100, G: 140, B: 180},
|
||||
TitleBg: color.RGB{R: 40, G: 60, B: 90},
|
||||
TitleFg: color.RGB{R: 255, G: 255, B: 255},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,10 +52,10 @@ func FullscreenOverlayOpts(title string) OverlayOpts {
|
||||
return OverlayOpts{
|
||||
Style: OverlayFullscreen,
|
||||
Title: title,
|
||||
Bg: terminal.RGB{R: 20, G: 20, B: 30},
|
||||
Fg: terminal.RGB{R: 100, G: 140, B: 180},
|
||||
TitleBg: terminal.RGB{R: 40, G: 60, B: 90},
|
||||
TitleFg: terminal.RGB{R: 255, G: 255, B: 255},
|
||||
Bg: color.RGB{R: 20, G: 20, B: 30},
|
||||
Fg: color.RGB{R: 100, G: 140, B: 180},
|
||||
TitleBg: color.RGB{R: 40, G: 60, B: 90},
|
||||
TitleFg: color.RGB{R: 255, G: 255, B: 255},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,11 +101,11 @@ func (r Region) renderFullscreenOverlay(opts OverlayOpts) OverlayResult {
|
||||
// Title bar
|
||||
if opts.Title != "" {
|
||||
titleBg := opts.TitleBg
|
||||
if titleBg == (terminal.RGB{}) {
|
||||
if titleBg == (color.RGB{}) {
|
||||
titleBg = opts.Fg
|
||||
}
|
||||
titleFg := opts.TitleFg
|
||||
if titleFg == (terminal.RGB{}) {
|
||||
if titleFg == (color.RGB{}) {
|
||||
titleFg = opts.Bg
|
||||
}
|
||||
|
||||
@@ -188,11 +191,11 @@ func (r Region) renderModalOverlay(opts OverlayOpts) OverlayResult {
|
||||
// Title in top border
|
||||
if opts.Title != "" && contentW > 2 {
|
||||
titleBg := opts.TitleBg
|
||||
if titleBg == (terminal.RGB{}) {
|
||||
if titleBg == (color.RGB{}) {
|
||||
titleBg = opts.Fg
|
||||
}
|
||||
titleFg := opts.TitleFg
|
||||
if titleFg == (terminal.RGB{}) {
|
||||
if titleFg == (color.RGB{}) {
|
||||
titleFg = opts.Bg
|
||||
}
|
||||
|
||||
@@ -224,8 +227,8 @@ func (r Region) renderModalOverlay(opts OverlayOpts) OverlayResult {
|
||||
func (r Region) renderFloatingOverlay(opts OverlayOpts) OverlayResult {
|
||||
// Same as modal but with shadow
|
||||
shadowColor := opts.ShadowColor
|
||||
if shadowColor == (terminal.RGB{}) {
|
||||
shadowColor = terminal.RGB{R: 10, G: 10, B: 15}
|
||||
if shadowColor == (color.RGB{}) {
|
||||
shadowColor = color.RGB{R: 10, G: 10, B: 15}
|
||||
}
|
||||
|
||||
// Calculate dimensions (same as modal)
|
||||
@@ -285,11 +288,11 @@ func (r Region) renderFloatingOverlay(opts OverlayOpts) OverlayResult {
|
||||
|
||||
if opts.Title != "" && contentW > 2 {
|
||||
titleBg := opts.TitleBg
|
||||
if titleBg == (terminal.RGB{}) {
|
||||
if titleBg == (color.RGB{}) {
|
||||
titleBg = opts.Fg
|
||||
}
|
||||
titleFg := opts.TitleFg
|
||||
if titleFg == (terminal.RGB{}) {
|
||||
if titleFg == (color.RGB{}) {
|
||||
titleFg = opts.Bg
|
||||
}
|
||||
|
||||
@@ -327,7 +330,7 @@ func (r Region) renderBorderTitleOverlay(opts OverlayOpts) OverlayResult {
|
||||
|
||||
if opts.Title != "" && r.W > 6 {
|
||||
titleFg := opts.TitleFg
|
||||
if titleFg == (terminal.RGB{}) {
|
||||
if titleFg == (color.RGB{}) {
|
||||
titleFg = opts.Fg
|
||||
}
|
||||
title := " " + opts.Title + " "
|
||||
@@ -370,4 +373,5 @@ func (o *OverlayState) Hide() {
|
||||
// Toggle switches visibility
|
||||
func (o *OverlayState) Toggle() {
|
||||
o.Visible = !o.Visible
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-6
@@ -1,6 +1,7 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
@@ -8,9 +9,9 @@ import (
|
||||
type PaneOpts struct {
|
||||
Title string
|
||||
Border LineType
|
||||
BorderFg terminal.RGB
|
||||
Bg terminal.RGB
|
||||
TitleFg terminal.RGB
|
||||
BorderFg color.RGB
|
||||
Bg color.RGB
|
||||
TitleFg color.RGB
|
||||
}
|
||||
|
||||
// Pane draws bordered pane with optional title, returns content region
|
||||
@@ -49,7 +50,7 @@ func (r Region) Pane(opts PaneOpts) Region {
|
||||
|
||||
// TitledPane fills region with background, draws centered title at top, returns content region
|
||||
// Content region starts at row 1 with full width
|
||||
func (r Region) TitledPane(title string, titleFg, bg terminal.RGB) Region {
|
||||
func (r Region) TitledPane(title string, titleFg, bg color.RGB) Region {
|
||||
r.Fill(bg)
|
||||
if title != "" && r.H > 0 {
|
||||
r.TextCenter(0, title, titleFg, bg, terminal.AttrBold)
|
||||
@@ -61,9 +62,10 @@ func (r Region) TitledPane(title string, titleFg, bg terminal.RGB) Region {
|
||||
}
|
||||
|
||||
// TitledPaneFocused is TitledPane with focus-dependent background
|
||||
func (r Region) TitledPaneFocused(title string, titleFg, bg, focusBg terminal.RGB, focused bool) Region {
|
||||
func (r Region) TitledPaneFocused(title string, titleFg, bg, focusBg color.RGB, focused bool) Region {
|
||||
if focused {
|
||||
bg = focusBg
|
||||
}
|
||||
return r.TitledPane(title, titleFg, bg)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-6
@@ -1,6 +1,7 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
@@ -12,7 +13,7 @@ const (
|
||||
)
|
||||
|
||||
// Progress draws horizontal progress bar (0.0-1.0)
|
||||
func (r Region) Progress(x, y, w int, pct float64, fg, bg terminal.RGB) {
|
||||
func (r Region) Progress(x, y, w int, pct float64, fg, bg color.RGB) {
|
||||
if y < 0 || y >= r.H || w <= 0 {
|
||||
return
|
||||
}
|
||||
@@ -43,7 +44,7 @@ func (r Region) Progress(x, y, w int, pct float64, fg, bg terminal.RGB) {
|
||||
}
|
||||
|
||||
// ProgressV draws vertical progress bar (fills bottom-up)
|
||||
func (r Region) ProgressV(x, y, h int, pct float64, fg, bg terminal.RGB) {
|
||||
func (r Region) ProgressV(x, y, h int, pct float64, fg, bg color.RGB) {
|
||||
if x < 0 || x >= r.W || h <= 0 {
|
||||
return
|
||||
}
|
||||
@@ -72,7 +73,7 @@ func (r Region) ProgressV(x, y, h int, pct float64, fg, bg terminal.RGB) {
|
||||
}
|
||||
|
||||
// Spinner draws spinner character based on frame counter
|
||||
func (r Region) Spinner(x, y int, frame int, fg terminal.RGB) {
|
||||
func (r Region) Spinner(x, y int, frame int, fg color.RGB) {
|
||||
if x < 0 || x >= r.W || y < 0 || y >= r.H {
|
||||
return
|
||||
}
|
||||
@@ -80,11 +81,11 @@ func (r Region) Spinner(x, y int, frame int, fg terminal.RGB) {
|
||||
if idx < 0 {
|
||||
idx = -idx
|
||||
}
|
||||
r.Cell(x, y, spinnerFrames[idx], fg, terminal.RGB{}, terminal.AttrNone)
|
||||
r.Cell(x, y, spinnerFrames[idx], fg, color.RGB{}, terminal.AttrNone)
|
||||
}
|
||||
|
||||
// Gauge draws labeled gauge with percentage
|
||||
func (r Region) Gauge(x, y, w int, value, max int, fg, bg terminal.RGB) {
|
||||
func (r Region) Gauge(x, y, w int, value, max int, fg, bg color.RGB) {
|
||||
if w < 5 || y < 0 || y >= r.H {
|
||||
return
|
||||
}
|
||||
@@ -121,4 +122,5 @@ func (r Region) Gauge(x, y, w int, value, max int, fg, bg terminal.RGB) {
|
||||
label = " " + string(rune('0'+pctInt)) + "%"
|
||||
}
|
||||
r.Text(x+2+barW, y, label, fg, bg, terminal.AttrNone)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
-21
@@ -1,6 +1,9 @@
|
||||
package tui
|
||||
|
||||
import "github.com/lixenwraith/terminal"
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// ProgressType specifies progress indicator variant
|
||||
type ProgressType uint8
|
||||
@@ -93,11 +96,11 @@ type ProgressOverlayOpts struct {
|
||||
Width int // Overlay width, 0 = auto
|
||||
Cancelable bool // Show cancel hint
|
||||
CancelKey string // e.g., "Esc"
|
||||
Fg terminal.RGB
|
||||
Bg terminal.RGB
|
||||
BarFg terminal.RGB
|
||||
BarBg terminal.RGB
|
||||
AccentFg terminal.RGB // Spinner/highlight color
|
||||
Fg color.RGB
|
||||
Bg color.RGB
|
||||
BarFg color.RGB
|
||||
BarBg color.RGB
|
||||
AccentFg color.RGB // Spinner/highlight color
|
||||
}
|
||||
|
||||
// DefaultProgressOpts returns sensible defaults
|
||||
@@ -111,11 +114,11 @@ func DefaultProgressOpts(title, message string, ptype ProgressType) ProgressOver
|
||||
BarStyle: BarStyleBlock,
|
||||
ShowPercent: true,
|
||||
Width: 40,
|
||||
Fg: terminal.RGB{R: 220, G: 220, B: 220},
|
||||
Bg: terminal.RGB{R: 30, G: 30, B: 40},
|
||||
BarFg: terminal.RGB{R: 80, G: 160, B: 255},
|
||||
BarBg: terminal.RGB{R: 50, G: 50, B: 60},
|
||||
AccentFg: terminal.RGB{R: 100, G: 200, B: 255},
|
||||
Fg: color.RGB{R: 220, G: 220, B: 220},
|
||||
Bg: color.RGB{R: 30, G: 30, B: 40},
|
||||
BarFg: color.RGB{R: 80, G: 160, B: 255},
|
||||
BarBg: color.RGB{R: 50, G: 50, B: 60},
|
||||
AccentFg: color.RGB{R: 100, G: 200, B: 255},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,12 +172,12 @@ func (r Region) ProgressOverlay(opts ProgressOverlayOpts) Region {
|
||||
case ProgressStyleShadow:
|
||||
// Draw shadow first
|
||||
shadow := r.Sub(overlay.X-r.X+1, overlay.Y-r.Y+1, overlayW, overlayH)
|
||||
shadow.Fill(terminal.RGB{R: 10, G: 10, B: 15})
|
||||
shadow.Fill(color.RGB{R: 10, G: 10, B: 15})
|
||||
borderLine = LineSingle
|
||||
case ProgressStyleNeon:
|
||||
borderLine = LineDouble
|
||||
opts.AccentFg = terminal.RGB{R: 0, G: 255, B: 200}
|
||||
opts.BarFg = terminal.RGB{R: 255, G: 0, B: 255}
|
||||
opts.AccentFg = color.RGB{R: 0, G: 255, B: 200}
|
||||
opts.BarFg = color.RGB{R: 255, G: 0, B: 255}
|
||||
case ProgressStyleRetro:
|
||||
borderLine = LineHeavy
|
||||
opts.BarStyle = BarStyleBlock
|
||||
@@ -242,7 +245,7 @@ func (r Region) ProgressOverlay(opts ProgressOverlayOpts) Region {
|
||||
if opts.CancelKey == "" {
|
||||
hint = "Esc to cancel"
|
||||
}
|
||||
content.TextCenter(y, hint, terminal.RGB{R: 120, G: 120, B: 130}, opts.Bg, terminal.AttrDim)
|
||||
content.TextCenter(y, hint, color.RGB{R: 120, G: 120, B: 130}, opts.Bg, terminal.AttrDim)
|
||||
}
|
||||
|
||||
return overlay
|
||||
@@ -301,7 +304,7 @@ func (r Region) renderProgressBar(bar Region, opts ProgressOverlayOpts) {
|
||||
|
||||
for x := 0; x < barW; x++ {
|
||||
var ch rune
|
||||
var fg terminal.RGB
|
||||
var fg color.RGB
|
||||
if x < filled {
|
||||
ch = chars[0]
|
||||
fg = opts.BarFg
|
||||
@@ -334,7 +337,7 @@ func (r Region) renderProgressBar(bar Region, opts ProgressOverlayOpts) {
|
||||
|
||||
for x := 0; x < barW; x++ {
|
||||
var ch rune
|
||||
var fg terminal.RGB
|
||||
var fg color.RGB
|
||||
if x >= pos && x < pos+markerW {
|
||||
ch = chars[0]
|
||||
fg = opts.BarFg
|
||||
@@ -365,11 +368,11 @@ func (r Region) renderProgressBar(bar Region, opts ProgressOverlayOpts) {
|
||||
|
||||
for x := 0; x < barW; x++ {
|
||||
var ch rune
|
||||
var fg terminal.RGB
|
||||
var fg color.RGB
|
||||
if x < filled {
|
||||
ch = chars[0]
|
||||
// Pulse the color
|
||||
fg = terminal.RGB{
|
||||
fg = color.RGB{
|
||||
R: uint8(float64(opts.BarFg.R) * (0.5 + intensity*0.5)),
|
||||
G: uint8(float64(opts.BarFg.G) * (0.5 + intensity*0.5)),
|
||||
B: uint8(float64(opts.BarFg.B) * (0.5 + intensity*0.5)),
|
||||
@@ -390,7 +393,7 @@ func (r Region) renderProgressBar(bar Region, opts ProgressOverlayOpts) {
|
||||
// ETA
|
||||
if opts.ShowETA != "" {
|
||||
etaX := bar.W - RuneLen(opts.ShowETA)
|
||||
bar.Text(etaX, 0, opts.ShowETA, terminal.RGB{R: 150, G: 150, B: 160}, opts.Bg, terminal.AttrDim)
|
||||
bar.Text(etaX, 0, opts.ShowETA, color.RGB{R: 150, G: 150, B: 160}, opts.Bg, terminal.AttrDim)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,4 +466,5 @@ func (p *ProgressState) Dismiss() {
|
||||
// Show displays the progress overlay
|
||||
func (p *ProgressState) Show() {
|
||||
p.Visible = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-6
@@ -1,6 +1,9 @@
|
||||
package tui
|
||||
|
||||
import "github.com/lixenwraith/terminal"
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// Region represents a rectangular area within a cell buffer
|
||||
// All coordinates are relative to the region's origin
|
||||
@@ -63,7 +66,7 @@ func (r Region) Inset(n int) Region {
|
||||
}
|
||||
|
||||
// Cell sets a single cell with bounds checking
|
||||
func (r Region) Cell(x, y int, ch rune, fg, bg terminal.RGB, attr terminal.Attr) {
|
||||
func (r Region) Cell(x, y int, ch rune, fg, bg color.RGB, attr terminal.Attr) {
|
||||
if x < 0 || x >= r.W || y < 0 || y >= r.H {
|
||||
return
|
||||
}
|
||||
@@ -83,17 +86,17 @@ func (r Region) Cell(x, y int, ch rune, fg, bg terminal.RGB, attr terminal.Attr)
|
||||
}
|
||||
|
||||
// Fill fills entire region with background color
|
||||
func (r Region) Fill(bg terminal.RGB) {
|
||||
func (r Region) Fill(bg color.RGB) {
|
||||
for y := 0; y < r.H; y++ {
|
||||
for x := 0; x < r.W; x++ {
|
||||
r.Cell(x, y, ' ', terminal.RGB{}, bg, terminal.AttrNone)
|
||||
r.Cell(x, y, ' ', color.RGB{}, bg, terminal.AttrNone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear fills region with spaces and zero colors
|
||||
func (r Region) Clear() {
|
||||
r.Fill(terminal.RGB{})
|
||||
r.Fill(color.RGB{})
|
||||
}
|
||||
|
||||
// Width returns region width
|
||||
@@ -109,4 +112,5 @@ func (r Region) Height() int {
|
||||
// Bounds returns absolute position and dimensions
|
||||
func (r Region) Bounds() (x, y, w, h int) {
|
||||
return r.X, r.Y, r.W, r.H
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-6
@@ -1,12 +1,15 @@
|
||||
package tui
|
||||
|
||||
import "github.com/lixenwraith/terminal"
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// Default spinner frames for Region.Spinner
|
||||
var spinnerFrames = spinnerSets[SpinnerBraille]
|
||||
|
||||
// Text renders text at position, truncates at region edge
|
||||
func (r Region) Text(x, y int, s string, fg, bg terminal.RGB, attr terminal.Attr) {
|
||||
func (r Region) Text(x, y int, s string, fg, bg color.RGB, attr terminal.Attr) {
|
||||
if y < 0 || y >= r.H {
|
||||
return
|
||||
}
|
||||
@@ -40,19 +43,19 @@ func (r Region) TextStyled(x, y int, s string, style Style) {
|
||||
}
|
||||
|
||||
// TextRight renders text right-aligned on row
|
||||
func (r Region) TextRight(y int, s string, fg, bg terminal.RGB, attr terminal.Attr) {
|
||||
func (r Region) TextRight(y int, s string, fg, bg color.RGB, attr terminal.Attr) {
|
||||
x := r.W - RuneLen(s)
|
||||
r.Text(x, y, s, fg, bg, attr)
|
||||
}
|
||||
|
||||
// TextCenter renders text centered on row
|
||||
func (r Region) TextCenter(y int, s string, fg, bg terminal.RGB, attr terminal.Attr) {
|
||||
func (r Region) TextCenter(y int, s string, fg, bg color.RGB, attr terminal.Attr) {
|
||||
x := (r.W - RuneLen(s)) / 2
|
||||
r.Text(x, y, s, fg, bg, attr)
|
||||
}
|
||||
|
||||
// TextBlock renders wrapped text within region bounds, returns number of lines rendered
|
||||
func (r Region) TextBlock(x, y int, text string, fg, bg terminal.RGB, attr terminal.Attr) int {
|
||||
func (r Region) TextBlock(x, y int, text string, fg, bg color.RGB, attr terminal.Attr) int {
|
||||
if x >= r.W || y >= r.H || text == "" {
|
||||
return 0
|
||||
}
|
||||
@@ -81,4 +84,3 @@ func (r Region) TextBlock(x, y int, text string, fg, bg terminal.RGB, attr termi
|
||||
func (r Region) TextBlockStyled(x, y int, text string, style Style) int {
|
||||
return r.TextBlock(x, y, text, style.Fg, style.Bg, style.Attr)
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -1,11 +1,12 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// ScrollBar draws vertical scrollbar track with thumb
|
||||
func (r Region) ScrollBar(x int, offset, visible, total int, fg terminal.RGB) {
|
||||
func (r Region) ScrollBar(x int, offset, visible, total int, fg color.RGB) {
|
||||
if x < 0 || x >= r.W || r.H < 1 {
|
||||
return
|
||||
}
|
||||
@@ -14,7 +15,7 @@ func (r Region) ScrollBar(x int, offset, visible, total int, fg terminal.RGB) {
|
||||
if total <= visible || trackH < 3 {
|
||||
// No scrolling needed or track too small
|
||||
for y := range trackH {
|
||||
r.Cell(x, y, '│', fg, terminal.RGB{}, terminal.AttrDim)
|
||||
r.Cell(x, y, '│', fg, color.RGB{}, terminal.AttrDim)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -42,12 +43,12 @@ func (r Region) ScrollBar(x int, offset, visible, total int, fg terminal.RGB) {
|
||||
} else {
|
||||
ch = '░'
|
||||
}
|
||||
r.Cell(x, y, ch, fg, terminal.RGB{}, terminal.AttrNone)
|
||||
r.Cell(x, y, ch, fg, color.RGB{}, terminal.AttrNone)
|
||||
}
|
||||
}
|
||||
|
||||
// ScrollIndicator draws compact indicator text (Top/Bot/XX%)
|
||||
func (r Region) ScrollIndicator(y int, offset, visible, total int, fg terminal.RGB) {
|
||||
func (r Region) ScrollIndicator(y int, offset, visible, total int, fg color.RGB) {
|
||||
if y < 0 || y >= r.H {
|
||||
return
|
||||
}
|
||||
@@ -68,6 +69,5 @@ func (r Region) ScrollIndicator(y int, offset, visible, total int, fg terminal.R
|
||||
}
|
||||
}
|
||||
|
||||
r.TextRight(y, text, fg, terminal.RGB{}, terminal.AttrDim)
|
||||
r.TextRight(y, text, fg, color.RGB{}, terminal.AttrDim)
|
||||
}
|
||||
|
||||
|
||||
+9
-6
@@ -1,6 +1,9 @@
|
||||
package tui
|
||||
|
||||
import "github.com/lixenwraith/terminal"
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// BarSection represents one segment of a status bar
|
||||
type BarSection struct {
|
||||
@@ -25,7 +28,7 @@ const (
|
||||
type BarOpts struct {
|
||||
Separator string // Between sections, default " │ "
|
||||
SepStyle Style // Separator styling
|
||||
Bg terminal.RGB
|
||||
Bg color.RGB
|
||||
Align BarAlign
|
||||
Padding int // Left/right padding, default 1
|
||||
}
|
||||
@@ -34,7 +37,7 @@ type BarOpts struct {
|
||||
func DefaultBarOpts() BarOpts {
|
||||
return BarOpts{
|
||||
Separator: " │ ",
|
||||
SepStyle: Style{Fg: terminal.RGB{R: 80, G: 80, B: 100}},
|
||||
SepStyle: Style{Fg: color.RGB{R: 80, G: 80, B: 100}},
|
||||
Padding: 1,
|
||||
Align: BarAlignRight,
|
||||
}
|
||||
@@ -55,7 +58,7 @@ func (r Region) StatusBar(y int, sections []BarSection, opts BarOpts) {
|
||||
|
||||
// Fill background
|
||||
for x := 0; x < r.W; x++ {
|
||||
r.Cell(x, y, ' ', terminal.RGB{}, opts.Bg, terminal.AttrNone)
|
||||
r.Cell(x, y, ' ', color.RGB{}, opts.Bg, terminal.AttrNone)
|
||||
}
|
||||
|
||||
sepLen := RuneLen(opts.Separator)
|
||||
@@ -193,7 +196,7 @@ func truncateSections(sections []BarSection, widths []int, sepLen, availW int) (
|
||||
}
|
||||
|
||||
// QuickStatusBar renders simple label:value pairs right-aligned
|
||||
func (r Region) QuickStatusBar(y int, pairs [][2]string, labelFg, valueFg, bg terminal.RGB) {
|
||||
func (r Region) QuickStatusBar(y int, pairs [][2]string, labelFg, valueFg, bg color.RGB) {
|
||||
sections := make([]BarSection, len(pairs))
|
||||
for i, p := range pairs {
|
||||
sections[i] = BarSection{
|
||||
@@ -207,4 +210,4 @@ func (r Region) QuickStatusBar(y int, pairs [][2]string, labelFg, valueFg, bg te
|
||||
Bg: bg,
|
||||
Align: BarAlignRight,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+7
-5
@@ -1,22 +1,24 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// Style bundles foreground, background, and attributes for text rendering
|
||||
type Style struct {
|
||||
Fg terminal.RGB
|
||||
Bg terminal.RGB
|
||||
Fg color.RGB
|
||||
Bg color.RGB
|
||||
Attr terminal.Attr
|
||||
}
|
||||
|
||||
// DefaultStyle returns style with zero values (transparent bg)
|
||||
func DefaultStyle(fg terminal.RGB) Style {
|
||||
func DefaultStyle(fg color.RGB) Style {
|
||||
return Style{Fg: fg}
|
||||
}
|
||||
|
||||
// IsZero returns true if style has no colors or attributes set
|
||||
func (s Style) IsZero() bool {
|
||||
return s.Fg == (terminal.RGB{}) && s.Bg == (terminal.RGB{}) && s.Attr == terminal.AttrNone
|
||||
}
|
||||
return s.Fg == (color.RGB{}) && s.Bg == (color.RGB{}) && s.Attr == terminal.AttrNone
|
||||
}
|
||||
|
||||
|
||||
+17
-15
@@ -1,6 +1,7 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
@@ -18,25 +19,25 @@ type TextFieldOpts struct {
|
||||
// DefaultTextFieldStyle returns default colors
|
||||
func DefaultTextFieldStyle() TextFieldStyle {
|
||||
return TextFieldStyle{
|
||||
TextFg: terminal.RGB{R: 220, G: 220, B: 220},
|
||||
TextBg: terminal.RGB{R: 30, G: 30, B: 40},
|
||||
CursorFg: terminal.RGB{R: 0, G: 0, B: 0},
|
||||
CursorBg: terminal.RGB{R: 200, G: 200, B: 200},
|
||||
PlaceholderFg: terminal.RGB{R: 100, G: 100, B: 110},
|
||||
PrefixFg: terminal.RGB{R: 150, G: 150, B: 180},
|
||||
BorderFg: terminal.RGB{R: 80, G: 80, B: 100},
|
||||
TextFg: color.RGB{R: 220, G: 220, B: 220},
|
||||
TextBg: color.RGB{R: 30, G: 30, B: 40},
|
||||
CursorFg: color.RGB{R: 0, G: 0, B: 0},
|
||||
CursorBg: color.RGB{R: 200, G: 200, B: 200},
|
||||
PlaceholderFg: color.RGB{R: 100, G: 100, B: 110},
|
||||
PrefixFg: color.RGB{R: 150, G: 150, B: 180},
|
||||
BorderFg: color.RGB{R: 80, G: 80, B: 100},
|
||||
}
|
||||
}
|
||||
|
||||
// TextFieldStyle defines text field colors
|
||||
type TextFieldStyle struct {
|
||||
TextFg terminal.RGB
|
||||
TextBg terminal.RGB
|
||||
CursorFg terminal.RGB
|
||||
CursorBg terminal.RGB
|
||||
PlaceholderFg terminal.RGB
|
||||
PrefixFg terminal.RGB
|
||||
BorderFg terminal.RGB
|
||||
TextFg color.RGB
|
||||
TextBg color.RGB
|
||||
CursorFg color.RGB
|
||||
CursorBg color.RGB
|
||||
PlaceholderFg color.RGB
|
||||
PrefixFg color.RGB
|
||||
BorderFg color.RGB
|
||||
}
|
||||
|
||||
// TextField renders text field and returns content height used
|
||||
@@ -168,4 +169,5 @@ func boolToInt(b bool) int {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+53
-50
@@ -1,63 +1,66 @@
|
||||
package tui
|
||||
|
||||
import "github.com/lixenwraith/terminal"
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
)
|
||||
|
||||
// Theme defines semantic colors for TUI components
|
||||
type Theme struct {
|
||||
Bg terminal.RGB
|
||||
Fg terminal.RGB
|
||||
FocusBg terminal.RGB
|
||||
CursorBg terminal.RGB
|
||||
Bg color.RGB
|
||||
Fg color.RGB
|
||||
FocusBg color.RGB
|
||||
CursorBg color.RGB
|
||||
|
||||
Selected terminal.RGB
|
||||
Unselected terminal.RGB
|
||||
Partial terminal.RGB
|
||||
Error terminal.RGB
|
||||
Warning terminal.RGB
|
||||
Selected color.RGB
|
||||
Unselected color.RGB
|
||||
Partial color.RGB
|
||||
Error color.RGB
|
||||
Warning color.RGB
|
||||
|
||||
Border terminal.RGB
|
||||
HeaderBg terminal.RGB
|
||||
HeaderFg terminal.RGB
|
||||
StatusFg terminal.RGB
|
||||
HintFg terminal.RGB
|
||||
InputBg terminal.RGB
|
||||
Border color.RGB
|
||||
HeaderBg color.RGB
|
||||
HeaderFg color.RGB
|
||||
StatusFg color.RGB
|
||||
HintFg color.RGB
|
||||
InputBg color.RGB
|
||||
|
||||
DirFg terminal.RGB
|
||||
FileFg terminal.RGB
|
||||
SymbolFg terminal.RGB
|
||||
DirFg color.RGB
|
||||
FileFg color.RGB
|
||||
SymbolFg color.RGB
|
||||
|
||||
SyntaxComment terminal.RGB
|
||||
SyntaxString terminal.RGB
|
||||
SyntaxKeyword terminal.RGB
|
||||
SyntaxType terminal.RGB
|
||||
SyntaxNumber terminal.RGB
|
||||
SyntaxSymbol terminal.RGB
|
||||
SyntaxComment color.RGB
|
||||
SyntaxString color.RGB
|
||||
SyntaxKeyword color.RGB
|
||||
SyntaxType color.RGB
|
||||
SyntaxNumber color.RGB
|
||||
SyntaxSymbol color.RGB
|
||||
}
|
||||
|
||||
// DefaultTheme provides reasonable defaults
|
||||
var DefaultTheme = Theme{
|
||||
Bg: terminal.RGB{R: 20, G: 20, B: 30},
|
||||
Fg: terminal.RGB{R: 200, G: 200, B: 200},
|
||||
FocusBg: terminal.RGB{R: 30, G: 35, B: 45},
|
||||
CursorBg: terminal.RGB{R: 50, G: 50, B: 70},
|
||||
Selected: terminal.RGB{R: 80, G: 200, B: 80},
|
||||
Unselected: terminal.RGB{R: 100, G: 100, B: 100},
|
||||
Partial: terminal.RGB{R: 80, G: 160, B: 220},
|
||||
Error: terminal.RGB{R: 255, G: 80, B: 80},
|
||||
Warning: terminal.RGB{R: 255, G: 80, B: 80},
|
||||
Border: terminal.RGB{R: 60, G: 80, B: 100},
|
||||
HeaderBg: terminal.RGB{R: 40, G: 60, B: 90},
|
||||
HeaderFg: terminal.RGB{R: 255, G: 255, B: 255},
|
||||
StatusFg: terminal.RGB{R: 140, G: 140, B: 140},
|
||||
HintFg: terminal.RGB{R: 100, G: 180, B: 200},
|
||||
InputBg: terminal.RGB{R: 30, G: 30, B: 50},
|
||||
DirFg: terminal.RGB{R: 130, G: 170, B: 220},
|
||||
FileFg: terminal.RGB{R: 200, G: 200, B: 200},
|
||||
SymbolFg: terminal.RGB{R: 180, G: 220, B: 220},
|
||||
SyntaxComment: terminal.RGB{R: 100, G: 110, B: 120},
|
||||
SyntaxString: terminal.RGB{R: 180, G: 220, B: 140},
|
||||
SyntaxKeyword: terminal.RGB{R: 180, G: 140, B: 220},
|
||||
SyntaxType: terminal.RGB{R: 80, G: 200, B: 200},
|
||||
SyntaxNumber: terminal.RGB{R: 220, G: 180, B: 120},
|
||||
SyntaxSymbol: terminal.RGB{R: 220, G: 180, B: 80},
|
||||
}
|
||||
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},
|
||||
}
|
||||
|
||||
|
||||
+27
-23
@@ -1,6 +1,9 @@
|
||||
package tui
|
||||
|
||||
import "github.com/lixenwraith/terminal"
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// ToastPosition specifies where toast renders
|
||||
type ToastPosition uint8
|
||||
@@ -46,26 +49,26 @@ var ToastIcons = map[ToastSeverity]rune{
|
||||
}
|
||||
|
||||
// ToastColors default colors per severity
|
||||
var ToastColors = map[ToastSeverity]struct{ Fg, Bg, Icon terminal.RGB }{
|
||||
var ToastColors = map[ToastSeverity]struct{ Fg, Bg, Icon color.RGB }{
|
||||
ToastInfo: {
|
||||
Fg: terminal.RGB{R: 200, G: 200, B: 200},
|
||||
Bg: terminal.RGB{R: 40, G: 40, B: 50},
|
||||
Icon: terminal.RGB{R: 100, G: 150, B: 255},
|
||||
Fg: color.RGB{R: 200, G: 200, B: 200},
|
||||
Bg: color.RGB{R: 40, G: 40, B: 50},
|
||||
Icon: color.RGB{R: 100, G: 150, B: 255},
|
||||
},
|
||||
ToastSuccess: {
|
||||
Fg: terminal.RGB{R: 220, G: 255, B: 220},
|
||||
Bg: terminal.RGB{R: 30, G: 60, B: 30},
|
||||
Icon: terminal.RGB{R: 80, G: 220, B: 80},
|
||||
Fg: color.RGB{R: 220, G: 255, B: 220},
|
||||
Bg: color.RGB{R: 30, G: 60, B: 30},
|
||||
Icon: color.RGB{R: 80, G: 220, B: 80},
|
||||
},
|
||||
ToastWarning: {
|
||||
Fg: terminal.RGB{R: 255, G: 240, B: 200},
|
||||
Bg: terminal.RGB{R: 60, G: 50, B: 20},
|
||||
Icon: terminal.RGB{R: 255, G: 200, B: 60},
|
||||
Fg: color.RGB{R: 255, G: 240, B: 200},
|
||||
Bg: color.RGB{R: 60, G: 50, B: 20},
|
||||
Icon: color.RGB{R: 255, G: 200, B: 60},
|
||||
},
|
||||
ToastError: {
|
||||
Fg: terminal.RGB{R: 255, G: 220, B: 220},
|
||||
Bg: terminal.RGB{R: 60, G: 25, B: 25},
|
||||
Icon: terminal.RGB{R: 255, G: 80, B: 80},
|
||||
Fg: color.RGB{R: 255, G: 220, B: 220},
|
||||
Bg: color.RGB{R: 60, G: 25, B: 25},
|
||||
Icon: color.RGB{R: 255, G: 80, B: 80},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -81,9 +84,9 @@ type ToastOpts struct {
|
||||
Padding int // Horizontal padding, default 1
|
||||
MarginX int // Margin from edge for floating positions
|
||||
MarginY int // Margin from edge for floating positions
|
||||
CustomFg terminal.RGB
|
||||
CustomBg terminal.RGB
|
||||
CustomIcon terminal.RGB
|
||||
CustomFg color.RGB
|
||||
CustomBg color.RGB
|
||||
CustomIcon color.RGB
|
||||
}
|
||||
|
||||
// DefaultToastOpts returns sensible defaults
|
||||
@@ -109,13 +112,13 @@ func (r Region) Toast(opts ToastOpts) Region {
|
||||
|
||||
// Resolve colors
|
||||
fg, bg, iconFg := opts.CustomFg, opts.CustomBg, opts.CustomIcon
|
||||
if fg == (terminal.RGB{}) {
|
||||
if fg == (color.RGB{}) {
|
||||
fg = ToastColors[opts.Severity].Fg
|
||||
}
|
||||
if bg == (terminal.RGB{}) {
|
||||
if bg == (color.RGB{}) {
|
||||
bg = ToastColors[opts.Severity].Bg
|
||||
}
|
||||
if iconFg == (terminal.RGB{}) {
|
||||
if iconFg == (color.RGB{}) {
|
||||
iconFg = ToastColors[opts.Severity].Icon
|
||||
}
|
||||
|
||||
@@ -218,7 +221,7 @@ func (r Region) Toast(opts ToastOpts) Region {
|
||||
case ToastStyleShadow:
|
||||
// Shadow offset
|
||||
shadowRegion := r.Sub(toastX+1, toastY+1, toastW, toastH)
|
||||
shadowRegion.Fill(terminal.RGB{R: 10, G: 10, B: 15})
|
||||
shadowRegion.Fill(color.RGB{R: 10, G: 10, B: 15})
|
||||
toastRegion.BoxFilled(LineSingle, fg, bg)
|
||||
r.renderToastContent(toastRegion.Inset(1), opts, fg, bg, iconFg, 0)
|
||||
}
|
||||
@@ -226,7 +229,7 @@ func (r Region) Toast(opts ToastOpts) Region {
|
||||
return toastRegion
|
||||
}
|
||||
|
||||
func (r Region) renderToastContent(content Region, opts ToastOpts, fg, bg, iconFg terminal.RGB, _ int) {
|
||||
func (r Region) renderToastContent(content Region, opts ToastOpts, fg, bg, iconFg color.RGB, _ int) {
|
||||
if content.W < 1 || content.H < 1 {
|
||||
return
|
||||
}
|
||||
@@ -298,4 +301,5 @@ func (t *ToastState) Show(opts ToastOpts, frames int) {
|
||||
t.Opts = opts
|
||||
t.FramesLeft = frames
|
||||
t.Visible = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-23
@@ -1,6 +1,9 @@
|
||||
package tui
|
||||
|
||||
import "github.com/lixenwraith/terminal"
|
||||
import (
|
||||
"github.com/lixenwraith/color"
|
||||
"github.com/lixenwraith/terminal"
|
||||
)
|
||||
|
||||
// ExpandIcon chars
|
||||
const (
|
||||
@@ -30,20 +33,20 @@ type TreeNode struct {
|
||||
Key string // Unique identifier for expansion state
|
||||
Label string // Display text
|
||||
Icon rune // Custom icon, 0 = auto (▶/▼ for expandable, • for leaf)
|
||||
IconFg terminal.RGB
|
||||
IconFg color.RGB
|
||||
Expandable bool // Has children
|
||||
Expanded bool // Currently expanded
|
||||
Depth int // Nesting level (0 = root)
|
||||
Check CheckState // Optional checkbox, CheckNone to skip
|
||||
CheckFg terminal.RGB
|
||||
CheckFg color.RGB
|
||||
Style Style // Text styling
|
||||
IsLast bool // Last sibling at this depth (for tree lines)
|
||||
Data any // Application payload
|
||||
|
||||
Suffix string // Secondary text after label (e.g., "(5 files)")
|
||||
SuffixStyle Style // Styling for suffix, zero = dimmed version of Style
|
||||
Badge rune // Icon between checkbox and label (e.g., '★'), 0 = none
|
||||
BadgeFg terminal.RGB // Badge color
|
||||
Suffix string // Secondary text after label (e.g., "(5 files)")
|
||||
SuffixStyle Style // Styling for suffix, zero = dimmed version of Style
|
||||
Badge rune // Icon between checkbox and label (e.g., '★'), 0 = none
|
||||
BadgeFg color.RGB // Badge color
|
||||
}
|
||||
|
||||
// ancestorHasMoreSiblings checks if there are more nodes at given depth after idx
|
||||
@@ -59,12 +62,12 @@ func (r Region) ancestorHasMoreSiblings(nodes []TreeNode, idx, depth int) bool {
|
||||
|
||||
// TreeOpts configures tree rendering
|
||||
type TreeOpts struct {
|
||||
CursorBg terminal.RGB
|
||||
DefaultBg terminal.RGB
|
||||
CursorBg color.RGB
|
||||
DefaultBg color.RGB
|
||||
IndentWidth int // Cells per depth, default 2
|
||||
IconWidth int // Width for icon column, default 2
|
||||
LineMode TreeLineMode
|
||||
LineFg terminal.RGB
|
||||
LineFg color.RGB
|
||||
}
|
||||
|
||||
// DefaultTreeOpts returns sensible defaults
|
||||
@@ -73,7 +76,7 @@ func DefaultTreeOpts() TreeOpts {
|
||||
IndentWidth: 2,
|
||||
IconWidth: 2,
|
||||
LineMode: TreeLinesNone,
|
||||
LineFg: terminal.RGB{R: 80, G: 80, B: 100},
|
||||
LineFg: color.RGB{R: 80, G: 80, B: 100},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +97,7 @@ func (r Region) Tree(nodes []TreeNode, cursor, scroll int, opts TreeOpts) int {
|
||||
}
|
||||
|
||||
lineFg := opts.LineFg
|
||||
if lineFg == (terminal.RGB{}) {
|
||||
if lineFg == (color.RGB{}) {
|
||||
lineFg = DefaultTheme.Border
|
||||
}
|
||||
|
||||
@@ -114,7 +117,7 @@ func (r Region) Tree(nodes []TreeNode, cursor, scroll int, opts TreeOpts) int {
|
||||
}
|
||||
|
||||
for x := 0; x < r.W; x++ {
|
||||
r.Cell(x, y, ' ', terminal.RGB{}, bg, terminal.AttrNone)
|
||||
r.Cell(x, y, ' ', color.RGB{}, bg, terminal.AttrNone)
|
||||
}
|
||||
|
||||
x := 0
|
||||
@@ -139,7 +142,7 @@ func (r Region) Tree(nodes []TreeNode, cursor, scroll int, opts TreeOpts) int {
|
||||
icon = IconBullet
|
||||
}
|
||||
}
|
||||
if iconFg == (terminal.RGB{}) {
|
||||
if iconFg == (color.RGB{}) {
|
||||
iconFg = lineFg
|
||||
}
|
||||
if x < r.W {
|
||||
@@ -148,10 +151,10 @@ func (r Region) Tree(nodes []TreeNode, cursor, scroll int, opts TreeOpts) int {
|
||||
x += iconW
|
||||
|
||||
// Checkbox
|
||||
if node.Check != CheckNone || node.CheckFg != (terminal.RGB{}) {
|
||||
if node.Check != CheckNone || node.CheckFg != (color.RGB{}) {
|
||||
if x+3 <= r.W {
|
||||
checkFg := node.CheckFg
|
||||
if checkFg == (terminal.RGB{}) {
|
||||
if checkFg == (color.RGB{}) {
|
||||
checkFg = node.Style.Fg
|
||||
}
|
||||
var ch rune
|
||||
@@ -176,7 +179,7 @@ func (r Region) Tree(nodes []TreeNode, cursor, scroll int, opts TreeOpts) int {
|
||||
if node.Badge != 0 {
|
||||
if x < r.W {
|
||||
badgeFg := node.BadgeFg
|
||||
if badgeFg == (terminal.RGB{}) {
|
||||
if badgeFg == (color.RGB{}) {
|
||||
badgeFg = node.Style.Fg
|
||||
}
|
||||
r.Cell(x, y, node.Badge, badgeFg, bg, terminal.AttrNone)
|
||||
@@ -186,7 +189,7 @@ func (r Region) Tree(nodes []TreeNode, cursor, scroll int, opts TreeOpts) int {
|
||||
|
||||
// Label
|
||||
style := node.Style
|
||||
if style.Bg == (terminal.RGB{}) {
|
||||
if style.Bg == (color.RGB{}) {
|
||||
style.Bg = bg
|
||||
}
|
||||
|
||||
@@ -214,15 +217,15 @@ func (r Region) Tree(nodes []TreeNode, cursor, scroll int, opts TreeOpts) int {
|
||||
// Suffix
|
||||
if suffix != "" {
|
||||
suffixStyle := node.SuffixStyle
|
||||
if suffixStyle.Fg == (terminal.RGB{}) {
|
||||
if suffixStyle.Fg == (color.RGB{}) {
|
||||
// Default: dimmed version of label style
|
||||
suffixStyle.Fg = terminal.RGB{
|
||||
suffixStyle.Fg = color.RGB{
|
||||
R: node.Style.Fg.R / 2,
|
||||
G: node.Style.Fg.G / 2,
|
||||
B: node.Style.Fg.B / 2,
|
||||
}
|
||||
}
|
||||
if suffixStyle.Bg == (terminal.RGB{}) {
|
||||
if suffixStyle.Bg == (color.RGB{}) {
|
||||
suffixStyle.Bg = bg
|
||||
}
|
||||
r.TextStyled(x, y, suffix, suffixStyle)
|
||||
@@ -235,7 +238,7 @@ func (r Region) Tree(nodes []TreeNode, cursor, scroll int, opts TreeOpts) int {
|
||||
}
|
||||
|
||||
// renderTreeLines draws connector lines for tree structure, returns x position after lines
|
||||
func (r Region) renderTreeLines(y, startX int, node TreeNode, nodes []TreeNode, idx, scroll, indentW int, fg terminal.RGB, bg terminal.RGB) int {
|
||||
func (r Region) renderTreeLines(y, startX int, node TreeNode, nodes []TreeNode, idx, scroll, indentW int, fg color.RGB, bg color.RGB) int {
|
||||
x := startX
|
||||
|
||||
for d := 0; d < node.Depth; d++ {
|
||||
@@ -334,4 +337,5 @@ func FindPrevSiblingIndex(nodes []TreeNode, idx int) int {
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user