New mouse design

capture bool not finalized
pull/363/head
Chris Miller 4 years ago
parent cd3c60e6d1
commit 9598ca2519

@ -69,11 +69,12 @@ type Application struct {
// An optional capture function which receives a mouse event and returns the
// event to be forwarded to the default mouse handler (nil if nothing should
// be forwarded).
mouseCapture func(event *EventMouse) *EventMouse
mouseCapture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)
lastMouseX, lastMouseY int
lastMouseX, lastMouseY int16 // track last mouse pos
mouseDownX, mouseDownY int16 // track last mouse down pos
lastMouseAct MouseAction
lastMouseBtn tcell.ButtonMask
lastMouseTarget Primitive // nil if none
}
// NewApplication creates and returns a new application.
@ -110,14 +111,14 @@ func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.Event
// This function can then choose to forward that event (or a
// different one) by returning it or stop the event processing by returning
// nil.
func (a *Application) SetMouseCapture(capture func(event *EventMouse) *EventMouse) *Application {
func (a *Application) SetMouseCapture(capture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)) *Application {
a.mouseCapture = capture
return a
}
// GetMouseCapture returns the function installed with SetMouseCapture() or nil
// if no such function has been installed.
func (a *Application) GetMouseCapture() func(event *EventMouse) *EventMouse {
func (a *Application) GetMouseCapture() func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction) {
return a.mouseCapture
}
@ -251,6 +252,7 @@ EventLoop:
inputCapture := a.inputCapture
mouseCapture := a.mouseCapture
screen := a.screen
root := a.root
a.RUnlock()
switch event := event.(type) {
@ -288,56 +290,38 @@ EventLoop:
atX, atY := event.Position()
btn := event.Buttons()
var ptarget Primitive
if a.lastMouseBtn != 0 {
// While a button is down, the same primitive gets events.
ptarget = a.lastMouseTarget
}
if ptarget == nil {
ptarget = a.GetPrimitiveAtPoint(atX, atY) // p under mouse.
if ptarget == nil {
ptarget = p // Fallback to focused.
}
}
a.lastMouseTarget = ptarget
// Calculate mouse actions.
var act MouseAction
if atX != a.lastMouseX || atY != a.lastMouseY {
act |= MouseMove
a.lastMouseX = atX
a.lastMouseY = atY
var action MouseAction
if atX != int(a.lastMouseX) || atY != int(a.lastMouseY) {
action |= MouseMove
a.lastMouseX = int16(atX)
a.lastMouseY = int16(atY)
}
btnDiff := btn ^ a.lastMouseBtn
if btnDiff != 0 {
if btn&btnDiff != 0 {
act |= MouseDown
}
if a.lastMouseBtn&btnDiff != 0 {
act |= MouseUp
}
if a.lastMouseBtn == tcell.Button1 && btn == 0 {
if ptarget == a.GetPrimitiveAtPoint(atX, atY) {
// Only if Button1 and mouse up over same p.
act |= MouseClick
}
}
a.lastMouseBtn = btn
action |= getMouseButtonAction(a.lastMouseBtn, btn)
if atX == int(a.mouseDownX) && atY == int(a.mouseDownY) {
action |= getMouseClickAction(a.lastMouseAct, action)
}
a.lastMouseAct = action
a.lastMouseBtn = btn
if action&(MouseLeftDown|MouseMiddleDown|MouseRightDown) != 0 {
a.mouseDownX = int16(atX)
a.mouseDownY = int16(atY)
}
event2 := NewEventMouse(event, ptarget, a, act)
// Intercept event.
if mouseCapture != nil {
event2 = mouseCapture(event2)
if event2 == nil {
event, action = mouseCapture(event, action)
if event == nil {
a.draw()
continue // Don't forward event.
}
}
if handler := ptarget.MouseHandler(); handler != nil {
handler(event2)
if handler := root.MouseHandler(); handler != nil {
//consumed, capture :=
handler(event, action, func(p Primitive) {
a.SetFocus(p)
})
a.draw()
}
}
@ -355,34 +339,6 @@ EventLoop:
return nil
}
func findAtPoint(atX, atY int, p Primitive) Primitive {
x, y, w, h := p.GetRect()
if atX < x || atY < y {
return nil
}
if atX >= x+w || atY >= y+h {
return nil
}
bestp := p
for _, pchild := range p.GetChildren() {
x := findAtPoint(atX, atY, pchild)
if x != nil {
// Always overwrite if we find another one,
// this is because if any overlap, the last one is "on top".
bestp = x
}
}
return bestp
}
// GetPrimitiveAtPoint returns the Primitive at the specified point, or nil.
// Note that this only works with a valid hierarchy of primitives (children)
func (a *Application) GetPrimitiveAtPoint(atX, atY int) Primitive {
a.RLock()
defer a.RUnlock()
return findAtPoint(atX, atY, a.root)
}
// Stop stops the application, causing Run() to return.
func (a *Application) Stop() {
a.Lock()

@ -62,7 +62,7 @@ type Box struct {
// An optional capture function which receives a mouse event and returns the
// event to be forwarded to the primitive's default mouse event handler (nil if
// nothing should be forwarded).
mouseCapture func(event *EventMouse) *EventMouse
mouseCapture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)
}
// NewBox returns a Box without a border.
@ -202,19 +202,20 @@ func (b *Box) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
// on to the provided (default) event handler.
//
// This is only meant to be used by subclassing primitives.
func (b *Box) WrapMouseHandler(mouseHandler func(*EventMouse)) func(*EventMouse) {
return func(event *EventMouse) {
func (b *Box) WrapMouseHandler(mouseHandler func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool)) func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool) {
return func(event *tcell.EventMouse, action MouseAction, setFocus func(p Primitive)) (consumed, capture bool) {
if b.mouseCapture != nil {
event = b.mouseCapture(event)
event, action = b.mouseCapture(event, action)
}
if event != nil && mouseHandler != nil {
mouseHandler(event)
consumed, capture = mouseHandler(event, action, setFocus)
}
return
}
}
// MouseHandler returns nil.
func (b *Box) MouseHandler() func(event *EventMouse) {
func (b *Box) MouseHandler() func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool) {
return b.WrapMouseHandler(nil)
}
@ -225,14 +226,20 @@ func (b *Box) MouseHandler() func(event *EventMouse) {
// be called.
//
// Providing a nil handler will remove a previously existing handler.
func (b *Box) SetMouseCapture(capture func(*EventMouse) *EventMouse) *Box {
func (b *Box) SetMouseCapture(capture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)) *Box {
b.mouseCapture = capture
return b
}
func (b *Box) InRect(atX, atY int) bool {
x, y, w, h := b.GetRect()
return atX >= x && atX < x+w &&
atY >= y && atY < y+h
}
// GetMouseCapture returns the function installed with SetMouseCapture() or nil
// if no such function has been installed.
func (b *Box) GetMouseCapture() func(*EventMouse) *EventMouse {
func (b *Box) GetMouseCapture() func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction) {
return b.mouseCapture
}
@ -397,8 +404,3 @@ func (b *Box) HasFocus() bool {
func (b *Box) GetFocusable() Focusable {
return b.focus
}
// GetChildren gets the children.
func (b *Box) GetChildren() []Primitive {
return nil
}

@ -137,13 +137,17 @@ func (b *Button) InputHandler() func(event *tcell.EventKey, setFocus func(p Prim
}
// MouseHandler returns the mouse handler for this primitive.
func (b *Button) MouseHandler() func(event *EventMouse) {
return b.WrapMouseHandler(func(event *EventMouse) {
func (b *Button) MouseHandler() func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool) {
return b.WrapMouseHandler(func(event *tcell.EventMouse, action MouseAction, setFocus func(p Primitive)) (consumed, capture bool) {
if !b.InRect(event.Position()) {
return false, false
}
// Process mouse event.
if event.Action()&MouseClick != 0 {
if action&MouseLeftClick != 0 {
if b.selected != nil {
b.selected()
}
}
return true, false
})
}

@ -203,14 +203,18 @@ func (c *Checkbox) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
}
// MouseHandler returns the mouse handler for this primitive.
func (c *Checkbox) MouseHandler() func(event *EventMouse) {
return c.WrapMouseHandler(func(event *EventMouse) {
func (c *Checkbox) MouseHandler() func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool) {
return c.WrapMouseHandler(func(event *tcell.EventMouse, action MouseAction, setFocus func(p Primitive)) (consumed, capture bool) {
if !c.InRect(event.Position()) {
return false, false
}
// Process mouse event.
if event.Action()&MouseClick != 0 {
if action&MouseLeftClick != 0 {
c.checked = !c.checked
if c.changed != nil {
c.changed(c.checked)
}
}
return true, false
})
}

@ -405,7 +405,7 @@ func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
d.evalPrefix()
}
d.openList(setFocus, nil)
d.openList(setFocus)
case tcell.KeyEscape, tcell.KeyTab, tcell.KeyBacktab:
if d.done != nil {
d.done(key)
@ -434,7 +434,7 @@ func (d *DropDown) evalPrefix() {
}
// Hand control over to the list.
func (d *DropDown) openList(setFocus func(Primitive), app *Application) {
func (d *DropDown) openList(setFocus func(Primitive)) {
d.open = true
optionBefore := d.currentOption
d.list.SetSelectedFunc(func(index int, mainText, secondaryText string, shortcut rune) {
@ -467,37 +467,6 @@ func (d *DropDown) openList(setFocus func(Primitive), app *Application) {
}
return event
})
if app != nil {
app.SetMouseCapture(func(event *EventMouse) *EventMouse {
if d.open {
// Forward the mouse event to the list.
atX, atY := event.Position()
x, y, w, h := d.list.GetInnerRect()
if atX >= x && atY >= y && atX < x+w && atY < y+h {
// Mouse is within the list.
if handler := d.list.MouseHandler(); handler != nil {
if event.Action()&MouseUp != 0 {
// Treat mouse up as click here.
// This allows you to expand and select in one go.
event = NewEventMouse(event.EventMouse,
event.Target(), event.Application(),
event.Action()|MouseClick)
}
handler(event)
return nil // handled
}
} else {
// Mouse not within the list.
if event.Action()&MouseDown != 0 {
// If a mouse button was pressed, cancel this capture.
app.SetMouseCapture(nil)
d.closeList(event.SetFocus)
}
}
}
return event
})
}
setFocus(d.list)
}
@ -524,18 +493,42 @@ func (d *DropDown) HasFocus() bool {
return d.hasFocus
}
func (d *DropDown) listClick(event *tcell.EventMouse, action MouseAction, setFocus func(p Primitive)) (consumed, capture bool) {
atX, atY := event.Position()
x, y, w, h := d.list.GetRect()
if atX >= x && atY >= y && atX < x+w && atY < y+h {
// Mouse is within the list.
if handler := d.list.MouseHandler(); handler != nil {
// Treat mouse up as click here.
// This allows you to expand and select in one go.
return handler(event, MouseLeftUp|MouseLeftClick, setFocus)
}
return true, false
}
return false, false
}
// MouseHandler returns the mouse handler for this primitive.
func (d *DropDown) MouseHandler() func(event *EventMouse) {
return d.WrapMouseHandler(func(event *EventMouse) {
func (d *DropDown) MouseHandler() func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool) {
return d.WrapMouseHandler(func(event *tcell.EventMouse, action MouseAction, setFocus func(p Primitive)) (consumed, capture bool) {
inRect := d.InRect(event.Position())
if !d.open && !inRect {
return false, false
}
// Process mouse event.
if event.Action()&MouseDown != 0 && event.Buttons()&tcell.Button1 != 0 {
//d.open = !d.open
//event.SetFocus(d)
if d.open {
d.closeList(event.SetFocus)
} else {
d.openList(event.SetFocus, event.Application())
if d.open && action&(MouseLeftDown|MouseLeftUp) != 0 { // Close it:
consumed, capture = d.listClick(event, action, setFocus)
if consumed {
d.closeList(setFocus)
return consumed, capture
}
if inRect && action&MouseLeftClick == 0 {
d.closeList(setFocus)
}
} else if !d.open && inRect && action&MouseLeftDown != 0 { // Open it:
d.openList(setFocus)
return true, true // capture
}
return true, false
})
}

@ -1,51 +0,0 @@
package tview
import "github.com/gdamore/tcell"
// EventKey is the key input event info.
// This exists for some consistency with EventMouse,
// even though it's just an alias to tcell.EventKey for backwards compatibility.
type EventKey = tcell.EventKey
// MouseAction are bit flags indicating what the mouse is logically doing.
type MouseAction int
const (
MouseDown MouseAction = 1 << iota
MouseUp
MouseClick // Button1 only.
MouseMove // The mouse position changed.
)
// EventMouse is the mouse event info.
type EventMouse struct {
*tcell.EventMouse
target Primitive
app *Application
action MouseAction
}
// Target gets the target Primitive of the mouse event.
func (e *EventMouse) Target() Primitive {
return e.target
}
// Application gets the event originating *Application.
func (e *EventMouse) Application() *Application {
return e.app
}
// MouseAction gets the mouse action of this event.
func (e *EventMouse) Action() MouseAction {
return e.action
}
// SetFocus will set focus to the primitive.
func (e *EventMouse) SetFocus(p Primitive) {
e.app.SetFocus(p)
}
// NewEventMouse creates a new mouse event.
func NewEventMouse(base *tcell.EventMouse, target Primitive, app *Application, action MouseAction) *EventMouse {
return &EventMouse{base, target, app, action}
}

@ -196,10 +196,19 @@ func (f *Flex) HasFocus() bool {
return false
}
func (f *Flex) GetChildren() []Primitive {
children := make([]Primitive, len(f.items))
for i, item := range f.items {
children[i] = item.Item
}
return children
// MouseHandler returns the mouse handler for this primitive.
func (f *Flex) MouseHandler() func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool) {
return f.WrapMouseHandler(func(event *tcell.EventMouse, action MouseAction, setFocus func(p Primitive)) (consumed, capture bool) {
if !f.InRect(event.Position()) {
return false, false
}
// Process mouse event.
for _, item := range f.items {
consumed, capture = item.Item.MouseHandler()(event, action, setFocus)
if consumed {
return consumed, capture
}
}
return true, false
})
}

@ -601,16 +601,25 @@ func (f *Form) focusIndex() int {
return -1
}
func (f *Form) GetChildren() []Primitive {
children := make([]Primitive, len(f.items)+len(f.buttons))
i := 0
for _, item := range f.items {
children[i] = item
i++
}
for _, button := range f.buttons {
children[i] = button
i++
}
return children
// MouseHandler returns the mouse handler for this primitive.
func (f *Form) MouseHandler() func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool) {
return f.WrapMouseHandler(func(event *tcell.EventMouse, action MouseAction, setFocus func(p Primitive)) (consumed, capture bool) {
if !f.InRect(event.Position()) {
return false, false
}
// Process mouse event.
for _, item := range f.items {
consumed, capture = item.MouseHandler()(event, action, setFocus)
if consumed {
return consumed, capture
}
}
for _, button := range f.buttons {
consumed, capture = button.MouseHandler()(event, action, setFocus)
if consumed {
return consumed, capture
}
}
return true, false
})
}

@ -156,6 +156,17 @@ func (f *Frame) HasFocus() bool {
return false
}
func (f *Frame) GetChildren() []Primitive {
return []Primitive{f.primitive}
// MouseHandler returns the mouse handler for this primitive.
func (f *Frame) MouseHandler() func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool) {
return f.WrapMouseHandler(func(event *tcell.EventMouse, action MouseAction, setFocus func(p Primitive)) (consumed, capture bool) {
if !f.InRect(event.Position()) {
return false, false
}
// Process mouse event.
consumed, capture = f.primitive.MouseHandler()(event, action, setFocus)
if consumed {
return consumed, capture
}
return true, false
})
}

@ -661,10 +661,19 @@ func (g *Grid) Draw(screen tcell.Screen) {
}
}
func (g *Grid) GetChildren() []Primitive {
children := make([]Primitive, len(g.items))
for i, item := range g.items {
children[i] = item.Item
}
return children
// MouseHandler returns the mouse handler for this primitive.
func (g *Grid) MouseHandler() func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool) {
return g.WrapMouseHandler(func(event *tcell.EventMouse, action MouseAction, setFocus func(p Primitive)) (consumed, capture bool) {
if !g.InRect(event.Position()) {
return false, false
}
// Process mouse event.
for _, item := range g.items {
consumed, capture = item.Item.MouseHandler()(event, action, setFocus)
if consumed {
return consumed, capture
}
}
return true, false
})
}

@ -593,11 +593,15 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
}
// MouseHandler returns the mouse handler for this primitive.
func (i *InputField) MouseHandler() func(event *EventMouse) {
return i.WrapMouseHandler(func(event *EventMouse) {
func (i *InputField) MouseHandler() func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool) {
return i.WrapMouseHandler(func(event *tcell.EventMouse, action MouseAction, setFocus func(p Primitive)) (consumed, capture bool) {
if !i.InRect(event.Position()) {
return false, false
}
// Process mouse event.
if event.Action()&MouseDown != 0 {
event.SetFocus(i)
if action&MouseLeftDown != 0 {
setFocus(i)
}
return true, false
})
}

@ -547,10 +547,13 @@ func (l *List) indexAtPoint(atX, atY int) int {
}
// MouseHandler returns the mouse handler for this primitive.
func (l *List) MouseHandler() func(event *EventMouse) {
return l.WrapMouseHandler(func(event *EventMouse) {
func (l *List) MouseHandler() func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool) {
return l.WrapMouseHandler(func(event *tcell.EventMouse, action MouseAction, setFocus func(p Primitive)) (consumed, capture bool) {
if !l.InRect(event.Position()) {
return false, false
}
// Process mouse event.
if event.Action()&MouseClick != 0 {
if action&MouseLeftClick != 0 {
atX, atY := event.Position()
index := l.indexAtPoint(atX, atY)
if index != -1 {
@ -567,5 +570,6 @@ func (l *List) MouseHandler() func(event *EventMouse) {
l.currentItem = index
}
}
return true, false
})
}

@ -170,6 +170,17 @@ func (m *Modal) Draw(screen tcell.Screen) {
m.frame.Draw(screen)
}
func (m *Modal) GetChildren() []Primitive {
return []Primitive{m.frame}
// MouseHandler returns the mouse handler for this primitive.
func (m *Modal) MouseHandler() func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool) {
return m.WrapMouseHandler(func(event *tcell.EventMouse, action MouseAction, setFocus func(p Primitive)) (consumed, capture bool) {
if !m.InRect(event.Position()) {
return false, false
}
// Process mouse event.
consumed, capture = m.frame.MouseHandler()(event, action, setFocus)
if consumed {
return consumed, capture
}
return true, false
})
}

@ -0,0 +1,103 @@
package tview
import (
"github.com/gdamore/tcell"
)
// MouseAction are bit flags indicating what the mouse is logically doing.
type MouseAction int32
const (
MouseMove MouseAction = 1 << iota
MouseLeftDown
MouseLeftUp
MouseLeftClick
MouseLeftDoubleClick
MouseMiddleDown
MouseMiddleUp
MouseMiddleClick
MouseMiddleDoubleClick
MouseRightDown
MouseRightUp
MouseRightClick
MouseRightDoubleClick
WheelUp
WheelDown
WheelLeft
WheelRight
)
// Does not set MouseMove or *Click actions.
func getMouseButtonAction(lastBtn, btn tcell.ButtonMask) MouseAction {
btnDiff := btn ^ lastBtn
var action MouseAction
if btnDiff&tcell.Button1 != 0 {
if btn&tcell.Button1 != 0 {
action |= MouseLeftDown
} else {
action |= MouseLeftUp
}
}
if btnDiff&tcell.Button2 != 0 {
if btn&tcell.Button2 != 0 {
action |= MouseMiddleDown
} else {
action |= MouseMiddleUp
}
}
if btnDiff&tcell.Button3 != 0 {
if btn&tcell.Button3 != 0 {
action |= MouseRightDown
} else {
action |= MouseRightUp
}
}
if btn&tcell.WheelUp != 0 {
action |= WheelUp
}
if btn&tcell.WheelDown != 0 {
action |= WheelDown
}
if btn&tcell.WheelLeft != 0 {
action |= WheelLeft
}
if btn&tcell.WheelRight != 0 {
action |= WheelRight
}
return action
}
// Do not call if the mouse moved.
// Sets the *Click, including *DoubleClick.
// This should be called last, after setting all the other flags.
func getMouseClickAction(lastAct, action MouseAction) MouseAction {
if action&MouseMove == 0 {
if action&MouseLeftUp != 0 {
if lastAct&(MouseLeftClick&MouseLeftDoubleClick) == 0 {
action |= MouseLeftClick
} else if lastAct&MouseLeftDoubleClick == 0 {
action |= MouseLeftDoubleClick
}
}
if action&MouseMiddleUp != 0 {
if lastAct&(MouseMiddleClick&MouseMiddleDoubleClick) == 0 {
action |= MouseMiddleClick
} else if lastAct&MouseMiddleDoubleClick == 0 {
action |= MouseMiddleDoubleClick
}
}
if action&MouseRightUp != 0 {
if lastAct&(MouseRightClick&MouseRightDoubleClick) == 0 {
action |= MouseRightClick
} else if lastAct&MouseRightDoubleClick == 0 {
action |= MouseRightDoubleClick
}
}
}
return action
}

@ -279,14 +279,21 @@ func (p *Pages) Draw(screen tcell.Screen) {
}
}
func (p *Pages) GetChildren() []Primitive {
var children []Primitive
for _, page := range p.pages {
// Considering invisible pages as not children.
// Even though we track all the pages, not all are "children" currently.
if page.Visible {
children = append(children, page.Item)
// MouseHandler returns the mouse handler for this primitive.
func (p *Pages) MouseHandler() func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool) {
return p.WrapMouseHandler(func(event *tcell.EventMouse, action MouseAction, setFocus func(p Primitive)) (consumed, capture bool) {
if !p.InRect(event.Position()) {
return false, false
}
}
return children
// Process mouse event.
for _, page := range p.pages {
if page.Visible {
consumed, capture = page.Item.MouseHandler()(event, action, setFocus)
if consumed {
return consumed, capture
}
}
}
return true, false
})
}

@ -44,9 +44,6 @@ type Primitive interface {
// GetFocusable returns the item's Focusable.
GetFocusable() Focusable
// GetChildren gets the children.
GetChildren() []Primitive
// MouseHandler returns a handler which receives mouse events.
// It is called by the Application class.
//
@ -55,5 +52,5 @@ type Primitive interface {
// The Box class provides functionality to intercept mouse events. If you
// subclass from Box, it is recommended that you wrap your handler using
// Box.WrapMouseHandler() so you inherit that functionality.
MouseHandler() func(event *EventMouse)
MouseHandler() func(*tcell.EventMouse, MouseAction, func(p Primitive)) (bool, bool)
}

Loading…
Cancel
Save