parent
aae1af2a19
commit
1f765c8695
321
application.go
321
application.go
|
@ -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 {
|
|||
// 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
|
|||
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 {
|
|||
}
|
||||
|
||||
// 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:
|
|||
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:
|
|||
|
||||
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:
|
|||
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
|
||||
// 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
|
||||
}
|
||||
if atX >= x+w || atY >= y+h {
|
||||
return nil
|
||||
|
||||
x, y := event.Position()
|
||||
buttons := event.Buttons()
|
||||
clickMoved := x != a.mouseDownX || y != a.mouseDownY
|
||||
buttonChanges := buttons ^ a.lastMouseButtons
|
||||
|
||||
if x != a.lastMouseX || y != a.lastMouseY {
|
||||
fire(MouseMove)
|
||||
a.lastMouseX = x
|
||||
a.lastMouseY = y
|
||||
}
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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()
|
||||
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 findAtPoint(atX, atY, a.root, nil)
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
findAtPoint(atX, atY, a.root, func(p Primitive) {
|
||||
buf = append(buf, p)
|
||||
})
|
||||
return buf
|
||||
return consumed, isMouseDownAction
|
||||
}
|
||||
|
||||
// Stop stops the application, causing Run() to return.
|
||||
|
|
60
box.go
60
box.go
|
@ -59,9 +59,9 @@ 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).
|
||||
mouseCapture func(event *EventMouse) *EventMouse
|
||||
// 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)
|
||||
|
||||
l sync.RWMutex
|
||||
}
|
||||
|
@ -229,50 +229,56 @@ 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(*EventMouse)) func(*EventMouse) {
|
||||
return func(event *EventMouse) {
|
||||
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) {
|
||||
return func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if b.mouseCapture != nil {
|
||||
event = b.mouseCapture(event)
|
||||
action, event = b.mouseCapture(action, event)
|
||||
}
|
||||
if event != nil && mouseHandler != nil {
|
||||
mouseHandler(event)
|
||||
consumed, capture = mouseHandler(action, event, setFocus)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// MouseHandler returns nil.
|
||||
func (b *Box) MouseHandler() func(event *EventMouse) {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
return b.WrapMouseHandler(nil)
|
||||
func (b *Box) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
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(*EventMouse) *EventMouse) *Box {
|
||||
b.l.Lock()
|
||||
defer b.l.Unlock()
|
||||
|
||||
func (b *Box) SetMouseCapture(capture func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse)) *Box {
|
||||
b.mouseCapture = capture
|
||||
return b
|
||||
}
|
||||
|
||||
// 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
|
||||
// if no such function has been installed.
|
||||
func (b *Box) GetMouseCapture() func(*EventMouse) *EventMouse {
|
||||
b.l.RLock()
|
||||
defer b.l.RUnlock()
|
||||
|
||||
func (b *Box) GetMouseCapture() func(action MouseAction, event *tcell.EventMouse) (MouseAction, *tcell.EventMouse) {
|
||||
return b.mouseCapture
|
||||
}
|
||||
|
||||
|
|
14
button.go
14
button.go
|
@ -167,13 +167,21 @@ 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(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return b.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if !b.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Process mouse event.
|
||||
if event.Action()&MouseClick != 0 {
|
||||
if action == MouseLeftClick {
|
||||
setFocus(b)
|
||||
if b.selected != nil {
|
||||
b.selected()
|
||||
}
|
||||
consumed = true
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
|
18
checkbox.go
18
checkbox.go
|
@ -279,16 +279,24 @@ 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(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) {
|
||||
x, y := event.Position()
|
||||
_, rectY, _, _ := c.GetInnerRect()
|
||||
if !c.InRect(x, y) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Process mouse event.
|
||||
if event.Action()&MouseClick != 0 {
|
||||
c.Lock()
|
||||
if action == MouseLeftClick && y == rectY {
|
||||
setFocus(c)
|
||||
c.checked = !c.checked
|
||||
c.Unlock()
|
||||
if c.changed != nil {
|
||||
c.changed(c.checked)
|
||||
}
|
||||
consumed = true
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
// Demo code for the Checkbox primitive.
|
||||
package main
|
||||
|
||||
import "gitlab.com/tslocum/cview"
|
||||
import (
|
||||
"gitlab.com/tslocum/cview"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cview.NewApplication()
|
||||
checkbox := cview.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 := cview.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(cview.NewBox().SetBorder(true).SetTitle("Middle (3 x height of Top)"), 0, 3, false).
|
||||
AddItem(cview.NewBox().SetBorder(true).SetTitle("Bottom (5 rows)"), 5, 1, false), 0, 2, false).
|
||||
AddItem(cview.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(cview.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, cview.AlignCenter, tcell.ColorRed).
|
||||
AddText("Footer middle", false, cview.AlignCenter, tcell.ColorGreen).
|
||||
AddText("Footer second middle", false, cview.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 := cview.NewApplication().SetRoot(grid, true).Run(); err != nil {
|
||||
if err := cview.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()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ const logo = `
|
|||
const (
|
||||
subtitle = `Terminal-based user interface toolkit`
|
||||
navigation = `Ctrl-N: Next slide Ctrl-P: Previous slide Ctrl-C: Exit`
|
||||
mouse = `(or use your mouse)`
|
||||
)
|
||||
|
||||
// Cover returns the cover page.
|
||||
|
@ -44,7 +45,8 @@ func Cover(nextSlide func()) (title string, content cview.Primitive) {
|
|||
SetBorders(0, 0, 0, 0, 0, 0).
|
||||
AddText(subtitle, true, cview.AlignCenter, tcell.ColorWhite).
|
||||
AddText("", true, cview.AlignCenter, tcell.ColorWhite).
|
||||
AddText(navigation, true, cview.AlignCenter, tcell.ColorDarkMagenta)
|
||||
AddText(navigation, true, cview.AlignCenter, tcell.ColorDarkMagenta).
|
||||
AddText(mouse, true, cview.AlignCenter, tcell.ColorDarkMagenta)
|
||||
|
||||
// Create a Flex layout that centers the logo and subtitle.
|
||||
flex := cview.NewFlex().
|
||||
|
|
|
@ -62,27 +62,29 @@ func main() {
|
|||
End,
|
||||
}
|
||||
|
||||
pages := cview.NewPages()
|
||||
|
||||
// The bottom row has some info on where we are.
|
||||
info := cview.NewTextView().
|
||||
SetDynamicColors(true).
|
||||
SetRegions(true).
|
||||
SetWrap(false)
|
||||
SetWrap(false).
|
||||
SetHighlightedFunc(func(added, removed, remaining []string) {
|
||||
pages.SwitchToPage(added[0])
|
||||
})
|
||||
|
||||
// Create the pages for all slides.
|
||||
currentSlide := 0
|
||||
info.Highlight(strconv.Itoa(currentSlide))
|
||||
pages := cview.NewPages()
|
||||
previousSlide := func() {
|
||||
currentSlide = (currentSlide - 1 + len(slides)) % len(slides)
|
||||
info.Highlight(strconv.Itoa(currentSlide)).
|
||||
slide, _ := strconv.Atoi(info.GetHighlights()[0])
|
||||
slide = (slide - 1 + len(slides)) % len(slides)
|
||||
info.Highlight(strconv.Itoa(slide)).
|
||||
ScrollToHighlight()
|
||||
pages.SwitchToPage(strconv.Itoa(currentSlide))
|
||||
}
|
||||
nextSlide := func() {
|
||||
currentSlide = (currentSlide + 1) % len(slides)
|
||||
info.Highlight(strconv.Itoa(currentSlide)).
|
||||
slide, _ := strconv.Atoi(info.GetHighlights()[0])
|
||||
slide = (slide + 1) % len(slides)
|
||||
info.Highlight(strconv.Itoa(slide)).
|
||||
ScrollToHighlight()
|
||||
pages.SwitchToPage(strconv.Itoa(currentSlide))
|
||||
}
|
||||
|
||||
cursor := 0
|
||||
|
@ -91,11 +93,12 @@ func main() {
|
|||
slideRegions = append(slideRegions, cursor)
|
||||
|
||||
title, primitive := slide(nextSlide)
|
||||
pages.AddPage(strconv.Itoa(index), primitive, true, index == currentSlide)
|
||||
pages.AddPage(strconv.Itoa(index), primitive, true, index == 0)
|
||||
fmt.Fprintf(info, `%d ["%d"][darkcyan]%s[white][""] `, index+1, index, title)
|
||||
|
||||
cursor += len(title) + 4
|
||||
}
|
||||
info.Highlight("0")
|
||||
|
||||
// Create the main layout.
|
||||
layout := cview.NewFlex().
|
||||
|
@ -113,37 +116,8 @@ func main() {
|
|||
return event
|
||||
})
|
||||
|
||||
app.EnableMouse()
|
||||
|
||||
var screenHeight int
|
||||
|
||||
app.SetAfterResizeFunc(func(_ int, height int) {
|
||||
screenHeight = height
|
||||
})
|
||||
|
||||
app.SetMouseCapture(func(event *cview.EventMouse) *cview.EventMouse {
|
||||
atX, atY := event.Position()
|
||||
if event.Action()&cview.MouseDown != 0 && atY == screenHeight-1 {
|
||||
slideClicked := -1
|
||||
for i, region := range slideRegions {
|
||||
if atX >= region {
|
||||
slideClicked = i
|
||||
}
|
||||
}
|
||||
if slideClicked >= 0 {
|
||||
currentSlide = slideClicked
|
||||
info.Highlight(strconv.Itoa(currentSlide)).
|
||||
ScrollToHighlight()
|
||||
pages.SwitchToPage(strconv.Itoa(currentSlide))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return event
|
||||
})
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ func main() {
|
|||
table.GetCell(row, column).SetTextColor(tcell.ColorRed)
|
||||
table.SetSelectable(false, false)
|
||||
})
|
||||
if err := app.SetRoot(table, true).Run(); err != nil {
|
||||
if err := app.SetRoot(table, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ func main() {
|
|||
}
|
||||
})
|
||||
textView.SetBorder(true)
|
||||
if err := app.SetRoot(textView, true).Run(); err != nil {
|
||||
if err := app.SetRoot(textView, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ func main() {
|
|||
}
|
||||
})
|
||||
|
||||
if err := cview.NewApplication().SetRoot(tree, true).Run(); err != nil {
|
||||
if err := cview.NewApplication().SetRoot(tree, true).EnableMouse(true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
4
doc.go
4
doc.go
|
@ -59,10 +59,6 @@ Application.EnableMouse documentation.
|
|||
|
||||
Mouse events are passed to:
|
||||
|
||||
- The handler set with SetTemporaryMouseCapture, which is reserved for use by
|
||||
widgets to temporarily intercept mouse events, such as to close a Dropdown when
|
||||
the user clicks outside of the list.
|
||||
|
||||
- The handler set with SetMouseCapture, which is reserved for use by application
|
||||
developers to permanently intercept mouse events.
|
||||
|
||||
|
|
20
doc_test.go
20
doc_test.go
|
@ -61,27 +61,33 @@ func ExampleNewApplication() {
|
|||
// Example of an application with mouse support.
|
||||
func ExampleApplication_EnableMouse() {
|
||||
// Initialize application and enable mouse support.
|
||||
app := NewApplication().EnableMouse()
|
||||
app := NewApplication()
|
||||
|
||||
// Create a textview.
|
||||
tv := NewTextView().SetText("Click somewhere!")
|
||||
|
||||
// Set a mouse capture function which prints where the mouse was clicked.
|
||||
app.SetMouseCapture(func(event *EventMouse) *EventMouse {
|
||||
if event.Action()&MouseDown != 0 && event.Buttons()&tcell.Button1 != 0 {
|
||||
app.SetMouseCapture(func(event *tcell.EventMouse, action MouseAction) (*tcell.EventMouse, MouseAction) {
|
||||
if action == MouseLeftClick || action == MouseLeftDoubleClick {
|
||||
actionLabel := "click"
|
||||
if action == MouseLeftDoubleClick {
|
||||
actionLabel = "double-click"
|
||||
}
|
||||
|
||||
x, y := event.Position()
|
||||
fmt.Fprintf(tv, "\nYou clicked at %d,%d! Amazing!", x, y)
|
||||
|
||||
fmt.Fprintf(tv, "\nYou %sed at %d,%d! Amazing!", actionLabel, x, y)
|
||||
|
||||
// Return nil to stop propagating the event to any remaining handlers.
|
||||
return nil
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
// Return the event to continue propagating it.
|
||||
return event
|
||||
return event, action
|
||||
})
|
||||
|
||||
// Run the application.
|
||||
if err := app.SetRoot(tv, true).Run(); err != nil {
|
||||
if err := app.EnableMouse(true).SetRoot(tv, true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
113
dropdown.go
113
dropdown.go
|
@ -81,6 +81,9 @@ type DropDown struct {
|
|||
// selection.
|
||||
selected func(text string, index int)
|
||||
|
||||
// Set to true when mouse dragging is in progress.
|
||||
dragging bool
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
|
@ -482,7 +485,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)
|
||||
|
@ -494,8 +497,7 @@ 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 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 {
|
||||
|
@ -504,31 +506,33 @@ func (d *DropDown) evalPrefix() {
|
|||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Prefix does not match any item. Remove last rune.
|
||||
r := []rune(d.prefix)
|
||||
d.prefix = string(r[:len(r)-1])
|
||||
}
|
||||
}
|
||||
|
||||
// Hand control over to the list.
|
||||
func (d *DropDown) openList(setFocus func(Primitive), app *Application) {
|
||||
// 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, app)
|
||||
d.closeList(setFocus)
|
||||
|
||||
// Trigger "selected" event.
|
||||
if d.selected != nil {
|
||||
d.Unlock()
|
||||
d.selected(d.options[d.currentOption].Text, d.currentOption)
|
||||
d.Lock()
|
||||
}
|
||||
if d.options[d.currentOption].Selected != nil {
|
||||
d.Unlock()
|
||||
d.options[d.currentOption].Selected()
|
||||
d.Lock()
|
||||
}
|
||||
}).SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyRune {
|
||||
|
@ -542,50 +546,20 @@ func (d *DropDown) openList(setFocus func(Primitive), app *Application) {
|
|||
d.evalPrefix()
|
||||
} else if event.Key() == tcell.KeyEscape {
|
||||
d.currentOption = optionBefore
|
||||
d.closeList(setFocus, app)
|
||||
d.closeList(setFocus)
|
||||
} else {
|
||||
d.prefix = ""
|
||||
}
|
||||
|
||||
return event
|
||||
})
|
||||
if app != nil {
|
||||
app.SetTemporaryMouseCapture(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.
|
||||
d.closeList(event.SetFocus, app)
|
||||
}
|
||||
}
|
||||
}
|
||||
return event
|
||||
})
|
||||
}
|
||||
|
||||
setFocus(d.list)
|
||||
}
|
||||
|
||||
func (d *DropDown) closeList(setFocus func(Primitive), app *Application) {
|
||||
if app != nil {
|
||||
app.SetTemporaryMouseCapture(nil)
|
||||
}
|
||||
|
||||
// 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() {
|
||||
setFocus(d)
|
||||
|
@ -612,20 +586,43 @@ func (d *DropDown) HasFocus() bool {
|
|||
}
|
||||
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (d *DropDown) MouseHandler() func(event *EventMouse) {
|
||||
return d.WrapMouseHandler(func(event *EventMouse) {
|
||||
// Process mouse event.
|
||||
if event.Action()&MouseDown != 0 && event.Buttons()&tcell.Button1 != 0 {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
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) {
|
||||
// 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 d.InRect(x, y), nil // No, and it's not expanded either. Ignore.
|
||||
}
|
||||
|
||||
//d.open = !d.open
|
||||
//event.SetFocus(d)
|
||||
if d.open {
|
||||
d.closeList(event.SetFocus, event.Application())
|
||||
} else {
|
||||
d.openList(event.SetFocus, event.Application())
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
|
47
events.go
47
events.go
|
@ -1,47 +0,0 @@
|
|||
package cview
|
||||
|
||||
import "github.com/gdamore/tcell"
|
||||
|
||||
// MouseAction are bit flags indicating what the mouse is logically doing.
|
||||
type MouseAction int
|
||||
|
||||
// All MouseActions
|
||||
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
|
||||
}
|
||||
|
||||
// Action 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}
|
||||
}
|
25
flex.go
25
flex.go
|
@ -226,14 +226,21 @@ func (f *Flex) HasFocus() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// GetChildren returns all primitives that have been added.
|
||||
func (f *Flex) GetChildren() []Primitive {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (f *Flex) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if !f.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
children := make([]Primitive, len(f.items))
|
||||
for i, item := range f.items {
|
||||
children[i] = item.Item
|
||||
}
|
||||
return children
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
|
48
form.go
48
form.go
|
@ -754,20 +754,38 @@ func (f *Form) focusIndex() int {
|
|||
return -1
|
||||
}
|
||||
|
||||
// GetChildren returns all primitives that have been added.
|
||||
func (f *Form) GetChildren() []Primitive {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (f *Form) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if !f.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
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
|
||||
// Determine items to pass mouse events to.
|
||||
for _, item := range f.items {
|
||||
consumed, capture = item.MouseHandler()(action, event, setFocus)
|
||||
if consumed {
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, button := range f.buttons {
|
||||
consumed, capture = button.MouseHandler()(action, event, setFocus)
|
||||
if consumed {
|
||||
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
|
||||
})
|
||||
}
|
||||
|
|
18
frame.go
18
frame.go
|
@ -14,8 +14,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://gitlab.com/tslocum/cview/wiki/Frame for an example.
|
||||
type Frame struct {
|
||||
|
@ -179,10 +179,14 @@ func (f *Frame) HasFocus() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// GetChildren returns all primitives that have been added.
|
||||
func (f *Frame) GetChildren() []Primitive {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (f *Frame) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return f.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if !f.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return []Primitive{f.primitive}
|
||||
// Pass mouse events on to contained primitive.
|
||||
return f.primitive.MouseHandler()(action, event, setFocus)
|
||||
})
|
||||
}
|
||||
|
|
25
grid.go
25
grid.go
|
@ -720,14 +720,21 @@ func (g *Grid) Draw(screen tcell.Screen) {
|
|||
}
|
||||
}
|
||||
|
||||
// GetChildren returns all primitives that have been added.
|
||||
func (g *Grid) GetChildren() []Primitive {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (g *Grid) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return g.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if !g.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
children := make([]Primitive, len(g.items))
|
||||
for i, item := range g.items {
|
||||
children[i] = item.Item
|
||||
}
|
||||
return children
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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,12 @@ type InputField struct {
|
|||
// this form item.
|
||||
finished func(tcell.Key)
|
||||
|
||||
// The x-coordinate of the input field as determined during the last call to Draw().
|
||||
fieldX int
|
||||
|
||||
// The number of bytes of the text string skipped ahead while drawing.
|
||||
offset int
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
|
@ -396,6 +399,7 @@ func (i *InputField) Draw(screen tcell.Screen) {
|
|||
}
|
||||
|
||||
// Draw input area.
|
||||
i.fieldX = x
|
||||
fieldWidth := i.fieldWidth
|
||||
if fieldWidth == 0 {
|
||||
fieldWidth = math.MaxInt32
|
||||
|
@ -681,11 +685,32 @@ 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) {
|
||||
// Process mouse event.
|
||||
if event.Action()&MouseDown != 0 {
|
||||
event.SetFocus(i)
|
||||
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) {
|
||||
x, y := event.Position()
|
||||
_, rectY, _, _ := i.GetInnerRect()
|
||||
if !i.InRect(x, y) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Process mouse event.
|
||||
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
|
||||
})
|
||||
}
|
||||
|
|
60
list.go
60
list.go
|
@ -696,54 +696,68 @@ 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 a negative value 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
|
||||
}
|
||||
index += l.offset
|
||||
|
||||
if n >= len(l.items) {
|
||||
if index >= len(l.items) {
|
||||
return -1
|
||||
}
|
||||
return n
|
||||
return index
|
||||
}
|
||||
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (l *List) MouseHandler() func(event *EventMouse) {
|
||||
return l.WrapMouseHandler(func(event *EventMouse) {
|
||||
// Process mouse event.
|
||||
if event.Action()&MouseClick != 0 {
|
||||
l.Lock()
|
||||
func (l *List) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return l.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if !l.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
atX, atY := event.Position()
|
||||
index := l.indexAtPoint(atX, atY)
|
||||
// Process mouse event.
|
||||
switch action {
|
||||
case MouseLeftClick:
|
||||
setFocus(l)
|
||||
index := l.indexAtPoint(event.Position())
|
||||
if index != -1 {
|
||||
item := l.items[index]
|
||||
if item.Selected != nil {
|
||||
l.Unlock()
|
||||
item.Selected()
|
||||
l.Lock()
|
||||
}
|
||||
if l.selected != nil {
|
||||
l.Unlock()
|
||||
l.selected(index, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
l.Lock()
|
||||
}
|
||||
if index != l.currentItem && l.changed != nil {
|
||||
l.Unlock()
|
||||
l.changed(index, item.MainText, item.SecondaryText, item.Shortcut)
|
||||
l.Lock()
|
||||
}
|
||||
l.currentItem = index
|
||||
}
|
||||
|
||||
l.Unlock()
|
||||
consumed = true
|
||||
case MouseScrollUp:
|
||||
if l.offset > 0 {
|
||||
l.offset--
|
||||
}
|
||||
consumed = true
|
||||
case MouseScrollDown:
|
||||
lines := len(l.items) - l.offset
|
||||
if l.showSecondaryText {
|
||||
lines *= 2
|
||||
}
|
||||
if _, _, _, height := l.GetInnerRect(); lines > height {
|
||||
l.offset++
|
||||
}
|
||||
consumed = true
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
|
19
modal.go
19
modal.go
|
@ -14,7 +14,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.
|
||||
|
@ -213,10 +213,15 @@ func (m *Modal) Draw(screen tcell.Screen) {
|
|||
m.frame.Draw(screen)
|
||||
}
|
||||
|
||||
// GetChildren returns all primitives that have been added.
|
||||
func (m *Modal) GetChildren() []Primitive {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
return []Primitive{m.frame}
|
||||
// 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) {
|
||||
// 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
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
|
34
pages.go
34
pages.go
|
@ -22,7 +22,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
|
||||
|
@ -368,18 +368,24 @@ func (p *Pages) Draw(screen tcell.Screen) {
|
|||
}
|
||||
}
|
||||
|
||||
// GetChildren returns all primitives that have been added.
|
||||
func (p *Pages) GetChildren() []Primitive {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
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(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return p.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
if !p.InRect(event.Position()) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return children
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
|
|
@ -44,16 +44,14 @@ type Primitive interface {
|
|||
// GetFocusable returns the item's Focusable.
|
||||
GetFocusable() Focusable
|
||||
|
||||
// GetChildren returns all child primitives that have been added.
|
||||
GetChildren() []Primitive
|
||||
|
||||
// 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
|
||||
// Box.WrapMouseHandler() so you inherit that functionality.
|
||||
MouseHandler() func(event *EventMouse)
|
||||
MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive)
|
||||
}
|
||||
|
|
89
table.go
89
table.go
|
@ -290,6 +290,13 @@ type Table struct {
|
|||
// The number of visible rows the last time the table was drawn.
|
||||
visibleRows int
|
||||
|
||||
// The indices of the visible columns as of the last time the table was drawn.
|
||||
visibleColumnIndices []int
|
||||
|
||||
// The net widths of the visible columns as of the last time the table was
|
||||
// drawn.
|
||||
visibleColumnWidths []int
|
||||
|
||||
// Visibility of the scroll bar.
|
||||
scrollBarVisibility ScrollBarVisibility
|
||||
|
||||
|
@ -454,8 +461,8 @@ func (t *Table) GetSelection() (row, column int) {
|
|||
// Select sets the selected cell. Depending on the selection settings
|
||||
// specified via SetSelectable(), this may be an entire row or column, or even
|
||||
// ignored completely. The "selection changed" event is fired if such a callback
|
||||
// is available (even if the selection ends up being the same as before, even if
|
||||
// cells are not selectable).
|
||||
// is available (even if the selection ends up being the same as before and even
|
||||
// if cells are not selectable).
|
||||
func (t *Table) Select(row, column int) *Table {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
@ -677,6 +684,49 @@ func (t *Table) GetColumnCount() int {
|
|||
return t.lastColumn + 1
|
||||
}
|
||||
|
||||
// cellAt returns the row and column located at the given screen coordinates.
|
||||
// Each returned value may be negative if there is no row and/or cell. This
|
||||
// function will also process coordinates outside the table's inner rectangle so
|
||||
// callers will need to check for bounds themselves.
|
||||
func (t *Table) cellAt(x, y int) (row, column int) {
|
||||
rectX, rectY, _, _ := t.GetInnerRect()
|
||||
|
||||
// Determine row as seen on screen.
|
||||
if t.borders {
|
||||
row = (y - rectY - 1) / 2
|
||||
} else {
|
||||
row = y - rectY
|
||||
}
|
||||
|
||||
// Respect fixed rows and row offset.
|
||||
if row >= 0 {
|
||||
if row >= t.fixedRows {
|
||||
row += t.rowOffset
|
||||
}
|
||||
if row >= len(t.cells) {
|
||||
row = -1
|
||||
}
|
||||
}
|
||||
|
||||
// Saerch for the clicked column.
|
||||
column = -1
|
||||
if x >= rectX {
|
||||
columnX := rectX
|
||||
if t.borders {
|
||||
columnX++
|
||||
}
|
||||
for index, width := range t.visibleColumnWidths {
|
||||
columnX += width + 1
|
||||
if x < columnX {
|
||||
column = t.visibleColumnIndices[index]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ScrollToBeginning scrolls the table to the beginning to that the top left
|
||||
// corner of the table is shown. Note that this position may be corrected if
|
||||
// there is a selection.
|
||||
|
@ -978,8 +1028,8 @@ ColumnLoop:
|
|||
cell.x, cell.y, cell.width = x+columnX+1, y+rowY, finalWidth
|
||||
_, printed := printWithStyle(screen, cell.Text, x+columnX+1, y+rowY, finalWidth, cell.Align, tcell.StyleDefault.Foreground(cell.Color)|tcell.Style(cell.Attributes))
|
||||
if TaggedStringWidth(cell.Text)-printed > 0 && printed > 0 {
|
||||
_, _, style, _ := screen.GetContent(x+columnX+1+finalWidth-1, y+rowY)
|
||||
printWithStyle(screen, string(SemigraphicsHorizontalEllipsis), x+columnX+1+finalWidth-1, y+rowY, 1, AlignLeft, style)
|
||||
_, _, style, _ := screen.GetContent(x+columnX+finalWidth, y+rowY)
|
||||
printWithStyle(screen, string(SemigraphicsHorizontalEllipsis), x+columnX+finalWidth, y+rowY, 1, AlignLeft, style)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1150,6 +1200,9 @@ ColumnLoop:
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remember column infos.
|
||||
t.visibleColumnIndices, t.visibleColumnWidths = columns, widths
|
||||
}
|
||||
|
||||
// InputHandler returns the handler for this primitive.
|
||||
|
@ -1378,3 +1431,31 @@ func (t *Table) InputHandler() func(event *tcell.EventKey, setFocus func(p Primi
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (t *Table) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
x, y := event.Position()
|
||||
if !t.InRect(x, y) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch action {
|
||||
case MouseLeftClick:
|
||||
if t.rowsSelectable || t.columnsSelectable {
|
||||
t.Select(t.cellAt(x, y))
|
||||
}
|
||||
consumed = true
|
||||
setFocus(t)
|
||||
case MouseScrollUp:
|
||||
t.trackEnd = false
|
||||
t.rowOffset--
|
||||
consumed = true
|
||||
case MouseScrollDown:
|
||||
t.rowOffset++
|
||||
consumed = true
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
|
306
textview.go
306
textview.go
|
@ -35,6 +35,16 @@ type textViewIndex struct {
|
|||
Region string // The starting region ID.
|
||||
}
|
||||
|
||||
// textViewRegion contains information about a region.
|
||||
type textViewRegion struct {
|
||||
// The region ID.
|
||||
ID string
|
||||
|
||||
// The starting and end screen position of the region as determined the last
|
||||
// time Draw() was called. A negative value indicates out-of-rect positions.
|
||||
FromX, FromY, ToX, ToY int
|
||||
}
|
||||
|
||||
// TextView is a box which displays text. It implements the io.Writer interface
|
||||
// so you can stream text to it. This does not trigger a redraw automatically
|
||||
// but if a handler is installed via SetChangedFunc(), you can cause it to be
|
||||
|
@ -104,6 +114,9 @@ type TextView struct {
|
|||
// The text alignment, one of AlignLeft, AlignCenter, or AlignRight.
|
||||
align int
|
||||
|
||||
// Information about visible regions as of the last call to Draw().
|
||||
regionInfos []*textViewRegion
|
||||
|
||||
// Indices into the "index" slice which correspond to the first line of the
|
||||
// first highlight and the last line of the last highlight. This is calculated
|
||||
// during re-indexing. Set to -1 if there is no current highlight.
|
||||
|
@ -161,6 +174,10 @@ type TextView struct {
|
|||
// highlight(s) into the visible screen.
|
||||
scrollToHighlights bool
|
||||
|
||||
// If true, setting new highlights will be a XOR instead of an overwrite
|
||||
// operation.
|
||||
toggleHighlights bool
|
||||
|
||||
// An optional function which is called when the content of the text view has
|
||||
// changed.
|
||||
changed func()
|
||||
|
@ -169,6 +186,10 @@ type TextView struct {
|
|||
// following keys: Escape, Enter, Tab, Backtab.
|
||||
done func(tcell.Key)
|
||||
|
||||
// An optional function which is called when one or more regions were
|
||||
// highlighted.
|
||||
highlighted func(added, removed, remaining []string)
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
|
@ -358,6 +379,18 @@ func (t *TextView) SetDoneFunc(handler func(key tcell.Key)) *TextView {
|
|||
return t
|
||||
}
|
||||
|
||||
// SetHighlightedFunc sets a handler which is called when the list of currently
|
||||
// highlighted regions change. It receives a list of region IDs which were newly
|
||||
// highlighted, those that are not highlighted anymore, and those that remain
|
||||
// highlighted.
|
||||
//
|
||||
// Note that because regions are only determined during drawing, this function
|
||||
// can only fire for regions that have existed during the last call to Draw().
|
||||
func (t *TextView) SetHighlightedFunc(handler func(added, removed, remaining []string)) *TextView {
|
||||
t.highlighted = handler
|
||||
return t
|
||||
}
|
||||
|
||||
// ScrollTo scrolls to the specified row and column (both starting with 0).
|
||||
func (t *TextView) ScrollTo(row, column int) *TextView {
|
||||
t.Lock()
|
||||
|
@ -426,18 +459,59 @@ func (t *TextView) clear() *TextView {
|
|||
return t
|
||||
}
|
||||
|
||||
// Highlight specifies which regions should be highlighted. See class
|
||||
// description for details on regions. Empty region strings are ignored.
|
||||
// Highlight specifies which regions should be highlighted. If highlight
|
||||
// toggling is set to true (see SetToggleHighlights()), the highlight of the
|
||||
// provided regions is toggled (highlighted regions are un-highlighted and vice
|
||||
// versa). If toggling is set to false, the provided regions are highlighted and
|
||||
// all other regions will not be highlighted (you may also provide nil to turn
|
||||
// off all highlights).
|
||||
//
|
||||
// For more information on regions, see class description. Empty region strings
|
||||
// are ignored.
|
||||
//
|
||||
// Text in highlighted regions will be drawn inverted, i.e. with their
|
||||
// background and foreground colors swapped.
|
||||
//
|
||||
// Calling this function will remove any previous highlights. To remove all
|
||||
// highlights, call this function without any arguments.
|
||||
func (t *TextView) Highlight(regionIDs ...string) *TextView {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
// Toggle highlights.
|
||||
if t.toggleHighlights {
|
||||
var newIDs []string
|
||||
HighlightLoop:
|
||||
for regionID := range t.highlights {
|
||||
for _, id := range regionIDs {
|
||||
if regionID == id {
|
||||
continue HighlightLoop
|
||||
}
|
||||
}
|
||||
newIDs = append(newIDs, regionID)
|
||||
}
|
||||
for _, regionID := range regionIDs {
|
||||
if _, ok := t.highlights[regionID]; !ok {
|
||||
newIDs = append(newIDs, regionID)
|
||||
}
|
||||
}
|
||||
regionIDs = newIDs
|
||||
} // Now we have a list of region IDs that end up being highlighted.
|
||||
|
||||
// Determine added and removed regions.
|
||||
var added, removed, remaining []string
|
||||
if t.highlighted != nil {
|
||||
for _, regionID := range regionIDs {
|
||||
if _, ok := t.highlights[regionID]; ok {
|
||||
remaining = append(remaining, regionID)
|
||||
delete(t.highlights, regionID)
|
||||
} else {
|
||||
added = append(added, regionID)
|
||||
}
|
||||
}
|
||||
for regionID := range t.highlights {
|
||||
removed = append(removed, regionID)
|
||||
}
|
||||
}
|
||||
|
||||
// Make new selection.
|
||||
t.highlights = make(map[string]struct{})
|
||||
for _, id := range regionIDs {
|
||||
if id == "" {
|
||||
|
@ -446,6 +520,12 @@ func (t *TextView) Highlight(regionIDs ...string) *TextView {
|
|||
t.highlights[id] = struct{}{}
|
||||
}
|
||||
t.index = nil
|
||||
|
||||
// Notify.
|
||||
if t.highlighted != nil && len(added) > 0 || len(removed) > 0 {
|
||||
t.highlighted(added, removed, remaining)
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
|
@ -460,6 +540,15 @@ func (t *TextView) GetHighlights() (regionIDs []string) {
|
|||
return
|
||||
}
|
||||
|
||||
// SetToggleHighlights sets a flag to determine how regions are highlighted.
|
||||
// When set to true, the Highlight() function (or a mouse click) will toggle the
|
||||
// provided/selected regions. When set to false, Highlight() (or a mouse click)
|
||||
// will simply highlight the provided regions.
|
||||
func (t *TextView) SetToggleHighlights(toggle bool) *TextView {
|
||||
t.toggleHighlights = toggle
|
||||
return t
|
||||
}
|
||||
|
||||
// ScrollToHighlight will cause the visible area to be scrolled so that the
|
||||
// highlighted regions appear in the visible area of the text view. This
|
||||
// repositioning happens the next time the text view is drawn. It happens only
|
||||
|
@ -833,6 +922,9 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
|
||||
// Re-index.
|
||||
t.reindexBuffer(width)
|
||||
if t.regions {
|
||||
t.regionInfos = nil
|
||||
}
|
||||
|
||||
// If we don't have an index, there's nothing to draw.
|
||||
if t.index == nil {
|
||||
|
@ -917,6 +1009,15 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
backgroundColor := index.BackgroundColor
|
||||
attributes := index.Attributes
|
||||
regionID := index.Region
|
||||
if t.regions && regionID != "" && (len(t.regionInfos) == 0 || t.regionInfos[len(t.regionInfos)-1].ID != regionID) {
|
||||
t.regionInfos = append(t.regionInfos, &textViewRegion{
|
||||
ID: regionID,
|
||||
FromX: x,
|
||||
FromY: y + line - t.lineOffset,
|
||||
ToX: -1,
|
||||
ToY: -1,
|
||||
})
|
||||
}
|
||||
|
||||
// Process tags.
|
||||
colorTagIndices, colorTags, regionIndices, regions, escapeIndices, strippedText, _ := decomposeString(text, t.dynamicColors, t.regions)
|
||||
|
@ -936,82 +1037,99 @@ func (t *TextView) Draw(screen tcell.Screen) {
|
|||
}
|
||||
|
||||
// Print the line.
|
||||
var colorPos, regionPos, escapePos, tagOffset, skipped int
|
||||
iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
// Process tags.
|
||||
for {
|
||||
if colorPos < len(colorTags) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
|
||||
// Get the color.
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
|
||||
tagOffset += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
|
||||
colorPos++
|
||||
} else if regionPos < len(regionIndices) && textPos+tagOffset >= regionIndices[regionPos][0] && textPos+tagOffset < regionIndices[regionPos][1] {
|
||||
// Get the region.
|
||||
regionID = regions[regionPos][1]
|
||||
tagOffset += regionIndices[regionPos][1] - regionIndices[regionPos][0]
|
||||
regionPos++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Skip the second-to-last character of an escape tag.
|
||||
if escapePos < len(escapeIndices) && textPos+tagOffset == escapeIndices[escapePos][1]-2 {
|
||||
tagOffset++
|
||||
escapePos++
|
||||
}
|
||||
|
||||
// Mix the existing style with the new style.
|
||||
_, _, existingStyle, _ := screen.GetContent(x+posX, y+line-t.lineOffset)
|
||||
_, background, _ := existingStyle.Decompose()
|
||||
style := overlayStyle(background, defaultStyle, foregroundColor, backgroundColor, attributes)
|
||||
|
||||
// Do we highlight this character?
|
||||
var highlighted bool
|
||||
if len(regionID) > 0 {
|
||||
if _, ok := t.highlights[regionID]; ok {
|
||||
highlighted = true
|
||||
}
|
||||
}
|
||||
if highlighted {
|
||||
fg, bg, _ := style.Decompose()
|
||||
if bg == tcell.ColorDefault {
|
||||
r, g, b := fg.RGB()
|
||||
c := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255}
|
||||
_, _, li := c.Hcl()
|
||||
if li < .5 {
|
||||
bg = tcell.ColorWhite
|
||||
if y+line-t.lineOffset >= 0 {
|
||||
var colorPos, regionPos, escapePos, tagOffset, skipped int
|
||||
iterateString(strippedText, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
|
||||
// Process tags.
|
||||
for {
|
||||
if colorPos < len(colorTags) && textPos+tagOffset >= colorTagIndices[colorPos][0] && textPos+tagOffset < colorTagIndices[colorPos][1] {
|
||||
// Get the color.
|
||||
foregroundColor, backgroundColor, attributes = styleFromTag(foregroundColor, backgroundColor, attributes, colorTags[colorPos])
|
||||
tagOffset += colorTagIndices[colorPos][1] - colorTagIndices[colorPos][0]
|
||||
colorPos++
|
||||
} else if regionPos < len(regionIndices) && textPos+tagOffset >= regionIndices[regionPos][0] && textPos+tagOffset < regionIndices[regionPos][1] {
|
||||
// Get the region.
|
||||
if regionID != "" && len(t.regionInfos) > 0 && t.regionInfos[len(t.regionInfos)-1].ID == regionID {
|
||||
// End last region.
|
||||
t.regionInfos[len(t.regionInfos)-1].ToX = x + posX
|
||||
t.regionInfos[len(t.regionInfos)-1].ToY = y + line - t.lineOffset
|
||||
}
|
||||
regionID = regions[regionPos][1]
|
||||
if regionID != "" {
|
||||
// Start new region.
|
||||
t.regionInfos = append(t.regionInfos, &textViewRegion{
|
||||
ID: regionID,
|
||||
FromX: x + posX,
|
||||
FromY: y + line - t.lineOffset,
|
||||
ToX: -1,
|
||||
ToY: -1,
|
||||
})
|
||||
}
|
||||
tagOffset += regionIndices[regionPos][1] - regionIndices[regionPos][0]
|
||||
regionPos++
|
||||
} else {
|
||||
bg = tcell.ColorBlack
|
||||
break
|
||||
}
|
||||
}
|
||||
style = style.Background(fg).Foreground(bg)
|
||||
}
|
||||
|
||||
// Skip to the right.
|
||||
if !t.wrap && skipped < skip {
|
||||
skipped += screenWidth
|
||||
return false
|
||||
}
|
||||
|
||||
// Stop at the right border.
|
||||
if posX+screenWidth > width {
|
||||
return true
|
||||
}
|
||||
|
||||
// Draw the character.
|
||||
for offset := screenWidth - 1; offset >= 0; offset-- {
|
||||
if offset == 0 {
|
||||
screen.SetContent(x+posX+offset, y+line-t.lineOffset, main, comb, style)
|
||||
} else {
|
||||
screen.SetContent(x+posX+offset, y+line-t.lineOffset, ' ', nil, style)
|
||||
// Skip the second-to-last character of an escape tag.
|
||||
if escapePos < len(escapeIndices) && textPos+tagOffset == escapeIndices[escapePos][1]-2 {
|
||||
tagOffset++
|
||||
escapePos++
|
||||
}
|
||||
}
|
||||
|
||||
// Advance.
|
||||
posX += screenWidth
|
||||
return false
|
||||
})
|
||||
// Mix the existing style with the new style.
|
||||
_, _, existingStyle, _ := screen.GetContent(x+posX, y+line-t.lineOffset)
|
||||
_, background, _ := existingStyle.Decompose()
|
||||
style := overlayStyle(background, defaultStyle, foregroundColor, backgroundColor, attributes)
|
||||
|
||||
// Do we highlight this character?
|
||||
var highlighted bool
|
||||
if regionID != "" {
|
||||
if _, ok := t.highlights[regionID]; ok {
|
||||
highlighted = true
|
||||
}
|
||||
}
|
||||
if highlighted {
|
||||
fg, bg, _ := style.Decompose()
|
||||
if bg == tcell.ColorDefault {
|
||||
r, g, b := fg.RGB()
|
||||
c := colorful.Color{R: float64(r) / 255, G: float64(g) / 255, B: float64(b) / 255}
|
||||
_, _, li := c.Hcl()
|
||||
if li < .5 {
|
||||
bg = tcell.ColorWhite
|
||||
} else {
|
||||
bg = tcell.ColorBlack
|
||||
}
|
||||
}
|
||||
style = style.Background(fg).Foreground(bg)
|
||||
}
|
||||
|
||||
// Skip to the right.
|
||||
if !t.wrap && skipped < skip {
|
||||
skipped += screenWidth
|
||||
return false
|
||||
}
|
||||
|
||||
// Stop at the right border.
|
||||
if posX+screenWidth > width {
|
||||
return true
|
||||
}
|
||||
|
||||
// Draw the character.
|
||||
for offset := screenWidth - 1; offset >= 0; offset-- {
|
||||
if offset == 0 {
|
||||
screen.SetContent(x+posX+offset, y+line-t.lineOffset, main, comb, style)
|
||||
} else {
|
||||
screen.SetContent(x+posX+offset, y+line-t.lineOffset, ' ', nil, style)
|
||||
}
|
||||
}
|
||||
|
||||
// Advance.
|
||||
posX += screenWidth
|
||||
return false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// If this view is not scrollable, we'll purge the buffer of lines that have
|
||||
|
@ -1090,3 +1208,41 @@ func (t *TextView) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (t *TextView) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
x, y := event.Position()
|
||||
if !t.InRect(x, y) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch action {
|
||||
case MouseLeftClick:
|
||||
if t.regions {
|
||||
// Find a region to highlight.
|
||||
for _, region := range t.regionInfos {
|
||||
if y == region.FromY && x < region.FromX ||
|
||||
y == region.ToY && x >= region.ToX ||
|
||||
region.FromY >= 0 && y < region.FromY ||
|
||||
region.ToY >= 0 && y > region.ToY {
|
||||
continue
|
||||
}
|
||||
t.Highlight(region.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
consumed = true
|
||||
setFocus(t)
|
||||
case MouseScrollUp:
|
||||
t.trackEnd = false
|
||||
t.lineOffset--
|
||||
consumed = true
|
||||
case MouseScrollDown:
|
||||
t.lineOffset++
|
||||
consumed = true
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
|
38
treeview.go
38
treeview.go
|
@ -911,3 +911,41 @@ func (t *TreeView) InputHandler() func(event *tcell.EventKey, setFocus func(p Pr
|
|||
t.process()
|
||||
})
|
||||
}
|
||||
|
||||
// MouseHandler returns the mouse handler for this primitive.
|
||||
func (t *TreeView) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
return t.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
||||
x, y := event.Position()
|
||||
if !t.InRect(x, y) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch action {
|
||||
case MouseLeftClick:
|
||||
_, rectY, _, _ := t.GetInnerRect()
|
||||
y -= rectY
|
||||
if y >= 0 && y < len(t.nodes) {
|
||||
node := t.nodes[y]
|
||||
if node.selectable {
|
||||
if t.currentNode != node && t.changed != nil {
|
||||
t.changed(node)
|
||||
}
|
||||
if t.selected != nil {
|
||||
t.selected(node)
|
||||
}
|
||||
t.currentNode = node
|
||||
}
|
||||
}
|
||||
consumed = true
|
||||
setFocus(t)
|
||||
case MouseScrollUp:
|
||||
t.movement = treeUp
|
||||
consumed = true
|
||||
case MouseScrollDown:
|
||||
t.movement = treeDown
|
||||
consumed = true
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue