|
|
|
@ -8,11 +8,52 @@ import (
@@ -8,11 +8,52 @@ import (
|
|
|
|
|
"github.com/gdamore/tcell" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// The size of the event/update/redraw channels.
|
|
|
|
|
const queueSize = 100 |
|
|
|
|
const ( |
|
|
|
|
// The size of the event/update/redraw channels.
|
|
|
|
|
queueSize = 100 |
|
|
|
|
|
|
|
|
|
// The minimum duration between resize event callbacks.
|
|
|
|
|
const resizeEventThrottle = 200 * time.Millisecond |
|
|
|
|
// The minimum time between two consecutive redraws.
|
|
|
|
|
redrawPause = 50 * time.Millisecond |
|
|
|
|
|
|
|
|
|
// The minimum duration between resize event callbacks.
|
|
|
|
|
resizeEventThrottle = 200 * time.Millisecond |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// DoubleClickInterval specifies the maximum time between clicks to register a
|
|
|
|
|
// double click rather than click.
|
|
|
|
|
var DoubleClickInterval = 500 * time.Millisecond |
|
|
|
|
|
|
|
|
|
// MouseAction indicates one of the actions the mouse is logically doing.
|
|
|
|
|
type MouseAction int16 |
|
|
|
|
|
|
|
|
|
// Available mouse actions.
|
|
|
|
|
const ( |
|
|
|
|
MouseMove MouseAction = iota |
|
|
|
|
MouseLeftDown |
|
|
|
|
MouseLeftUp |
|
|
|
|
MouseLeftClick |
|
|
|
|
MouseLeftDoubleClick |
|
|
|
|
MouseMiddleDown |
|
|
|
|
MouseMiddleUp |
|
|
|
|
MouseMiddleClick |
|
|
|
|
MouseMiddleDoubleClick |
|
|
|
|
MouseRightDown |
|
|
|
|
MouseRightUp |
|
|
|
|
MouseRightClick |
|
|
|
|
MouseRightDoubleClick |
|
|
|
|
MouseScrollUp |
|
|
|
|
MouseScrollDown |
|
|
|
|
MouseScrollLeft |
|
|
|
|
MouseScrollRight |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
// queuedUpdate represented the execution of f queued by
|
|
|
|
|
// Application.QueueUpdate(). The "done" channel receives exactly one element
|
|
|
|
|
// after f has executed.
|
|
|
|
|
type queuedUpdate struct { |
|
|
|
|
f func() |
|
|
|
|
done chan struct{} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Application represents the top node of an application.
|
|
|
|
|
//
|
|
|
|
@ -84,14 +125,13 @@ type Application struct {
@@ -84,14 +125,13 @@ 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) |
|
|
|
|
|
|
|
|
|
// A temporary capture function overriding the above.
|
|
|
|
|
tempMouseCapture func(event *EventMouse) *EventMouse |
|
|
|
|
|
|
|
|
|
lastMouseX, lastMouseY int |
|
|
|
|
lastMouseBtn tcell.ButtonMask |
|
|
|
|
lastMouseTarget Primitive // nil if none
|
|
|
|
|
mouseCapturingPrimitive Primitive // A Primitive returned by a MouseHandler which will capture future mouse events.
|
|
|
|
|
lastMouseX, lastMouseY int // The last position of the mouse.
|
|
|
|
|
mouseDownX, mouseDownY int // The position of the mouse when its button was last pressed.
|
|
|
|
|
lastMouseClick time.Time // The time when a mouse button was last clicked.
|
|
|
|
|
lastMouseButtons tcell.ButtonMask // The last mouse button state.
|
|
|
|
|
|
|
|
|
|
sync.RWMutex |
|
|
|
|
} |
|
|
|
@ -131,45 +171,22 @@ func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.Event
@@ -131,45 +171,22 @@ func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.Event
|
|
|
|
|
return a.inputCapture |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// SetMouseCapture sets a function which captures mouse events before they are
|
|
|
|
|
// SetMouseCapture sets a function which captures mouse events (consisting of
|
|
|
|
|
// the original tcell mouse event and the semantic mouse action) before they are
|
|
|
|
|
// forwarded to the appropriate mouse event handler. 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 { |
|
|
|
|
a.Lock() |
|
|
|
|
// the event processing by returning a nil mouse event.
|
|
|
|
|
func (a *Application) SetMouseCapture(capture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)) *Application { |
|
|
|
|
a.mouseCapture = capture |
|
|
|
|
a.Unlock() |
|
|
|
|
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 { |
|
|
|
|
a.RLock() |
|
|
|
|
defer a.RUnlock() |
|
|
|
|
|
|
|
|
|
func (a *Application) GetMouseCapture() func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction) { |
|
|
|
|
return a.mouseCapture |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// SetTemporaryMouseCapture temporarily overrides the normal capture function.
|
|
|
|
|
// Calling this function from anywhere other than a widget may result in
|
|
|
|
|
// unexpected behavior.
|
|
|
|
|
func (a *Application) SetTemporaryMouseCapture(capture func(event *EventMouse) *EventMouse) *Application { |
|
|
|
|
a.Lock() |
|
|
|
|
a.tempMouseCapture = capture |
|
|
|
|
a.Unlock() |
|
|
|
|
return a |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// GetTemporaryMouseCapture returns the function installed with
|
|
|
|
|
// SetTemporaryMouseCapture() or nil if no such function has been installed.
|
|
|
|
|
func (a *Application) GetTemporaryMouseCapture() func(event *EventMouse) *EventMouse { |
|
|
|
|
a.RLock() |
|
|
|
|
defer a.RUnlock() |
|
|
|
|
|
|
|
|
|
return a.tempMouseCapture |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// SetScreen allows you to provide your own tcell.Screen object. For most
|
|
|
|
|
// applications, this is not needed and you should be familiar with
|
|
|
|
|
// tcell.Screen when using this function.
|
|
|
|
@ -199,10 +216,17 @@ func (a *Application) SetScreen(screen tcell.Screen) *Application {
@@ -199,10 +216,17 @@ func (a *Application) SetScreen(screen tcell.Screen) *Application {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// EnableMouse enables mouse events.
|
|
|
|
|
func (a *Application) EnableMouse() *Application { |
|
|
|
|
func (a *Application) EnableMouse(enable bool) *Application { |
|
|
|
|
a.Lock() |
|
|
|
|
a.enableMouse = true |
|
|
|
|
a.Unlock() |
|
|
|
|
defer a.Unlock() |
|
|
|
|
if enable != a.enableMouse && a.screen != nil { |
|
|
|
|
if enable { |
|
|
|
|
a.screen.EnableMouse() |
|
|
|
|
} else { |
|
|
|
|
a.screen.DisableMouse() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
a.enableMouse = enable |
|
|
|
|
return a |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -301,10 +325,7 @@ EventLoop:
@@ -301,10 +325,7 @@ EventLoop:
|
|
|
|
|
a.RLock() |
|
|
|
|
p := a.focus |
|
|
|
|
inputCapture := a.inputCapture |
|
|
|
|
mouseCapture := a.mouseCapture |
|
|
|
|
tempMouseCapture := a.tempMouseCapture |
|
|
|
|
screen := a.screen |
|
|
|
|
root := a.root |
|
|
|
|
a.RUnlock() |
|
|
|
|
|
|
|
|
|
switch event := event.(type) { |
|
|
|
@ -368,85 +389,14 @@ EventLoop:
@@ -368,85 +389,14 @@ EventLoop:
|
|
|
|
|
|
|
|
|
|
a.draw() |
|
|
|
|
case *tcell.EventMouse: |
|
|
|
|
atX, atY := event.Position() |
|
|
|
|
btn := event.Buttons() |
|
|
|
|
|
|
|
|
|
pstack := a.appendStackAtPoint(nil, atX, atY) |
|
|
|
|
var punderMouse Primitive |
|
|
|
|
if len(pstack) > 0 { |
|
|
|
|
punderMouse = pstack[len(pstack)-1] |
|
|
|
|
} |
|
|
|
|
var ptarget Primitive |
|
|
|
|
if a.lastMouseBtn != 0 { |
|
|
|
|
// While a button is down, the same primitive gets events.
|
|
|
|
|
ptarget = a.lastMouseTarget |
|
|
|
|
} |
|
|
|
|
if ptarget == nil { |
|
|
|
|
ptarget = punderMouse |
|
|
|
|
if ptarget == nil { |
|
|
|
|
ptarget = root // Fallback to root.
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
a.lastMouseTarget = ptarget |
|
|
|
|
|
|
|
|
|
// Calculate mouse actions.
|
|
|
|
|
var act MouseAction |
|
|
|
|
if atX != a.lastMouseX || atY != a.lastMouseY { |
|
|
|
|
act |= MouseMove |
|
|
|
|
a.lastMouseX = atX |
|
|
|
|
a.lastMouseY = 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 == punderMouse { |
|
|
|
|
// Only if Button1 and mouse up over same p.
|
|
|
|
|
act |= MouseClick |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
a.lastMouseBtn = btn |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
event2 := NewEventMouse(event, ptarget, a, act) |
|
|
|
|
|
|
|
|
|
// Intercept event.
|
|
|
|
|
if tempMouseCapture != nil { |
|
|
|
|
event2 = tempMouseCapture(event2) |
|
|
|
|
if event2 == nil { |
|
|
|
|
a.draw() |
|
|
|
|
continue // Don't forward event.
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if mouseCapture != nil { |
|
|
|
|
event2 = mouseCapture(event2) |
|
|
|
|
if event2 == nil { |
|
|
|
|
a.draw() |
|
|
|
|
continue // Don't forward event.
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if ptarget == punderMouse { |
|
|
|
|
// Observe mouse events inward ("capture")
|
|
|
|
|
for _, pp := range pstack { |
|
|
|
|
// If the primitive has this ObserveMouseEvent func.
|
|
|
|
|
if pp, ok := pp.(interface { |
|
|
|
|
ObserveMouseEvent(*EventMouse) |
|
|
|
|
}); ok { |
|
|
|
|
pp.ObserveMouseEvent(event2) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if handler := ptarget.MouseHandler(); handler != nil { |
|
|
|
|
handler(event2) |
|
|
|
|
consumed, isMouseDownAction := a.fireMouseActions(event) |
|
|
|
|
if consumed { |
|
|
|
|
a.draw() |
|
|
|
|
} |
|
|
|
|
a.lastMouseButtons = event.Buttons() |
|
|
|
|
if isMouseDownAction { |
|
|
|
|
a.mouseDownX, a.mouseDownY = event.Position() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// If we have updates, now is the time to execute them.
|
|
|
|
@ -462,48 +412,103 @@ EventLoop:
@@ -462,48 +412,103 @@ EventLoop:
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func findAtPoint(atX, atY int, p Primitive, capture func(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 |
|
|
|
|
} |
|
|
|
|
if capture != nil { |
|
|
|
|
capture(p) |
|
|
|
|
} |
|
|
|
|
bestp := p |
|
|
|
|
for _, pchild := range p.GetChildren() { |
|
|
|
|
x := findAtPoint(atX, atY, pchild, capture) |
|
|
|
|
if x != nil { |
|
|
|
|
// Always overwrite if we find another one,
|
|
|
|
|
// this is because if any overlap, the last one is "on top".
|
|
|
|
|
bestp = x |
|
|
|
|
// fireMouseActions analyzes the provided mouse event, derives mouse actions
|
|
|
|
|
// from it and then forwards them to the corresponding primitives.
|
|
|
|
|
func (a *Application) fireMouseActions(event *tcell.EventMouse) (consumed, isMouseDownAction bool) { |
|
|
|
|
// We want to relay follow-up events to the same target primitive.
|
|
|
|
|
var targetPrimitive Primitive |
|
|
|
|
|
|
|
|
|
// Helper function to fire a mouse action.
|
|
|
|
|
fire := func(action MouseAction) { |
|
|
|
|
switch action { |
|
|
|
|
case MouseLeftDown, MouseMiddleDown, MouseRightDown: |
|
|
|
|
isMouseDownAction = true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Intercept event.
|
|
|
|
|
if a.mouseCapture != nil { |
|
|
|
|
event, action = a.mouseCapture(event, action) |
|
|
|
|
if event == nil { |
|
|
|
|
consumed = true |
|
|
|
|
return // Don't forward event.
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Determine the target primitive.
|
|
|
|
|
var primitive, capturingPrimitive Primitive |
|
|
|
|
if a.mouseCapturingPrimitive != nil { |
|
|
|
|
primitive = a.mouseCapturingPrimitive |
|
|
|
|
targetPrimitive = a.mouseCapturingPrimitive |
|
|
|
|
} else if targetPrimitive != nil { |
|
|
|
|
primitive = targetPrimitive |
|
|
|
|
} else { |
|
|
|
|
primitive = a.root |
|
|
|
|
} |
|
|
|
|
if primitive != nil { |
|
|
|
|
if handler := primitive.MouseHandler(); handler != nil { |
|
|
|
|
var wasConsumed bool |
|
|
|
|
wasConsumed, capturingPrimitive = handler(action, event, func(p Primitive) { |
|
|
|
|
a.SetFocus(p) |
|
|
|
|
}) |
|
|
|
|
if wasConsumed { |
|
|
|
|
consumed = true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
a.mouseCapturingPrimitive = capturingPrimitive |
|
|
|
|
} |
|
|
|
|
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() |
|
|
|
|
x, y := event.Position() |
|
|
|
|
buttons := event.Buttons() |
|
|
|
|
clickMoved := x != a.mouseDownX || y != a.mouseDownY |
|
|
|
|
buttonChanges := buttons ^ a.lastMouseButtons |
|
|
|
|
|
|
|
|
|
return findAtPoint(atX, atY, a.root, nil) |
|
|
|
|
} |
|
|
|
|
if x != a.lastMouseX || y != a.lastMouseY { |
|
|
|
|
fire(MouseMove) |
|
|
|
|
a.lastMouseX = x |
|
|
|
|
a.lastMouseY = y |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// The last element appended to buf is the primitive clicked,
|
|
|
|
|
// the preceeding are its parents.
|
|
|
|
|
func (a *Application) appendStackAtPoint(buf []Primitive, atX, atY int) []Primitive { |
|
|
|
|
a.RLock() |
|
|
|
|
defer a.RUnlock() |
|
|
|
|
for _, buttonEvent := range []struct { |
|
|
|
|
button tcell.ButtonMask |
|
|
|
|
down, up, click, dclick MouseAction |
|
|
|
|
}{ |
|
|
|
|
{tcell.Button1, MouseLeftDown, MouseLeftUp, MouseLeftClick, MouseLeftDoubleClick}, |
|
|
|
|
{tcell.Button2, MouseMiddleDown, MouseMiddleUp, MouseMiddleClick, MouseMiddleDoubleClick}, |
|
|
|
|
{tcell.Button3, MouseRightDown, MouseRightUp, MouseRightClick, MouseRightDoubleClick}, |
|
|
|
|
} { |
|
|
|
|
if buttonChanges&buttonEvent.button != 0 { |
|
|
|
|
if buttons&buttonEvent.button != 0 { |
|
|
|
|
fire(buttonEvent.down) |
|
|
|
|
} else { |
|
|
|
|
fire(buttonEvent.up) |
|
|
|
|
if !clickMoved { |
|
|
|
|
if a.lastMouseClick.Add(DoubleClickInterval).Before(time.Now()) { |
|
|
|
|
fire(buttonEvent.click) |
|
|
|
|
a.lastMouseClick = time.Now() |
|
|
|
|
} else { |
|
|
|
|
fire(buttonEvent.dclick) |
|
|
|
|
a.lastMouseClick = time.Time{} // reset
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
findAtPoint(atX, atY, a.root, func(p Primitive) { |
|
|
|
|
buf = append(buf, p) |
|
|
|
|
}) |
|
|
|
|
return buf |
|
|
|
|
for _, wheelEvent := range []struct { |
|
|
|
|
button tcell.ButtonMask |
|
|
|
|
action MouseAction |
|
|
|
|
}{ |
|
|
|
|
{tcell.WheelUp, MouseScrollUp}, |
|
|
|
|
{tcell.WheelDown, MouseScrollDown}, |
|
|
|
|
{tcell.WheelLeft, MouseScrollLeft}, |
|
|
|
|
{tcell.WheelRight, MouseScrollRight}} { |
|
|
|
|
if buttons&wheelEvent.button != 0 { |
|
|
|
|
fire(wheelEvent.action) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return consumed, isMouseDownAction |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Stop stops the application, causing Run() to return.
|
|
|
|
|