mirror of
https://github.com/jroimartin/gocui.git
synced 2025-04-24 13:48:51 +08:00
Introduce GUI managers to replace layout functions
This commit is contained in:
parent
fc121d98fd
commit
40dbad569f
@ -53,7 +53,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
|
@ -102,7 +102,7 @@ func main() {
|
||||
|
||||
g.Highlight = true
|
||||
g.SelFgColor = gocui.ColorGreen
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
|
@ -47,11 +47,10 @@ func main() {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
if err := g.SetKeybinding("main", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
g.SetLayout(layout)
|
||||
if err := g.SetKeybinding("main", gocui.KeyCtrlI, gocui.ModNone, overwrite); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
|
@ -197,7 +197,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
if err := keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
g.Highlight = true
|
||||
g.SelFgColor = gocui.ColorRed
|
||||
|
||||
|
@ -30,7 +30,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
if err := keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
|
@ -38,7 +38,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
|
@ -18,7 +18,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
if err := initKeybindings(g); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
if err := keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
if err := keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
if err := initKeybindings(g); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ func main() {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
g.SetManagerFunc(layout)
|
||||
if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
|
17
doc.go
17
doc.go
@ -13,22 +13,23 @@ Create a new GUI:
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
// Set layout and key bindings
|
||||
// Set GUI managers and key bindings
|
||||
// ...
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
// handle error
|
||||
}
|
||||
|
||||
Set the layout function:
|
||||
Set GUI managers:
|
||||
|
||||
g.SetLayout(fcn)
|
||||
g.SetManager(mgr1, mgr2)
|
||||
|
||||
On each iteration of the GUI's main loop, the "layout function" is executed.
|
||||
These layout functions can be used to set-up and update the application's main
|
||||
views, being possible to freely switch between them. Also, it is important to
|
||||
mention that a main loop iteration is executed on each reported event
|
||||
(key-press, mouse event, window resize, etc).
|
||||
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
|
||||
|
73
gui.go
73
gui.go
@ -10,14 +10,6 @@ import (
|
||||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
// Handler represents a handler that can be used to update or modify the GUI.
|
||||
type Handler func(*Gui) error
|
||||
|
||||
// userEvent represents an event triggered by the user.
|
||||
type userEvent struct {
|
||||
h Handler
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrQuit is used to decide if the MainLoop finished successfully.
|
||||
ErrQuit = errors.New("quit")
|
||||
@ -33,7 +25,7 @@ type Gui struct {
|
||||
userEvents chan userEvent
|
||||
views []*View
|
||||
currentView *View
|
||||
layout Handler
|
||||
managers []Manager
|
||||
keybindings []*keybinding
|
||||
maxX, maxY int
|
||||
|
||||
@ -217,14 +209,14 @@ func (g *Gui) CurrentView() *View {
|
||||
// SetKeybinding creates a new keybinding. If viewname equals to ""
|
||||
// (empty string) then the keybinding will apply to all views. key must
|
||||
// be a rune or a Key.
|
||||
func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, h KeybindingHandler) error {
|
||||
func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, handler func(*Gui, *View) error) error {
|
||||
var kb *keybinding
|
||||
|
||||
k, ch, err := getKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
kb = newKeybinding(viewname, k, ch, mod, h)
|
||||
kb = newKeybinding(viewname, k, ch, mod, handler)
|
||||
g.keybindings = append(g.keybindings, kb)
|
||||
return nil
|
||||
}
|
||||
@ -269,24 +261,49 @@ func getKey(key interface{}) (Key, rune, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Execute executes the given handler. This function can be called safely from
|
||||
// userEvent represents an event triggered by the user.
|
||||
type userEvent struct {
|
||||
f func(*Gui) error
|
||||
}
|
||||
|
||||
// Execute executes the given function. This function can be called safely from
|
||||
// a goroutine in order to update the GUI. It is important to note that it
|
||||
// won't be executed immediately, instead it will be added to the user events
|
||||
// queue.
|
||||
func (g *Gui) Execute(h Handler) {
|
||||
go func() { g.userEvents <- userEvent{h: h} }()
|
||||
func (g *Gui) Execute(f func(*Gui) error) {
|
||||
go func() { g.userEvents <- userEvent{f: f} }()
|
||||
}
|
||||
|
||||
// SetLayout sets the current layout. A layout is a function that
|
||||
// will be called every time the gui is redrawn, it must contain
|
||||
// the base views and its initializations.
|
||||
func (g *Gui) SetLayout(layout Handler) {
|
||||
g.layout = layout
|
||||
// A Manager is in charge of GUI's layout and can be used to build widgets.
|
||||
type Manager interface {
|
||||
// Layout is called every time the GUI is redrawn, it must contain the
|
||||
// base views and its initializations.
|
||||
Layout(*Gui) error
|
||||
}
|
||||
|
||||
// The ManagerFunc type is an adapter to allow the use of ordinary functions as
|
||||
// Managers. If f is a function with the appropriate signature, ManagerFunc(f)
|
||||
// is an Manager object that calls f.
|
||||
type ManagerFunc func(v *Gui) error
|
||||
|
||||
// Layout calls f(g)
|
||||
func (f ManagerFunc) Layout(g *Gui) error {
|
||||
return f(g)
|
||||
}
|
||||
|
||||
// SetManager sets the given GUI managers.
|
||||
func (g *Gui) SetManager(managers ...Manager) {
|
||||
g.managers = managers
|
||||
g.currentView = nil
|
||||
g.views = nil
|
||||
go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }()
|
||||
}
|
||||
|
||||
// SetManagerFunc sets the given manager function.
|
||||
func (g *Gui) SetManagerFunc(manager func(v *Gui) error) {
|
||||
g.SetManager(ManagerFunc(manager))
|
||||
}
|
||||
|
||||
// MainLoop runs the main loop until an error is returned. A successful
|
||||
// finish should return ErrQuit.
|
||||
func (g *Gui) MainLoop() error {
|
||||
@ -315,7 +332,7 @@ func (g *Gui) MainLoop() error {
|
||||
return err
|
||||
}
|
||||
case ev := <-g.userEvents:
|
||||
if err := ev.h(g); err != nil {
|
||||
if err := ev.f(g); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -337,7 +354,7 @@ func (g *Gui) consumeevents() error {
|
||||
return err
|
||||
}
|
||||
case ev := <-g.userEvents:
|
||||
if err := ev.h(g); err != nil {
|
||||
if err := ev.f(g); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
@ -361,10 +378,6 @@ func (g *Gui) handleEvent(ev *termbox.Event) error {
|
||||
|
||||
// flush updates the gui, re-drawing frames and buffers.
|
||||
func (g *Gui) flush() error {
|
||||
if g.layout == nil {
|
||||
return errors.New("Null layout")
|
||||
}
|
||||
|
||||
termbox.Clear(termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
|
||||
|
||||
maxX, maxY := termbox.Size()
|
||||
@ -376,8 +389,10 @@ func (g *Gui) flush() error {
|
||||
}
|
||||
g.maxX, g.maxY = maxX, maxY
|
||||
|
||||
if err := g.layout(g); err != nil {
|
||||
return err
|
||||
for _, m := range g.managers {
|
||||
if err := m.Layout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, v := range g.views {
|
||||
if v.Frame {
|
||||
@ -558,11 +573,11 @@ func (g *Gui) onKey(ev *termbox.Event) error {
|
||||
// and event.
|
||||
func (g *Gui) execKeybindings(v *View, ev *termbox.Event) error {
|
||||
for _, kb := range g.keybindings {
|
||||
if kb.h == nil {
|
||||
if kb.handler == nil {
|
||||
continue
|
||||
}
|
||||
if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(v) {
|
||||
if err := kb.h(g, v); err != nil {
|
||||
if err := kb.handler(g, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -6,19 +6,42 @@ package gocui
|
||||
|
||||
import "github.com/nsf/termbox-go"
|
||||
|
||||
type (
|
||||
// Key represents special keys or keys combinations.
|
||||
Key termbox.Key
|
||||
// Keybidings are used to link a given key-press event with a handler.
|
||||
type keybinding struct {
|
||||
viewName string
|
||||
key Key
|
||||
ch rune
|
||||
mod Modifier
|
||||
handler func(*Gui, *View) error
|
||||
}
|
||||
|
||||
// Modifier allows to define special keys combinations. They can be used
|
||||
// in combination with Keys or Runes when a new keybinding is defined.
|
||||
Modifier termbox.Modifier
|
||||
// newKeybinding returns a new Keybinding object.
|
||||
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, handler func(*Gui, *View) error) (kb *keybinding) {
|
||||
kb = &keybinding{
|
||||
viewName: viewname,
|
||||
key: key,
|
||||
ch: ch,
|
||||
mod: mod,
|
||||
handler: handler,
|
||||
}
|
||||
return kb
|
||||
}
|
||||
|
||||
// KeybindingHandler represents the handler linked to a specific
|
||||
// keybindings. The handler is called when a key-press event satisfies a
|
||||
// configured keybinding.
|
||||
KeybindingHandler func(*Gui, *View) error
|
||||
)
|
||||
// matchKeypress returns if the keybinding matches the keypress.
|
||||
func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
|
||||
return kb.key == key && kb.ch == ch && kb.mod == mod
|
||||
}
|
||||
|
||||
// matchView returns if the keybinding matches the current view.
|
||||
func (kb *keybinding) 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 termbox.Key
|
||||
|
||||
// Special keys.
|
||||
const (
|
||||
@ -100,42 +123,12 @@ const (
|
||||
KeyCtrl8 = Key(termbox.KeyCtrl8)
|
||||
)
|
||||
|
||||
// Modifier allows to define special keys combinations. They can be used
|
||||
// in combination with Keys or Runes when a new keybinding is defined.
|
||||
type Modifier termbox.Modifier
|
||||
|
||||
// Modifiers.
|
||||
const (
|
||||
ModNone Modifier = Modifier(0)
|
||||
ModAlt = Modifier(termbox.ModAlt)
|
||||
)
|
||||
|
||||
// Keybidings are used to link a given key-press event with a handler.
|
||||
type keybinding struct {
|
||||
viewName string
|
||||
key Key
|
||||
ch rune
|
||||
mod Modifier
|
||||
h KeybindingHandler
|
||||
}
|
||||
|
||||
// newKeybinding returns a new Keybinding object.
|
||||
func newKeybinding(viewname string, key Key, ch rune, mod Modifier, h KeybindingHandler) (kb *keybinding) {
|
||||
kb = &keybinding{
|
||||
viewName: viewname,
|
||||
key: key,
|
||||
ch: ch,
|
||||
mod: mod,
|
||||
h: h,
|
||||
}
|
||||
return kb
|
||||
}
|
||||
|
||||
// matchKeypress returns if the keybinding matches the keypress.
|
||||
func (kb *keybinding) matchKeypress(key Key, ch rune, mod Modifier) bool {
|
||||
return kb.key == key && kb.ch == ch && kb.mod == mod
|
||||
}
|
||||
|
||||
// matchView returns if the keybinding matches the current view.
|
||||
func (kb *keybinding) matchView(v *View) bool {
|
||||
if kb.viewName == "" {
|
||||
return true
|
||||
}
|
||||
return v != nil && kb.viewName == v.name
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user