application.go
3585 bytes
1package tui
2
3import (
4 "fmt"
5 "math"
6 "strings"
7
8 "github.com/gdamore/tcell/v2"
9 "github.com/rivo/tview"
10
11 "git.theedgeofrage.com/TheEdgeOfRage/rpn/rpn"
12)
13
14const calculatorPage = "calculator"
15
16const helpPage = "help"
17
18type Calculator interface {
19 Eval(input string) (rpn.Event, error)
20 Values() []float64
21}
22
23type Application struct {
24 application *tview.Application
25 calculator Calculator
26 pages *tview.Pages
27 layout *tview.Flex
28 stack *tview.TextView
29 status *tview.TextView
30 input *tview.InputField
31}
32
33func New(calculator Calculator) *Application {
34 stack := tview.NewTextView().SetWrap(false)
35 stack.SetBorder(true).SetTitle("Stack")
36
37 app := &Application{
38 application: tview.NewApplication(),
39 calculator: calculator,
40 stack: stack,
41 status: tview.NewTextView().
42 SetWrap(false),
43 input: tview.NewInputField().
44 SetLabel("> ").
45 SetFieldBackgroundColor(tcell.ColorBlack).
46 SetFieldTextColor(tcell.ColorWhite),
47 }
48
49 app.input.SetDoneFunc(app.submit)
50
51 app.layout = tview.NewFlex().
52 SetDirection(tview.FlexRow).
53 AddItem(nil, 0, 1, false).
54 AddItem(app.stack, 0, 1, false).
55 AddItem(app.status, 0, 0, false).
56 AddItem(app.input, 1, 0, true)
57
58 app.pages = tview.NewPages().AddPage(calculatorPage, app.layout, true, true)
59 app.application.
60 SetRoot(app.pages, true).
61 SetFocus(app.input).
62 SetInputCapture(app.captureInput).
63 SetBeforeDrawFunc(app.resizeStack)
64 app.renderStack()
65
66 return app
67}
68
69func (a *Application) Run() error {
70 return a.application.Run()
71}
72
73func (a *Application) captureInput(event *tcell.EventKey) *tcell.EventKey {
74 if event.Key() == tcell.KeyCtrlD {
75 a.application.Stop()
76 return nil
77 }
78 return event
79}
80
81func (a *Application) submit(key tcell.Key) {
82 if key != tcell.KeyEnter {
83 return
84 }
85
86 input := strings.TrimSpace(a.input.GetText())
87 a.input.SetText("")
88 if input == "" {
89 return
90 }
91
92 event, err := a.calculator.Eval(input)
93 a.renderStack()
94 if err != nil {
95 a.status.SetText(err.Error())
96 return
97 }
98
99 a.status.SetText("")
100 switch event {
101 case rpn.EventHelp:
102 a.showHelp()
103 case rpn.EventExit:
104 a.application.Stop()
105 }
106}
107
108func (a *Application) renderStack() {
109 values := a.calculator.Values()
110 lines := make([]string, len(values))
111 for i, value := range values {
112 lines[i] = formatValue(value)
113 }
114 a.stack.SetText(strings.Join(lines, "\n")).ScrollToEnd()
115}
116
117func (a *Application) resizeStack(screen tcell.Screen) bool {
118 _, height := screen.Size()
119 statusHeight := 0
120 if a.status.GetText(true) != "" {
121 statusHeight = 1
122 }
123 a.layout.ResizeItem(a.status, statusHeight, 0)
124
125 stackHeight := len(a.calculator.Values()) + 2
126 maxStackHeight := height - statusHeight - 1
127 if stackHeight > maxStackHeight {
128 stackHeight = maxStackHeight
129 }
130 if stackHeight < 0 {
131 stackHeight = 0
132 }
133 a.layout.ResizeItem(a.stack, stackHeight, 0)
134 return false
135}
136
137func (a *Application) showHelp() {
138 help := tview.NewModal().
139 SetText(rpn.HelpText).
140 SetBackgroundColor(tcell.ColorBlack).
141 SetTextColor(tcell.ColorWhite).
142 SetButtonBackgroundColor(tcell.ColorBlack).
143 SetButtonTextColor(tcell.ColorWhite).
144 SetButtonActivatedStyle(tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite)).
145 AddButtons([]string{"Close"}).
146 SetDoneFunc(func(buttonIndex int, buttonLabel string) {
147 a.pages.RemovePage(helpPage)
148 a.application.SetFocus(a.input)
149 })
150 a.pages.AddPage(helpPage, help, true, true)
151 a.application.SetFocus(help)
152}
153
154func formatValue(value float64) string {
155 if value == math.Trunc(value) {
156 return fmt.Sprintf("%d", int64(value))
157 }
158 return fmt.Sprintf("%f", value)
159}