mirror of
https://github.com/jroimartin/gocui.git
synced 2025-04-26 13:48:49 +08:00
345 lines
7.8 KiB
Go
345 lines
7.8 KiB
Go
package gocui
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/jroimartin/termbox-go"
|
|
)
|
|
|
|
var ErrorQuit = errors.New("quit")
|
|
|
|
const (
|
|
RuneCornerTopLeft = '┌'
|
|
RuneCornerTopRight = '┐'
|
|
RuneCornerBottomLeft = '└'
|
|
RuneCornerBottomRight = '┘'
|
|
RuneSideVertical = '│'
|
|
RuneSideHorizontal = '─'
|
|
RuneIntersection = '┼'
|
|
RuneIntersectionLeft = '├'
|
|
RuneIntersectionRight = '┤'
|
|
RuneIntersectionTop = '┬'
|
|
RuneIntersectionBottom = '┴'
|
|
RuneTriangleLeft = '◄'
|
|
RuneTriangleRight = '►'
|
|
RuneArrowLeft = '←'
|
|
RuneArrowRight = '→'
|
|
)
|
|
|
|
type Gui struct {
|
|
events chan termbox.Event
|
|
views []*View
|
|
currentView *View
|
|
BgColor, FgColor termbox.Attribute
|
|
}
|
|
|
|
func NewGui() (g *Gui) {
|
|
return &Gui{}
|
|
}
|
|
|
|
func (g *Gui) Init() (err error) {
|
|
g.events = make(chan termbox.Event, 20)
|
|
g.BgColor = termbox.ColorWhite
|
|
g.FgColor = termbox.ColorBlack
|
|
return termbox.Init()
|
|
}
|
|
|
|
func (g *Gui) Close() {
|
|
termbox.Close()
|
|
}
|
|
|
|
func (g *Gui) Size() (x, y int) {
|
|
return termbox.Size()
|
|
}
|
|
|
|
func (g *Gui) AddView(name string, x0, y0, x1, y1 int) (v *View, err error) {
|
|
maxX, maxY := termbox.Size()
|
|
|
|
if x0 < -1 || y0 < -1 || x1 < -1 || y1 < -1 ||
|
|
x0 > maxX || y0 > maxY || x1 > maxX || y1 > maxY ||
|
|
x0 >= x1 || y0 >= y1 {
|
|
return nil, errors.New("invalid points")
|
|
}
|
|
|
|
for _, v := range g.views {
|
|
if name == v.Name {
|
|
return nil, errors.New("invalid name")
|
|
}
|
|
}
|
|
|
|
v = NewView(name, x0, y0, x1, y1)
|
|
g.views = append(g.views, v)
|
|
return v, nil
|
|
}
|
|
|
|
func (g *Gui) MainLoop() (err error) {
|
|
go func() {
|
|
for {
|
|
g.events <- termbox.PollEvent()
|
|
}
|
|
}()
|
|
|
|
if err := g.resize(); err != nil {
|
|
return err
|
|
}
|
|
if err := g.draw(); err != nil {
|
|
return err
|
|
}
|
|
// TODO: Set initial cursor position
|
|
//termbox.SetCursor(10, 10)
|
|
termbox.Flush()
|
|
|
|
for {
|
|
select {
|
|
case 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
|
|
}
|
|
termbox.Flush()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *Gui) SetCell(x, y int, ch rune) {
|
|
termbox.SetCell(x, y, ch, g.FgColor, g.BgColor)
|
|
}
|
|
|
|
func (g *Gui) GetCell(x, y int) (ch rune, err error) {
|
|
maxX, maxY := termbox.Size()
|
|
if x < 0 || y < 0 || x >= maxX || y >= maxY {
|
|
return 0, errors.New("invalid point")
|
|
}
|
|
c := termbox.CellBuffer()[y*maxX+x]
|
|
return c.Ch, nil
|
|
}
|
|
|
|
func (g *Gui) consumeevents() (err error) {
|
|
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) (err error) {
|
|
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() (err error) {
|
|
for _, v := range g.views {
|
|
if err := g.drawView(v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *Gui) drawView(v *View) (err error) {
|
|
return nil
|
|
}
|
|
|
|
func (g *Gui) resize() (err error) {
|
|
termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
|
|
if err := g.resizeView(); err != nil {
|
|
return err
|
|
}
|
|
if err := g.drawFrames(); err != nil {
|
|
return err
|
|
}
|
|
if err := g.drawFrameIntersections(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
|
|
}
|
|
|
|
func (g *Gui) drawFrames() (err error) {
|
|
maxX, maxY := termbox.Size()
|
|
for _, v := range g.views {
|
|
if v.Y0 != -1 {
|
|
if v.X0 != -1 {
|
|
g.SetCell(v.X0, v.Y0, RuneCornerTopLeft)
|
|
}
|
|
if v.X1 != maxX {
|
|
g.SetCell(v.X1, v.Y0, RuneCornerTopRight)
|
|
}
|
|
}
|
|
if v.Y1 != maxY {
|
|
if v.X0 != -1 {
|
|
g.SetCell(v.X0, v.Y1, RuneCornerBottomLeft)
|
|
}
|
|
if v.X1 != maxX {
|
|
g.SetCell(v.X1, v.Y1, RuneCornerBottomRight)
|
|
}
|
|
}
|
|
for x := v.X0 + 1; x < v.X1; x++ {
|
|
if v.Y0 != -1 {
|
|
g.SetCell(x, v.Y0, RuneSideHorizontal)
|
|
}
|
|
if v.Y1 != maxY {
|
|
g.SetCell(x, v.Y1, RuneSideHorizontal)
|
|
}
|
|
}
|
|
for y := v.Y0 + 1; y < v.Y1; y++ {
|
|
if v.X0 != -1 {
|
|
g.SetCell(v.X0, y, RuneSideVertical)
|
|
}
|
|
if v.X1 != maxX {
|
|
g.SetCell(v.X1, y, RuneSideVertical)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *Gui) drawFrameIntersections() (err error) {
|
|
for _, v := range g.views {
|
|
// ┌
|
|
if ch, err := g.GetCell(v.X0, v.Y0); err == nil {
|
|
switch ch {
|
|
case RuneCornerTopLeft: // '┌'
|
|
// Nothing
|
|
case RuneCornerTopRight: // '┐'
|
|
g.SetCell(v.X0, v.Y0, RuneIntersectionTop)
|
|
case RuneCornerBottomLeft: // '└'
|
|
g.SetCell(v.X0, v.Y0, RuneIntersectionLeft)
|
|
case RuneCornerBottomRight: // '┘'
|
|
g.SetCell(v.X0, v.Y0, RuneIntersection)
|
|
case RuneSideVertical: // '│'
|
|
g.SetCell(v.X0, v.Y0, RuneIntersectionLeft)
|
|
case RuneSideHorizontal: // '─'
|
|
g.SetCell(v.X0, v.Y0, RuneIntersectionTop)
|
|
case RuneIntersection: // '┼'
|
|
// Nothing
|
|
case RuneIntersectionLeft: // '├'
|
|
// Nothing
|
|
case RuneIntersectionRight: // '┤'
|
|
g.SetCell(v.X0, v.Y0, RuneIntersection)
|
|
case RuneIntersectionTop: // '┬'
|
|
// Nothing
|
|
case RuneIntersectionBottom: // '┴'
|
|
g.SetCell(v.X0, v.Y0, RuneIntersection)
|
|
}
|
|
}
|
|
|
|
// ┐
|
|
if ch, err := g.GetCell(v.X1, v.Y0); err == nil {
|
|
switch ch {
|
|
case RuneCornerTopLeft: // '┌'
|
|
g.SetCell(v.X1, v.Y0, RuneIntersectionTop)
|
|
case RuneCornerTopRight: // '┐'
|
|
// Nothing
|
|
case RuneCornerBottomLeft: // '└'
|
|
g.SetCell(v.X1, v.Y0, RuneIntersection)
|
|
case RuneCornerBottomRight: // '┘'
|
|
g.SetCell(v.X1, v.Y0, RuneIntersectionRight)
|
|
case RuneSideVertical: // '│'
|
|
g.SetCell(v.X1, v.Y0, RuneIntersectionRight)
|
|
case RuneSideHorizontal: // '─'
|
|
g.SetCell(v.X1, v.Y0, RuneIntersectionTop)
|
|
case RuneIntersection: // '┼'
|
|
// Nothing
|
|
case RuneIntersectionLeft: // '├'
|
|
g.SetCell(v.X1, v.Y0, RuneIntersection)
|
|
case RuneIntersectionRight: // '┤'
|
|
// Nothing
|
|
case RuneIntersectionTop: // '┬'
|
|
// Nothing
|
|
case RuneIntersectionBottom: // '┴'
|
|
g.SetCell(v.X1, v.Y0, RuneIntersection)
|
|
}
|
|
}
|
|
|
|
// └
|
|
if ch, err := g.GetCell(v.X0, v.Y1); err == nil {
|
|
switch ch {
|
|
case RuneCornerTopLeft: // '┌'
|
|
g.SetCell(v.X0, v.Y1, RuneIntersectionLeft)
|
|
case RuneCornerTopRight: // '┐'
|
|
g.SetCell(v.X0, v.Y1, RuneIntersection)
|
|
case RuneCornerBottomLeft: // '└'
|
|
// Nothing
|
|
case RuneCornerBottomRight: // '┘'
|
|
g.SetCell(v.X0, v.Y1, RuneIntersectionBottom)
|
|
case RuneSideVertical: // '│'
|
|
g.SetCell(v.X0, v.Y1, RuneIntersectionLeft)
|
|
case RuneSideHorizontal: // '─'
|
|
g.SetCell(v.X0, v.Y1, RuneIntersectionBottom)
|
|
case RuneIntersection: // '┼'
|
|
// Nothing
|
|
case RuneIntersectionLeft: // '├'
|
|
// Nothing
|
|
case RuneIntersectionRight: // '┤'
|
|
g.SetCell(v.X0, v.Y1, RuneIntersection)
|
|
case RuneIntersectionTop: // '┬'
|
|
g.SetCell(v.X0, v.Y1, RuneIntersection)
|
|
case RuneIntersectionBottom: // '┴'
|
|
// Nothing
|
|
}
|
|
}
|
|
|
|
// ┘
|
|
if ch, err := g.GetCell(v.X1, v.Y1); err == nil {
|
|
switch ch {
|
|
case RuneCornerTopLeft: // '┌'
|
|
g.SetCell(v.X1, v.Y1, RuneIntersection)
|
|
case RuneCornerTopRight: // '┐'
|
|
g.SetCell(v.X1, v.Y1, RuneIntersectionRight)
|
|
case RuneCornerBottomLeft: // '└'
|
|
g.SetCell(v.X1, v.Y1, RuneIntersectionBottom)
|
|
case RuneCornerBottomRight: // '┘'
|
|
// Nothing
|
|
case RuneSideVertical: // '│'
|
|
g.SetCell(v.X1, v.Y1, RuneIntersectionRight)
|
|
case RuneSideHorizontal: // '─'
|
|
g.SetCell(v.X1, v.Y1, RuneIntersectionBottom)
|
|
case RuneIntersection: // '┼'
|
|
// Nothing
|
|
case RuneIntersectionLeft: // '├'
|
|
g.SetCell(v.X1, v.Y1, RuneIntersection)
|
|
case RuneIntersectionRight: // '┤'
|
|
// Nothing
|
|
case RuneIntersectionTop: // '┬'
|
|
g.SetCell(v.X1, v.Y1, RuneIntersection)
|
|
case RuneIntersectionBottom: // '┴'
|
|
// Nothing
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *Gui) resizeView() (err error) {
|
|
return nil
|
|
}
|
|
|
|
func (g *Gui) onKey(ev *termbox.Event) (err error) {
|
|
switch ev.Key {
|
|
case termbox.KeyCtrlC:
|
|
return ErrorQuit
|
|
default:
|
|
return nil
|
|
}
|
|
}
|