Make mouse events first-class bindings. Remove more from termbox compat.go

pull/232/head
Simon Roberts 3 years ago
parent b49ff6cbdb
commit e2dda02f3c
No known key found for this signature in database
GPG Key ID: 0F30F99E6B771FD4

@ -85,7 +85,5 @@ func DefaultShortcuts() map[string]string {
"<": "scroll_left",
"+": "show_price_alert_add_menu",
"\\\\": "toggle_table_fullscreen",
"scrollup": "move_up_or_previous_page",
"scrolldown": "move_down_or_next_page",
}
}

@ -99,10 +99,6 @@ func (ct *Cointop) ParseKeys(s string) (interface{}, tcell.ModMask) {
// key = tcell.KeyEnd
case "\\\\":
key = '\\'
case "scrollup":
key = gocui.MouseWheelUp // TODO: change to mouse event
case "scrolldown":
key = gocui.MouseWheelDown // TODO: change to mouse event
}
if key == nil {
@ -313,13 +309,16 @@ func (ct *Cointop) SetKeybindings() error {
// ct.DeleteKeybindingMod(key, mod, "")
// mouse events
ct.SetKeybindingMod(gocui.MouseLeft, tcell.ModNone, ct.Keyfn(ct.MouseLeftClick), ct.Views.Table.Name()) // click to focus
ct.SetMousebindingMod(tcell.Button1, tcell.ModNone, ct.Keyfn(ct.MouseLeftClick), ct.Views.Table.Name()) // click to focus
// clicking table headers sorts table
ct.SetKeybindingMod(gocui.MouseLeft, tcell.ModNone, ct.Keyfn(ct.TableHeaderMouseLeftClick), ct.Views.TableHeader.Name())
ct.SetKeybindingMod(gocui.MouseLeft, tcell.ModNone, ct.Keyfn(ct.StatusbarMouseLeftClick), ct.Views.Statusbar.Name())
ct.SetMousebindingMod(tcell.Button1, tcell.ModNone, ct.Keyfn(ct.TableHeaderMouseLeftClick), ct.Views.TableHeader.Name())
ct.SetMousebindingMod(tcell.Button1, tcell.ModNone, ct.Keyfn(ct.StatusbarMouseLeftClick), ct.Views.Statusbar.Name())
// debug mouse clicks
ct.SetKeybindingMod(gocui.MouseLeft, tcell.ModNone, ct.Keyfn(ct.MouseDebug), "")
ct.SetMousebindingMod(tcell.Button1, tcell.ModNone, ct.Keyfn(ct.MouseDebug), "")
ct.SetMousebindingMod(tcell.WheelUp, tcell.ModNone, ct.Keyfn(ct.CursorUpOrPreviousPage), ct.Views.Table.Name())
ct.SetMousebindingMod(tcell.WheelDown, tcell.ModNone, ct.Keyfn(ct.CursorDownOrNextPage), ct.Views.Table.Name())
// character key press to select option
// TODO: use scrolling table
@ -327,7 +326,7 @@ func (ct *Cointop) SetKeybindings() error {
for i, k := range keys {
ct.SetKeybindingMod(alphanumericcharacters[i], tcell.ModNone, ct.Keyfn(ct.SetCurrencyConverstionFn(k)), ct.Views.Menu.Name())
}
ct.SetKeybindingMod(gocui.MouseLeft, tcell.ModNone, ct.Keyfn(ct.ConversionMouseLeftClick), ct.Views.Menu.Name())
ct.SetMousebindingMod(tcell.Button1, tcell.ModNone, ct.Keyfn(ct.ConversionMouseLeftClick), ct.Views.Menu.Name())
return nil
}
@ -344,7 +343,7 @@ func (ct *Cointop) MouseDebug() error {
// SetKeybindingMod sets the keybinding modifier key
func (ct *Cointop) SetKeybindingMod(key interface{}, mod tcell.ModMask, callback func(g *gocui.Gui, v *gocui.View) error, view string) error {
// TODO: take EventKey
// TODO: take EventKey?
var err error
switch t := key.(type) {
case tcell.Key:
@ -355,6 +354,11 @@ func (ct *Cointop) SetKeybindingMod(key interface{}, mod tcell.ModMask, callback
return err
}
// SetMousebindingMod adds a binding for a mouse eventdef
func (ct *Cointop) SetMousebindingMod(btn tcell.ButtonMask, mod tcell.ModMask, callback func(g *gocui.Gui, v *gocui.View) error, view string) error {
return ct.g.SetMousebinding(view, btn, mod, callback)
}
// DeleteKeybinding ...
func (ct *Cointop) DeleteKeybinding(shortcutKey string) error {
key, mod := ct.ParseKeys(shortcutKey)

@ -1,118 +0,0 @@
// Copyright 2014 The gocui Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package gocui allows to create console user interfaces.
Create a new GUI:
g, err := gocui.NewGui(gocui.OutputNormal)
if err != nil {
// handle error
}
defer g.Close()
// Set GUI managers and key bindings
// ...
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
// handle error
}
Set GUI managers:
g.SetManager(mgr1, mgr2)
Managers are in charge of GUI's layout and can be used to build widgets. On
each iteration of the GUI's main loop, the Layout function of each configured
manager is executed. Managers are used to set-up and update the application's
main views, being possible to freely change them during execution. Also, it is
important to mention that a main loop iteration is executed on each reported
event (key-press, mouse event, window resize, etc).
GUIs are composed by Views, you can think of it as buffers. Views implement the
io.ReadWriter interface, so you can just write to them if you want to modify
their content. The same is valid for reading.
Create and initialize a view with absolute coordinates:
if v, err := g.SetView("viewname", 2, 2, 22, 7); err != nil {
if err != gocui.ErrUnknownView {
// handle error
}
fmt.Fprintln(v, "This is a new view")
// ...
}
Views can also be created using relative coordinates:
maxX, maxY := g.Size()
if v, err := g.SetView("viewname", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
// ...
}
Configure keybindings:
if err := g.SetKeybinding("viewname", gocui.KeyEnter, tcell.ModNone, fcn); err != nil {
// handle error
}
gocui implements full mouse support that can be enabled with:
g.Mouse = true
Mouse events are handled like any other keybinding:
if err := g.SetKeybinding("viewname", gocui.MouseLeft, tcell.ModNone, fcn); err != nil {
// handle error
}
IMPORTANT: Views can only be created, destroyed or updated in three ways: from
the Layout function within managers, from keybinding callbacks or via
*Gui.Update(). The reason for this is that it allows gocui to be
concurrent-safe. So, if you want to update your GUI from a goroutine, you must
use *Gui.Update(). For example:
g.Update(func(g *gocui.Gui) error {
v, err := g.View("viewname")
if err != nil {
// handle error
}
v.Clear()
fmt.Fprintln(v, "Writing from different goroutines")
return nil
})
By default, gocui provides a basic edition mode. This mode can be extended
and customized creating a new Editor and assigning it to *View.Editor:
type Editor interface {
Edit(v *View, key Key, ch rune, mod Modifier)
}
DefaultEditor can be taken as example to create your own custom Editor:
var DefaultEditor Editor = EditorFunc(simpleEditor)
func simpleEditor(v *View, key Key, ch rune, mod Modifier) {
switch {
case ch != 0 && mod == 0:
v.EditWrite(ch)
case key == KeySpace:
v.EditWrite(' ')
case key == KeyBackspace || key == KeyBackspace2:
v.EditDelete(true)
// ...
}
}
Colored text:
Views allow to add colored text using ANSI colors. For example:
fmt.Fprintln(v, "\x1b[0;31mHello world")
For more information, see the examples in folder "_examples/".
*/
package gocui

@ -5,21 +5,19 @@
package gocui
import (
"github.com/cointop-sh/cointop/pkg/termbox"
"github.com/gdamore/tcell/v2"
)
// Keybidings are used to link a given key-press event with a handler.
type keybinding struct {
// eventBinding are used to link a given key-press event with a handler.
type eventBinding struct {
viewName string
ev tcell.Event // ignore the Time
handler func(*Gui, *View) error
}
// newKeybinding returns a new Keybinding object.
func newKeybinding(viewname string, key tcell.Key, ch rune, mod tcell.ModMask, handler func(*Gui, *View) error) (kb *keybinding) {
// TODO: take Event
kb = &keybinding{
// newKeybinding returns a new eventBinding object for a key event.
func newKeybinding(viewname string, key tcell.Key, ch rune, mod tcell.ModMask, handler func(*Gui, *View) error) (kb *eventBinding) {
kb = &eventBinding{
viewName: viewname,
ev: tcell.NewEventKey(key, ch, mod),
handler: handler,
@ -27,8 +25,9 @@ func newKeybinding(viewname string, key tcell.Key, ch rune, mod tcell.ModMask, h
return kb
}
func newMouseBinding(viewname string, btn tcell.ButtonMask, mod tcell.ModMask, handler func(*Gui, *View) error) (kb *keybinding) {
kb = &keybinding{
// newKeybinding returns a new eventBinding object for a mouse event.
func newMouseBinding(viewname string, btn tcell.ButtonMask, mod tcell.ModMask, handler func(*Gui, *View) error) (kb *eventBinding) {
kb = &eventBinding{
viewName: viewname,
ev: tcell.NewEventMouse(0, 0, btn, mod),
handler: handler,
@ -36,7 +35,7 @@ func newMouseBinding(viewname string, btn tcell.ButtonMask, mod tcell.ModMask, h
return kb
}
func (kb *keybinding) matchEvent(e tcell.Event) bool {
func (kb *eventBinding) matchEvent(e tcell.Event) bool {
// TODO: check mask not ==mod?
switch tev := e.(type) {
case *tcell.EventKey:
@ -56,23 +55,10 @@ func (kb *keybinding) matchEvent(e tcell.Event) bool {
return false
}
// matchView returns if the keybinding matches the current view.
func (kb *keybinding) matchView(v *View) bool {
// matchView returns if the eventBinding matches the current view.
func (kb *eventBinding) matchView(v *View) bool {
if kb.viewName == "" {
return true
}
return v != nil && kb.viewName == v.name
}
// Key represents special keys or keys combinations.
// type Key tcell.Key
// Special keys.
const (
MouseLeft = termbox.MouseLeft
MouseMiddle = termbox.MouseMiddle
MouseRight = termbox.MouseRight
MouseRelease = termbox.MouseRelease
MouseWheelUp = termbox.MouseWheelUp
MouseWheelDown = termbox.MouseWheelDown
)

@ -31,16 +31,16 @@ const ( // TODO: die
)
// Gui represents the whole User Interface, including the views, layouts
// and keybindings.
// and eventBindings.
type Gui struct {
tbEvents chan tcell.Event
userEvents chan userEvent
views []*View
currentView *View
managers []Manager
keybindings []*keybinding
maxX, maxY int
outputMode OutputMode // TODO: die
tbEvents chan tcell.Event
userEvents chan userEvent
views []*View
currentView *View
managers []Manager
eventBindings []*eventBinding
maxX, maxY int
outputMode OutputMode // TODO: die
// BgColor and FgColor allow to configure the background and foreground
// colors of the GUI.
@ -258,63 +258,70 @@ func (g *Gui) CurrentView() *View {
return g.currentView
}
// SetKeybinding creates a new keybinding. If viewname equals to ""
// (empty string) then the keybinding will apply to all views. key must
// SetKeybinding creates a new eventBinding. If viewname equals to ""
// (empty string) then the eventBinding will apply to all views. key must
// be a rune or a Key.
// TODO: split into key/mouse bindings?
func (g *Gui) SetKeybinding(viewname string, key tcell.Key, ch rune, mod tcell.ModMask, handler func(*Gui, *View) error) error {
var kb *keybinding
//var kb *eventBinding
// k, ch, err := getKey(key)
// if err != nil {
// return err
// }
// TODO: get rid of this ugly mess
switch key {
case termbox.MouseLeft:
kb = newMouseBinding(viewname, tcell.Button1, mod, handler)
case termbox.MouseMiddle:
kb = newMouseBinding(viewname, tcell.Button3, mod, handler)
case termbox.MouseRight:
kb = newMouseBinding(viewname, tcell.Button2, mod, handler)
case termbox.MouseWheelUp:
kb = newMouseBinding(viewname, tcell.WheelUp, mod, handler)
case termbox.MouseWheelDown:
kb = newMouseBinding(viewname, tcell.WheelDown, mod, handler)
default:
kb = newKeybinding(viewname, key, ch, mod, handler)
}
g.keybindings = append(g.keybindings, kb)
//switch key {
//case termbox.MouseLeft:
// kb = newMouseBinding(viewname, tcell.Button1, mod, handler)
//case termbox.MouseMiddle:
// kb = newMouseBinding(viewname, tcell.Button3, mod, handler)
//case termbox.MouseRight:
// kb = newMouseBinding(viewname, tcell.Button2, mod, handler)
//case termbox.MouseWheelUp:
// kb = newMouseBinding(viewname, tcell.WheelUp, mod, handler)
//case termbox.MouseWheelDown:
// kb = newMouseBinding(viewname, tcell.WheelDown, mod, handler)
//default:
// kb = newKeybinding(viewname, key, ch, mod, handler)
//}
kb := newKeybinding(viewname, key, ch, mod, handler)
g.eventBindings = append(g.eventBindings, kb)
return nil
}
func (g *Gui) SetMousebinding(viewname string, btn tcell.ButtonMask, mod tcell.ModMask, handler func(*Gui, *View) error) error {
kb := newMouseBinding(viewname, btn, mod, handler)
g.eventBindings = append(g.eventBindings, kb)
return nil
}
// DeleteKeybinding deletes a keybinding.
// DeleteKeybinding deletes a eventBinding.
func (g *Gui) DeleteKeybinding(viewname string, key tcell.Key, ch rune, mod tcell.ModMask) error {
// k, ch, err := getKey(key)
// if err != nil {
// return err
// }
for i, kb := range g.keybindings {
for i, kb := range g.eventBindings {
if kbe, ok := kb.ev.(*tcell.EventKey); ok {
if kb.viewName == viewname && kbe.Rune() == ch && kbe.Key() == key && kbe.Modifiers() == mod {
g.keybindings = append(g.keybindings[:i], g.keybindings[i+1:]...)
g.eventBindings = append(g.eventBindings[:i], g.eventBindings[i+1:]...)
return nil
}
}
}
return errors.New("keybinding not found")
return errors.New("eventBinding not found")
}
// DeleteKeybindings deletes all keybindings of view.
// DeleteKeybindings deletes all eventBindings of view.
func (g *Gui) DeleteKeybindings(viewname string) {
var s []*keybinding
for _, kb := range g.keybindings {
var s []*eventBinding
for _, kb := range g.eventBindings {
if kb.viewName != viewname {
s = append(s, kb)
}
}
g.keybindings = s
g.eventBindings = s
}
// getKey takes an empty interface with a key and returns the corresponding
@ -362,18 +369,18 @@ func (f ManagerFunc) Layout(g *Gui) error {
}
// SetManager sets the given GUI managers. It deletes all views and
// keybindings.
// eventBindings.
func (g *Gui) SetManager(managers ...Manager) {
g.managers = managers
g.currentView = nil
g.views = nil
g.keybindings = nil
g.eventBindings = nil
go func() { g.tbEvents <- tcell.NewEventResize(0, 0) }()
}
// SetManagerFunc sets the given manager function. It deletes all views and
// keybindings.
// eventBindings.
func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
g.SetManager(ManagerFunc(manager))
}
@ -612,13 +619,13 @@ func (g *Gui) draw(v *View) error {
return nil
}
// onEvent manages key/mouse events. A keybinding handler is called when
// a key-press or mouse event satisfies a configured keybinding. Furthermore,
// onEvent manages key/mouse events. A eventBinding handler is called when
// a key-press or mouse event satisfies a configured eventBinding. Furthermore,
// currentView's internal buffer is modified if currentView.Editable is true.
func (g *Gui) onEvent(ev tcell.Event) error {
switch tev := ev.(type) {
case *tcell.EventKey:
matched, err := g.execKeybindings(g.currentView, ev)
matched, err := g.execEventBindings(g.currentView, ev)
if err != nil {
return err
}
@ -636,7 +643,7 @@ func (g *Gui) onEvent(ev tcell.Event) error {
// If the key-binding wants to move the cursor, it should call SetCursorFromCurrentMouseEvent()
// Not all mouse events will want to do this (eg: scroll wheel)
g.CurrentEvent = ev
if _, err := g.execKeybindings(v, g.CurrentEvent); err != nil {
if _, err := g.execEventBindings(v, g.CurrentEvent); err != nil {
return err
}
}
@ -669,18 +676,16 @@ func (g *Gui) SetCursorFromCurrentMouseEvent() error {
return nil
}
// execKeybindings executes the keybinding handlers that match the passed view
// execEventBindings executes the handlers that match the passed view
// and event. The value of matched is true if there is a match and no errors.
// TODO: rename to more generic - it's not just keys (incl mouse)
func (g *Gui) execKeybindings(v *View, xev tcell.Event) (matched bool, err error) {
// log.Debugf("XXX hunting for k=%d c=%d mod=%d", ev.Key, ev.Ch, ev.Mod)
func (g *Gui) execEventBindings(v *View, xev tcell.Event) (matched bool, err error) {
matched = false
for _, kb := range g.keybindings {
for _, kb := range g.eventBindings {
if kb.handler == nil {
continue
}
if kb.matchEvent(xev) && kb.matchView(v) {
// log.Debugf("XXX dispatching %s", ev)
if err := kb.handler(g, v); err != nil {
return false, err
}

@ -42,7 +42,7 @@ type View struct {
Editable bool
// Editor allows to define the editor that manages the edition mode,
// including keybindings or cursor behaviour. DefaultEditor is used by
// including eventBindings or cursor behaviour. DefaultEditor is used by
// default.
Editor Editor

@ -103,7 +103,6 @@ func Clear(fg, bg Attribute) {
}
}
// OutputMode represents an output mode, which determines how colors
// are used. See the termbox documentation for an explanation.
type OutputMode int
@ -151,21 +150,6 @@ func SetCell(x, y int, ch rune, fg, bg Attribute) {
screen.SetContent(x, y, ch, nil, st)
}
// Keys codes.
const (
MouseLeft = tcell.KeyF63 // arbitrary assignments
MouseRight = tcell.KeyF62
MouseMiddle = tcell.KeyF61
MouseRelease = tcell.KeyF60
MouseWheelUp = tcell.KeyF59
MouseWheelDown = tcell.KeyF58
)
// Modifiers.
const (
ModAlt = tcell.ModAlt
)
// PollEvent blocks until an event is ready, and then returns it.
func PollEvent() tcell.Event {
return screen.PollEvent()

@ -37,7 +37,7 @@ func evtKbd(e tcell.EventKey) EvtKbd {
pre := ""
mod := ""
if e.Modifiers() == termbox.ModAlt {
if e.Modifiers() == tcell.ModAlt {
mod = "M-"
}
if e.Rune() == 0 {

Loading…
Cancel
Save