From 12b0fecd9b9bce3f4a23754a5c0121598ed2f38a Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Mon, 30 Jul 2018 18:22:52 +0200 Subject: vendor gocui on the master branch because of no release in a while --- vendor/github.com/jroimartin/gocui/gui.go | 372 +++++++++++++++--------------- 1 file changed, 181 insertions(+), 191 deletions(-) (limited to 'vendor/github.com/jroimartin/gocui/gui.go') 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 } -- cgit