From 454d759a426585c3a567a0f283e0adce10e4c2a9 Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Sat, 5 Jun 2021 10:24:43 -0700 Subject: [PATCH] Add Button.SetCursorRune and CheckBox.SetCursorRune Cursors are now shown within Buttons and CheckBoxes by default. Resolves #62. --- CHANGELOG | 4 +++- README.md | 4 ++-- button.go | 21 +++++++++++++++++++-- checkbox.go | 26 ++++++++++++++++++++++---- demos/button/main.go | 1 - demos/presentation/introduction.go | 4 ++-- dropdown.go | 4 ++-- form.go | 8 ++++---- styles.go | 7 +++++++ textview.go | 9 +++++---- 10 files changed, 66 insertions(+), 22 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c81945e..cdfa792 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,10 @@ v1.5.6 (WIP) - Add TrueColorTags option and do not use TrueColor tag values by default - Add TextView.SetHighlightForegroundColor and TextView.SetHighlightBackgroundColor +- Add Button.SetCursorRune (cursors are now shown within Buttons when focused by default) +- Add CheckBox.SetCursorRune (cursors are now shown within CheckBoxes when focused by default) +- Add DropDown.SetAlwaysDrawDropDownSymbol (DropDown symbols are now shown only when focused by default) - 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/README.md b/README.md index f313e08..bc59eaf 100644 --- a/README.md +++ b/README.md @@ -67,10 +67,10 @@ func main() { ``` Examples are available via [godoc](https://docs.rocketnine.space/code.rocketnine.space/tslocum/cview#pkg-examples) -and in the [demos](https://code.rocketnine.space/tslocum/cview/src/branch/master/demos) subdirectory. +and in the [demos](https://code.rocketnine.space/tslocum/cview/src/branch/master/demos) directory. For a presentation highlighting the features of this package, compile and run -the program in the [demos/presentation](https://code.rocketnine.space/tslocum/cview/src/branch/master/demos/presentation) subdirectory. +the program in the [demos/presentation](https://code.rocketnine.space/tslocum/cview/src/branch/master/demos/presentation) directory. ## Documentation diff --git a/button.go b/button.go index 2c3025b..11ac130 100644 --- a/button.go +++ b/button.go @@ -4,6 +4,7 @@ import ( "sync" "github.com/gdamore/tcell/v2" + "github.com/mattn/go-runewidth" ) // Button is labeled box that triggers an action when selected. @@ -29,6 +30,9 @@ type Button struct { // key is provided indicating which key was pressed to leave (tab or backtab). blur func(tcell.Key) + // An optional rune which is drawn after the label when the button is focused. + cursorRune rune + sync.RWMutex } @@ -36,13 +40,14 @@ type Button struct { func NewButton(label string) *Button { box := NewBox() box.SetRect(0, 0, TaggedStringWidth(label)+4, 1) - box.SetBackgroundColor(Styles.ContrastBackgroundColor) + box.SetBackgroundColor(Styles.MoreContrastBackgroundColor) return &Button{ Box: box, label: []byte(label), labelColor: Styles.PrimaryTextColor, labelColorFocused: Styles.PrimaryTextColor, - backgroundColorFocused: Styles.MoreContrastBackgroundColor, + cursorRune: Styles.ButtonCursorRune, + backgroundColorFocused: Styles.ContrastBackgroundColor, } } @@ -79,6 +84,14 @@ func (b *Button) SetLabelColorFocused(color tcell.Color) { b.labelColorFocused = color } +// SetCursorRune sets the rune to show within the button when it is focused. +func (b *Button) SetCursorRune(rune rune) { + b.Lock() + defer b.Unlock() + + b.cursorRune = rune +} + // SetBackgroundColorFocused sets the background color of the button text when // the button is in focus. func (b *Button) SetBackgroundColorFocused(color tcell.Color) { @@ -141,6 +154,10 @@ func (b *Button) Draw(screen tcell.Screen) { labelColor := b.labelColor if b.focus.HasFocus() { labelColor = b.labelColorFocused + // Draw cursor. + if b.cursorRune != 0 { + Print(screen, []byte(string(b.cursorRune)), x+width-(width-runewidth.StringWidth(string(b.label)))/2+1, y, width, AlignLeft, labelColor) + } } Print(screen, b.label, x, y, width, AlignCenter, labelColor) } diff --git a/checkbox.go b/checkbox.go index 8af1e3c..bf37312 100644 --- a/checkbox.go +++ b/checkbox.go @@ -58,6 +58,9 @@ type CheckBox struct { // The rune to show when the checkbox is checked checkedRune rune + // An optional rune to show within the checkbox when it is focused + cursorRune rune + sync.RWMutex } @@ -66,11 +69,12 @@ func NewCheckBox() *CheckBox { return &CheckBox{ Box: NewBox(), labelColor: Styles.SecondaryTextColor, - fieldBackgroundColor: Styles.ContrastBackgroundColor, + fieldBackgroundColor: Styles.MoreContrastBackgroundColor, + fieldBackgroundColorFocused: Styles.ContrastBackgroundColor, fieldTextColor: Styles.PrimaryTextColor, checkedRune: Styles.CheckBoxCheckedRune, + cursorRune: Styles.CheckBoxCursorRune, labelColorFocused: ColorUnset, - fieldBackgroundColorFocused: ColorUnset, fieldTextColorFocused: ColorUnset, } } @@ -91,6 +95,14 @@ func (c *CheckBox) SetCheckedRune(rune rune) { c.checkedRune = rune } +// SetCursorRune sets the rune to show within the checkbox when it is focused. +func (c *CheckBox) SetCursorRune(rune rune) { + c.Lock() + defer c.Unlock() + + c.cursorRune = rune +} + // IsChecked returns whether or not the box is checked. func (c *CheckBox) IsChecked() bool { c.RLock() @@ -248,11 +260,13 @@ func (c *CheckBox) Draw(screen tcell.Screen) { c.Lock() defer c.Unlock() + hasFocus := c.GetFocusable().HasFocus() + // Select colors labelColor := c.labelColor fieldBackgroundColor := c.fieldBackgroundColor fieldTextColor := c.fieldTextColor - if c.GetFocusable().HasFocus() { + if hasFocus { if c.labelColorFocused != ColorUnset { labelColor = c.labelColorFocused } @@ -291,9 +305,13 @@ func (c *CheckBox) Draw(screen tcell.Screen) { if !c.checked { checkedRune = ' ' } + rightRune := ' ' + if c.cursorRune != 0 && hasFocus { + rightRune = c.cursorRune + } screen.SetContent(x, y, ' ', nil, fieldStyle) screen.SetContent(x+1, y, checkedRune, nil, fieldStyle) - screen.SetContent(x+2, y, ' ', nil, fieldStyle) + screen.SetContent(x+2, y, rightRune, nil, fieldStyle) if len(c.message) > 0 { Print(screen, c.message, x+4, y, len(c.message), AlignLeft, labelColor) diff --git a/demos/button/main.go b/demos/button/main.go index 0ad962d..696b265 100644 --- a/demos/button/main.go +++ b/demos/button/main.go @@ -8,7 +8,6 @@ func main() { app.EnableMouse(true) button := cview.NewButton("Hit Enter to close") - button.SetBorder(true) button.SetRect(0, 0, 22, 3) button.SetSelectedFunc(func() { app.Stop() diff --git a/demos/presentation/introduction.go b/demos/presentation/introduction.go index dc8ad70..0d098c4 100644 --- a/demos/presentation/introduction.go +++ b/demos/presentation/introduction.go @@ -8,8 +8,8 @@ func Introduction(nextSlide func()) (title string, info string, content cview.Pr listText := [][]string{ {"A Go package for terminal based UIs", "with a special focus on rich interactive widgets"}, - {"Based on github.com/gdamore/tcell", "Like termbox but better (see tcell docs)"}, - {"Designed to be simple", `"Hello world" is 5 lines of code`}, + {"Based on github.com/gdamore/tcell", "Supports Linux, FreeBSD, MacOS and Windows"}, + {"Designed to be simple", `"Hello world" is less than 20 lines of code`}, {"Good for data entry", `For charts, use "termui" - for low-level views, use "gocui" - ...`}, {"Supports context menus", "Right click on one of these items or press Alt+Enter"}, {"Extensive documentation", "Demo code is available for each widget"}, diff --git a/dropdown.go b/dropdown.go index 5dd88ca..8946da2 100644 --- a/dropdown.go +++ b/dropdown.go @@ -154,14 +154,14 @@ func NewDropDown() *DropDown { list.SetSelectedTextColor(Styles.PrimitiveBackgroundColor) list.SetSelectedBackgroundColor(Styles.PrimaryTextColor) list.SetHighlightFullLine(true) - list.SetBackgroundColor(Styles.MoreContrastBackgroundColor) + list.SetBackgroundColor(Styles.ContrastBackgroundColor) d := &DropDown{ Box: NewBox(), currentOption: -1, list: list, labelColor: Styles.SecondaryTextColor, - fieldBackgroundColor: Styles.ContrastBackgroundColor, + fieldBackgroundColor: Styles.MoreContrastBackgroundColor, fieldTextColor: Styles.PrimaryTextColor, prefixTextColor: Styles.ContrastSecondaryTextColor, dropDownSymbol: Styles.DropDownSymbol, diff --git a/form.go b/form.go index 01d4ca4..ab9f2d0 100644 --- a/form.go +++ b/form.go @@ -151,12 +151,12 @@ func NewForm() *Form { Box: box, itemPadding: 1, labelColor: Styles.SecondaryTextColor, - fieldBackgroundColor: Styles.ContrastBackgroundColor, - fieldBackgroundColorFocused: Styles.MoreContrastBackgroundColor, + fieldBackgroundColor: Styles.MoreContrastBackgroundColor, + fieldBackgroundColorFocused: Styles.ContrastBackgroundColor, fieldTextColor: Styles.PrimaryTextColor, fieldTextColorFocused: Styles.PrimaryTextColor, - buttonBackgroundColor: Styles.ContrastBackgroundColor, - buttonBackgroundColorFocused: Styles.MoreContrastBackgroundColor, + buttonBackgroundColor: Styles.MoreContrastBackgroundColor, + buttonBackgroundColorFocused: Styles.ContrastBackgroundColor, buttonTextColor: Styles.PrimaryTextColor, buttonTextColorFocused: Styles.PrimaryTextColor, labelColorFocused: ColorUnset, diff --git a/styles.go b/styles.go index 2b0ea16..52d54bc 100644 --- a/styles.go +++ b/styles.go @@ -22,8 +22,12 @@ type Theme struct { ContrastBackgroundColor tcell.Color // Background color for contrasting elements. MoreContrastBackgroundColor tcell.Color // Background color for even more contrasting elements. + // Button + ButtonCursorRune rune // The symbol to draw at the end of button labels when focused. + // Check box CheckBoxCheckedRune rune + CheckBoxCursorRune rune // The symbol to draw within the checkbox when focused. // Context menu ContextMenuPaddingTop int @@ -63,7 +67,10 @@ var Styles = Theme{ ContrastBackgroundColor: tcell.ColorGreen.TrueColor(), MoreContrastBackgroundColor: tcell.ColorDarkGreen.TrueColor(), + ButtonCursorRune: '◀', + CheckBoxCheckedRune: 'X', + CheckBoxCursorRune: '◀', ContextMenuPaddingTop: 0, ContextMenuPaddingBottom: 0, diff --git a/textview.go b/textview.go index ec0a983..3771acf 100644 --- a/textview.go +++ b/textview.go @@ -14,14 +14,15 @@ import ( ) var ( - openColorRegex = regexp.MustCompile(`\[([a-zA-Z]*|#[0-9a-zA-Z]*)$`) - openRegionRegex = regexp.MustCompile(`\["[a-zA-Z0-9_,;: \-\.]*"?$`) - newLineRegex = regexp.MustCompile(`\r?\n`) - // TabSize is the number of spaces with which a tab character will be replaced. TabSize = 4 ) +var ( + openColorRegex = regexp.MustCompile(`\[([a-zA-Z]*|#[0-9a-zA-Z]*)$`) + openRegionRegex = regexp.MustCompile(`\["[a-zA-Z0-9_,;: \-\.]*"?$`) +) + // textViewIndex contains information about each line displayed in the text // view. type textViewIndex struct {