2015-01-31 20:39:43 +01:00
|
|
|
// Copyright 2014 The gocui Authors. All rights reserved.
|
2014-01-14 20:11:12 +01:00
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2013-12-27 21:36:26 +01:00
|
|
|
package gocui
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2015-02-02 00:42:34 +01:00
|
|
|
"sync"
|
2013-12-27 22:02:56 +01:00
|
|
|
|
2013-12-31 21:23:28 +01:00
|
|
|
"github.com/nsf/termbox-go"
|
2013-12-27 21:36:26 +01:00
|
|
|
)
|
|
|
|
|
2014-01-10 20:21:54 +01:00
|
|
|
var (
|
2016-01-23 16:07:42 +01:00
|
|
|
// ErrQuit is used to decide if the MainLoop finished succesfully.
|
|
|
|
ErrQuit = errors.New("quit")
|
2014-01-19 17:03:52 +01:00
|
|
|
|
2016-01-23 16:07:42 +01:00
|
|
|
// ErrUnknownView allows to assert if a View must be initialized.
|
|
|
|
ErrUnknownView = errors.New("unknown view")
|
2014-01-10 20:21:54 +01:00
|
|
|
)
|
2013-12-27 21:36:26 +01:00
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// Gui represents the whole User Interface, including the views, layouts
|
|
|
|
// and keybindings.
|
2013-12-27 21:36:26 +01:00
|
|
|
type Gui struct {
|
2014-01-16 23:01:53 +01:00
|
|
|
events chan termbox.Event
|
|
|
|
views []*View
|
|
|
|
currentView *View
|
|
|
|
layout func(*Gui) error
|
|
|
|
keybindings []*keybinding
|
|
|
|
maxX, maxY int
|
|
|
|
|
2015-02-02 00:42:34 +01:00
|
|
|
// Protects the gui from being flushed concurrently.
|
|
|
|
mu sync.Mutex
|
|
|
|
|
2014-01-21 07:59:53 +01:00
|
|
|
// 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.
|
2014-01-10 12:38:08 +01:00
|
|
|
SelBgColor, SelFgColor Attribute
|
2014-01-21 07:59:53 +01:00
|
|
|
|
|
|
|
// If ShowCursor is true then the cursor is enabled.
|
|
|
|
ShowCursor bool
|
2015-12-11 11:51:43 +00:00
|
|
|
|
2016-01-23 16:07:42 +01:00
|
|
|
// If EnableMouse is true then mouse events will be enabled.
|
2015-12-11 11:51:43 +00:00
|
|
|
EnableMouse bool
|
2013-12-27 21:36:26 +01:00
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// NewGui returns a new Gui object.
|
2014-01-16 00:28:16 +01:00
|
|
|
func NewGui() *Gui {
|
2013-12-27 21:36:26 +01:00
|
|
|
return &Gui{}
|
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// Init initializes the library. This function must be called before
|
|
|
|
// any other functions.
|
2014-01-16 00:28:16 +01:00
|
|
|
func (g *Gui) Init() error {
|
|
|
|
if err := termbox.Init(); err != nil {
|
2013-12-31 21:23:28 +01:00
|
|
|
return err
|
|
|
|
}
|
2013-12-27 21:36:26 +01:00
|
|
|
g.events = make(chan termbox.Event, 20)
|
2013-12-31 21:23:28 +01:00
|
|
|
g.maxX, g.maxY = termbox.Size()
|
2014-01-10 12:38:08 +01:00
|
|
|
g.BgColor = ColorBlack
|
|
|
|
g.FgColor = ColorWhite
|
2013-12-31 21:23:28 +01:00
|
|
|
return nil
|
2013-12-27 21:36:26 +01:00
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// Close finalizes the library. It should be called after a successful
|
|
|
|
// initialization and when gocui is not needed anymore.
|
2013-12-27 21:36:26 +01:00
|
|
|
func (g *Gui) Close() {
|
|
|
|
termbox.Close()
|
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// Size returns the terminal's size.
|
2013-12-27 21:36:26 +01:00
|
|
|
func (g *Gui) Size() (x, y int) {
|
2013-12-31 21:23:28 +01:00
|
|
|
return g.maxX, g.maxY
|
2013-12-27 21:36:26 +01:00
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// 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.
|
2014-01-16 00:28:16 +01:00
|
|
|
func (g *Gui) SetRune(x, y int, ch rune) error {
|
2014-01-02 18:34:58 +01:00
|
|
|
if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
|
2014-01-04 02:50:49 +01:00
|
|
|
return errors.New("invalid point")
|
2014-01-02 18:34:58 +01:00
|
|
|
}
|
2014-01-09 21:55:23 +01:00
|
|
|
termbox.SetCell(x, y, ch, termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
|
2014-01-02 18:34:58 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// Rune returns the rune contained in the cell at the given position.
|
|
|
|
// It checks if the position is valid.
|
2014-01-16 23:01:53 +01:00
|
|
|
func (g *Gui) Rune(x, y int) (rune, error) {
|
2014-01-02 18:34:58 +01:00
|
|
|
if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
|
2014-01-20 23:28:08 +01:00
|
|
|
return ' ', errors.New("invalid point")
|
2014-01-02 18:34:58 +01:00
|
|
|
}
|
|
|
|
c := termbox.CellBuffer()[y*g.maxX+x]
|
|
|
|
return c.Ch, nil
|
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// 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
|
2016-01-23 16:07:42 +01:00
|
|
|
// ErrUnknownView is returned, which allows to assert if the View must
|
2014-01-19 17:03:52 +01:00
|
|
|
// be initialized. It checks if the position is valid.
|
2014-01-16 00:28:16 +01:00
|
|
|
func (g *Gui) SetView(name string, x0, y0, x1, y1 int) (*View, error) {
|
2014-01-04 02:50:49 +01:00
|
|
|
if x0 >= x1 || y0 >= y1 {
|
|
|
|
return nil, errors.New("invalid dimensions")
|
2013-12-27 21:36:26 +01:00
|
|
|
}
|
2014-01-21 07:59:53 +01:00
|
|
|
if name == "" {
|
|
|
|
return nil, errors.New("invalid name")
|
|
|
|
}
|
2013-12-28 18:49:01 +01:00
|
|
|
|
2015-01-25 14:07:14 +01:00
|
|
|
if v, err := g.View(name); err == nil {
|
2014-01-16 23:01:53 +01:00
|
|
|
v.x0 = x0
|
|
|
|
v.y0 = y0
|
|
|
|
v.x1 = x1
|
|
|
|
v.y1 = y1
|
2015-02-23 16:37:49 +01:00
|
|
|
v.tainted = true
|
2014-01-04 02:50:49 +01:00
|
|
|
return v, nil
|
2013-12-28 18:49:01 +01:00
|
|
|
}
|
|
|
|
|
2014-01-16 00:28:16 +01:00
|
|
|
v := newView(name, x0, y0, x1, y1)
|
2014-05-03 15:20:46 +02:00
|
|
|
v.BgColor, v.FgColor = g.BgColor, g.FgColor
|
|
|
|
v.SelBgColor, v.SelFgColor = g.SelBgColor, g.SelFgColor
|
2013-12-27 21:36:26 +01:00
|
|
|
g.views = append(g.views, v)
|
2016-01-23 16:07:42 +01:00
|
|
|
return v, ErrUnknownView
|
2013-12-27 21:36:26 +01:00
|
|
|
}
|
|
|
|
|
2015-01-25 14:07:14 +01:00
|
|
|
// View returns a pointer to the view with the given name, or error
|
2016-01-23 16:07:42 +01:00
|
|
|
// ErrUnknownView if a view with that name does not exist.
|
2015-01-25 14:07:14 +01:00
|
|
|
func (g *Gui) View(name string) (*View, error) {
|
2014-01-04 03:40:45 +01:00
|
|
|
for _, v := range g.views {
|
2014-01-16 23:01:53 +01:00
|
|
|
if v.name == name {
|
2015-01-25 14:07:14 +01:00
|
|
|
return v, nil
|
2014-01-04 02:50:49 +01:00
|
|
|
}
|
|
|
|
}
|
2016-01-23 16:07:42 +01:00
|
|
|
return nil, ErrUnknownView
|
2015-01-25 14:07:14 +01:00
|
|
|
}
|
|
|
|
|
2016-01-23 16:07:42 +01:00
|
|
|
// 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.
|
2015-01-25 14:07:14 +01:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2016-01-23 16:07:42 +01:00
|
|
|
return 0, 0, 0, 0, ErrUnknownView
|
2014-01-04 02:50:49 +01:00
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// DeleteView deletes a view by name.
|
2014-01-16 00:28:16 +01:00
|
|
|
func (g *Gui) DeleteView(name string) error {
|
2014-01-04 03:40:45 +01:00
|
|
|
for i, v := range g.views {
|
2014-01-16 23:01:53 +01:00
|
|
|
if v.name == name {
|
2014-01-04 03:40:45 +01:00
|
|
|
g.views = append(g.views[:i], g.views[i+1:]...)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
2016-01-23 16:07:42 +01:00
|
|
|
return ErrUnknownView
|
2014-01-06 01:18:00 +01:00
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// SetCurrentView gives the focus to a given view.
|
2014-01-16 00:28:16 +01:00
|
|
|
func (g *Gui) SetCurrentView(name string) error {
|
2014-01-06 01:47:22 +01:00
|
|
|
for _, v := range g.views {
|
2014-01-16 23:01:53 +01:00
|
|
|
if v.name == name {
|
2014-01-10 20:46:43 +01:00
|
|
|
g.currentView = v
|
2014-01-06 01:47:22 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
2016-01-23 16:07:42 +01:00
|
|
|
return ErrUnknownView
|
2014-01-06 01:47:22 +01:00
|
|
|
}
|
|
|
|
|
2014-01-27 22:50:02 +01:00
|
|
|
// CurrentView returns the currently focused view, or nil if no view
|
2014-01-27 22:40:46 +01:00
|
|
|
// owns the focus.
|
|
|
|
func (g *Gui) CurrentView() *View {
|
|
|
|
return g.currentView
|
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// 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 {
|
2014-01-11 20:29:16 +01:00
|
|
|
var kb *keybinding
|
2014-01-06 01:18:00 +01:00
|
|
|
|
|
|
|
switch k := key.(type) {
|
|
|
|
case Key:
|
2014-01-19 17:03:52 +01:00
|
|
|
kb = newKeybinding(viewname, k, 0, mod, h)
|
2014-01-06 01:18:00 +01:00
|
|
|
case rune:
|
2014-01-19 17:03:52 +01:00
|
|
|
kb = newKeybinding(viewname, 0, k, mod, h)
|
2014-01-06 01:18:00 +01:00
|
|
|
default:
|
|
|
|
return errors.New("unknown type")
|
|
|
|
}
|
|
|
|
g.keybindings = append(g.keybindings, kb)
|
|
|
|
return nil
|
2014-01-04 03:40:45 +01:00
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// SetLayout sets the current layout. A layout is a function that
|
|
|
|
// will be called everytime the gui is re-drawed, it must contain
|
|
|
|
// the base views and its initializations.
|
2014-01-10 20:21:54 +01:00
|
|
|
func (g *Gui) SetLayout(layout func(*Gui) error) {
|
|
|
|
g.layout = layout
|
2014-01-10 20:46:43 +01:00
|
|
|
g.currentView = nil
|
|
|
|
g.views = nil
|
2014-01-10 20:21:54 +01:00
|
|
|
go func() { g.events <- termbox.Event{Type: termbox.EventResize} }()
|
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// MainLoop runs the main loop until an error is returned. A successful
|
2016-01-23 16:07:42 +01:00
|
|
|
// finish should return ErrQuit.
|
2014-01-16 00:28:16 +01:00
|
|
|
func (g *Gui) MainLoop() error {
|
2013-12-27 21:36:26 +01:00
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
g.events <- termbox.PollEvent()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2016-01-23 16:07:42 +01:00
|
|
|
inputMode := termbox.InputAlt
|
2015-12-11 11:51:43 +00:00
|
|
|
if g.EnableMouse == true {
|
2016-01-23 16:07:42 +01:00
|
|
|
inputMode |= termbox.InputMouse
|
2015-12-11 11:51:43 +00:00
|
|
|
}
|
2016-01-23 16:07:42 +01:00
|
|
|
termbox.SetInputMode(inputMode)
|
2015-12-11 11:51:43 +00:00
|
|
|
|
2014-01-18 13:47:08 +01:00
|
|
|
if err := g.Flush(); err != nil {
|
2013-12-27 21:36:26 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
for {
|
2014-01-10 12:38:08 +01:00
|
|
|
ev := <-g.events
|
|
|
|
if err := g.handleEvent(&ev); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := g.consumeevents(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-01-18 13:47:08 +01:00
|
|
|
if err := g.Flush(); err != nil {
|
2014-01-10 12:38:08 +01:00
|
|
|
return err
|
2013-12-27 21:36:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// consumeevents handles the remaining events in the events pool.
|
2014-01-16 00:28:16 +01:00
|
|
|
func (g *Gui) consumeevents() error {
|
2013-12-27 21:36:26 +01:00
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case ev := <-g.events:
|
|
|
|
if err := g.handleEvent(&ev); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// handleEvent handles an event, based on its type (key-press, error,
|
|
|
|
// etc.)
|
2014-01-16 00:28:16 +01:00
|
|
|
func (g *Gui) handleEvent(ev *termbox.Event) error {
|
2013-12-27 21:36:26 +01:00
|
|
|
switch ev.Type {
|
2015-12-11 11:51:43 +00:00
|
|
|
case termbox.EventKey, termbox.EventMouse:
|
2013-12-27 21:36:26 +01:00
|
|
|
return g.onKey(ev)
|
|
|
|
case termbox.EventError:
|
|
|
|
return ev.Err
|
|
|
|
default:
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-02 01:04:33 +01:00
|
|
|
// Flush updates the gui, re-drawing frames and buffers.
|
|
|
|
//
|
|
|
|
// Flush is safe for concurrent use by multiple goroutines. However it is
|
|
|
|
// important to note that it will make the layout function to be called, which
|
|
|
|
// could lead to a dead lock if the same mutex is used in both the function
|
|
|
|
// calling Flush and the layout function.
|
2014-01-18 13:47:08 +01:00
|
|
|
func (g *Gui) Flush() error {
|
2015-02-02 00:42:34 +01:00
|
|
|
g.mu.Lock()
|
|
|
|
defer g.mu.Unlock()
|
|
|
|
|
2014-01-10 20:21:54 +01:00
|
|
|
if g.layout == nil {
|
2014-01-04 02:50:49 +01:00
|
|
|
return errors.New("Null layout")
|
|
|
|
}
|
|
|
|
|
2014-01-10 12:38:08 +01:00
|
|
|
termbox.Clear(termbox.Attribute(g.FgColor), termbox.Attribute(g.BgColor))
|
2015-02-03 17:09:21 +01:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2014-01-10 20:21:54 +01:00
|
|
|
if err := g.layout(g); err != nil {
|
2013-12-29 21:29:29 +01:00
|
|
|
return err
|
|
|
|
}
|
2014-01-18 13:47:08 +01:00
|
|
|
for _, v := range g.views {
|
2014-10-18 15:47:24 +02:00
|
|
|
if v.Frame {
|
2014-10-17 17:22:28 -04:00
|
|
|
if err := g.drawFrame(v); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-01-18 13:47:08 +01:00
|
|
|
}
|
2014-10-17 17:22:28 -04:00
|
|
|
|
2014-01-18 13:47:08 +01:00
|
|
|
if err := g.draw(v); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-12-29 21:29:29 +01:00
|
|
|
}
|
2013-12-30 14:19:22 +01:00
|
|
|
if err := g.drawIntersections(); err != nil {
|
2013-12-29 21:29:29 +01:00
|
|
|
return err
|
|
|
|
}
|
2014-01-18 13:47:08 +01:00
|
|
|
termbox.Flush()
|
2013-12-29 21:29:29 +01:00
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// drawFrame draws the horizontal and vertical edges of a view.
|
2014-01-18 13:47:08 +01:00
|
|
|
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
|
2013-12-29 21:29:29 +01:00
|
|
|
}
|
|
|
|
}
|
2014-01-18 13:47:08 +01:00
|
|
|
if v.y1 > -1 && v.y1 < g.maxY {
|
|
|
|
if err := g.SetRune(x, v.y1, '─'); err != nil {
|
|
|
|
return err
|
2013-12-29 21:29:29 +01:00
|
|
|
}
|
2014-01-18 13:47:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
2014-01-02 18:34:58 +01:00
|
|
|
}
|
2014-01-18 13:47:08 +01:00
|
|
|
}
|
|
|
|
if v.x1 > -1 && v.x1 < g.maxX {
|
|
|
|
if err := g.SetRune(v.x1, y, '│'); err != nil {
|
|
|
|
return err
|
2013-12-29 21:29:29 +01:00
|
|
|
}
|
2013-12-27 21:36:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2013-12-29 21:29:29 +01:00
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// draw manages the cursor and calls the draw function of a view.
|
|
|
|
func (g *Gui) draw(v *View) error {
|
|
|
|
if g.ShowCursor {
|
|
|
|
if v := g.currentView; v != nil {
|
|
|
|
maxX, maxY := v.Size()
|
2014-01-20 23:03:28 +01:00
|
|
|
cx, cy := v.cx, v.cy
|
2014-01-19 17:03:52 +01:00
|
|
|
if v.cx >= maxX {
|
|
|
|
cx = maxX - 1
|
|
|
|
}
|
|
|
|
if v.cy >= maxY {
|
|
|
|
cy = maxY - 1
|
|
|
|
}
|
2015-02-16 23:39:37 +01:00
|
|
|
v.cx, v.cy = cx, cy
|
2014-01-19 17:03:52 +01:00
|
|
|
termbox.SetCursor(v.x0+v.cx+1, v.y0+v.cy+1)
|
|
|
|
}
|
|
|
|
} 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.
|
2014-01-16 00:28:16 +01:00
|
|
|
func (g *Gui) drawIntersections() error {
|
2013-12-29 21:29:29 +01:00
|
|
|
for _, v := range g.views {
|
2014-01-16 23:01:53 +01:00
|
|
|
if ch, ok := g.intersectionRune(v.x0, v.y0); ok {
|
|
|
|
if err := g.SetRune(v.x0, v.y0, ch); err != nil {
|
2014-01-02 18:34:58 +01:00
|
|
|
return err
|
|
|
|
}
|
2013-12-29 21:29:29 +01:00
|
|
|
}
|
2014-01-16 23:01:53 +01:00
|
|
|
if ch, ok := g.intersectionRune(v.x0, v.y1); ok {
|
|
|
|
if err := g.SetRune(v.x0, v.y1, ch); err != nil {
|
2014-01-02 18:34:58 +01:00
|
|
|
return err
|
|
|
|
}
|
2013-12-29 21:29:29 +01:00
|
|
|
}
|
2014-01-16 23:01:53 +01:00
|
|
|
if ch, ok := g.intersectionRune(v.x1, v.y0); ok {
|
|
|
|
if err := g.SetRune(v.x1, v.y0, ch); err != nil {
|
2014-01-02 18:34:58 +01:00
|
|
|
return err
|
|
|
|
}
|
2013-12-29 21:29:29 +01:00
|
|
|
}
|
2014-01-16 23:01:53 +01:00
|
|
|
if ch, ok := g.intersectionRune(v.x1, v.y1); ok {
|
|
|
|
if err := g.SetRune(v.x1, v.y1, ch); err != nil {
|
2014-01-02 18:34:58 +01:00
|
|
|
return err
|
|
|
|
}
|
2013-12-29 21:29:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// intersectionRune returns the correct intersection rune at a given
|
|
|
|
// point.
|
2014-01-16 23:01:53 +01:00
|
|
|
func (g *Gui) intersectionRune(x, y int) (rune, bool) {
|
2013-12-31 21:23:28 +01:00
|
|
|
if x < 0 || y < 0 || x >= g.maxX || y >= g.maxY {
|
2014-01-20 23:28:08 +01:00
|
|
|
return ' ', false
|
2013-12-30 14:19:22 +01:00
|
|
|
}
|
|
|
|
|
2014-01-16 23:01:53 +01:00
|
|
|
chTop, _ := g.Rune(x, y-1)
|
2013-12-31 01:37:34 +01:00
|
|
|
top := verticalRune(chTop)
|
2014-01-16 23:01:53 +01:00
|
|
|
chBottom, _ := g.Rune(x, y+1)
|
2013-12-31 01:37:34 +01:00
|
|
|
bottom := verticalRune(chBottom)
|
2014-01-16 23:01:53 +01:00
|
|
|
chLeft, _ := g.Rune(x-1, y)
|
2013-12-31 01:37:34 +01:00
|
|
|
left := horizontalRune(chLeft)
|
2014-01-16 23:01:53 +01:00
|
|
|
chRight, _ := g.Rune(x+1, y)
|
2013-12-31 01:37:34 +01:00
|
|
|
right := horizontalRune(chRight)
|
2013-12-30 14:19:22 +01:00
|
|
|
|
2014-01-16 00:28:16 +01:00
|
|
|
var ch rune
|
2013-12-30 14:19:22 +01:00
|
|
|
switch {
|
2013-12-31 01:37:34 +01:00
|
|
|
case !top && bottom && !left && right:
|
2013-12-30 14:45:02 +01:00
|
|
|
ch = '┌'
|
2013-12-31 01:37:34 +01:00
|
|
|
case !top && bottom && left && !right:
|
2013-12-30 14:45:02 +01:00
|
|
|
ch = '┐'
|
2013-12-31 01:37:34 +01:00
|
|
|
case top && !bottom && !left && right:
|
2013-12-30 14:45:02 +01:00
|
|
|
ch = '└'
|
2013-12-31 01:37:34 +01:00
|
|
|
case top && !bottom && left && !right:
|
2013-12-30 14:45:02 +01:00
|
|
|
ch = '┘'
|
2013-12-31 01:37:34 +01:00
|
|
|
case top && bottom && left && right:
|
2013-12-30 14:45:02 +01:00
|
|
|
ch = '┼'
|
2013-12-31 01:37:34 +01:00
|
|
|
case top && bottom && !left && right:
|
2013-12-30 14:45:02 +01:00
|
|
|
ch = '├'
|
2013-12-31 01:37:34 +01:00
|
|
|
case top && bottom && left && !right:
|
2013-12-30 14:45:02 +01:00
|
|
|
ch = '┤'
|
2013-12-31 01:37:34 +01:00
|
|
|
case !top && bottom && left && right:
|
2013-12-30 14:45:02 +01:00
|
|
|
ch = '┬'
|
2013-12-31 01:37:34 +01:00
|
|
|
case top && !bottom && left && right:
|
2013-12-30 14:45:02 +01:00
|
|
|
ch = '┴'
|
2013-12-30 14:19:22 +01:00
|
|
|
default:
|
2014-01-20 23:28:08 +01:00
|
|
|
return ' ', false
|
2013-12-30 14:19:22 +01:00
|
|
|
}
|
|
|
|
return ch, true
|
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// verticalRune returns if the given character is a vertical rune.
|
2013-12-30 14:37:27 +01:00
|
|
|
func verticalRune(ch rune) bool {
|
2013-12-30 14:45:02 +01:00
|
|
|
if ch == '│' || ch == '┼' || ch == '├' || ch == '┤' {
|
2013-12-30 14:37:27 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// verticalRune returns if the given character is a horizontal rune.
|
2013-12-30 14:37:27 +01:00
|
|
|
func horizontalRune(ch rune) bool {
|
2013-12-30 14:45:02 +01:00
|
|
|
if ch == '─' || ch == '┼' || ch == '┬' || ch == '┴' {
|
2013-12-30 14:37:27 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2014-01-19 17:03:52 +01:00
|
|
|
// onKey manages key-press events. A keybinding handler is called when
|
2016-01-23 16:07:42 +01:00
|
|
|
// a key-press or mouse event satisfies a configured keybinding. Furthermore,
|
2014-01-20 23:10:16 +01:00
|
|
|
// currentView's internal buffer is modified if currentView.Editable is true.
|
2014-01-16 00:28:16 +01:00
|
|
|
func (g *Gui) onKey(ev *termbox.Event) error {
|
2016-01-23 16:07:42 +01:00
|
|
|
var curView *View
|
2015-02-13 21:02:56 +01:00
|
|
|
|
2016-01-23 16:07:42 +01:00
|
|
|
switch ev.Type {
|
|
|
|
case termbox.EventKey:
|
|
|
|
if g.currentView != nil && g.currentView.Editable && Edit != nil {
|
|
|
|
Edit(g.currentView, Key(ev.Key), ev.Ch, Modifier(ev.Mod))
|
|
|
|
}
|
|
|
|
curView = g.currentView
|
|
|
|
case termbox.EventMouse:
|
|
|
|
if v, err := g.ViewByPosition(ev.MouseX, ev.MouseY); err == nil {
|
|
|
|
curView = v
|
|
|
|
}
|
2015-02-13 21:02:56 +01:00
|
|
|
}
|
2016-01-23 16:07:42 +01:00
|
|
|
|
2014-01-06 01:18:00 +01:00
|
|
|
for _, kb := range g.keybindings {
|
2015-02-13 21:02:56 +01:00
|
|
|
if kb.h == nil {
|
|
|
|
continue
|
|
|
|
}
|
2016-01-23 16:07:42 +01:00
|
|
|
if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(curView) {
|
|
|
|
if err := kb.h(g, curView); err != nil {
|
2015-02-13 21:02:56 +01:00
|
|
|
return err
|
|
|
|
}
|
2014-01-06 01:18:00 +01:00
|
|
|
}
|
2013-12-27 21:36:26 +01:00
|
|
|
}
|
2014-01-06 01:18:00 +01:00
|
|
|
return nil
|
2013-12-27 21:36:26 +01:00
|
|
|
}
|