mirror of https://github.com/rivo/tview.git
Added mouse handling
This commit is contained in:
parent
685bf6da76
commit
96875c75b9
120
application.go
120
application.go
|
@ -38,6 +38,9 @@ type Application struct {
|
||||||
// Whether or not the application resizes the root primitive.
|
// Whether or not the application resizes the root primitive.
|
||||||
rootFullscreen bool
|
rootFullscreen bool
|
||||||
|
|
||||||
|
// Enable mouse events?
|
||||||
|
enableMouse bool
|
||||||
|
|
||||||
// An optional capture function which receives a key event and returns the
|
// An optional capture function which receives a key event and returns the
|
||||||
// event to be forwarded to the default input handler (nil if nothing should
|
// event to be forwarded to the default input handler (nil if nothing should
|
||||||
// be forwarded).
|
// be forwarded).
|
||||||
|
@ -62,6 +65,33 @@ type Application struct {
|
||||||
// (screen.Init() and draw() will be called implicitly). A value of nil will
|
// (screen.Init() and draw() will be called implicitly). A value of nil will
|
||||||
// stop the application.
|
// stop the application.
|
||||||
screenReplacement chan tcell.Screen
|
screenReplacement chan tcell.Screen
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventKey is the key input event info.
|
||||||
|
// This exists for some consistency with EventMouse,
|
||||||
|
// even though it's just an alias to *tcell.EventKey for backwards compatibility.
|
||||||
|
type EventKey = *tcell.EventKey
|
||||||
|
|
||||||
|
// EventMouse is the mouse event info.
|
||||||
|
type EventMouse struct {
|
||||||
|
*tcell.EventMouse
|
||||||
|
Target Primitive
|
||||||
|
Application *Application
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsZero returns true if this is a zero object.
|
||||||
|
func (e EventMouse) IsZero() bool {
|
||||||
|
return e == EventMouse{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFocus will set focus to the primitive.
|
||||||
|
func (e EventMouse) SetFocus(p Primitive) {
|
||||||
|
e.Application.SetFocus(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApplication creates and returns a new application.
|
// NewApplication creates and returns a new application.
|
||||||
|
@ -93,6 +123,22 @@ func (a *Application) GetInputCapture() func(event *tcell.EventKey) *tcell.Event
|
||||||
return a.inputCapture
|
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.
|
||||||
|
func (a *Application) SetMouseCapture(capture func(event EventMouse) EventMouse) *Application {
|
||||||
|
a.mouseCapture = capture
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMouseCapture returns the function installed with SetMouseCapture() or nil
|
||||||
|
// if no such function has been installed.
|
||||||
|
func (a *Application) GetMouseCapture() func(event EventMouse) EventMouse {
|
||||||
|
return a.mouseCapture
|
||||||
|
}
|
||||||
|
|
||||||
// SetScreen allows you to provide your own tcell.Screen object. For most
|
// SetScreen allows you to provide your own tcell.Screen object. For most
|
||||||
// applications, this is not needed and you should be familiar with
|
// applications, this is not needed and you should be familiar with
|
||||||
// tcell.Screen when using this function.
|
// tcell.Screen when using this function.
|
||||||
|
@ -121,6 +167,13 @@ func (a *Application) SetScreen(screen tcell.Screen) *Application {
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnableMouse enables mouse events.
|
||||||
|
func (a *Application) EnableMouse() {
|
||||||
|
a.Lock()
|
||||||
|
a.enableMouse = true
|
||||||
|
a.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// Run starts the application and thus the event loop. This function returns
|
// Run starts the application and thus the event loop. This function returns
|
||||||
// when Stop() was called.
|
// when Stop() was called.
|
||||||
func (a *Application) Run() error {
|
func (a *Application) Run() error {
|
||||||
|
@ -138,6 +191,9 @@ func (a *Application) Run() error {
|
||||||
a.Unlock()
|
a.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if a.enableMouse {
|
||||||
|
a.screen.EnableMouse()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We catch panics to clean up because they mess up the terminal.
|
// We catch panics to clean up because they mess up the terminal.
|
||||||
|
@ -207,13 +263,15 @@ EventLoop:
|
||||||
break EventLoop
|
break EventLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.RLock()
|
||||||
|
p := a.focus
|
||||||
|
inputCapture := a.inputCapture
|
||||||
|
mouseCapture := a.mouseCapture
|
||||||
|
screen := a.screen
|
||||||
|
a.RUnlock()
|
||||||
|
|
||||||
switch event := event.(type) {
|
switch event := event.(type) {
|
||||||
case *tcell.EventKey:
|
case *tcell.EventKey:
|
||||||
a.RLock()
|
|
||||||
p := a.focus
|
|
||||||
inputCapture := a.inputCapture
|
|
||||||
a.RUnlock()
|
|
||||||
|
|
||||||
// Intercept keys.
|
// Intercept keys.
|
||||||
if inputCapture != nil {
|
if inputCapture != nil {
|
||||||
event = inputCapture(event)
|
event = inputCapture(event)
|
||||||
|
@ -238,14 +296,32 @@ EventLoop:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *tcell.EventResize:
|
case *tcell.EventResize:
|
||||||
a.RLock()
|
|
||||||
screen := a.screen
|
|
||||||
a.RUnlock()
|
|
||||||
if screen == nil {
|
if screen == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
screen.Clear()
|
screen.Clear()
|
||||||
a.draw()
|
a.draw()
|
||||||
|
case *tcell.EventMouse:
|
||||||
|
atX, atY := event.Position()
|
||||||
|
ptarget := a.GetPrimitiveAtPoint(atX, atY) // p under mouse.
|
||||||
|
if ptarget == nil {
|
||||||
|
ptarget = p // Fallback to focused.
|
||||||
|
}
|
||||||
|
event2 := EventMouse{event, ptarget, a}
|
||||||
|
|
||||||
|
// Intercept event.
|
||||||
|
if mouseCapture != nil {
|
||||||
|
event2 = mouseCapture(event2)
|
||||||
|
if event2.IsZero() {
|
||||||
|
a.draw()
|
||||||
|
continue // Don't forward event.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler := ptarget.MouseHandler(); handler != nil {
|
||||||
|
handler(event2)
|
||||||
|
a.draw()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have updates, now is the time to execute them.
|
// If we have updates, now is the time to execute them.
|
||||||
|
@ -261,6 +337,34 @@ EventLoop:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findAtPoint(atX, atY int, p Primitive) Primitive {
|
||||||
|
x, y, w, h := p.GetRect()
|
||||||
|
if atX < x || atY < y {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if atX >= x+w || atY >= y+h {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
bestp := p
|
||||||
|
for _, pchild := range p.GetChildren() {
|
||||||
|
x := findAtPoint(atX, atY, pchild)
|
||||||
|
if x != nil {
|
||||||
|
// Always overwrite if we find another one,
|
||||||
|
// this is because if any overlap, the last one is "on top".
|
||||||
|
bestp = x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPrimitiveAtPoint returns the Primitive at the specified point, or nil.
|
||||||
|
// Note that this only works with a valid hierarchy of primitives (children)
|
||||||
|
func (a *Application) GetPrimitiveAtPoint(atX, atY int) Primitive {
|
||||||
|
a.RLock()
|
||||||
|
defer a.RUnlock()
|
||||||
|
return findAtPoint(atX, atY, a.root)
|
||||||
|
}
|
||||||
|
|
||||||
// Stop stops the application, causing Run() to return.
|
// Stop stops the application, causing Run() to return.
|
||||||
func (a *Application) Stop() {
|
func (a *Application) Stop() {
|
||||||
a.Lock()
|
a.Lock()
|
||||||
|
|
49
box.go
49
box.go
|
@ -58,6 +58,11 @@ type Box struct {
|
||||||
|
|
||||||
// An optional function which is called before the box is drawn.
|
// An optional function which is called before the box is drawn.
|
||||||
draw func(screen tcell.Screen, x, y, width, height int) (int, int, int, int)
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBox returns a Box without a border.
|
// NewBox returns a Box without a border.
|
||||||
|
@ -192,6 +197,45 @@ func (b *Box) GetInputCapture() func(event *tcell.EventKey) *tcell.EventKey {
|
||||||
return b.inputCapture
|
return b.inputCapture
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// This is only meant to be used by subclassing primitives.
|
||||||
|
func (b *Box) WrapMouseHandler(mouseHandler func(EventMouse)) func(EventMouse) {
|
||||||
|
return func(event EventMouse) {
|
||||||
|
if b.mouseCapture != nil {
|
||||||
|
event = b.mouseCapture(event)
|
||||||
|
}
|
||||||
|
if !event.IsZero() && mouseHandler != nil {
|
||||||
|
mouseHandler(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MouseHandler returns nil.
|
||||||
|
func (b *Box) MouseHandler() func(event EventMouse) {
|
||||||
|
return b.WrapMouseHandler(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// Providing a nil handler will remove a previously existing handler.
|
||||||
|
func (b *Box) SetMouseCapture(capture func(EventMouse) EventMouse) *Box {
|
||||||
|
b.mouseCapture = capture
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMouseCapture returns the function installed with SetMouseCapture() or nil
|
||||||
|
// if no such function has been installed.
|
||||||
|
func (b *Box) GetMouseCapture() func(EventMouse) EventMouse {
|
||||||
|
return b.mouseCapture
|
||||||
|
}
|
||||||
|
|
||||||
// SetBackgroundColor sets the box's background color.
|
// SetBackgroundColor sets the box's background color.
|
||||||
func (b *Box) SetBackgroundColor(color tcell.Color) *Box {
|
func (b *Box) SetBackgroundColor(color tcell.Color) *Box {
|
||||||
b.backgroundColor = color
|
b.backgroundColor = color
|
||||||
|
@ -353,3 +397,8 @@ func (b *Box) HasFocus() bool {
|
||||||
func (b *Box) GetFocusable() Focusable {
|
func (b *Box) GetFocusable() Focusable {
|
||||||
return b.focus
|
return b.focus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetChildren gets the children.
|
||||||
|
func (b *Box) GetChildren() []Primitive {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
12
button.go
12
button.go
|
@ -135,3 +135,15 @@ func (b *Button) InputHandler() func(event *tcell.EventKey, setFocus func(p Prim
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InputHandler returns the handler for this primitive.
|
||||||
|
func (b *Button) MouseHandler() func(event EventMouse) {
|
||||||
|
return b.WrapMouseHandler(func(event EventMouse) {
|
||||||
|
// Process mouse event.
|
||||||
|
if event.Buttons()&tcell.Button1 != 0 {
|
||||||
|
if b.selected != nil {
|
||||||
|
b.selected()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
8
flex.go
8
flex.go
|
@ -195,3 +195,11 @@ func (f *Flex) HasFocus() bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Flex) GetChildren() []Primitive {
|
||||||
|
children := make([]Primitive, len(f.items))
|
||||||
|
for i, item := range f.items {
|
||||||
|
children[i] = item.Item
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
14
form.go
14
form.go
|
@ -600,3 +600,17 @@ func (f *Form) focusIndex() int {
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Form) GetChildren() []Primitive {
|
||||||
|
children := make([]Primitive, len(f.items)+len(f.buttons))
|
||||||
|
i := 0
|
||||||
|
for _, item := range f.items {
|
||||||
|
children[i] = item
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for _, button := range f.buttons {
|
||||||
|
children[i] = button
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
4
frame.go
4
frame.go
|
@ -155,3 +155,7 @@ func (f *Frame) HasFocus() bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Frame) GetChildren() []Primitive {
|
||||||
|
return []Primitive{f.primitive}
|
||||||
|
}
|
||||||
|
|
8
grid.go
8
grid.go
|
@ -660,3 +660,11 @@ func (g *Grid) Draw(screen tcell.Screen) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Grid) GetChildren() []Primitive {
|
||||||
|
children := make([]Primitive, len(g.items))
|
||||||
|
for i, item := range g.items {
|
||||||
|
children[i] = item.Item
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
4
modal.go
4
modal.go
|
@ -169,3 +169,7 @@ func (m *Modal) Draw(screen tcell.Screen) {
|
||||||
m.frame.SetRect(x, y, width, height)
|
m.frame.SetRect(x, y, width, height)
|
||||||
m.frame.Draw(screen)
|
m.frame.Draw(screen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Modal) GetChildren() []Primitive {
|
||||||
|
return []Primitive{m.frame}
|
||||||
|
}
|
||||||
|
|
12
pages.go
12
pages.go
|
@ -278,3 +278,15 @@ func (p *Pages) Draw(screen tcell.Screen) {
|
||||||
page.Item.Draw(screen)
|
page.Item.Draw(screen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Pages) GetChildren() []Primitive {
|
||||||
|
var children []Primitive
|
||||||
|
for _, page := range p.pages {
|
||||||
|
// Considering invisible pages as not children.
|
||||||
|
// Even though we track all the pages, not all are "children" currently.
|
||||||
|
if page.Visible {
|
||||||
|
children = append(children, page.Item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
13
primitive.go
13
primitive.go
|
@ -43,4 +43,17 @@ type Primitive interface {
|
||||||
|
|
||||||
// GetFocusable returns the item's Focusable.
|
// GetFocusable returns the item's Focusable.
|
||||||
GetFocusable() Focusable
|
GetFocusable() Focusable
|
||||||
|
|
||||||
|
// GetChildren gets the children.
|
||||||
|
GetChildren() []Primitive
|
||||||
|
|
||||||
|
// MouseHandler returns a handler which receives mouse events.
|
||||||
|
// It is called by the Application class.
|
||||||
|
//
|
||||||
|
// A zero value of EventMouse{} may also be returned to stop propagation.
|
||||||
|
//
|
||||||
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue