diff options
author | Michael Muré <batolettre@gmail.com> | 2018-07-30 18:11:53 +0200 |
---|---|---|
committer | Michael Muré <batolettre@gmail.com> | 2018-07-30 18:11:53 +0200 |
commit | bb9168f98a9dd50a7215652ab77a1c46615064cd (patch) | |
tree | 8ee16c1b5c6078cd48427c799375b78c95749bcd /vendor/github.com/jroimartin/gocui/gui.go | |
parent | 29bb7364c8e873c72a386f4f6fa26248e5355cb2 (diff) | |
download | git-bug-bb9168f98a9dd50a7215652ab77a1c46615064cd.tar.gz |
vendor gocui
Diffstat (limited to 'vendor/github.com/jroimartin/gocui/gui.go')
-rw-r--r-- | vendor/github.com/jroimartin/gocui/gui.go | 646 |
1 files changed, 646 insertions, 0 deletions
diff --git a/vendor/github.com/jroimartin/gocui/gui.go b/vendor/github.com/jroimartin/gocui/gui.go new file mode 100644 index 00000000..39579dc4 --- /dev/null +++ b/vendor/github.com/jroimartin/gocui/gui.go @@ -0,0 +1,646 @@ +// Copyright 2014 The gocui Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gocui + +import ( + "errors" + + "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") + + // ErrUnknownView allows to assert if a View must be initialized. + ErrUnknownView = errors.New("unknown view") +) + +// Gui represents the whole User Interface, including the views, layouts +// and keybindings. +type Gui struct { + tbEvents chan termbox.Event + userEvents chan userEvent + views []*View + currentView *View + layout Handler + keybindings []*keybinding + maxX, maxY int + + // 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, SelFgColor Attribute + + // If Cursor is true then the cursor is enabled. + Cursor bool + + // If Mouse is true then mouse events will be enabled. + Mouse bool + + // If InputEsc is true, when ESC sequence is in the buffer and it doesn't + // 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 +} + +// 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 { + if err := termbox.Init(); err != nil { + return err + } + 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 +} + +// Close finalizes the library. It should be called after a successful +// initialization and when gocui is not needed anymore. +func (g *Gui) Close() { + termbox.Close() +} + +// Size returns the terminal's size. +func (g *Gui) Size() (x, y int) { + return g.maxX, g.maxY +} + +// 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 { + 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)) + return nil +} + +// Rune returns the rune contained in the cell at the given position. +// It checks if the position is valid. +func (g *Gui) Rune(x, y int) (rune, error) { + if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY { + return ' ', errors.New("invalid point") + } + c := termbox.CellBuffer()[y*g.maxX+x] + return c.Ch, nil +} + +// SetView creates a new view with its top-left corner at (x0, y0) +// and the bottom-right one at (x1, y1). If a view with the same name +// already exists, its dimensions are updated; otherwise, the error +// ErrUnknownView is returned, which allows to assert if the View must +// be initialized. It checks if the position is valid. +func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) { + if x0 >= x1 || y0 >= y1 { + return nil, errors.New("invalid dimensions") + } + if name == "" { + return nil, errors.New("invalid name") + } + + if v, err := g.View(name); err == nil { + v.x0 = x0 + v.y0 = y0 + v.x1 = x1 + v.y1 = y1 + v.tainted = true + return v, nil + } + + v := newView(name, x0, y0, x1, y1) + v.BgColor, v.FgColor = g.BgColor, g.FgColor + v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor + g.views = append(g.views, v) + return v, ErrUnknownView +} + +// SetViewOnTop sets the given view on top of the existing ones. +func (g *Gui) SetViewOnTop(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(s, v) + return v, nil + } + } + return nil, ErrUnknownView +} + +// 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) { + for _, v := range g.views { + if v.name == name { + return v, nil + } + } + return nil, ErrUnknownView +} + +// 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 { + if x > v.x0 && x < v.x1 && y > v.y0 && y < v.y1 { + return v, nil + } + } + return nil, ErrUnknownView +} + +// ViewPosition returns the coordinates of the view with the given name, or +// error ErrUnknownView if a view with that name does not exist. +func (g *Gui) ViewPosition(name string) (x0, y0, x1, y1 int, err error) { + for _, v := range g.views { + if v.name == name { + return v.x0, v.y0, v.x1, v.y1, nil + } + } + return 0, 0, 0, 0, ErrUnknownView +} + +// DeleteView deletes a view by name. +func (g *Gui) DeleteView(name string) error { + for i, v := range g.views { + if v.name == name { + g.views = append(g.views[:i], g.views[i+1:]...) + return nil + } + } + return ErrUnknownView +} + +// SetCurrentView gives the focus to a given view. +func (g *Gui) SetCurrentView(name string) error { + for _, v := range g.views { + if v.name == name { + g.currentView = v + return nil + } + } + return ErrUnknownView +} + +// CurrentView returns the currently focused view, or nil if no view +// owns the focus. +func (g *Gui) CurrentView() *View { + return g.currentView +} + +// 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 { + var kb *keybinding + + k, ch, err := getKey(key) + if err != nil { + return err + } + kb = newKeybinding(viewname, k, ch, mod, h) + g.keybindings = append(g.keybindings, kb) + return nil +} + +// DeleteKeybinding deletes a keybinding. +func (g *Gui) DeleteKeybinding(viewname string, key interface{}, mod Modifier) error { + k, ch, err := getKey(key) + if err != nil { + return err + } + + for i, kb := range g.keybindings { + if kb.viewName == viewname && kb.ch == ch && kb.key == k && kb.mod == mod { + g.keybindings = append(g.keybindings[:i], g.keybindings[i+1:]...) + return nil + } + } + return errors.New("keybinding not found") +} + +// DeleteKeybindings deletes all keybindings of view. +func (g *Gui) DeleteKeybindings(viewname string) { + var s []*keybinding + for _, kb := range g.keybindings { + if kb.viewName != viewname { + s = append(s, kb) + } + } + g.keybindings = s +} + +// getKey takes an empty interface with a key and returns the corresponding +// typed Key or rune. +func getKey(key interface{}) (Key, rune, error) { + switch t := key.(type) { + case Key: + return t, 0, nil + case rune: + return 0, t, nil + default: + return 0, 0, errors.New("unknown type") + } +} + +// 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} }() +} + +// 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 + g.currentView = nil + g.views = nil + go func() { g.tbEvents <- termbox.Event{Type: termbox.EventResize} }() +} + +// MainLoop runs the main loop until an error is returned. A successful +// finish should return ErrQuit. +func (g *Gui) MainLoop() error { + go func() { + for { + g.tbEvents <- termbox.PollEvent() + } + }() + + inputMode := termbox.InputAlt + if g.InputEsc { + inputMode = termbox.InputEsc + } + if g.Mouse { + inputMode |= termbox.InputMouse + } + termbox.SetInputMode(inputMode) + + if err := g.flush(); err != nil { + return err + } + for { + select { + case ev := <-g.tbEvents: + if err := g.handleEvent(&ev); err != nil { + return err + } + case ev := <-g.userEvents: + if err := ev.h(g); err != nil { + return err + } + } + if err := g.consumeevents(); err != nil { + return err + } + if err := g.flush(); err != nil { + return err + } + } +} + +// consumeevents handles the remaining events in the events pool. +func (g *Gui) consumeevents() error { + for { + select { + case ev := <-g.tbEvents: + if err := g.handleEvent(&ev); err != nil { + return err + } + case ev := <-g.userEvents: + if err := ev.h(g); err != nil { + return err + } + default: + return nil + } + } +} + +// handleEvent handles an event, based on its type (key-press, error, +// etc.) +func (g *Gui) handleEvent(ev *termbox.Event) error { + switch ev.Type { + case termbox.EventKey, termbox.EventMouse: + return g.onKey(ev) + case termbox.EventError: + return ev.Err + default: + return nil + } +} + +// 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() + // if GUI's size has changed, we need to redraw all views + if maxX != g.maxX || maxY != g.maxY { + for _, v := range g.views { + v.tainted = true + } + } + g.maxX, g.maxY = maxX, maxY + + if err := g.layout(g); err != nil { + return err + } + for _, v := range g.views { + if v.Frame { + if err := g.drawFrame(v); err != nil { + return err + } + if err := g.drawCorners(v); err != nil { + return err + } + if v.Title != "" { + if err := g.drawTitle(v); 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 { + 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 { + return err + } + } + if v.y1 > -1 && v.y1 < g.maxY { + if err := g.SetRune(x, v.y1, '─'); err != nil { + return err + } + } + } + for y := v.y0 + 1; y < v.y1 && y < g.maxY; y++ { + if y < 0 { + continue + } + if v.x0 > -1 && v.x0 < g.maxX { + if err := g.SetRune(v.x0, y, '│'); err != nil { + return err + } + } + if v.x1 > -1 && v.x1 < g.maxX { + if err := g.SetRune(v.x1, y, '│'); err != nil { + return err + } + } + } + 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 + } + } + 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 + } + } + return nil +} + +// drawTitle draws the title of the view. +func (g *Gui) drawTitle(v *View) error { + if v.y0 < 0 || v.y0 >= g.maxY { + return nil + } + + for i, ch := range v.Title { + x := v.x0 + i + 2 + if x < 0 { + continue + } else if x > v.x1-2 || x >= g.maxX { + break + } + if err := g.SetRune(x, v.y0, ch); err != nil { + return err + } + } + return nil +} + +// 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 v.cy < 0 { + v.cy = 0 + } else if v.cy >= vMaxY { + v.cy = vMaxY - 1 + } + + gMaxX, gMaxY := g.Size() + cx, cy := v.x0+v.cx+1, v.y0+v.cy+1 + if cx >= 0 && cx < gMaxX && cy >= 0 && cy < gMaxY { + termbox.SetCursor(cx, cy) + } else { + termbox.HideCursor() + } + } + } else { + termbox.HideCursor() + } + + v.clearRunes() + if err := v.draw(); err != nil { + return err + } + 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 { + 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)) + } + case termbox.EventMouse: + mx, my := ev.MouseX, ev.MouseY + v, err := g.ViewByPosition(mx, my) + if err != nil { + break + } + if err := v.SetCursor(mx-v.x0-1, my-v.y0-1); err != nil { + return err + } + if err := g.execKeybindings(v, ev); err != nil { + return err + } + } + + return nil +} + +// execKeybindings executes the keybinding handlers that match the passed view +// and event. +func (g *Gui) execKeybindings(v *View, ev *termbox.Event) error { + for _, kb := range g.keybindings { + if kb.h == 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 + } + } + } + return nil +} |