Added/fixed comments, some structural changes/bugfixes for mouse support. Table, TextView, and TreeView still open. Closes #363

pull/422/head
Oliver 4 years ago
parent 160d8fda1d
commit 9af6826328

@ -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

@ -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
})
}

@ -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)
}
}

@ -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
})
}

@ -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
})
}

@ -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
}
}
// 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 true, nil
return
})
}

@ -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)
})
}

@ -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
})
}

@ -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
})
}

@ -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
})
}

@ -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…
Cancel
Save