Introduce GUI managers to replace layout functions

This commit is contained in:
Roi Martin 2016-10-24 08:36:23 +02:00
parent fc121d98fd
commit 40dbad569f
19 changed files with 107 additions and 99 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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)
}

View File

@ -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)

View File

@ -197,7 +197,7 @@ func main() {
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
if err := keybindings(g); err != nil {
log.Panicln(err)
}

View File

@ -27,7 +27,7 @@ func main() {
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
g.Highlight = true
g.SelFgColor = gocui.ColorRed

View File

@ -30,7 +30,7 @@ func main() {
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
if err := keybindings(g); err != nil {
log.Panicln(err)
}

View File

@ -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)

View File

@ -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)

View File

@ -18,7 +18,7 @@ func main() {
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
if err := initKeybindings(g); err != nil {
log.Fatalln(err)
}

View File

@ -18,7 +18,7 @@ func main() {
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
if err := keybindings(g); err != nil {
log.Panicln(err)
}

View File

@ -18,7 +18,7 @@ func main() {
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
if err := keybindings(g); err != nil {
log.Panicln(err)
}

View File

@ -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)
}

View File

@ -21,7 +21,7 @@ func main() {
}
defer g.Close()
g.SetLayout(layout)
g.SetManagerFunc(layout)
if err := initKeybindings(g); err != nil {
log.Fatalln(err)
}

View File

@ -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)
}

View File

@ -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
View File

@ -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
View File

@ -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
}
}

View File

@ -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
}