diff --git a/README.md b/README.md index 446f2f4..68770ad 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/_examples/active.go b/_examples/active.go index 807e986..2c6a2d5 100644 --- a/_examples/active.go +++ b/_examples/active.go @@ -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) diff --git a/_examples/bufs.go b/_examples/bufs.go index 744dae4..0db460c 100644 --- a/_examples/bufs.go +++ b/_examples/bufs.go @@ -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) } diff --git a/_examples/colors.go b/_examples/colors.go index 765718d..9b7eb50 100644 --- a/_examples/colors.go +++ b/_examples/colors.go @@ -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) diff --git a/_examples/demo.go b/_examples/demo.go index 4c54658..97c0fc0 100644 --- a/_examples/demo.go +++ b/_examples/demo.go @@ -197,7 +197,7 @@ func main() { } defer g.Close() - g.SetLayout(layout) + g.SetManagerFunc(layout) if err := keybindings(g); err != nil { log.Panicln(err) } diff --git a/_examples/dynamic.go b/_examples/dynamic.go index 714355e..46ceb3e 100644 --- a/_examples/dynamic.go +++ b/_examples/dynamic.go @@ -27,7 +27,7 @@ func main() { } defer g.Close() - g.SetLayout(layout) + g.SetManagerFunc(layout) g.Highlight = true g.SelFgColor = gocui.ColorRed diff --git a/_examples/goroutine.go b/_examples/goroutine.go index 293162e..1f86b83 100644 --- a/_examples/goroutine.go +++ b/_examples/goroutine.go @@ -30,7 +30,7 @@ func main() { } defer g.Close() - g.SetLayout(layout) + g.SetManagerFunc(layout) if err := keybindings(g); err != nil { log.Panicln(err) } diff --git a/_examples/hello.go b/_examples/hello.go index dbdcf9c..34f2fee 100644 --- a/_examples/hello.go +++ b/_examples/hello.go @@ -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) diff --git a/_examples/layout.go b/_examples/layout.go index 3e3627c..0813878 100644 --- a/_examples/layout.go +++ b/_examples/layout.go @@ -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) diff --git a/_examples/mask.go b/_examples/mask.go index 8729454..ff0308b 100644 --- a/_examples/mask.go +++ b/_examples/mask.go @@ -18,7 +18,7 @@ func main() { } defer g.Close() - g.SetLayout(layout) + g.SetManagerFunc(layout) if err := initKeybindings(g); err != nil { log.Fatalln(err) } diff --git a/_examples/mouse.go b/_examples/mouse.go index 3eb781a..c8aa259 100644 --- a/_examples/mouse.go +++ b/_examples/mouse.go @@ -18,7 +18,7 @@ func main() { } defer g.Close() - g.SetLayout(layout) + g.SetManagerFunc(layout) if err := keybindings(g); err != nil { log.Panicln(err) } diff --git a/_examples/ontop.go b/_examples/ontop.go index 921989e..dd85a79 100644 --- a/_examples/ontop.go +++ b/_examples/ontop.go @@ -18,7 +18,7 @@ func main() { } defer g.Close() - g.SetLayout(layout) + g.SetManagerFunc(layout) if err := keybindings(g); err != nil { log.Panicln(err) } diff --git a/_examples/overlap.go b/_examples/overlap.go index c6bad67..e0abd4f 100644 --- a/_examples/overlap.go +++ b/_examples/overlap.go @@ -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) } diff --git a/_examples/stdin.go b/_examples/stdin.go index 070dc6a..700ec78 100644 --- a/_examples/stdin.go +++ b/_examples/stdin.go @@ -21,7 +21,7 @@ func main() { } defer g.Close() - g.SetLayout(layout) + g.SetManagerFunc(layout) if err := initKeybindings(g); err != nil { log.Fatalln(err) } diff --git a/_examples/title.go b/_examples/title.go index 763ddeb..746a9bb 100644 --- a/_examples/title.go +++ b/_examples/title.go @@ -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) } diff --git a/_examples/wrap.go b/_examples/wrap.go index 88119db..3a3c791 100644 --- a/_examples/wrap.go +++ b/_examples/wrap.go @@ -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) } diff --git a/doc.go b/doc.go index dce8ff1..53c1e84 100644 --- a/doc.go +++ b/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 diff --git a/gui.go b/gui.go index 9e40c3a..d1f6abb 100644 --- a/gui.go +++ b/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 } } diff --git a/keybinding.go b/keybinding.go index 6151682..946864d 100644 --- a/keybinding.go +++ b/keybinding.go @@ -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 -}