tinybox

Owner: IIIlllIIIllI URL: git@github.com:nyangkosense/tinybox.git

examples/demo/main.go

package main

import (
	"fmt"
	"log"
	"time"

	tb "github.com/nyangkosense/tinybox/pkg"
)

type model struct {
	tick      int
	message   string
	cursorOn  bool
	mouseOn   bool
	spinnerIx int
	logs      []string
}

func newModel() *model {
	m := &model{message: "Press ? for help", cursorOn: true}
	m.log("demo ready")
	return m
}

func (m *model) log(format string, args ...interface{}) {
	entry := fmt.Sprintf(format, args...)
	m.logs = append(m.logs, entry)
	if len(m.logs) > 6 {
		m.logs = m.logs[len(m.logs)-6:]
	}
}

func main() {
	if err := tb.Init(); err != nil {
		log.Fatal(err)
	}
	defer tb.Close()

	m := newModel()
	tb.SetCursorStyle(tb.CursorUnderline)

	loop(m)
}

func loop(m *model) {
	spinners := []rune{'|', '/', '-', '\\'}

	for {
		draw(m, spinners[m.spinnerIx%len(spinners)])

		evt, err := tb.PollEventTimeout(200 * time.Millisecond)
		if err != nil {
			if err.Error() == "timeout" {
				m.tick++
				m.spinnerIx++
				continue
			}
			m.log("input error: %v", err)
			continue
		}

		switch evt.Type {
		case tb.EventKey:
			if handled := handleKey(m, evt); handled {
				return
			}
		case tb.EventMouse:
			action := "released"
			if evt.Press {
				action = "pressed"
			}
			m.log("mouse %s at %d,%d", action, evt.X, evt.Y)
		case tb.EventResize:
			w, h := tb.Size()
			m.log("resize: %dx%d", w, h)
		case tb.EventPaste:
			m.log("paste event")
		}

		m.tick++
		m.spinnerIx++
	}
}

func handleKey(m *model, evt tb.Event) bool {
	switch evt.Key {
	case tb.KeyCtrlC:
		return true
	case tb.KeyArrowUp:
		m.message = "Arrow up"
	case tb.KeyArrowDown:
		m.message = "Arrow down"
	case tb.KeyArrowLeft:
		m.message = "Arrow left"
	case tb.KeyArrowRight:
		m.message = "Arrow right"
	}

	switch evt.Ch {
	case 'q', 'Q':
		return true
	case 'c', 'C':
		m.cursorOn = !m.cursorOn
		tb.SetCursorVisible(m.cursorOn)
		m.log("cursor %v", boolLabel(m.cursorOn))
	case 'm', 'M':
		m.mouseOn = !m.mouseOn
		if m.mouseOn {
			tb.EnableMouse()
		} else {
			tb.DisableMouse()
		}
		m.log("mouse tracking %v", boolLabel(m.mouseOn))
	case '?':
		m.message = "Keys: Q quit, C cursor, M mouse, P bell, S suspend"
	case 'p', 'P':
		tb.Bell()
		m.log("bell triggered")
	case 's', 'S':
		m.message = "Suspending... resume with fg"
		tb.Present()
		tb.Suspend()
		m.message = "Resumed"
	default:
		if evt.Ch != 0 {
			m.log("key '%c'", evt.Ch)
		}
	}
	return false
}

func draw(m *model, spinner rune) {
	tb.Clear()
	w, h := tb.Size()

	drawHeader(w, spinner)
	drawBody(w, h, m)
	drawFooter(w, h, m)

	tb.SetCursor(2, 2)
	if m.cursorOn {
		tb.SetCursorVisible(true)
	}

	tb.Present()
}

func drawHeader(width int, spinner rune) {
	tb.SetColor(15, 4)
	tb.Fill(0, 0, width, 1, ' ')
	tb.DrawTextLeft(0, fmt.Sprintf(" tinybox demo %c", spinner), 15, 4)
	tb.DrawTextRight(0, time.Now().Format("15:04:05"), 15, 4)
}

func drawBody(width, height int, m *model) {
	boxHeight := height - 4
	if boxHeight < 6 {
		return
	}

	tb.Box(1, 1, width-2, boxHeight)
	tb.SetColor(14, 0)
	tb.PrintAt(3, 2, "Controls")

	tb.SetColor(15, 0)
	items := []string{
		"Q: quit", "C: toggle cursor", "M: toggle mouse tracking",
		"P: bell", "S: suspend", "Arrows: update status",
	}

	for i, item := range items {
		tb.PrintAt(3, 4+i, item)
	}

	logY := 4 + len(items) + 1
	tb.SetColor(14, 0)
	tb.PrintAt(3, logY, "Recent events")

	for i, entry := range m.logs {
		tb.SetColor(10, 0)
		tb.PrintAt(5, logY+2+i, entry)
	}
}

func drawFooter(width, height int, m *model) {
	tb.SetColor(0, 7)
	tb.Fill(0, height-2, width, 2, ' ')
	tb.PrintAt(1, height-2, fmt.Sprintf(" Status: %s", m.message))
	tb.PrintAt(1, height-1, fmt.Sprintf(" Cursor: %s   Mouse: %s   Tick: %d",
		boolLabel(m.cursorOn), boolLabel(m.mouseOn), m.tick))
}

func boolLabel(state bool) string {
	if state {
		return "ON"
	}
	return "OFF"
}