diff --git a/CHANGELOG b/CHANGELOG index 692d961..c81945e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ v1.5.6 (WIP) - Add TrueColorTags option and do not use TrueColor tag values by default - Add TextView.SetHighlightForegroundColor and TextView.SetHighlightBackgroundColor +- Add DropDown.SetDropDownOpenSymbolRune +- Add DropDown.SetAlwaysDrawDropDownSymbol (DropDown symbols are now only drawn when focused by default) - Draw additional accents when rendering a list divider - Update List, Table and TreeView to not handle Tab or Backtab - Allow specifying TabbedPanels switcher height diff --git a/button.go b/button.go index 9587808..2c3025b 100644 --- a/button.go +++ b/button.go @@ -4,7 +4,6 @@ import ( "sync" "github.com/gdamore/tcell/v2" - "github.com/lucasb-eyer/go-colorful" ) // Button is labeled box that triggers an action when selected. @@ -37,24 +36,13 @@ type Button struct { func NewButton(label string) *Button { box := NewBox() box.SetRect(0, 0, TaggedStringWidth(label)+4, 1) - bg := Styles.PrimaryTextColor - if bg == tcell.ColorDefault { - r, g, b := Styles.InverseTextColor.RGB() - c := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255} - _, _, li := c.Hcl() - if li < .5 { - bg = tcell.ColorWhite.TrueColor() - } else { - bg = tcell.ColorBlack.TrueColor() - } - } - box.SetBackgroundColor(bg) + box.SetBackgroundColor(Styles.ContrastBackgroundColor) return &Button{ Box: box, label: []byte(label), - labelColor: Styles.InverseTextColor, + labelColor: Styles.PrimaryTextColor, labelColorFocused: Styles.PrimaryTextColor, - backgroundColorFocused: Styles.ContrastBackgroundColor, + backgroundColorFocused: Styles.MoreContrastBackgroundColor, } } diff --git a/demos/presentation/colors.go b/demos/presentation/colors.go index 7eea4c9..0c000be 100644 --- a/demos/presentation/colors.go +++ b/demos/presentation/colors.go @@ -18,7 +18,7 @@ The [black:red]tags [black:green]look [black:yellow]like [::u]this: [#00ff00[]` // Colors demonstrates how to use colors. -func Colors(nextSlide func()) (title string, content cview.Primitive) { +func Colors(nextSlide func()) (title string, info string, content cview.Primitive) { tv := cview.NewTextView() tv.SetBorder(true) tv.SetTitle("A [red]c[yellow]o[green]l[darkcyan]o[blue]r[darkmagenta]f[red]u[yellow]l[white] [black:red]c[:yellow]o[:green]l[:darkcyan]o[:blue]r[:darkmagenta]f[:red]u[:yellow]l[white:] [::bu]title") @@ -29,5 +29,5 @@ func Colors(nextSlide func()) (title string, content cview.Primitive) { nextSlide() }) - return "Colors", Center(44, 16, tv) + return "Colors", "", Center(44, 16, tv) } diff --git a/demos/presentation/cover.go b/demos/presentation/cover.go index 08fb82d..8791da1 100644 --- a/demos/presentation/cover.go +++ b/demos/presentation/cover.go @@ -16,14 +16,10 @@ const logo = ` ======= == === ======== ==== ==== ` -const ( - subtitle = `Terminal-based user interface toolkit` - mouse = `Navigate with your keyboard or mouse.` - navigation = `Next slide: Ctrl-N Previous: Ctrl-P Exit: Ctrl-C` -) +const subtitle = "Terminal-based user interface toolkit" // Cover returns the cover page. -func Cover(nextSlide func()) (title string, content cview.Primitive) { +func Cover(nextSlide func()) (title string, info string, content cview.Primitive) { // What's the size of the logo? lines := strings.Split(logo, "\n") logoWidth := 0 @@ -43,10 +39,7 @@ func Cover(nextSlide func()) (title string, content cview.Primitive) { // Create a frame for the subtitle and navigation infos. frame := cview.NewFrame(cview.NewBox()) frame.SetBorders(0, 0, 0, 0, 0, 0) - frame.AddText(subtitle, true, cview.AlignCenter, tcell.ColorWhite.TrueColor()) - frame.AddText("", true, cview.AlignCenter, tcell.ColorWhite.TrueColor()) - frame.AddText(mouse, true, cview.AlignCenter, tcell.ColorDarkMagenta.TrueColor()) - frame.AddText(navigation, true, cview.AlignCenter, tcell.ColorDarkMagenta.TrueColor()) + frame.AddText(subtitle, true, cview.AlignCenter, tcell.ColorDarkMagenta.TrueColor()) // Create a Flex layout that centers the logo and subtitle. subFlex := cview.NewFlex() @@ -60,5 +53,5 @@ func Cover(nextSlide func()) (title string, content cview.Primitive) { flex.AddItem(subFlex, logoHeight, 1, true) flex.AddItem(frame, 0, 10, false) - return "Start", flex + return "Start", appInfo, flex } diff --git a/demos/presentation/end.go b/demos/presentation/end.go index 63f6b17..0adfcd5 100644 --- a/demos/presentation/end.go +++ b/demos/presentation/end.go @@ -8,12 +8,12 @@ import ( ) // End shows the final slide. -func End(nextSlide func()) (title string, content cview.Primitive) { +func End(nextSlide func()) (title string, info string, content cview.Primitive) { textView := cview.NewTextView() textView.SetDoneFunc(func(key tcell.Key) { nextSlide() }) url := "https://code.rocketnine.space/tslocum/cview" fmt.Fprint(textView, url) - return "End", Center(len(url), 1, textView) + return "End", "", Center(len(url), 1, textView) } diff --git a/demos/presentation/flex.go b/demos/presentation/flex.go index 7b2f6cd..d6a160c 100644 --- a/demos/presentation/flex.go +++ b/demos/presentation/flex.go @@ -13,7 +13,7 @@ func demoBox(title string) *cview.Box { } // Flex demonstrates flexbox layout. -func Flex(nextSlide func()) (title string, content cview.Primitive) { +func Flex(nextSlide func()) (title string, info string, content cview.Primitive) { modalShown := false panels := cview.NewPanels() @@ -50,5 +50,5 @@ func Flex(nextSlide func()) (title string, content cview.Primitive) { panels.AddPanel("flex", flex, true, true) panels.AddPanel("modal", modal, false, false) - return "Flex", panels + return "Flex", "", panels } diff --git a/demos/presentation/form.go b/demos/presentation/form.go index 02a438e..53a9dce 100644 --- a/demos/presentation/form.go +++ b/demos/presentation/form.go @@ -29,7 +29,7 @@ const form = `[green]package[white] main }` // Form demonstrates forms. -func Form(nextSlide func()) (title string, content cview.Primitive) { +func Form(nextSlide func()) (title string, info string, content cview.Primitive) { f := cview.NewForm() f.AddInputField("First name:", "", 20, nil, nil) f.AddInputField("Last name:", "", 20, nil, nil) @@ -40,5 +40,5 @@ func Form(nextSlide func()) (title string, content cview.Primitive) { f.AddButton("Cancel", nextSlide) f.SetBorder(true) f.SetTitle("Employee Information") - return "Form", Code(f, 36, 15, form) + return "Form", formInfo, Code(f, 36, 15, form) } diff --git a/demos/presentation/grid.go b/demos/presentation/grid.go index 440e3df..32ae13f 100644 --- a/demos/presentation/grid.go +++ b/demos/presentation/grid.go @@ -6,7 +6,7 @@ import ( ) // Grid demonstrates the grid layout. -func Grid(nextSlide func()) (title string, content cview.Primitive) { +func Grid(nextSlide func()) (title string, info string, content cview.Primitive) { modalShown := false panels := cview.NewPanels() @@ -57,5 +57,5 @@ func Grid(nextSlide func()) (title string, content cview.Primitive) { panels.AddPanel("grid", grid, true, true) panels.AddPanel("modal", modal, false, false) - return "Grid", panels + return "Grid", "", panels } diff --git a/demos/presentation/inputfield.go b/demos/presentation/inputfield.go index a7129c4..23a8588 100644 --- a/demos/presentation/inputfield.go +++ b/demos/presentation/inputfield.go @@ -30,12 +30,12 @@ const inputField = `[green]package[white] main }` // InputField demonstrates the InputField. -func InputField(nextSlide func()) (title string, content cview.Primitive) { +func InputField(nextSlide func()) (title string, info string, content cview.Primitive) { input := cview.NewInputField() input.SetLabel("Enter a number: ") input.SetAcceptanceFunc(cview.InputFieldInteger) input.SetDoneFunc(func(key tcell.Key) { nextSlide() }) - return "InputField", Code(input, 30, 1, inputField) + return "InputField", "", Code(input, 30, 1, inputField) } diff --git a/demos/presentation/introduction.go b/demos/presentation/introduction.go index 805c363..dc8ad70 100644 --- a/demos/presentation/introduction.go +++ b/demos/presentation/introduction.go @@ -3,7 +3,7 @@ package main import "code.rocketnine.space/tslocum/cview" // Introduction returns a cview.List with the highlights of the cview package. -func Introduction(nextSlide func()) (title string, content cview.Primitive) { +func Introduction(nextSlide func()) (title string, info string, content cview.Primitive) { list := cview.NewList() listText := [][]string{ @@ -58,5 +58,5 @@ func Introduction(nextSlide func()) (title string, content cview.Primitive) { }) reset() - return "Introduction", Center(80, 12, list) + return "Introduction", listInfo, Center(80, 12, list) } diff --git a/demos/presentation/main.go b/demos/presentation/main.go index cabd99a..13f6a0e 100644 --- a/demos/presentation/main.go +++ b/demos/presentation/main.go @@ -24,10 +24,19 @@ import ( "github.com/gdamore/tcell/v2" ) -// Slide is a function which returns the slide's main primitive and its title. -// It receives a "nextSlide" function which can be called to advance the -// presentation to the next slide. -type Slide func(nextSlide func()) (title string, content cview.Primitive) +const ( + appInfo = "Next slide: Ctrl-N Previous: Ctrl-P Exit: Ctrl-C (Navigate with your keyboard or mouse)" + listInfo = "Next item: J, Down Previous item: K, Up Open context menu: Alt+Enter" + textViewInfo = "Scroll down: J, Down, PageDown Scroll up: K, Up, PageUp" + sliderInfo = "Decrease: H, J, Left, Down Increase: K, L, Right, Up" + formInfo = "Next field: Tab Previous field: Shift+Tab Select: Enter" + windowInfo = "Windows may be dragged an resized using the mouse." +) + +// Slide is a function which returns the slide's title, any applicable +// information and its main primitive, its. It receives a "nextSlide" function +// which can be called to advance the presentation to the next slide. +type Slide func(nextSlide func()) (title string, info string, content cview.Primitive) // The application. var app = cview.NewApplication() @@ -83,8 +92,21 @@ func main() { for index, slide := range slides { slideRegions = append(slideRegions, cursor) - title, primitive := slide(nextSlide) - panels.AddTab(strconv.Itoa(index), title, primitive) + title, info, primitive := slide(nextSlide) + + h := cview.NewTextView() + if info != "" { + h.SetDynamicColors(true) + h.SetText(" [" + cview.ColorHex(cview.Styles.SecondaryTextColor) + "]Info:[-] " + info) + } + + // Create a Flex layout that centers the logo and subtitle. + f := cview.NewFlex() + f.SetDirection(cview.FlexRow) + f.AddItem(h, 1, 1, false) + f.AddItem(primitive, 0, 1, true) + + panels.AddTab(strconv.Itoa(index), title, f) cursor += len(title) + 4 } diff --git a/demos/presentation/slider.go b/demos/presentation/slider.go index 035722c..856f32c 100644 --- a/demos/presentation/slider.go +++ b/demos/presentation/slider.go @@ -32,7 +32,7 @@ const sliderCode = `[green]package[white] main }` // Slider demonstrates the Slider. -func Slider(nextSlide func()) (title string, content cview.Primitive) { +func Slider(nextSlide func()) (title string, info string, content cview.Primitive) { slider := cview.NewSlider() slider.SetLabel("Volume: 0%") slider.SetChangedFunc(func(value int) { @@ -41,5 +41,5 @@ func Slider(nextSlide func()) (title string, content cview.Primitive) { slider.SetDoneFunc(func(key tcell.Key) { nextSlide() }) - return "Slider", Code(slider, 30, 1, sliderCode) + return "Slider", sliderInfo, Code(slider, 30, 1, sliderCode) } diff --git a/demos/presentation/table.go b/demos/presentation/table.go index 90ff710..6545024 100644 --- a/demos/presentation/table.go +++ b/demos/presentation/table.go @@ -248,7 +248,7 @@ const tableSelectCell = `[green]func[white] [yellow]main[white]() { }` // Table demonstrates the Table. -func Table(nextSlide func()) (title string, content cview.Primitive) { +func Table(nextSlide func()) (title string, info string, content cview.Primitive) { table := cview.NewTable() table.SetFixed(1, 1) for row, line := range strings.Split(tableData, "\n") { @@ -378,5 +378,5 @@ func Table(nextSlide func()) (title string, content cview.Primitive) { flex.AddItem(subFlex, 0, 1, true) flex.AddItem(code, codeWidth, 1, false) - return "Table", flex + return "Table", "", flex } diff --git a/demos/presentation/textview.go b/demos/presentation/textview.go index cbcf2ba..f737ab1 100644 --- a/demos/presentation/textview.go +++ b/demos/presentation/textview.go @@ -30,7 +30,7 @@ const textView1 = `[green]func[white] [yellow]main[white]() { }` // TextView1 demonstrates the basic text view. -func TextView1(nextSlide func()) (title string, content cview.Primitive) { +func TextView1(nextSlide func()) (title string, info string, content cview.Primitive) { textView := cview.NewTextView() textView.SetTextColor(tcell.ColorYellow.TrueColor()) textView.SetDoneFunc(func(key tcell.Key) { @@ -57,7 +57,7 @@ func TextView1(nextSlide func()) (title string, content cview.Primitive) { textView.SetBorder(true) textView.SetTitle("TextView implements io.Writer") textView.ScrollToEnd() - return "TextView 1", Code(textView, 36, 13, textView1) + return "TextView 1", textViewInfo, Code(textView, 36, 13, textView1) } const textView2 = `[green]package[white] main @@ -108,7 +108,7 @@ const textView2 = `[green]package[white] main }` // TextView2 demonstrates the extended text view. -func TextView2(nextSlide func()) (title string, content cview.Primitive) { +func TextView2(nextSlide func()) (title string, info string, content cview.Primitive) { codeView := cview.NewTextView() codeView.SetWrap(false) fmt.Fprint(codeView, textView2) @@ -159,5 +159,5 @@ func TextView2(nextSlide func()) (title string, content cview.Primitive) { flex.AddItem(textView, 0, 1, true) flex.AddItem(codeView, 0, 1, false) - return "TextView 2", flex + return "TextView 2", textViewInfo, flex } diff --git a/demos/presentation/treeview.go b/demos/presentation/treeview.go index cf67971..ca9e1a1 100644 --- a/demos/presentation/treeview.go +++ b/demos/presentation/treeview.go @@ -118,7 +118,7 @@ var rootNode = &node{ }} // TreeView demonstrates the tree view. -func TreeView(nextSlide func()) (title string, content cview.Primitive) { +func TreeView(nextSlide func()) (title string, info string, content cview.Primitive) { treeNextSlide = nextSlide tree.SetBorder(true) tree.SetTitle("TreeView") @@ -161,5 +161,5 @@ func TreeView(nextSlide func()) (title string, content cview.Primitive) { flex.AddItem(tree, 0, 1, true) flex.AddItem(treeCode, codeWidth, 1, false) - return "TreeView", flex + return "TreeView", "", flex } diff --git a/demos/presentation/window.go b/demos/presentation/window.go index cea9cb2..4285b9e 100644 --- a/demos/presentation/window.go +++ b/demos/presentation/window.go @@ -7,7 +7,7 @@ import ( const loremIpsumText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." // Window returns the window page. -func Window(nextSlide func()) (title string, content cview.Primitive) { +func Window(nextSlide func()) (title string, info string, content cview.Primitive) { wm := cview.NewWindowManager() list := cview.NewList() @@ -34,5 +34,5 @@ func Window(nextSlide func()) (title string, content cview.Primitive) { wm.Add(w1, w2) - return "Window", wm + return "Window", windowInfo, wm } diff --git a/dropdown.go b/dropdown.go index 3f1225a..5dd88ca 100644 --- a/dropdown.go +++ b/dropdown.go @@ -134,9 +134,15 @@ type DropDown struct { // The chars to show when the option's text gets shortened. abbreviationChars string - // The symbol to draw at the end of the field. + // The symbol to draw at the end of the field when closed. dropDownSymbol rune + // The symbol to draw at the end of the field when opened. + dropDownOpenSymbol rune + + // A flag that determines whether the drop down symbol is always drawn. + alwaysDrawDropDownSymbol bool + sync.RWMutex } @@ -144,7 +150,7 @@ type DropDown struct { func NewDropDown() *DropDown { list := NewList() list.ShowSecondaryText(false) - list.SetMainTextColor(Styles.PrimitiveBackgroundColor) + list.SetMainTextColor(Styles.SecondaryTextColor) list.SetSelectedTextColor(Styles.PrimitiveBackgroundColor) list.SetSelectedBackgroundColor(Styles.PrimaryTextColor) list.SetHighlightFullLine(true) @@ -159,6 +165,7 @@ func NewDropDown() *DropDown { fieldTextColor: Styles.PrimaryTextColor, prefixTextColor: Styles.ContrastSecondaryTextColor, dropDownSymbol: Styles.DropDownSymbol, + dropDownOpenSymbol: Styles.DropDownOpenSymbol, abbreviationChars: Styles.DropDownAbbreviationChars, labelColorFocused: ColorUnset, fieldBackgroundColorFocused: ColorUnset, @@ -178,6 +185,22 @@ func (d *DropDown) SetDropDownSymbolRune(symbol rune) { d.dropDownSymbol = symbol } +// SetDropDownOpenSymbolRune sets the rune to be drawn at the end of the +// dropdown field to indicate that the a dropdown is open. +func (d *DropDown) SetDropDownOpenSymbolRune(symbol rune) { + d.Lock() + defer d.Unlock() + d.dropDownOpenSymbol = symbol +} + +// SetAlwaysDrawDropDownSymbol sets a flad that determines whether the drop +// down symbol is always drawn. The symbol is normally only drawn when focused. +func (d *DropDown) SetAlwaysDrawDropDownSymbol(alwaysDraw bool) { + d.Lock() + defer d.Unlock() + d.alwaysDrawDropDownSymbol = alwaysDraw +} + // SetCurrentOption sets the index of the currently selected option. This may // be a negative value to indicate that no option is currently selected. Calling // this function will also trigger the "selected" callback (if there is one). @@ -594,7 +617,13 @@ func (d *DropDown) Draw(screen tcell.Screen) { } // Draw drop-down symbol - screen.SetContent(x+fieldWidth-2, y, d.dropDownSymbol, nil, new(tcell.Style).Foreground(fieldTextColor).Background(fieldBackgroundColor)) + if d.alwaysDrawDropDownSymbol || d._hasFocus() { + symbol := d.dropDownSymbol + if d.open { + symbol = d.dropDownOpenSymbol + } + screen.SetContent(x+fieldWidth-2, y, symbol, nil, new(tcell.Style).Foreground(fieldTextColor).Background(fieldBackgroundColor)) + } // Draw options list. if hasFocus && d.open { @@ -742,6 +771,10 @@ func (d *DropDown) HasFocus() bool { d.RLock() defer d.RUnlock() + return d._hasFocus() +} + +func (d *DropDown) _hasFocus() bool { if d.open { return d.list.HasFocus() } diff --git a/form.go b/form.go index cf3f3b6..01d4ca4 100644 --- a/form.go +++ b/form.go @@ -151,14 +151,14 @@ func NewForm() *Form { Box: box, itemPadding: 1, labelColor: Styles.SecondaryTextColor, - fieldBackgroundColor: Styles.MoreContrastBackgroundColor, - fieldBackgroundColorFocused: Styles.ContrastBackgroundColor, - fieldTextColor: Styles.InverseTextColor, - fieldTextColorFocused: Styles.InverseTextColor, - buttonBackgroundColor: Styles.MoreContrastBackgroundColor, - buttonBackgroundColorFocused: Styles.ContrastBackgroundColor, - buttonTextColor: Styles.InverseTextColor, - buttonTextColorFocused: Styles.InverseTextColor, + fieldBackgroundColor: Styles.ContrastBackgroundColor, + fieldBackgroundColorFocused: Styles.MoreContrastBackgroundColor, + fieldTextColor: Styles.PrimaryTextColor, + fieldTextColorFocused: Styles.PrimaryTextColor, + buttonBackgroundColor: Styles.ContrastBackgroundColor, + buttonBackgroundColorFocused: Styles.MoreContrastBackgroundColor, + buttonTextColor: Styles.PrimaryTextColor, + buttonTextColorFocused: Styles.PrimaryTextColor, labelColorFocused: ColorUnset, } diff --git a/inputfield.go b/inputfield.go index 5cb4620..717089b 100644 --- a/inputfield.go +++ b/inputfield.go @@ -144,10 +144,10 @@ func NewInputField() *InputField { return &InputField{ Box: NewBox(), labelColor: Styles.SecondaryTextColor, - fieldBackgroundColor: Styles.PrimaryTextColor, - fieldBackgroundColorFocused: Styles.ContrastBackgroundColor, - fieldTextColor: Styles.ContrastPrimaryTextColor, - fieldTextColorFocused: Styles.ContrastPrimaryTextColor, + fieldBackgroundColor: Styles.ContrastBackgroundColor, + fieldBackgroundColorFocused: Styles.MoreContrastBackgroundColor, + fieldTextColor: Styles.PrimaryTextColor, + fieldTextColorFocused: Styles.PrimaryTextColor, placeholderTextColor: Styles.ContrastSecondaryTextColor, autocompleteListTextColor: Styles.PrimitiveBackgroundColor, autocompleteListBackgroundColor: Styles.MoreContrastBackgroundColor, diff --git a/slider.go b/slider.go index 3143446..f72a8a8 100644 --- a/slider.go +++ b/slider.go @@ -65,9 +65,9 @@ func NewSlider() *Slider { increment: 10, labelColor: Styles.SecondaryTextColor, fieldBackgroundColor: Styles.ContrastBackgroundColor, + fieldBackgroundColorFocused: Styles.MoreContrastBackgroundColor, fieldTextColor: Styles.PrimaryTextColor, labelColorFocused: ColorUnset, - fieldBackgroundColorFocused: ColorUnset, fieldTextColorFocused: ColorUnset, } return s diff --git a/styles.go b/styles.go index c65f631..2b0ea16 100644 --- a/styles.go +++ b/styles.go @@ -33,7 +33,8 @@ type Theme struct { // Drop down DropDownAbbreviationChars string // The chars to show when the option's text gets shortened. - DropDownSymbol rune // The symbol to draw at the end of the field. + DropDownSymbol rune // The symbol to draw at the end of the field when closed. + DropDownOpenSymbol rune // The symbol to draw at the end of the field when opened. // Scroll bar ScrollBarColor tcell.Color @@ -59,8 +60,8 @@ var Styles = Theme{ ContrastSecondaryTextColor: tcell.ColorLightSlateGray.TrueColor(), PrimitiveBackgroundColor: tcell.ColorBlack.TrueColor(), - ContrastBackgroundColor: tcell.ColorLimeGreen.TrueColor(), - MoreContrastBackgroundColor: tcell.ColorGreen.TrueColor(), + ContrastBackgroundColor: tcell.ColorGreen.TrueColor(), + MoreContrastBackgroundColor: tcell.ColorDarkGreen.TrueColor(), CheckBoxCheckedRune: 'X', @@ -70,7 +71,8 @@ var Styles = Theme{ ContextMenuPaddingRight: 1, DropDownAbbreviationChars: "...", - DropDownSymbol: '▼', + DropDownSymbol: '◀', + DropDownOpenSymbol: '▼', ScrollBarColor: tcell.ColorWhite.TrueColor(),