mirror of
https://github.com/rivo/tview.git
synced 2025-04-24 13:48:56 +08:00
Added/fixed comments, some structural changes/bugfixes for mouse support. Table, TextView, and TreeView still open. Closes #363
This commit is contained in:
parent
160d8fda1d
commit
9af6826328
174
application.go
174
application.go
@ -15,6 +15,34 @@ const (
|
||||
redrawPause = 50 * 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
|
||||
WheelUp
|
||||
WheelDown
|
||||
WheelLeft
|
||||
WheelRight
|
||||
)
|
||||
|
||||
// queuedUpdate represented the execution of f queued by
|
||||
// Application.QueueUpdate(). The "done" channel receives exactly one element
|
||||
// after f has executed.
|
||||
@ -52,7 +80,7 @@ type Application struct {
|
||||
// Whether or not the application resizes the root primitive.
|
||||
rootFullscreen bool
|
||||
|
||||
// Enable mouse events?
|
||||
// Set to true if mouse events are enabled.
|
||||
enableMouse bool
|
||||
|
||||
// An optional capture function which receives a key event and returns the
|
||||
@ -85,13 +113,11 @@ type Application struct {
|
||||
// be forwarded).
|
||||
mouseCapture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)
|
||||
|
||||
// An optional mouse capture Primitive returned from the MouseHandler.
|
||||
mouseHandlerCapture Primitive
|
||||
|
||||
lastMouseX, lastMouseY int // track last mouse pos
|
||||
mouseDownX, mouseDownY int // track last mouse down pos
|
||||
lastClickTime time.Time
|
||||
lastMouseBtn tcell.ButtonMask
|
||||
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.
|
||||
}
|
||||
|
||||
// NewApplication creates and returns a new application.
|
||||
@ -123,11 +149,11 @@ func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.Event
|
||||
return a.inputCapture
|
||||
}
|
||||
|
||||
// SetMouseCapture sets a function which captures mouse events 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.
|
||||
// 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 a nil mouse event.
|
||||
func (a *Application) SetMouseCapture(capture func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction)) *Application {
|
||||
a.mouseCapture = capture
|
||||
return a
|
||||
@ -334,8 +360,7 @@ EventLoop:
|
||||
if consumed {
|
||||
a.draw()
|
||||
}
|
||||
// Keep state:
|
||||
a.lastMouseBtn = event.Buttons()
|
||||
a.lastMouseButtons = event.Buttons()
|
||||
if isMouseDownAction {
|
||||
a.mouseDownX, a.mouseDownY = event.Position()
|
||||
}
|
||||
@ -355,64 +380,64 @@ EventLoop:
|
||||
return nil
|
||||
}
|
||||
|
||||
// fireMouseActions determines each mouse action from mouse events
|
||||
// and fires the appropriate mouse handlers and mouse captures.
|
||||
// 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) {
|
||||
a.RLock()
|
||||
root := a.root
|
||||
mouseCapture := a.mouseCapture
|
||||
a.RUnlock()
|
||||
// We want to relay follow-up events to the same target primitive.
|
||||
var targetPrimitive Primitive
|
||||
|
||||
// Fire a mouse action.
|
||||
mouseEv := func(action MouseAction) {
|
||||
// Helper function to fire a mouse action.
|
||||
fire := func(action MouseAction) {
|
||||
switch action {
|
||||
case MouseLeftDown, MouseMiddleDown, MouseRightDown:
|
||||
isMouseDownAction = true
|
||||
}
|
||||
|
||||
// Intercept event.
|
||||
if mouseCapture != nil {
|
||||
event, action = mouseCapture(event, action)
|
||||
if a.mouseCapture != nil {
|
||||
event, action = a.mouseCapture(event, action)
|
||||
if event == nil {
|
||||
consumed = true
|
||||
return // Don't forward event.
|
||||
}
|
||||
}
|
||||
|
||||
var handlerTarget Primitive
|
||||
if a.mouseHandlerCapture != nil { // Check if already captured.
|
||||
handlerTarget = a.mouseHandlerCapture
|
||||
// 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 {
|
||||
handlerTarget = root
|
||||
primitive = a.root
|
||||
}
|
||||
|
||||
var newHandlerCapture Primitive // None by default.
|
||||
if handlerTarget != nil {
|
||||
if handler := handlerTarget.MouseHandler(); handler != nil {
|
||||
hconsumed := false
|
||||
hconsumed, newHandlerCapture = handler(action, event, func(p Primitive) {
|
||||
if primitive != nil {
|
||||
if handler := primitive.MouseHandler(); handler != nil {
|
||||
var wasConsumed bool
|
||||
wasConsumed, capturingPrimitive = handler(action, event, func(p Primitive) {
|
||||
a.SetFocus(p)
|
||||
})
|
||||
if hconsumed {
|
||||
if wasConsumed {
|
||||
consumed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
a.mouseHandlerCapture = newHandlerCapture
|
||||
a.mouseCapturingPrimitive = capturingPrimitive
|
||||
}
|
||||
|
||||
atX, atY := event.Position()
|
||||
ebuttons := event.Buttons()
|
||||
clickMoved := atX != a.mouseDownX || atY != a.mouseDownY
|
||||
btnDiff := ebuttons ^ a.lastMouseBtn
|
||||
x, y := event.Position()
|
||||
buttons := event.Buttons()
|
||||
clickMoved := x != a.mouseDownX || y != a.mouseDownY
|
||||
buttonChanges := buttons ^ a.lastMouseButtons
|
||||
|
||||
if atX != a.lastMouseX || atY != a.lastMouseY {
|
||||
mouseEv(MouseMove)
|
||||
a.lastMouseX = atX
|
||||
a.lastMouseY = atY
|
||||
if x != a.lastMouseX || y != a.lastMouseY {
|
||||
fire(MouseMove)
|
||||
a.lastMouseX = x
|
||||
a.lastMouseY = y
|
||||
}
|
||||
|
||||
for _, x := range []struct {
|
||||
for _, buttonEvent := range []struct {
|
||||
button tcell.ButtonMask
|
||||
down, up, click, dclick MouseAction
|
||||
}{
|
||||
@ -420,32 +445,34 @@ func (a *Application) fireMouseActions(event *tcell.EventMouse) (consumed, isMou
|
||||
{tcell.Button2, MouseMiddleDown, MouseMiddleUp, MouseMiddleClick, MouseMiddleDoubleClick},
|
||||
{tcell.Button3, MouseRightDown, MouseRightUp, MouseRightClick, MouseRightDoubleClick},
|
||||
} {
|
||||
if btnDiff&x.button != 0 {
|
||||
if ebuttons&x.button != 0 {
|
||||
mouseEv(x.down)
|
||||
if buttonChanges&buttonEvent.button != 0 {
|
||||
if buttons&buttonEvent.button != 0 {
|
||||
fire(buttonEvent.down)
|
||||
} else {
|
||||
mouseEv(x.up)
|
||||
fire(buttonEvent.up)
|
||||
if !clickMoved {
|
||||
if a.lastClickTime.Add(DoubleClickInterval).Before(time.Now()) {
|
||||
mouseEv(x.click)
|
||||
a.lastClickTime = time.Now()
|
||||
if a.lastMouseClick.Add(DoubleClickInterval).Before(time.Now()) {
|
||||
fire(buttonEvent.click)
|
||||
a.lastMouseClick = time.Now()
|
||||
} else {
|
||||
mouseEv(x.dclick)
|
||||
a.lastClickTime = time.Time{} // reset
|
||||
fire(buttonEvent.dclick)
|
||||
a.lastMouseClick = time.Time{} // reset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, x := range []struct {
|
||||
for _, wheelEvent := range []struct {
|
||||
button tcell.ButtonMask
|
||||
action MouseAction
|
||||
}{
|
||||
{tcell.WheelUp, WheelUp}, {tcell.WheelDown, WheelDown},
|
||||
{tcell.WheelLeft, WheelLeft}, {tcell.WheelRight, WheelRight}} {
|
||||
if ebuttons&x.button != 0 {
|
||||
mouseEv(x.action)
|
||||
{tcell.WheelUp, WheelUp},
|
||||
{tcell.WheelDown, WheelDown},
|
||||
{tcell.WheelLeft, WheelLeft},
|
||||
{tcell.WheelRight, WheelRight}} {
|
||||
if buttons&wheelEvent.button != 0 {
|
||||
fire(wheelEvent.action)
|
||||
}
|
||||
}
|
||||
|
||||
@ -699,30 +726,3 @@ func (a *Application) QueueEvent(event tcell.Event) *Application {
|
||||
a.events <- event
|
||||
return a
|
||||
}
|
||||
|
||||
// MouseAction indicates one of the actions the mouse is logically doing.
|
||||
type MouseAction int16
|
||||
|
||||
const (
|
||||
MouseMove MouseAction = iota
|
||||
MouseLeftDown
|
||||
MouseLeftUp
|
||||
MouseLeftClick
|
||||
MouseLeftDoubleClick
|
||||
MouseMiddleDown
|
||||
MouseMiddleUp
|
||||
MouseMiddleClick
|
||||
MouseMiddleDoubleClick
|
||||
MouseRightDown
|
||||
MouseRightUp
|
||||
MouseRightClick
|
||||
MouseRightDoubleClick
|
||||
WheelUp
|
||||
WheelDown
|
||||
WheelLeft
|
||||
WheelRight
|
||||
)
|
||||
|
||||
// DoubleClickInterval specifies the maximum time between clicks
|
||||
// to register a double click rather than click.
|
||||
var DoubleClickInterval = 500 * time.Millisecond
|
||||
|
36
box.go
36
box.go
@ -61,8 +61,8 @@ type Box struct {
|
||||
draw func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)
|
||||
|
||||
// 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).
|
||||
// event to be forwarded to the primitive's default mouse event handler (at
|
||||
// least one nil if nothing should be forwarded).
|
||||
mouseCapture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)
|
||||
}
|
||||
|
||||
@ -199,8 +199,8 @@ func (b *Box) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
|
||||
}
|
||||
|
||||
// WrapMouseHandler wraps a mouse event handler (see MouseHandler()) with the
|
||||
// functionality to capture input (see SetMouseCapture()) before passing it
|
||||
// on to the provided (default) event handler.
|
||||
// functionality to capture mouse events (see SetMouseCapture()) before passing
|
||||
// them on to the provided (default) event handler.
|
||||
//
|
||||
// This is only meant to be used by subclassing primitives.
|
||||
func (b *Box) WrapMouseHandler(mouseHandler func(MouseAction, *tcell.EventMouse, func(p Primitive)) (bool, Primitive)) func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
@ -217,14 +217,21 @@ func (b *Box) WrapMouseHandler(mouseHandler func(MouseAction, *tcell.EventMouse,
|
||||
|
||||
// MouseHandler returns nil.
|
||||
func (b *Box) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return b.WrapMouseHandler(nil)
|
||||
return b.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if action == MouseLeftClick && b.InRect(event.Position()) {
|
||||
setFocus(b)
|
||||
consumed = true
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// SetMouseCapture installs a function which captures events before they are
|
||||
// forwarded to the primitive's default event handler. This function can
|
||||
// then choose to forward that event (or a different one) to the default
|
||||
// handler by returning it. If nil is returned, the default handler will not
|
||||
// be called.
|
||||
// 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 primitive's default mouse event handler. This function can
|
||||
// then choose to forward that event (or a different one) by returning it or
|
||||
// returning a nil mouse event, in which case the default handler will not be
|
||||
// called.
|
||||
//
|
||||
// Providing a nil handler will remove a previously existing handler.
|
||||
func (b *Box) SetMouseCapture(capture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)) *Box {
|
||||
@ -232,10 +239,11 @@ func (b *Box) SetMouseCapture(capture func(action MouseAction, event *tcell.Even
|
||||
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
|
||||
// InRect returns true if the given coordinate is within the bounds of the box's
|
||||
// rectangle.
|
||||
func (b *Box) InRect(x, y int) bool {
|
||||
rectX, rectY, width, height := b.GetRect()
|
||||
return x >= rectX && x < rectX+width && y >= rectY && y < rectY+height
|
||||
}
|
||||
|
||||
// GetMouseCapture returns the function installed with SetMouseCapture() or nil
|
||||
|
@ -142,12 +142,16 @@ func (b *Button) MouseHandler() func(action MouseAction, event *tcell.EventMouse
|
||||
if !b.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Process mouse event.
|
||||
if action == MouseLeftClick {
|
||||
setFocus(b)
|
||||
if b.selected != nil {
|
||||
b.selected()
|
||||
}
|
||||
consumed = true
|
||||
}
|
||||
return true, nil
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
12
checkbox.go
12
checkbox.go
@ -205,16 +205,22 @@ 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(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return c.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if !c.InRect(event.Position()) {
|
||||
x, y := event.Position()
|
||||
_, rectY, _, _ := c.GetInnerRect()
|
||||
if !c.InRect(x, y) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Process mouse event.
|
||||
if action == MouseLeftClick {
|
||||
if action == MouseLeftClick && y == rectY {
|
||||
setFocus(c)
|
||||
c.checked = !c.checked
|
||||
if c.changed != nil {
|
||||
c.changed(c.checked)
|
||||
}
|
||||
consumed = true
|
||||
}
|
||||
return true, nil
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ func main() {
|
||||
app.Stop()
|
||||
})
|
||||
button.SetBorder(true).SetRect(0, 0, 22, 3)
|
||||
if err := app.SetRoot(button, false).Run(); err != nil {
|
||||
if err := app.SetRoot(button, false).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import "github.com/rivo/tview"
|
||||
func main() {
|
||||
app := tview.NewApplication()
|
||||
checkbox := tview.NewCheckbox().SetLabel("Hit Enter to check box: ")
|
||||
if err := app.SetRoot(checkbox, true).Run(); err != nil {
|
||||
if err := app.SetRoot(checkbox, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ func main() {
|
||||
dropdown := tview.NewDropDown().
|
||||
SetLabel("Select an option (hit Enter): ").
|
||||
SetOptions([]string{"First", "Second", "Third", "Fourth", "Fifth"}, nil)
|
||||
if err := app.SetRoot(dropdown, true).Run(); err != nil {
|
||||
if err := app.SetRoot(dropdown, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ func main() {
|
||||
AddItem(tview.NewBox().SetBorder(true).SetTitle("Middle (3 x height of Top)"), 0, 3, false).
|
||||
AddItem(tview.NewBox().SetBorder(true).SetTitle("Bottom (5 rows)"), 5, 1, false), 0, 2, false).
|
||||
AddItem(tview.NewBox().SetBorder(true).SetTitle("Right (20 cols)"), 20, 1, false)
|
||||
if err := app.SetRoot(flex, true).Run(); err != nil {
|
||||
if err := app.SetRoot(flex, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ func main() {
|
||||
app.Stop()
|
||||
})
|
||||
form.SetBorder(true).SetTitle("Enter some data").SetTitleAlign(tview.AlignLeft)
|
||||
if err := app.SetRoot(form, true).Run(); err != nil {
|
||||
if err := app.SetRoot(form, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ func main() {
|
||||
AddText("Header second middle", true, tview.AlignCenter, tcell.ColorRed).
|
||||
AddText("Footer middle", false, tview.AlignCenter, tcell.ColorGreen).
|
||||
AddText("Footer second middle", false, tview.AlignCenter, tcell.ColorGreen)
|
||||
if err := app.SetRoot(frame, true).Run(); err != nil {
|
||||
if err := app.SetRoot(frame, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ func main() {
|
||||
AddItem(main, 1, 1, 1, 1, 0, 100, false).
|
||||
AddItem(sideBar, 1, 2, 1, 1, 0, 100, false)
|
||||
|
||||
if err := tview.NewApplication().SetRoot(grid, true).Run(); err != nil {
|
||||
if err := tview.NewApplication().SetRoot(grid, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ func main() {
|
||||
SetDoneFunc(func(key tcell.Key) {
|
||||
app.Stop()
|
||||
})
|
||||
if err := app.SetRoot(inputField, true).Run(); err != nil {
|
||||
if err := app.SetRoot(inputField, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,7 @@ func main() {
|
||||
AddItem("Quit", "Press to exit", 'q', func() {
|
||||
app.Stop()
|
||||
})
|
||||
app.EnableMouse(true)
|
||||
if err := app.SetRoot(list, true).Run(); err != nil {
|
||||
if err := app.SetRoot(list, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ func main() {
|
||||
app.Stop()
|
||||
}
|
||||
})
|
||||
if err := app.SetRoot(modal, false).Run(); err != nil {
|
||||
if err := app.SetRoot(modal, false).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ func main() {
|
||||
page == 0)
|
||||
}(page)
|
||||
}
|
||||
if err := app.SetRoot(pages, true).Run(); err != nil {
|
||||
if err := app.SetRoot(pages, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -91,10 +91,8 @@ func main() {
|
||||
return event
|
||||
})
|
||||
|
||||
app.EnableMouse(true)
|
||||
|
||||
// Start the application.
|
||||
if err := app.SetRoot(layout, true).Run(); err != nil {
|
||||
if err := app.SetRoot(layout, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
77
dropdown.go
77
dropdown.go
@ -79,6 +79,8 @@ type DropDown struct {
|
||||
// A callback function which is called when the user changes the drop-down's
|
||||
// selection.
|
||||
selected func(text string, index int)
|
||||
|
||||
dragging bool // Set to true when mouse dragging is in progress.
|
||||
}
|
||||
|
||||
// NewDropDown returns a new drop-down.
|
||||
@ -417,8 +419,8 @@ func (d *DropDown) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
|
||||
})
|
||||
}
|
||||
|
||||
// A helper function which selects an item in the drop-down list based on
|
||||
// the current prefix.
|
||||
// evalPrefix is selects an item in the drop-down list based on the current
|
||||
// prefix.
|
||||
func (d *DropDown) evalPrefix() {
|
||||
if len(d.prefix) > 0 {
|
||||
for index, option := range d.options {
|
||||
@ -427,17 +429,23 @@ func (d *DropDown) evalPrefix() {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Prefix does not match any item. Remove last rune.
|
||||
|
||||
// Prefix does not match any item. Remove last rune. TODO: Use uniseg here.
|
||||
r := []rune(d.prefix)
|
||||
d.prefix = string(r[:len(r)-1])
|
||||
}
|
||||
}
|
||||
|
||||
// Hand control over to the list.
|
||||
// openList hands control over to the embedded List primitive.
|
||||
func (d *DropDown) openList(setFocus func(Primitive)) {
|
||||
d.open = true
|
||||
optionBefore := d.currentOption
|
||||
|
||||
d.list.SetSelectedFunc(func(index int, mainText, secondaryText string, shortcut rune) {
|
||||
if d.dragging {
|
||||
return // If we're dragging the mouse, we don't want to trigger any events.
|
||||
}
|
||||
|
||||
// An option was selected. Close the list again.
|
||||
d.currentOption = index
|
||||
d.closeList(setFocus)
|
||||
@ -465,11 +473,15 @@ func (d *DropDown) openList(setFocus func(Primitive)) {
|
||||
} else {
|
||||
d.prefix = ""
|
||||
}
|
||||
|
||||
return event
|
||||
})
|
||||
|
||||
setFocus(d.list)
|
||||
}
|
||||
|
||||
// closeList closes the embedded List element by hiding it and removing focus
|
||||
// from it.
|
||||
func (d *DropDown) closeList(setFocus func(Primitive)) {
|
||||
d.open = false
|
||||
if d.list.HasFocus() {
|
||||
@ -493,39 +505,44 @@ func (d *DropDown) HasFocus() bool {
|
||||
return d.hasFocus
|
||||
}
|
||||
|
||||
func (d *DropDown) listClick(event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool) {
|
||||
if !d.list.InRect(event.Position()) {
|
||||
return false
|
||||
}
|
||||
// Mouse is within the list.
|
||||
if handler := d.list.MouseHandler(); handler != nil {
|
||||
consumed, _ := handler(MouseLeftClick, event, setFocus)
|
||||
return consumed
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (d *DropDown) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return d.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
inRect := d.InRect(event.Position()) // mouse in the dropdown box itself, not the list.
|
||||
// Was the mouse event in the drop-down box itself (or on its label)?
|
||||
x, y := event.Position()
|
||||
_, rectY, _, _ := d.GetInnerRect()
|
||||
inRect := y == rectY
|
||||
if !d.open && !inRect {
|
||||
return false, nil
|
||||
return d.InRect(x, y), nil // No, and it's not expanded either. Ignore.
|
||||
}
|
||||
// Process mouse event.
|
||||
if action == MouseLeftClick {
|
||||
if !d.open { // not open
|
||||
|
||||
// Handle dragging. Clicks are implicitly handled by this logic.
|
||||
switch action {
|
||||
case MouseLeftDown:
|
||||
consumed = d.open || inRect
|
||||
capture = d
|
||||
if !d.open {
|
||||
d.openList(setFocus)
|
||||
} else { // if d.open
|
||||
if !inRect {
|
||||
d.listClick(event, setFocus)
|
||||
}
|
||||
d.closeList(setFocus)
|
||||
d.dragging = true
|
||||
} else if consumed, _ := d.list.MouseHandler()(MouseLeftClick, event, setFocus); !consumed {
|
||||
d.closeList(setFocus) // Close drop-down if clicked outside of it.
|
||||
}
|
||||
case MouseMove:
|
||||
if d.dragging {
|
||||
// We pretend it's a left click so we can see the selection during
|
||||
// dragging. Because we don't act upon it, it's not a problem.
|
||||
d.list.MouseHandler()(MouseLeftClick, event, setFocus)
|
||||
consumed = true
|
||||
capture = d
|
||||
}
|
||||
case MouseLeftUp:
|
||||
if d.dragging {
|
||||
d.dragging = false
|
||||
d.list.MouseHandler()(MouseLeftClick, event, setFocus)
|
||||
consumed = true
|
||||
}
|
||||
}
|
||||
if d.open {
|
||||
return true, d // capture
|
||||
}
|
||||
return inRect, nil
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
8
flex.go
8
flex.go
@ -208,13 +208,15 @@ func (f *Flex) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
||||
if !f.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
// Process mouse event.
|
||||
|
||||
// Pass mouse events along to the first child item that takes it.
|
||||
for _, item := range f.items {
|
||||
consumed, capture = item.Item.MouseHandler()(action, event, setFocus)
|
||||
if consumed {
|
||||
return consumed, capture
|
||||
return
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
21
form.go
21
form.go
@ -626,19 +626,32 @@ func (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
||||
if !f.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
// Process mouse event.
|
||||
|
||||
// Determine items to pass mouse events to.
|
||||
for _, item := range f.items {
|
||||
consumed, capture = item.MouseHandler()(action, event, setFocus)
|
||||
if consumed {
|
||||
return consumed, capture
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, button := range f.buttons {
|
||||
consumed, capture = button.MouseHandler()(action, event, setFocus)
|
||||
if consumed {
|
||||
return consumed, capture
|
||||
return
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
|
||||
// A mouse click anywhere else will return the focus to the last selected
|
||||
// element.
|
||||
if action == MouseLeftClick {
|
||||
if f.focusedElement < len(f.items) {
|
||||
setFocus(f.items[f.focusedElement])
|
||||
} else if f.focusedElement < len(f.items)+len(f.buttons) {
|
||||
setFocus(f.buttons[f.focusedElement-len(f.items)])
|
||||
}
|
||||
consumed = true
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
13
frame.go
13
frame.go
@ -12,8 +12,8 @@ type frameText struct {
|
||||
Color tcell.Color // The text color.
|
||||
}
|
||||
|
||||
// Frame is a wrapper which adds a border around another primitive. The top area
|
||||
// (header) and the bottom area (footer) may also contain text.
|
||||
// Frame is a wrapper which adds space around another primitive. In addition,
|
||||
// the top area (header) and the bottom area (footer) may also contain text.
|
||||
//
|
||||
// See https://github.com/rivo/tview/wiki/Frame for an example.
|
||||
type Frame struct {
|
||||
@ -162,11 +162,8 @@ func (f *Frame) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
||||
if !f.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
// Process mouse event.
|
||||
consumed, capture = f.primitive.MouseHandler()(action, event, setFocus)
|
||||
if consumed {
|
||||
return consumed, capture
|
||||
}
|
||||
return true, nil
|
||||
|
||||
// Pass mouse events on to contained primitive.
|
||||
return f.primitive.MouseHandler()(action, event, setFocus)
|
||||
})
|
||||
}
|
||||
|
8
grid.go
8
grid.go
@ -667,13 +667,15 @@ func (g *Grid) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
||||
if !g.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
// Process mouse event.
|
||||
|
||||
// Pass mouse events along to the first child item that takes it.
|
||||
for _, item := range g.items {
|
||||
consumed, capture = item.Item.MouseHandler()(action, event, setFocus)
|
||||
if consumed {
|
||||
return consumed, capture
|
||||
return
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
@ -69,9 +69,6 @@ type InputField struct {
|
||||
// The cursor position as a byte index into the text string.
|
||||
cursorPos int
|
||||
|
||||
// The number of bytes of the text string skipped ahead while drawing.
|
||||
offset int
|
||||
|
||||
// An optional autocomplete function which receives the current text of the
|
||||
// input field and returns a slice of strings to be displayed in a drop-down
|
||||
// selection.
|
||||
@ -96,6 +93,9 @@ type InputField struct {
|
||||
// A callback function set by the Form class and called when the user leaves
|
||||
// this form item.
|
||||
finished func(tcell.Key)
|
||||
|
||||
fieldX int // The x-coordinate of the input field as determined during the last call to Draw().
|
||||
offset int // The number of bytes of the text string skipped ahead while drawing.
|
||||
}
|
||||
|
||||
// NewInputField returns a new input field.
|
||||
@ -326,6 +326,7 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
||||
}
|
||||
|
||||
// Draw input area.
|
||||
i.fieldX = x
|
||||
fieldWidth := i.fieldWidth
|
||||
if fieldWidth == 0 {
|
||||
fieldWidth = math.MaxInt32
|
||||
@ -595,13 +596,30 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (i *InputField) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return i.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if !i.InRect(event.Position()) {
|
||||
x, y := event.Position()
|
||||
_, rectY, _, _ := i.GetInnerRect()
|
||||
if !i.InRect(x, y) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Process mouse event.
|
||||
if action == MouseLeftDown {
|
||||
if action == MouseLeftClick && y == rectY {
|
||||
// Determine where to place the cursor.
|
||||
if x >= i.fieldX {
|
||||
if !iterateString(i.text, func(main rune, comb []rune, textPos int, textWidth int, screenPos int, screenWidth int) bool {
|
||||
if x-i.fieldX < screenPos+screenWidth {
|
||||
i.cursorPos = textPos
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}) {
|
||||
i.cursorPos = len(i.text)
|
||||
}
|
||||
}
|
||||
setFocus(i)
|
||||
consumed = true
|
||||
}
|
||||
return true, nil
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
26
list.go
26
list.go
@ -559,22 +559,23 @@ func (l *List) InputHandler() func(event *tcell.EventKey, setFocus func(p Primit
|
||||
})
|
||||
}
|
||||
|
||||
// returns -1 if not found.
|
||||
func (l *List) indexAtPoint(atX, atY int) int {
|
||||
_, y, _, h := l.GetInnerRect()
|
||||
if atY < y || atY >= y+h {
|
||||
// indexAtPoint returns the index of the list item found at the given position
|
||||
// or -1 if there is no such list item.
|
||||
func (l *List) indexAtPoint(x, y int) int {
|
||||
rectX, rectY, width, height := l.GetInnerRect()
|
||||
if rectX < 0 || rectX >= rectX+width || y < rectY || y >= rectY+height {
|
||||
return -1
|
||||
}
|
||||
|
||||
n := atY - y
|
||||
index := y - rectY
|
||||
if l.showSecondaryText {
|
||||
n /= 2
|
||||
index /= 2
|
||||
}
|
||||
|
||||
if n >= len(l.items) {
|
||||
if index >= len(l.items) {
|
||||
return -1
|
||||
}
|
||||
return n
|
||||
return index
|
||||
}
|
||||
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
@ -583,10 +584,11 @@ func (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
||||
if !l.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Process mouse event.
|
||||
if action == MouseLeftClick {
|
||||
atX, atY := event.Position()
|
||||
index := l.indexAtPoint(atX, atY)
|
||||
setFocus(l)
|
||||
index := l.indexAtPoint(event.Position())
|
||||
if index != -1 {
|
||||
item := l.items[index]
|
||||
if item.Selected != nil {
|
||||
@ -600,7 +602,9 @@ func (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
||||
}
|
||||
l.currentItem = index
|
||||
}
|
||||
consumed = true
|
||||
}
|
||||
return true, nil
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
16
modal.go
16
modal.go
@ -12,7 +12,7 @@ import (
|
||||
type Modal struct {
|
||||
*Box
|
||||
|
||||
// The framed embedded in the modal.
|
||||
// The frame embedded in the modal.
|
||||
frame *Frame
|
||||
|
||||
// The form embedded in the modal's frame.
|
||||
@ -179,14 +179,12 @@ func (m *Modal) Draw(screen tcell.Screen) {
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (m *Modal) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return m.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if !m.InRect(event.Position()) {
|
||||
return false, nil
|
||||
// Pass mouse events on to the form.
|
||||
consumed, capture = m.form.MouseHandler()(action, event, setFocus)
|
||||
if !consumed && action == MouseLeftClick && m.InRect(event.Position()) {
|
||||
setFocus(m)
|
||||
consumed = true
|
||||
}
|
||||
// Process mouse event.
|
||||
consumed, capture = m.frame.MouseHandler()(action, event, setFocus)
|
||||
if consumed {
|
||||
return consumed, capture
|
||||
}
|
||||
return true, nil
|
||||
return
|
||||
})
|
||||
}
|
||||
|
13
pages.go
13
pages.go
@ -20,7 +20,7 @@ type page struct {
|
||||
type Pages struct {
|
||||
*Box
|
||||
|
||||
// The contained pages.
|
||||
// The contained pages. (Visible) pages are drawn from back to front.
|
||||
pages []*page
|
||||
|
||||
// We keep a reference to the function which allows us to set the focus to
|
||||
@ -285,15 +285,18 @@ func (p *Pages) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
||||
if !p.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
// Process mouse event.
|
||||
for _, page := range p.pages {
|
||||
|
||||
// Pass mouse events along to the last visible page item that takes it.
|
||||
for index := len(p.pages) - 1; index >= 0; index-- {
|
||||
page := p.pages[index]
|
||||
if page.Visible {
|
||||
consumed, capture = page.Item.MouseHandler()(action, event, setFocus)
|
||||
if consumed {
|
||||
return consumed, capture
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
@ -47,7 +47,8 @@ type Primitive interface {
|
||||
// MouseHandler returns a handler which receives mouse events.
|
||||
// It is called by the Application class.
|
||||
//
|
||||
// A value of nil may also be returned to stop propagation.
|
||||
// A value of nil may also be returned to stop the downward propagation of
|
||||
// mouse events.
|
||||
//
|
||||
// The Box class provides functionality to intercept mouse events. If you
|
||||
// subclass from Box, it is recommended that you wrap your handler using
|
||||
|
Loading…
x
Reference in New Issue
Block a user