diff --git a/application.go b/application.go index 67ef6dd..ff6bf97 100644 --- a/application.go +++ b/application.go @@ -305,17 +305,19 @@ EventLoop: switch event := event.(type) { case *tcell.EventKey: a.RLock() - p := a.focus + root := a.root inputCapture := a.inputCapture a.RUnlock() // Intercept keys. + var draw bool if inputCapture != nil { event = inputCapture(event) if event == nil { a.draw() continue // Don't forward event. } + draw = true } // Ctrl-C closes the application. @@ -323,15 +325,20 @@ EventLoop: a.Stop() } - // Pass other key events to the currently focused primitive. - if p != nil { - if handler := p.InputHandler(); handler != nil { + // Pass other key events to the root primitive. + if root != nil && root.GetFocusable().HasFocus() { + if handler := root.InputHandler(); handler != nil { handler(event, func(p Primitive) { a.SetFocus(p) }) - a.draw() + draw = true } } + + // Redraw. + if draw { + a.draw() + } case *tcell.EventResize: if time.Since(lastRedraw) < redrawPause { if redrawTimer != nil { diff --git a/demos/presentation/flex.go b/demos/presentation/flex.go index 22638c7..c9c75a6 100644 --- a/demos/presentation/flex.go +++ b/demos/presentation/flex.go @@ -9,25 +9,24 @@ import ( func Flex(nextSlide func()) (title string, content tview.Primitive) { modalShown := false pages := tview.NewPages() - textView := tview.NewTextView(). - SetDoneFunc(func(key tcell.Key) { - if modalShown { - nextSlide() - modalShown = false - } else { - pages.ShowPage("modal") - modalShown = true - } - }) - textView.SetBorder(true).SetTitle("Flexible width, twice of middle column") flex := tview.NewFlex(). - AddItem(textView, 0, 2, true). + AddItem(tview.NewBox().SetBorder(true).SetTitle("Flexible width, twice of middle column"), 0, 2, true). AddItem(tview.NewFlex(). SetDirection(tview.FlexRow). AddItem(tview.NewBox().SetBorder(true).SetTitle("Flexible width"), 0, 1, false). AddItem(tview.NewBox().SetBorder(true).SetTitle("Fixed height"), 15, 1, false). AddItem(tview.NewBox().SetBorder(true).SetTitle("Flexible height"), 0, 1, false), 0, 1, false). AddItem(tview.NewBox().SetBorder(true).SetTitle("Fixed width"), 30, 1, false) + flex.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if modalShown { + nextSlide() + modalShown = false + } else { + pages.ShowPage("modal") + modalShown = true + } + return event + }) modal := tview.NewModal(). SetText("Resize the window to see the effect of the flexbox parameters"). AddButtons([]string{"Ok"}).SetDoneFunc(func(buttonIndex int, buttonLabel string) { diff --git a/demos/presentation/grid.go b/demos/presentation/grid.go index fc05521..3a04d14 100644 --- a/demos/presentation/grid.go +++ b/demos/presentation/grid.go @@ -11,18 +11,9 @@ func Grid(nextSlide func()) (title string, content tview.Primitive) { pages := tview.NewPages() newPrimitive := func(text string) tview.Primitive { - return tview.NewTextView(). - SetTextAlign(tview.AlignCenter). - SetText(text). - SetDoneFunc(func(key tcell.Key) { - if modalShown { - nextSlide() - modalShown = false - } else { - pages.ShowPage("modal") - modalShown = true - } - }) + return tview.NewFrame(nil). + SetBorders(0, 0, 0, 0, 0, 0). + AddText(text, true, tview.AlignCenter, tcell.ColorWhite) } menu := newPrimitive("Menu") @@ -35,6 +26,16 @@ func Grid(nextSlide func()) (title string, content tview.Primitive) { SetBorders(true). AddItem(newPrimitive("Header"), 0, 0, 1, 3, 0, 0, true). AddItem(newPrimitive("Footer"), 2, 0, 1, 3, 0, 0, false) + grid.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if modalShown { + nextSlide() + modalShown = false + } else { + pages.ShowPage("modal") + modalShown = true + } + return event + }) // Layout for screens narrower than 100 cells (menu and side bar are hidden). grid.AddItem(menu, 0, 0, 0, 0, 0, 0, false). diff --git a/demos/presentation/main.go b/demos/presentation/main.go index 065d798..2510830 100644 --- a/demos/presentation/main.go +++ b/demos/presentation/main.go @@ -88,8 +88,10 @@ func main() { app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyCtrlN { nextSlide() + return nil } else if event.Key() == tcell.KeyCtrlP { previousSlide() + return nil } return event }) diff --git a/dropdown.go b/dropdown.go index 89d50c1..4e54e4b 100644 --- a/dropdown.go +++ b/dropdown.go @@ -396,6 +396,14 @@ func (d *DropDown) Draw(screen tcell.Screen) { // InputHandler returns the handler for this primitive. 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 handler := d.list.InputHandler(); handler != nil { + handler(event, setFocus) + } + return + } + // Process key event. switch key := event.Key(); key { case tcell.KeyEnter, tcell.KeyRune, tcell.KeyDown: diff --git a/flex.go b/flex.go index 3343e86..9a62756 100644 --- a/flex.go +++ b/flex.go @@ -223,3 +223,17 @@ func (f *Flex) MouseHandler() func(action MouseAction, event *tcell.EventMouse, return }) } + +// InputHandler returns the handler for this primitive. +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 != nil && item.Item.GetFocusable().HasFocus() { + if handler := item.Item.InputHandler(); handler != nil { + handler(event, setFocus) + return + } + } + } + }) +} diff --git a/form.go b/form.go index d022fab..71b26c3 100644 --- a/form.go +++ b/form.go @@ -661,3 +661,26 @@ func (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse, return }) } + +// InputHandler returns the handler for this primitive. +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 handler := item.InputHandler(); handler != nil { + handler(event, setFocus) + return + } + } + } + + for _, button := range f.buttons { + if button.GetFocusable().HasFocus() { + if handler := button.InputHandler(); handler != nil { + handler(event, setFocus) + return + } + } + } + }) +} diff --git a/frame.go b/frame.go index 1ea9d6e..eb8c900 100644 --- a/frame.go +++ b/frame.go @@ -19,7 +19,7 @@ type frameText struct { type Frame struct { *Box - // The contained primitive. + // The contained primitive. May be nil. primitive Primitive // The lines of text to be displayed. @@ -30,7 +30,8 @@ type Frame struct { } // NewFrame returns a new frame around the given primitive. The primitive's -// size will be changed to fit within this frame. +// size will be changed to fit within this frame. The primitive may be nil, in +// which case no other primitive is embedded in the frame. func NewFrame(primitive Primitive) *Frame { box := NewBox() @@ -127,33 +128,42 @@ func (f *Frame) Draw(screen tcell.Screen) { } // Set the size of the contained primitive. - if topMax > top { - top = topMax + f.header - } - if bottomMin < bottom { - bottom = bottomMin - f.footer - } - if top > bottom { - return // No space for the primitive. - } - f.primitive.SetRect(x, top, width, bottom+1-top) + if f.primitive != nil { + if topMax > top { + top = topMax + f.header + } + if bottomMin < bottom { + bottom = bottomMin - f.footer + } + if top > bottom { + return // No space for the primitive. + } + f.primitive.SetRect(x, top, width, bottom+1-top) - // Finally, draw the contained primitive. - f.primitive.Draw(screen) + // Finally, draw the contained primitive. + f.primitive.Draw(screen) + } } // Focus is called when this primitive receives focus. func (f *Frame) Focus(delegate func(p Primitive)) { - delegate(f.primitive) + if f.primitive != nil { + delegate(f.primitive) + } else { + f.hasFocus = true + } } // HasFocus returns whether or not this primitive has focus. func (f *Frame) HasFocus() bool { + if f.primitive == nil { + return f.hasFocus + } focusable, ok := f.primitive.(Focusable) if ok { return focusable.HasFocus() } - return false + return f.hasFocus } // MouseHandler returns the mouse handler for this primitive. @@ -164,6 +174,25 @@ func (f *Frame) MouseHandler() func(action MouseAction, event *tcell.EventMouse, } // Pass mouse events on to contained primitive. - return f.primitive.MouseHandler()(action, event, setFocus) + if f.primitive != nil { + return f.primitive.MouseHandler()(action, event, setFocus) + } + + return false, nil + }) +} + +// InputHandler returns the handler for this primitive. +func (f *Frame) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { + return f.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { + if f.primitive == nil { + return + } + if f.primitive.GetFocusable().HasFocus() { + if handler := f.primitive.InputHandler(); handler != nil { + handler(event, setFocus) + return + } + } }) } diff --git a/go.mod b/go.mod index 770297c..dc77408 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.12 require ( github.com/gdamore/tcell v1.3.0 github.com/lucasb-eyer/go-colorful v1.0.3 - github.com/mattn/go-runewidth v0.0.8 + github.com/mattn/go-runewidth v0.0.9 github.com/rivo/uniseg v0.1.0 - golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 // indirect + golang.org/x/sys v0.0.0-20200817155316-9781c653f443 // indirect golang.org/x/text v0.3.2 // indirect ) diff --git a/go.sum b/go.sum index c280e9e..7566d43 100644 --- a/go.sum +++ b/go.sum @@ -10,14 +10,14 @@ github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tW github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= -github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443 h1:X18bCaipMcoJGm27Nv7zr4XYPKGUy92GtqboKC2Hxaw= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= diff --git a/grid.go b/grid.go index 5b247bf..8066384 100644 --- a/grid.go +++ b/grid.go @@ -271,6 +271,20 @@ func (g *Grid) HasFocus() bool { // InputHandler returns the handler for this primitive. func (g *Grid) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) { return g.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) { + if !g.hasFocus { + // Pass event on to child primitive. + for _, item := range g.items { + if item != nil && item.Item.GetFocusable().HasFocus() { + if handler := item.Item.InputHandler(); handler != nil { + handler(event, setFocus) + return + } + } + } + return + } + + // Process our own key events if we have direct focus. switch event.Key() { case tcell.KeyRune: switch event.Rune() { diff --git a/modal.go b/modal.go index f534113..2d557f0 100644 --- a/modal.go +++ b/modal.go @@ -188,3 +188,15 @@ func (m *Modal) MouseHandler() func(action MouseAction, event *tcell.EventMouse, return }) } + +// 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 handler := m.frame.InputHandler(); handler != nil { + handler(event, setFocus) + return + } + } + }) +} diff --git a/pages.go b/pages.go index 0955045..0ce9656 100644 --- a/pages.go +++ b/pages.go @@ -300,3 +300,17 @@ func (p *Pages) MouseHandler() func(action MouseAction, event *tcell.EventMouse, return }) } + +// InputHandler returns the handler for this primitive. +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 handler := page.Item.InputHandler(); handler != nil { + handler(event, setFocus) + return + } + } + } + }) +} diff --git a/table.go b/table.go index 992ccda..3ce2503 100644 --- a/table.go +++ b/table.go @@ -1250,8 +1250,8 @@ func (t *Table) MouseHandler() func(action MouseAction, event *tcell.EventMouse, if t.rowsSelectable || t.columnsSelectable { t.Select(t.cellAt(x, y)) } - consumed = true setFocus(t) + consumed = true case MouseScrollUp: t.trackEnd = false t.rowOffset-- diff --git a/textview.go b/textview.go index ee82341..bc117fd 100644 --- a/textview.go +++ b/textview.go @@ -1173,8 +1173,8 @@ func (t *TextView) MouseHandler() func(action MouseAction, event *tcell.EventMou break } } - consumed = true setFocus(t) + consumed = true case MouseScrollUp: t.trackEnd = false t.lineOffset--