From 3670319cd675fca4eea4034226e0e1a07c409065 Mon Sep 17 00:00:00 2001 From: Oliver <480930+rivo@users.noreply.github.com> Date: Wed, 27 Dec 2017 16:04:21 +0100 Subject: [PATCH] Fixed a number of bugs and added missing useful functions. --- application.go | 27 +++++++++++++++++++++++++- button.go | 18 ++++++++++++----- frame.go | 2 +- inputfield.go | 44 +----------------------------------------- pages.go | 52 ++++++++++++++++++++++++++++++++++++++++++++++++-- textview.go | 18 +++++++++++++++++ util.go | 42 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 151 insertions(+), 52 deletions(-) diff --git a/application.go b/application.go index 54038e1..c3dd560 100644 --- a/application.go +++ b/application.go @@ -23,6 +23,9 @@ type Application struct { // The root primitive to be seen on the screen. root Primitive + // Whether or not the application resizes the root primitive. + rootAutoSize bool + // Key overrides. keyOverrides map[tcell.Key]func(p Primitive) bool @@ -103,6 +106,10 @@ func (a *Application) Run() error { }() // Draw the screen for the first time. + if a.rootAutoSize && a.root != nil { + width, height := a.screen.Size() + a.root.SetRect(0, 0, width, height) + } a.Unlock() a.Draw() @@ -158,6 +165,13 @@ func (a *Application) Run() error { } } case *tcell.EventResize: + if a.rootAutoSize && a.root != nil { + a.Lock() + width, height := a.screen.Size() + a.root.SetRect(0, 0, width, height) + a.Unlock() + a.Draw() + } a.Draw() } } @@ -198,15 +212,26 @@ func (a *Application) Draw() *Application { // SetRoot sets the root primitive for this application. This function must be // called or nothing will be displayed when the application starts. -func (a *Application) SetRoot(root Primitive) *Application { +func (a *Application) SetRoot(root Primitive, autoSize bool) *Application { a.Lock() defer a.Unlock() a.root = root + a.rootAutoSize = autoSize return a } +// ResizeToFullScreen resizes the given primitive such that it fills the entire +// screen. +func (a *Application) ResizeToFullScreen(p Primitive) *Application { + a.RLock() + width, height := a.screen.Size() + a.RUnlock() + p.SetRect(0, 0, width, height) + return a +} + // SetFocus sets the focus on a new primitive. All key events will be redirected // to that primitive. Callers must ensure that the primitive will handle key // events. diff --git a/button.go b/button.go index 8e863cc..c9c472e 100644 --- a/button.go +++ b/button.go @@ -31,6 +31,7 @@ type Button struct { // NewButton returns a new input field. func NewButton(label string) *Button { box := NewBox().SetBackgroundColor(tcell.ColorBlue) + box.SetRect(0, 0, len([]rune(label))+4, 1) return &Button{ Box: box, label: label, @@ -92,21 +93,28 @@ func (b *Button) SetBlurFunc(handler func(key tcell.Key)) *Button { // Draw draws this primitive onto the screen. func (b *Button) Draw(screen tcell.Screen) { // Draw the box. + borderColor := b.borderColor backgroundColor := b.backgroundColor if b.focus.HasFocus() { b.backgroundColor = b.backgroundColorActivated + b.borderColor = b.labelColorActivated + defer func() { + b.borderColor = borderColor + }() } b.Box.Draw(screen) b.backgroundColor = backgroundColor // Draw label. x, y, width, height := b.GetInnerRect() - y = y + height/2 - labelColor := b.labelColor - if b.focus.HasFocus() { - labelColor = b.labelColorActivated + if width > 0 && height > 0 { + y = y + height/2 + labelColor := b.labelColor + if b.focus.HasFocus() { + labelColor = b.labelColorActivated + } + Print(screen, b.label, x, y, width, AlignCenter, labelColor) } - Print(screen, b.label, x, y, width, AlignCenter, labelColor) if b.focus.HasFocus() { screen.HideCursor() diff --git a/frame.go b/frame.go index a675bb0..51bf3e4 100644 --- a/frame.go +++ b/frame.go @@ -12,7 +12,7 @@ type frameText struct { Color tcell.Color // The text color. } -// Frame is a wrapper which adds a border around another box. The top area +// Frame is a wrapper which adds a border around another primitive. The top area // (header) and the bottom area (footer) may also contain text. type Frame struct { *Box diff --git a/inputfield.go b/inputfield.go index 274038a..2292567 100644 --- a/inputfield.go +++ b/inputfield.go @@ -3,52 +3,10 @@ package tview import ( "math" "regexp" - "strconv" "github.com/gdamore/tcell" ) -var ( - // InputFieldInteger accepts integers. - InputFieldInteger func(text string, ch rune) bool - - // InputFieldFloat accepts floating-point numbers. - InputFieldFloat func(text string, ch rune) bool - - // InputFieldMaxLength returns an input field accept handler which accepts - // input strings up to a given length. Use it like this: - // - // inputField.SetAcceptanceFunc(InputFieldMaxLength(10)) // Accept up to 10 characters. - InputFieldMaxLength func(maxLength int) func(text string, ch rune) bool -) - -// Package initialization. -func init() { - // Initialize the predefined handlers. - - InputFieldInteger = func(text string, ch rune) bool { - if text == "-" { - return true - } - _, err := strconv.Atoi(text) - return err == nil - } - - InputFieldFloat = func(text string, ch rune) bool { - if text == "-" || text == "." { - return true - } - _, err := strconv.ParseFloat(text, 64) - return err == nil - } - - InputFieldMaxLength = func(maxLength int) func(text string, ch rune) bool { - return func(text string, ch rune) bool { - return len([]rune(text)) <= maxLength - } - } -} - // InputField is a one-line box (three lines if there is a title) where the // user can enter text. type InputField struct { @@ -229,7 +187,7 @@ func (i *InputField) setCursor(screen tcell.Screen) { rightLimit -= 2 } fieldLength := len([]rune(i.text)) - if fieldLength > i.fieldLength-1 { + if i.fieldLength > 0 && fieldLength > i.fieldLength-1 { fieldLength = i.fieldLength - 1 } x += len([]rune(i.label)) + fieldLength diff --git a/pages.go b/pages.go index 2434460..3ebfe95 100644 --- a/pages.go +++ b/pages.go @@ -1,6 +1,8 @@ package tview -import "github.com/gdamore/tcell" +import ( + "github.com/gdamore/tcell" +) // page represents one page of a Pages object. type page struct { @@ -18,6 +20,10 @@ type Pages struct { // The contained pages. pages []*page + // We keep a reference to the function which allows us to set the focus to + // a newly visible page. + setFocus func(p Primitive) + // An optional handler which is called whenever the visibility or the order of // pages changes. changed func() @@ -46,10 +52,11 @@ func (p *Pages) SetChangedFunc(handler func()) *Pages { // Visible pages will be drawn in the order they were added (unless that order // was changed in one of the other functions). func (p *Pages) AddPage(name string, item Primitive, visible bool) *Pages { - p.pages = append(p.pages, &page{Item: item, Name: name, Visible: true}) + p.pages = append(p.pages, &page{Item: item, Name: name, Visible: visible}) if p.changed != nil { p.changed() } + p.refocus() return p } @@ -64,6 +71,7 @@ func (p *Pages) RemovePage(name string) *Pages { break } } + p.refocus() return p } @@ -79,6 +87,7 @@ func (p *Pages) ShowPage(name string) *Pages { break } } + p.refocus() return p } @@ -93,6 +102,7 @@ func (p *Pages) HidePage(name string) *Pages { break } } + p.refocus() return p } @@ -109,6 +119,7 @@ func (p *Pages) SwitchToPage(name string) *Pages { if p.changed != nil { p.changed() } + p.refocus() return p } @@ -127,6 +138,7 @@ func (p *Pages) SendToFront(name string) *Pages { break } } + p.refocus() return p } @@ -145,6 +157,7 @@ func (p *Pages) SendToBack(name string) *Pages { break } } + p.refocus() return p } @@ -158,6 +171,36 @@ func (p *Pages) HasFocus() bool { return false } +// Focus is called by the application when the primitive receives focus. +func (p *Pages) Focus(delegate func(p Primitive)) { + p.setFocus = delegate + var topItem Primitive + for _, page := range p.pages { + if page.Visible { + topItem = page.Item + } + } + if topItem != nil { + delegate(topItem) + } +} + +// refocus sets the focus to the topmost visible page but only if we have focus. +func (p *Pages) refocus() { + if !p.HasFocus() || p.setFocus == nil { + return + } + var topItem Primitive + for _, page := range p.pages { + if page.Visible { + topItem = page.Item + } + } + if topItem != nil { + p.setFocus(topItem) + } +} + // Draw draws this primitive onto the screen. func (p *Pages) Draw(screen tcell.Screen) { for _, page := range p.pages { @@ -167,3 +210,8 @@ func (p *Pages) Draw(screen tcell.Screen) { page.Item.Draw(screen) } } + +// InputHandler returns the handler for this primitive. +func (p *Pages) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { + return func(event *tcell.EventKey, setFocus func(p Primitive)) {} +} diff --git a/textview.go b/textview.go index 45d2303..cfa763a 100644 --- a/textview.go +++ b/textview.go @@ -272,6 +272,14 @@ func (t *TextView) Highlight(regionIDs ...string) *TextView { return t } +// GetHighlights returns the IDs of all currently highlighted regions. +func (t *TextView) GetHighlights() (regionIDs []string) { + for id := range t.highlights { + regionIDs = append(regionIDs, id) + } + return +} + // ScrollToHighlight will cause the visible area to be scrolled so that the // highlighted regions appear in the visible area of the text view. This // repositioning happens the next time the text view is drawn. It happens only @@ -452,6 +460,16 @@ func (t *TextView) reindexBuffer(width int) { regions = regionPattern.FindAllStringSubmatch(str, -1) } + // We also keep a reference to empty lines. + if len(str) == 0 { + t.index = append(t.index, &textViewIndex{ + Line: index, + Pos: 0, + Color: color, + Region: regionID, + }) + } + // Break down the line. var currentTag, currentRegion, currentWidth int for pos := range str { diff --git a/util.go b/util.go index 2788f11..b34ff89 100644 --- a/util.go +++ b/util.go @@ -2,6 +2,7 @@ package tview import ( "math" + "strconv" "strings" "github.com/gdamore/tcell" @@ -36,6 +37,47 @@ const ( GraphicsEllipsis = '\u2026' ) +var ( + // InputFieldInteger accepts integers. + InputFieldInteger func(text string, ch rune) bool + + // InputFieldFloat accepts floating-point numbers. + InputFieldFloat func(text string, ch rune) bool + + // InputFieldMaxLength returns an input field accept handler which accepts + // input strings up to a given length. Use it like this: + // + // inputField.SetAcceptanceFunc(InputFieldMaxLength(10)) // Accept up to 10 characters. + InputFieldMaxLength func(maxLength int) func(text string, ch rune) bool +) + +// Package initialization. +func init() { + // Initialize the predefined handlers. + + InputFieldInteger = func(text string, ch rune) bool { + if text == "-" { + return true + } + _, err := strconv.Atoi(text) + return err == nil + } + + InputFieldFloat = func(text string, ch rune) bool { + if text == "-" || text == "." { + return true + } + _, err := strconv.ParseFloat(text, 64) + return err == nil + } + + InputFieldMaxLength = func(maxLength int) func(text string, ch rune) bool { + return func(text string, ch rune) bool { + return len([]rune(text)) <= maxLength + } + } +} + // Print prints text onto the screen into the given box at (x,y,maxWidth,1), // no exceeding that box. "align" is one of AlignLeft, AlignCenter, or // AlignRight. The screen's background color will be maintained.