aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/jroimartin/gocui/gui.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/jroimartin/gocui/gui.go')
-rw-r--r--vendor/github.com/jroimartin/gocui/gui.go372
1 files changed, 181 insertions, 191 deletions
diff --git a/vendor/github.com/jroimartin/gocui/gui.go b/vendor/github.com/jroimartin/gocui/gui.go
index 39579dc4..9499d3c3 100644
--- a/vendor/github.com/jroimartin/gocui/gui.go
+++ b/vendor/github.com/jroimartin/gocui/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")
@@ -26,6 +18,17 @@ var (
ErrUnknownView = errors.New("unknown view")
)
+// OutputMode represents the terminal's output mode (8 or 256 colors).
+type OutputMode termbox.OutputMode
+
+const (
+ // OutputNormal provides 8-colors terminal mode.
+ OutputNormal = OutputMode(termbox.OutputNormal)
+
+ // Output256 provides 256-colors terminal mode.
+ Output256 = OutputMode(termbox.Output256)
+)
+
// Gui represents the whole User Interface, including the views, layouts
// and keybindings.
type Gui struct {
@@ -33,18 +36,23 @@ type Gui struct {
userEvents chan userEvent
views []*View
currentView *View
- layout Handler
+ managers []Manager
keybindings []*keybinding
maxX, maxY int
+ outputMode OutputMode
// BgColor and FgColor allow to configure the background and foreground
// colors of the GUI.
BgColor, FgColor Attribute
- // SelBgColor and SelFgColor are used to configure the background and
- // foreground colors of the selected line, when it is highlighted.
+ // SelBgColor and SelFgColor allow to configure the background and
+ // foreground colors of the frame of the current view.
SelBgColor, SelFgColor Attribute
+ // If Highlight is true, Sel{Bg,Fg}Colors will be used to draw the
+ // frame of the current view.
+ Highlight bool
+
// If Cursor is true then the cursor is enabled.
Cursor bool
@@ -55,30 +63,31 @@ type Gui struct {
// match any known sequence, ESC means KeyEsc.
InputEsc bool
- // Editor allows to define the editor that manages the edition mode,
- // including keybindings or cursor behaviour. DefaultEditor is used by
- // default.
- Editor Editor
+ // If ASCII is true then use ASCII instead of unicode to draw the
+ // interface. Using ASCII is more portable.
+ ASCII bool
}
-// NewGui returns a new Gui object.
-func NewGui() *Gui {
- return &Gui{}
-}
-
-// Init initializes the library. This function must be called before
-// any other functions.
-func (g *Gui) Init() error {
+// NewGui returns a new Gui object with a given output mode.
+func NewGui(mode OutputMode) (*Gui, error) {
if err := termbox.Init(); err != nil {
- return err
+ return nil, err
}
+
+ g := &Gui{}
+
+ g.outputMode = mode
+ termbox.SetOutputMode(termbox.OutputMode(mode))
+
g.tbEvents = make(chan termbox.Event, 20)
g.userEvents = make(chan userEvent, 20)
+
g.maxX, g.maxY = termbox.Size()
- g.BgColor = ColorBlack
- g.FgColor = ColorWhite
- g.Editor = DefaultEditor
- return nil
+
+ g.BgColor, g.FgColor = ColorDefault, ColorDefault
+ g.SelBgColor, g.SelFgColor = ColorDefault, ColorDefault
+
+ return g, nil
}
// Close finalizes the library. It should be called after a successful
@@ -94,12 +103,12 @@ func (g *Gui) Size() (x, y int) {
// SetRune writes a rune at the given point, relative to the top-left
// corner of the terminal. It checks if the position is valid and applies
-// the gui's colors.
-func (g *Gui) SetRune(x, y int, ch rune) error {
+// the given colors.
+func (g *Gui) SetRune(x, y int, ch rune, fgColor, bgColor Attribute) error {
if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
return errors.New("invalid point")
}
- termbox.SetCell(x, y, ch, termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
+ termbox.SetCell(x, y, ch, termbox.Attribute(fgColor), termbox.Attribute(bgColor))
return nil
}
@@ -135,7 +144,7 @@ func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
return v, nil
}
- v := newView(name, x0, y0, x1, y1)
+ v := newView(name, x0, y0, x1, y1, g.outputMode)
v.BgColor, v.FgColor = g.BgColor, g.FgColor
v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
g.views = append(g.views, v)
@@ -154,6 +163,23 @@ func (g *Gui) SetViewOnTop(name string) (*View, error) {
return nil, ErrUnknownView
}
+// SetViewOnBottom sets the given view on bottom of the existing ones.
+func (g *Gui) SetViewOnBottom(name string) (*View, error) {
+ for i, v := range g.views {
+ if v.name == name {
+ s := append(g.views[:i], g.views[i+1:]...)
+ g.views = append([]*View{v}, s...)
+ return v, nil
+ }
+ }
+ return nil, ErrUnknownView
+}
+
+// Views returns all the views in the GUI.
+func (g *Gui) Views() []*View {
+ return g.views
+}
+
// View returns a pointer to the view with the given name, or error
// ErrUnknownView if a view with that name does not exist.
func (g *Gui) View(name string) (*View, error) {
@@ -168,7 +194,9 @@ func (g *Gui) View(name string) (*View, error) {
// ViewByPosition returns a pointer to a view matching the given position, or
// error ErrUnknownView if a view in that position does not exist.
func (g *Gui) ViewByPosition(x, y int) (*View, error) {
- for _, v := range g.views {
+ // traverse views in reverse order checking top views first
+ for i := len(g.views); i > 0; i-- {
+ v := g.views[i-1]
if x > v.x0 && x < v.x1 && y > v.y0 && y < v.y1 {
return v, nil
}
@@ -199,14 +227,14 @@ func (g *Gui) DeleteView(name string) error {
}
// SetCurrentView gives the focus to a given view.
-func (g *Gui) SetCurrentView(name string) error {
+func (g *Gui) SetCurrentView(name string) (*View, error) {
for _, v := range g.views {
if v.name == name {
g.currentView = v
- return nil
+ return v, nil
}
}
- return ErrUnknownView
+ return nil, ErrUnknownView
}
// CurrentView returns the currently focused view, or nil if no view
@@ -218,14 +246,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
}
@@ -270,24 +298,54 @@ func getKey(key interface{}) (Key, rune, error) {
}
}
-// Execute executes the given handler. 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} }()
+// userEvent represents an event triggered by the user.
+type userEvent struct {
+ f func(*Gui) error
}
-// 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
+// Update executes the passed function. This method can be called safely from a
+// goroutine in order to update the GUI. It is important to note that the
+// passed function won't be executed immediately, instead it will be added to
+// the user events queue. Given that Update spawns a goroutine, the order in
+// which the user events will be handled is not guaranteed.
+func (g *Gui) Update(f func(*Gui) error) {
+ go func() { g.userEvents <- userEvent{f: f} }()
+}
+
+// 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(*Gui) error
+
+// Layout calls f(g)
+func (f ManagerFunc) Layout(g *Gui) error {
+ return f(g)
+}
+
+// SetManager sets the given GUI managers. It deletes all views and
+// keybindings.
+func (g *Gui) SetManager(managers ...Manager) {
+ g.managers = managers
g.currentView = nil
g.views = nil
+ g.keybindings = nil
+
go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }()
}
+// SetManagerFunc sets the given manager function. It deletes all views and
+// keybindings.
+func (g *Gui) SetManagerFunc(manager func(*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 {
@@ -316,7 +374,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
}
}
@@ -338,7 +396,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:
@@ -362,10 +420,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()
@@ -377,49 +431,60 @@ 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 {
- if err := g.drawFrame(v); err != nil {
+ var fgColor, bgColor Attribute
+ if g.Highlight && v == g.currentView {
+ fgColor = g.SelFgColor
+ bgColor = g.SelBgColor
+ } else {
+ fgColor = g.FgColor
+ bgColor = g.BgColor
+ }
+
+ if err := g.drawFrameEdges(v, fgColor, bgColor); err != nil {
return err
}
- if err := g.drawCorners(v); err != nil {
+ if err := g.drawFrameCorners(v, fgColor, bgColor); err != nil {
return err
}
if v.Title != "" {
- if err := g.drawTitle(v); err != nil {
+ if err := g.drawTitle(v, fgColor, bgColor); err != nil {
return err
}
}
}
-
if err := g.draw(v); err != nil {
return err
}
}
- if err := g.drawIntersections(); err != nil {
- return err
- }
termbox.Flush()
return nil
-
}
-// drawFrame draws the horizontal and vertical edges of a view.
-func (g *Gui) drawFrame(v *View) error {
+// drawFrameEdges draws the horizontal and vertical edges of a view.
+func (g *Gui) drawFrameEdges(v *View, fgColor, bgColor Attribute) error {
+ runeH, runeV := '─', '│'
+ if g.ASCII {
+ runeH, runeV = '-', '|'
+ }
+
for x := v.x0 + 1; x < v.x1 && x < g.maxX; x++ {
if x < 0 {
continue
}
if v.y0 > -1 && v.y0 < g.maxY {
- if err := g.SetRune(x, v.y0, '─'); err != nil {
+ if err := g.SetRune(x, v.y0, runeH, fgColor, bgColor); err != nil {
return err
}
}
if v.y1 > -1 && v.y1 < g.maxY {
- if err := g.SetRune(x, v.y1, '─'); err != nil {
+ if err := g.SetRune(x, v.y1, runeH, fgColor, bgColor); err != nil {
return err
}
}
@@ -429,12 +494,12 @@ func (g *Gui) drawFrame(v *View) error {
continue
}
if v.x0 > -1 && v.x0 < g.maxX {
- if err := g.SetRune(v.x0, y, '│'); err != nil {
+ if err := g.SetRune(v.x0, y, runeV, fgColor, bgColor); err != nil {
return err
}
}
if v.x1 > -1 && v.x1 < g.maxX {
- if err := g.SetRune(v.x1, y, '│'); err != nil {
+ if err := g.SetRune(v.x1, y, runeV, fgColor, bgColor); err != nil {
return err
}
}
@@ -442,33 +507,30 @@ func (g *Gui) drawFrame(v *View) error {
return nil
}
-// drawCorners draws the corners of the view.
-func (g *Gui) drawCorners(v *View) error {
- if v.x0 >= 0 && v.y0 >= 0 && v.x0 < g.maxX && v.y0 < g.maxY {
- if err := g.SetRune(v.x0, v.y0, '┌'); err != nil {
- return err
- }
+// drawFrameCorners draws the corners of the view.
+func (g *Gui) drawFrameCorners(v *View, fgColor, bgColor Attribute) error {
+ runeTL, runeTR, runeBL, runeBR := '┌', '┐', '└', '┘'
+ if g.ASCII {
+ runeTL, runeTR, runeBL, runeBR = '+', '+', '+', '+'
}
- if v.x1 >= 0 && v.y0 >= 0 && v.x1 < g.maxX && v.y0 < g.maxY {
- if err := g.SetRune(v.x1, v.y0, '┐'); err != nil {
- return err
- }
- }
- if v.x0 >= 0 && v.y1 >= 0 && v.x0 < g.maxX && v.y1 < g.maxY {
- if err := g.SetRune(v.x0, v.y1, '└'); err != nil {
- return err
- }
- }
- if v.x1 >= 0 && v.y1 >= 0 && v.x1 < g.maxX && v.y1 < g.maxY {
- if err := g.SetRune(v.x1, v.y1, '┘'); err != nil {
- return err
+
+ corners := []struct {
+ x, y int
+ ch rune
+ }{{v.x0, v.y0, runeTL}, {v.x1, v.y0, runeTR}, {v.x0, v.y1, runeBL}, {v.x1, v.y1, runeBR}}
+
+ for _, c := range corners {
+ if c.x >= 0 && c.y >= 0 && c.x < g.maxX && c.y < g.maxY {
+ if err := g.SetRune(c.x, c.y, c.ch, fgColor, bgColor); err != nil {
+ return err
+ }
}
}
return nil
}
// drawTitle draws the title of the view.
-func (g *Gui) drawTitle(v *View) error {
+func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error {
if v.y0 < 0 || v.y0 >= g.maxY {
return nil
}
@@ -480,7 +542,7 @@ func (g *Gui) drawTitle(v *View) error {
} else if x > v.x1-2 || x >= g.maxX {
break
}
- if err := g.SetRune(x, v.y0, ch); err != nil {
+ if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil {
return err
}
}
@@ -490,21 +552,21 @@ func (g *Gui) drawTitle(v *View) error {
// draw manages the cursor and calls the draw function of a view.
func (g *Gui) draw(v *View) error {
if g.Cursor {
- if v := g.currentView; v != nil {
- vMaxX, vMaxY := v.Size()
- if v.cx < 0 {
- v.cx = 0
- } else if v.cx >= vMaxX {
- v.cx = vMaxX - 1
+ if curview := g.currentView; curview != nil {
+ vMaxX, vMaxY := curview.Size()
+ if curview.cx < 0 {
+ curview.cx = 0
+ } else if curview.cx >= vMaxX {
+ curview.cx = vMaxX - 1
}
- if v.cy < 0 {
- v.cy = 0
- } else if v.cy >= vMaxY {
- v.cy = vMaxY - 1
+ if curview.cy < 0 {
+ curview.cy = 0
+ } else if curview.cy >= vMaxY {
+ curview.cy = vMaxY - 1
}
gMaxX, gMaxY := g.Size()
- cx, cy := v.x0+v.cx+1, v.y0+v.cy+1
+ cx, cy := curview.x0+curview.cx+1, curview.y0+curview.cy+1
if cx >= 0 && cx < gMaxX && cy >= 0 && cy < gMaxY {
termbox.SetCursor(cx, cy)
} else {
@@ -522,95 +584,21 @@ func (g *Gui) draw(v *View) error {
return nil
}
-// drawIntersections draws the corners of each view, based on the type
-// of the edges that converge at these points.
-func (g *Gui) drawIntersections() error {
- for _, v := range g.views {
- if ch, ok := g.intersectionRune(v.x0, v.y0); ok {
- if err := g.SetRune(v.x0, v.y0, ch); err != nil {
- return err
- }
- }
- if ch, ok := g.intersectionRune(v.x0, v.y1); ok {
- if err := g.SetRune(v.x0, v.y1, ch); err != nil {
- return err
- }
- }
- if ch, ok := g.intersectionRune(v.x1, v.y0); ok {
- if err := g.SetRune(v.x1, v.y0, ch); err != nil {
- return err
- }
- }
- if ch, ok := g.intersectionRune(v.x1, v.y1); ok {
- if err := g.SetRune(v.x1, v.y1, ch); err != nil {
- return err
- }
- }
- }
- return nil
-}
-
-// intersectionRune returns the correct intersection rune at a given
-// point.
-func (g *Gui) intersectionRune(x, y int) (rune, bool) {
- if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
- return ' ', false
- }
-
- chTop, _ := g.Rune(x, y-1)
- top := verticalRune(chTop)
- chBottom, _ := g.Rune(x, y+1)
- bottom := verticalRune(chBottom)
- chLeft, _ := g.Rune(x-1, y)
- left := horizontalRune(chLeft)
- chRight, _ := g.Rune(x+1, y)
- right := horizontalRune(chRight)
-
- var ch rune
- switch {
- case top && bottom && left && right:
- ch = '┼'
- case top && bottom && !left && right:
- ch = '├'
- case top && bottom && left && !right:
- ch = '┤'
- case !top && bottom && left && right:
- ch = '┬'
- case top && !bottom && left && right:
- ch = '┴'
- default:
- return ' ', false
- }
- return ch, true
-}
-
-// verticalRune returns if the given character is a vertical rune.
-func verticalRune(ch rune) bool {
- if ch == '│' || ch == '┼' || ch == '├' || ch == '┤' {
- return true
- }
- return false
-}
-
-// verticalRune returns if the given character is a horizontal rune.
-func horizontalRune(ch rune) bool {
- if ch == '─' || ch == '┼' || ch == '┬' || ch == '┴' {
- return true
- }
- return false
-}
-
// onKey manages key-press events. A keybinding handler is called when
// a key-press or mouse event satisfies a configured keybinding. Furthermore,
// currentView's internal buffer is modified if currentView.Editable is true.
func (g *Gui) onKey(ev *termbox.Event) error {
switch ev.Type {
case termbox.EventKey:
- if err := g.execKeybindings(g.currentView, ev); err != nil {
+ matched, err := g.execKeybindings(g.currentView, ev)
+ if err != nil {
return err
}
- if g.currentView != nil && g.currentView.Editable && g.Editor != nil {
- g.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
+ if matched {
+ break
+ }
+ if g.currentView != nil && g.currentView.Editable && g.currentView.Editor != nil {
+ g.currentView.Editor.Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
}
case termbox.EventMouse:
mx, my := ev.MouseX, ev.MouseY
@@ -621,7 +609,7 @@ func (g *Gui) onKey(ev *termbox.Event) error {
if err := v.SetCursor(mx-v.x0-1, my-v.y0-1); err != nil {
return err
}
- if err := g.execKeybindings(v, ev); err != nil {
+ if _, err := g.execKeybindings(v, ev); err != nil {
return err
}
}
@@ -630,17 +618,19 @@ func (g *Gui) onKey(ev *termbox.Event) error {
}
// execKeybindings executes the keybinding handlers that match the passed view
-// and event.
-func (g *Gui) execKeybindings(v *View, ev *termbox.Event) error {
+// and event. The value of matched is true if there is a match and no errors.
+func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) {
+ matched = false
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 {
- return err
+ if err := kb.handler(g, v); err != nil {
+ return false, err
}
+ matched = true
}
}
- return nil
+ return matched, nil
}