diff --git a/README.md b/README.md index 44cc9bd..b33638a 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ I try really hard to keep this project backwards compatible. Your software shoul - a new version of an imported package (most likely [`tcell`](https://github.com/gdamore/tcell)) changes in such a way that forces me to make changes in `tview` as well, - I fix something that I consider a bug, rather than a feature, something that does not work as originally intended, -- I make changes to "internal" interfaces such as [`Primitive`](https://pkg.go.dev/github.com/rivo/tview#Primitive) or [`Focusable`](https://pkg.go.dev/github.com/rivo/tview#Focusable). You shouldn't need these interfaces unless you're writing your own primitives for `tview`. (Yes, I realize these are public interfaces. This has advantages as well as disadvantages. For the time being, it is what it is.) +- I make changes to "internal" interfaces such as [`Primitive`](https://pkg.go.dev/github.com/rivo/tview#Primitive). You shouldn't need these interfaces unless you're writing your own primitives for `tview`. (Yes, I realize these are public interfaces. This has advantages as well as disadvantages. For the time being, it is what it is.) ## Your Feedback diff --git a/application.go b/application.go index 03a6f28..92e8afe 100644 --- a/application.go +++ b/application.go @@ -325,7 +325,7 @@ EventLoop: } // Pass other key events to the root primitive. - if root != nil && root.GetFocusable().HasFocus() { + if root != nil && root.HasFocus() { if handler := root.InputHandler(); handler != nil { handler(event, func(p Primitive) { a.SetFocus(p) diff --git a/box.go b/box.go index 2193c00..62bc896 100644 --- a/box.go +++ b/box.go @@ -42,10 +42,6 @@ type Box struct { // The alignment of the title. titleAlign int - // Provides a way to find out if this box has focus. We always go through - // this interface because it may be overridden by implementing classes. - focus Focusable - // Whether or not this box has focus. hasFocus bool @@ -74,7 +70,6 @@ func NewBox() *Box { titleColor: Styles.TitleColor, titleAlign: AlignCenter, } - b.focus = b return b } @@ -320,6 +315,16 @@ func (b *Box) SetTitleAlign(align int) *Box { // Draw draws this primitive onto the screen. func (b *Box) Draw(screen tcell.Screen) { + b.DrawForSubclass(screen, b) +} + +// DrawForSubclass draws this box under the assumption that primitive p is a +// subclass of this box. This is needed e.g. to draw proper box frames which +// depend on the subclass's focus. +// +// Only call this function from your own custom primitives. It is not needed in +// applications that have no custom primitives. +func (b *Box) DrawForSubclass(screen tcell.Screen, p Primitive) { // Don't draw anything if there is no space. if b.width <= 0 || b.height <= 0 { return @@ -340,7 +345,7 @@ func (b *Box) Draw(screen tcell.Screen) { // Draw border. if b.border && b.width >= 2 && b.height >= 2 { var vertical, horizontal, topLeft, topRight, bottomLeft, bottomRight rune - if b.focus.HasFocus() { + if p.HasFocus() { horizontal = Borders.HorizontalFocus vertical = Borders.VerticalFocus topLeft = Borders.TopLeftFocus @@ -403,8 +408,3 @@ func (b *Box) Blur() { func (b *Box) HasFocus() bool { return b.hasFocus } - -// GetFocusable returns the item's Focusable. -func (b *Box) GetFocusable() Focusable { - return b.focus -} diff --git a/button.go b/button.go index 0e499a4..97e9ff5 100644 --- a/button.go +++ b/button.go @@ -97,14 +97,14 @@ func (b *Button) Draw(screen tcell.Screen) { // Draw the box. borderColor := b.GetBorderColor() backgroundColor := b.GetBackgroundColor() - if b.focus.HasFocus() { + if b.HasFocus() { b.SetBackgroundColor(b.backgroundColorActivated) b.SetBorderColor(b.labelColorActivated) defer func() { b.SetBorderColor(borderColor) }() } - b.Box.Draw(screen) + b.Box.DrawForSubclass(screen, b) b.backgroundColor = backgroundColor // Draw label. @@ -112,7 +112,7 @@ func (b *Button) Draw(screen tcell.Screen) { if width > 0 && height > 0 { y = y + height/2 labelColor := b.labelColor - if b.focus.HasFocus() { + if b.HasFocus() { labelColor = b.labelColorActivated } Print(screen, b.label, x, y, width, AlignCenter, labelColor) diff --git a/checkbox.go b/checkbox.go index 3d20f8c..7d0e1a7 100644 --- a/checkbox.go +++ b/checkbox.go @@ -144,7 +144,7 @@ func (c *Checkbox) SetFinishedFunc(handler func(key tcell.Key)) FormItem { // Draw draws this primitive onto the screen. func (c *Checkbox) Draw(screen tcell.Screen) { - c.Box.Draw(screen) + c.Box.DrawForSubclass(screen, c) // Prepare x, y, width, height := c.GetInnerRect() @@ -168,7 +168,7 @@ func (c *Checkbox) Draw(screen tcell.Screen) { // Draw checkbox. fieldStyle := tcell.StyleDefault.Background(c.fieldBackgroundColor).Foreground(c.fieldTextColor) - if c.focus.HasFocus() { + if c.HasFocus() { fieldStyle = fieldStyle.Background(c.fieldTextColor).Foreground(c.fieldBackgroundColor) } checkedRune := 'X' diff --git a/demos/inputfield/autocomplete.go b/demos/inputfield/autocomplete/main.go similarity index 100% rename from demos/inputfield/autocomplete.go rename to demos/inputfield/autocomplete/main.go diff --git a/demos/inputfield/autocompleteasync.go b/demos/inputfield/autocompleteasync/main.go similarity index 100% rename from demos/inputfield/autocompleteasync.go rename to demos/inputfield/autocompleteasync/main.go diff --git a/demos/primitive/main.go b/demos/primitive/main.go index a98172b..398a45e 100644 --- a/demos/primitive/main.go +++ b/demos/primitive/main.go @@ -25,7 +25,7 @@ func NewRadioButtons(options []string) *RadioButtons { // Draw draws this primitive onto the screen. func (r *RadioButtons) Draw(screen tcell.Screen) { - r.Box.Draw(screen) + r.Box.DrawForSubclass(screen, r) x, y, width, height := r.GetInnerRect() for index, option := range r.options { @@ -59,12 +59,34 @@ func (r *RadioButtons) InputHandler() func(event *tcell.EventKey, setFocus func( }) } +// MouseHandler returns the mouse handler for this primitive. +func (r *RadioButtons) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { + return r.WrapMouseHandler(func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { + x, y := event.Position() + _, rectY, _, _ := r.GetInnerRect() + if !r.InRect(x, y) { + return false, nil + } + + if action == tview.MouseLeftClick { + setFocus(r) + index := y - rectY + if index >= 0 && index < len(r.options) { + r.currentOption = index + consumed = true + } + } + + return + }) +} + func main() { radioButtons := NewRadioButtons([]string{"Lions", "Elephants", "Giraffes"}) radioButtons.SetBorder(true). SetTitle("Radio Button Demo"). SetRect(0, 0, 30, 5) - if err := tview.NewApplication().SetRoot(radioButtons, false).Run(); err != nil { + if err := tview.NewApplication().SetRoot(radioButtons, false).EnableMouse(true).Run(); err != nil { panic(err) } } diff --git a/doc.go b/doc.go index 4bceea2..1e11412 100644 --- a/doc.go +++ b/doc.go @@ -169,8 +169,7 @@ Type Hierarchy All widgets listed above contain the Box type. All of Box's functions are therefore available for all widgets, too. -All widgets also implement the Primitive interface. There is also the Focusable -interface which is used to override functions in subclassing types. +All widgets also implement the Primitive interface. The tview package is based on https://github.com/gdamore/tcell. It uses types and constants from that package (e.g. colors and keyboard values). diff --git a/dropdown.go b/dropdown.go index 360e12b..a4a26ac 100644 --- a/dropdown.go +++ b/dropdown.go @@ -103,8 +103,6 @@ func NewDropDown() *DropDown { prefixTextColor: Styles.ContrastSecondaryTextColor, } - d.focus = d - return d } @@ -288,7 +286,7 @@ func (d *DropDown) SetFinishedFunc(handler func(key tcell.Key)) FormItem { // Draw draws this primitive onto the screen. func (d *DropDown) Draw(screen tcell.Screen) { - d.Box.Draw(screen) + d.Box.DrawForSubclass(screen, d) // Prepare. x, y, width, height := d.GetInnerRect() @@ -340,7 +338,7 @@ func (d *DropDown) Draw(screen tcell.Screen) { fieldWidth = rightLimit - x } fieldStyle := tcell.StyleDefault.Background(d.fieldBackgroundColor) - if d.GetFocusable().HasFocus() && !d.open { + if d.HasFocus() && !d.open { fieldStyle = fieldStyle.Background(d.fieldTextColor) } for index := 0; index < fieldWidth; index++ { @@ -365,7 +363,7 @@ func (d *DropDown) Draw(screen tcell.Screen) { text = d.currentOptionPrefix + d.options[d.currentOption].Text + d.currentOptionSuffix } // Just show the current selection. - if d.GetFocusable().HasFocus() && !d.open { + if d.HasFocus() && !d.open { color = d.fieldBackgroundColor } Print(screen, text, x, y, fieldWidth, AlignLeft, color) @@ -397,7 +395,7 @@ func (d *DropDown) Draw(screen tcell.Screen) { func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { return d.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { // If the list has focus, let it process its own key events. - if d.list.GetFocusable().HasFocus() { + if d.list.HasFocus() { if handler := d.list.InputHandler(); handler != nil { handler(event, setFocus) } diff --git a/flex.go b/flex.go index fda3f70..a574d3c 100644 --- a/flex.go +++ b/flex.go @@ -53,7 +53,6 @@ func NewFlex() *Flex { Box: NewBox().SetBackgroundColor(tcell.ColorDefault), direction: FlexColumn, } - f.focus = f return f } @@ -122,7 +121,7 @@ func (f *Flex) ResizeItem(p Primitive, fixedSize, proportion int) *Flex { // Draw draws this primitive onto the screen. func (f *Flex) Draw(screen tcell.Screen) { - f.Box.Draw(screen) + f.Box.DrawForSubclass(screen, f) // Calculate size and position of the items. @@ -173,7 +172,7 @@ func (f *Flex) Draw(screen tcell.Screen) { pos += size if item.Item != nil { - if item.Item.GetFocusable().HasFocus() { + if item.Item.HasFocus() { defer item.Item.Draw(screen) } else { item.Item.Draw(screen) @@ -195,7 +194,7 @@ func (f *Flex) Focus(delegate func(p Primitive)) { // HasFocus returns whether or not this primitive has focus. func (f *Flex) HasFocus() bool { for _, item := range f.items { - if item.Item != nil && item.Item.GetFocusable().HasFocus() { + if item.Item != nil && item.Item.HasFocus() { return true } } @@ -228,7 +227,7 @@ func (f *Flex) MouseHandler() func(action MouseAction, event *tcell.EventMouse, func (f *Flex) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { for _, item := range f.items { - if item.Item != nil && item.Item.GetFocusable().HasFocus() { + if item.Item != nil && item.Item.HasFocus() { if handler := item.Item.InputHandler(); handler != nil { handler(event, setFocus) return diff --git a/focusable.go b/focusable.go deleted file mode 100644 index 99fdaaf..0000000 --- a/focusable.go +++ /dev/null @@ -1,8 +0,0 @@ -package tview - -// Focusable provides a method which determines if a primitive has focus. -// Composed primitives may be focused based on the focused state of their -// contained primitives. -type Focusable interface { - HasFocus() bool -} diff --git a/form.go b/form.go index b576b91..6567e8d 100644 --- a/form.go +++ b/form.go @@ -96,8 +96,6 @@ func NewForm() *Form { buttonTextColor: Styles.PrimaryTextColor, } - f.focus = f - return f } @@ -363,7 +361,7 @@ func (f *Form) SetCancelFunc(callback func()) *Form { // Draw draws this primitive onto the screen. func (f *Form) Draw(screen tcell.Screen) { - f.Box.Draw(screen) + f.Box.DrawForSubclass(screen, f) // Determine the actual item that has focus. if index := f.focusIndex(); index >= 0 { @@ -430,7 +428,7 @@ func (f *Form) Draw(screen tcell.Screen) { positions[index].y = y positions[index].width = itemWidth positions[index].height = 1 - if item.GetFocusable().HasFocus() { + if item.HasFocus() { focusedPosition = positions[index] } @@ -524,7 +522,7 @@ func (f *Form) Draw(screen tcell.Screen) { } // Draw items with focus last (in case of overlaps). - if item.GetFocusable().HasFocus() { + if item.HasFocus() { defer item.Draw(screen) } else { item.Draw(screen) @@ -608,12 +606,12 @@ func (f *Form) HasFocus() bool { // has focus. func (f *Form) focusIndex() int { for index, item := range f.items { - if item.GetFocusable().HasFocus() { + if item.HasFocus() { return index } } for index, button := range f.buttons { - if button.focus.HasFocus() { + if button.HasFocus() { return len(f.items) + index } } @@ -666,7 +664,7 @@ func (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse, func (f *Form) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { for _, item := range f.items { - if item != nil && item.GetFocusable().HasFocus() { + if item != nil && item.HasFocus() { if handler := item.InputHandler(); handler != nil { handler(event, setFocus) return @@ -675,7 +673,7 @@ func (f *Form) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit } for _, button := range f.buttons { - if button.GetFocusable().HasFocus() { + if button.HasFocus() { if handler := button.InputHandler(); handler != nil { handler(event, setFocus) return diff --git a/frame.go b/frame.go index 7beddc8..47e5640 100644 --- a/frame.go +++ b/frame.go @@ -46,8 +46,6 @@ func NewFrame(primitive Primitive) *Frame { right: 1, } - f.focus = f - return f } @@ -83,7 +81,7 @@ func (f *Frame) SetBorders(top, bottom, header, footer, left, right int) *Frame // Draw draws this primitive onto the screen. func (f *Frame) Draw(screen tcell.Screen) { - f.Box.Draw(screen) + f.Box.DrawForSubclass(screen, f) // Calculate start positions. x, top, width, height := f.GetInnerRect() @@ -159,11 +157,7 @@ func (f *Frame) HasFocus() bool { if f.primitive == nil { return f.hasFocus } - focusable, ok := f.primitive.(Focusable) - if ok { - return focusable.HasFocus() - } - return f.hasFocus + return f.primitive.HasFocus() } // MouseHandler returns the mouse handler for this primitive. @@ -188,7 +182,7 @@ func (f *Frame) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi if f.primitive == nil { return } - if f.primitive.GetFocusable().HasFocus() { + if f.primitive.HasFocus() { if handler := f.primitive.InputHandler(); handler != nil { handler(event, setFocus) return diff --git a/grid.go b/grid.go index 718c9e0..ceabcfc 100644 --- a/grid.go +++ b/grid.go @@ -71,7 +71,6 @@ func NewGrid() *Grid { Box: NewBox().SetBackgroundColor(tcell.ColorDefault), bordersColor: Styles.GraphicsColor, } - g.focus = g return g } @@ -261,7 +260,7 @@ func (g *Grid) Blur() { // HasFocus returns whether or not this primitive has focus. func (g *Grid) HasFocus() bool { for _, item := range g.items { - if item.visible && item.Item.GetFocusable().HasFocus() { + if item.visible && item.Item.HasFocus() { return true } } @@ -274,7 +273,7 @@ func (g *Grid) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit if !g.hasFocus { // Pass event on to child primitive. for _, item := range g.items { - if item != nil && item.Item.GetFocusable().HasFocus() { + if item != nil && item.Item.HasFocus() { if handler := item.Item.InputHandler(); handler != nil { handler(event, setFocus) return @@ -319,7 +318,7 @@ func (g *Grid) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit // Draw draws this primitive onto the screen. func (g *Grid) Draw(screen tcell.Screen) { - g.Box.Draw(screen) + g.Box.DrawForSubclass(screen, g) x, y, width, height := g.GetInnerRect() screenWidth, screenHeight := screen.Size() @@ -497,7 +496,7 @@ func (g *Grid) Draw(screen tcell.Screen) { } item.x, item.y, item.w, item.h = px, py, pw, ph item.visible = true - if primitive.GetFocusable().HasFocus() { + if primitive.HasFocus() { focus = item } } diff --git a/inputfield.go b/inputfield.go index 68db2ee..635da32 100644 --- a/inputfield.go +++ b/inputfield.go @@ -303,7 +303,7 @@ func (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem { // Draw draws this primitive onto the screen. func (i *InputField) Draw(screen tcell.Screen) { - i.Box.Draw(screen) + i.Box.DrawForSubclass(screen, i) // Prepare x, y, width, height := i.GetInnerRect() @@ -428,7 +428,7 @@ func (i *InputField) Draw(screen tcell.Screen) { } // Set cursor. - if i.focus.HasFocus() { + if i.HasFocus() { screen.ShowCursor(x+cursorScreenPos, y) } } diff --git a/list.go b/list.go index 74e2c79..c291a53 100644 --- a/list.go +++ b/list.go @@ -389,7 +389,7 @@ func (l *List) Clear() *List { // Draw draws this primitive onto the screen. func (l *List) Draw(screen tcell.Screen) { - l.Box.Draw(screen) + l.Box.DrawForSubclass(screen, l) // Determine the dimensions. x, y, width, height := l.GetInnerRect() diff --git a/modal.go b/modal.go index 5df65aa..4e9aa5c 100644 --- a/modal.go +++ b/modal.go @@ -49,7 +49,6 @@ func NewModal() *Modal { m.frame.SetBorder(true). SetBackgroundColor(Styles.ContrastBackgroundColor). SetBorderPadding(1, 1, 1, 1) - m.focus = m return m } @@ -192,7 +191,7 @@ func (m *Modal) MouseHandler() func(action MouseAction, event *tcell.EventMouse, // InputHandler returns the handler for this primitive. func (m *Modal) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { return m.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { - if m.frame.GetFocusable().HasFocus() { + if m.frame.HasFocus() { if handler := m.frame.InputHandler(); handler != nil { handler(event, setFocus) return diff --git a/pages.go b/pages.go index b9010d2..9dca792 100644 --- a/pages.go +++ b/pages.go @@ -37,7 +37,6 @@ func NewPages() *Pages { p := &Pages{ Box: NewBox(), } - p.focus = p return p } @@ -240,7 +239,7 @@ func (p *Pages) GetFrontPage() (name string, item Primitive) { // HasFocus returns whether or not this primitive has focus. func (p *Pages) HasFocus() bool { for _, page := range p.pages { - if page.Item.GetFocusable().HasFocus() { + if page.Item.HasFocus() { return true } } @@ -266,7 +265,7 @@ func (p *Pages) Focus(delegate func(p Primitive)) { // Draw draws this primitive onto the screen. func (p *Pages) Draw(screen tcell.Screen) { - p.Box.Draw(screen) + p.Box.DrawForSubclass(screen, p) for _, page := range p.pages { if !page.Visible { continue @@ -305,7 +304,7 @@ func (p *Pages) MouseHandler() func(action MouseAction, event *tcell.EventMouse, func (p *Pages) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { return p.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { for _, page := range p.pages { - if page.Item.GetFocusable().HasFocus() { + if page.Item.HasFocus() { if handler := page.Item.InputHandler(); handler != nil { handler(event, setFocus) return diff --git a/primitive.go b/primitive.go index 30c82e9..71a4ce9 100644 --- a/primitive.go +++ b/primitive.go @@ -38,12 +38,13 @@ type Primitive interface { // Implementers may call delegate() to pass the focus on to another primitive. Focus(delegate func(p Primitive)) + // HasFocus determines if the primitive has focus. This function must return + // true also if one of this primitive's child elements has focus. + HasFocus() bool + // Blur is called by the application when the primitive loses focus. Blur() - // GetFocusable returns the item's Focusable. - GetFocusable() Focusable - // MouseHandler returns a handler which receives mouse events. // It is called by the Application class. // diff --git a/table.go b/table.go index 4b509b5..0c10d90 100644 --- a/table.go +++ b/table.go @@ -623,7 +623,7 @@ func (t *Table) ScrollToEnd() *Table { // Draw draws this primitive onto the screen. func (t *Table) Draw(screen tcell.Screen) { - t.Box.Draw(screen) + t.Box.DrawForSubclass(screen, t) // What's our available screen space? _, totalHeight := screen.Size() diff --git a/textview.go b/textview.go index e4f2a84..de08642 100644 --- a/textview.go +++ b/textview.go @@ -845,9 +845,9 @@ func (t *TextView) reindexBuffer(width int) { // Draw draws this primitive onto the screen. func (t *TextView) Draw(screen tcell.Screen) { + t.Box.DrawForSubclass(screen, t) t.Lock() defer t.Unlock() - t.Box.Draw(screen) totalWidth, totalHeight := screen.Size() // Get the available size. diff --git a/treeview.go b/treeview.go index e616597..fe000fe 100644 --- a/treeview.go +++ b/treeview.go @@ -571,7 +571,7 @@ func (t *TreeView) process() { // Draw draws this primitive onto the screen. func (t *TreeView) Draw(screen tcell.Screen) { - t.Box.Draw(screen) + t.Box.DrawForSubclass(screen, t) if t.root == nil { return }