gocui/gui.go

378 lines
7.4 KiB
Go
Raw Normal View History

2014-01-14 20:11:12 +01:00
// 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.
2013-12-27 21:36:26 +01:00
package gocui
import (
"errors"
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
)
var (
ErrorQuit error = errors.New("quit")
2014-01-10 20:25:37 +01:00
ErrorUnkView error = errors.New("unknown view")
)
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
2014-01-10 12:38:08 +01:00
BgColor, FgColor Attribute
SelBgColor, SelFgColor Attribute
ShowCursor bool
2013-12-27 21:36:26 +01:00
}
func NewGui() *Gui {
2013-12-27 21:36:26 +01:00
return &Gui{}
}
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
}
func (g *Gui) Close() {
termbox.Close()
}
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
}
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-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-04 02:50:49 +01:00
return 0, errors.New("invalid point")
2014-01-02 18:34:58 +01:00
}
c := termbox.CellBuffer()[y*g.maxX+x]
return c.Ch, nil
}
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-16 23:01:53 +01:00
if v := g.View(name); v != nil {
v.x0 = x0
v.y0 = y0
v.x1 = x1
v.y1 = y1
2014-01-04 02:50:49 +01:00
return v, nil
}
v := newView(name, x0, y0, x1, y1)
2014-01-10 12:38:08 +01: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)
return v, ErrorUnkView
2013-12-27 21:36:26 +01:00
}
2014-01-16 23:01:53 +01:00
func (g *Gui) View(name string) *View {
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 {
2014-01-04 02:50:49 +01:00
return v
}
}
return nil
}
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
}
}
2014-01-10 20:25:37 +01:00
return ErrorUnkView
}
func (g *Gui) SetCurrentView(name string) error {
for _, v := range g.views {
2014-01-16 23:01:53 +01:00
if v.name == name {
g.currentView = v
return nil
}
}
2014-01-10 20:25:37 +01:00
return ErrorUnkView
}
func (g *Gui) SetKeybinding(viewname string, key interface{}, mod Modifier, cb KeybindingCB) error {
2014-01-11 20:29:16 +01:00
var kb *keybinding
switch k := key.(type) {
case Key:
2014-01-11 20:29:16 +01:00
kb = newKeybinding(viewname, k, 0, mod, cb)
case rune:
2014-01-11 20:29:16 +01:00
kb = newKeybinding(viewname, 0, k, mod, cb)
default:
return errors.New("unknown type")
}
g.keybindings = append(g.keybindings, kb)
return nil
2014-01-04 03:40:45 +01:00
}
func (g *Gui) SetLayout(layout func(*Gui) error) {
g.layout = layout
g.currentView = nil
g.views = nil
go func() { g.events <- termbox.Event{Type: termbox.EventResize} }()
}
func (g *Gui) MainLoop() error {
2013-12-27 21:36:26 +01:00
go func() {
for {
g.events <- termbox.PollEvent()
}
}()
termbox.SetInputMode(termbox.InputAlt)
2013-12-27 21:36:26 +01:00
if err := g.resize(); err != nil {
return err
}
if err := g.draw(); err != nil {
return err
}
2013-12-27 21:36:26 +01:00
termbox.Flush()
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
}
if err := g.draw(); err != nil {
return err
2013-12-27 21:36:26 +01:00
}
2014-01-10 12:38:08 +01:00
termbox.Flush()
2013-12-27 21:36:26 +01:00
}
return nil
}
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
}
}
}
func (g *Gui) handleEvent(ev *termbox.Event) error {
2013-12-27 21:36:26 +01:00
switch ev.Type {
case termbox.EventKey:
return g.onKey(ev)
case termbox.EventResize:
return g.resize()
case termbox.EventError:
return ev.Err
default:
return nil
}
}
func (g *Gui) draw() error {
2014-01-06 13:56:28 +01:00
if g.ShowCursor {
if v := g.currentView; v != nil {
maxX, maxY := v.Size()
2014-01-16 23:01:53 +01:00
cx, cy := v.Cursor()
if v.cx >= maxX {
cx = maxX - 1
}
2014-01-16 23:01:53 +01:00
if v.cy >= maxY {
cy = maxY - 1
}
if err := v.SetCursor(cx, cy); err != nil {
return nil
}
2014-01-16 23:01:53 +01:00
termbox.SetCursor(v.x0+v.cx+1, v.y0+v.cy+1)
2014-01-06 13:56:28 +01:00
}
} else {
termbox.HideCursor()
}
2013-12-27 21:36:26 +01:00
for _, v := range g.views {
2014-01-13 20:15:39 +01:00
v.clearRunes()
2014-01-11 20:29:16 +01:00
if err := v.draw(); err != nil {
2013-12-27 21:36:26 +01:00
return err
}
}
return nil
}
2013-12-27 21:36:26 +01:00
func (g *Gui) resize() error {
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))
2014-01-04 02:50:49 +01:00
g.maxX, g.maxY = termbox.Size()
if err := g.layout(g); err != nil {
return err
}
if err := g.drawFrames(); err != nil {
return err
}
2013-12-30 14:19:22 +01:00
if err := g.drawIntersections(); err != nil {
return err
}
return nil
}
func (g *Gui) drawFrames() error {
2013-12-27 21:36:26 +01:00
for _, v := range g.views {
2014-01-16 23:01:53 +01:00
for x := v.x0 + 1; x < v.x1 && x < g.maxX; x++ {
2014-01-02 18:34:58 +01:00
if x < 0 {
continue
}
2014-01-16 23:01:53 +01:00
if v.y0 > -1 && v.y0 < g.maxY {
if err := g.SetRune(x, v.y0, '─'); err != nil {
2014-01-02 18:34:58 +01:00
return err
}
}
2014-01-16 23:01:53 +01:00
if v.y1 > -1 && v.y1 < g.maxY {
if err := g.SetRune(x, v.y1, '─'); err != nil {
2014-01-02 18:34:58 +01:00
return err
}
}
}
2014-01-16 23:01:53 +01:00
for y := v.y0 + 1; y < v.y1 && y < g.maxY; y++ {
2014-01-02 18:34:58 +01:00
if y < 0 {
continue
}
2014-01-16 23:01:53 +01:00
if v.x0 > -1 && v.x0 < g.maxX {
if err := g.SetRune(v.x0, y, '│'); err != nil {
2014-01-02 18:34:58 +01:00
return err
}
}
2014-01-16 23:01:53 +01:00
if v.x1 > -1 && v.x1 < g.maxX {
if err := g.SetRune(v.x1, y, '│'); err != nil {
2014-01-02 18:34:58 +01:00
return err
}
}
2013-12-27 21:36:26 +01:00
}
}
return nil
}
func (g *Gui) drawIntersections() error {
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
}
}
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
}
}
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
}
}
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
}
}
}
return nil
}
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 {
2013-12-30 14:19:22 +01:00
return 0, false
}
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
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:
return 0, false
}
return ch, true
}
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
}
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
}
func (g *Gui) onKey(ev *termbox.Event) error {
for _, kb := range g.keybindings {
if ev.Ch == kb.Ch && Key(ev.Key) == kb.Key && Modifier(ev.Mod) == kb.Mod &&
2014-01-16 23:01:53 +01:00
(kb.ViewName == "" || (g.currentView != nil && kb.ViewName == g.currentView.name)) {
2014-01-11 22:34:56 +01:00
if kb.CB == nil {
return nil
}
return kb.CB(g, g.currentView)
}
2013-12-27 21:36:26 +01:00
}
return nil
2013-12-27 21:36:26 +01:00
}