306 lines
7.1 KiB
Go
306 lines
7.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/gdamore/tcell/v2"
|
|
"github.com/rivo/tview"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type mode string
|
|
|
|
const (
|
|
modeNormal mode = "normal"
|
|
modeFocus mode = "focus"
|
|
modeSearch mode = "search"
|
|
modeEx mode = "ex"
|
|
)
|
|
|
|
type UI struct {
|
|
Mode mode
|
|
App *tview.Application
|
|
Fields []field
|
|
Nodes []*node
|
|
Viewport *viewport
|
|
|
|
// UI things
|
|
Pages *tview.Pages
|
|
TopFlex *tview.Flex
|
|
Field *tview.Pages
|
|
FieldBar *tview.Flex
|
|
BottomBar *tview.Pages
|
|
ExInput *tview.InputField
|
|
ExOutput *tview.TextView
|
|
}
|
|
|
|
func (ui *UI) setMode(m mode) {
|
|
ui.Mode = m
|
|
switch ui.Mode {
|
|
case modeNormal:
|
|
ui.App.SetFocus(ui.TopFlex)
|
|
ui.ExInput.SetText("")
|
|
case modeEx:
|
|
ui.App.SetFocus(ui.ExInput)
|
|
}
|
|
}
|
|
|
|
func (ui *UI) handleInput(event *tcell.EventKey) *tcell.EventKey {
|
|
switch ui.Mode {
|
|
case modeNormal:
|
|
switch event.Rune() {
|
|
case ':':
|
|
ui.setMode(modeEx)
|
|
return nil
|
|
case 'h':
|
|
ui.Viewport.X--
|
|
return nil
|
|
case 'j':
|
|
ui.Viewport.Y++
|
|
return nil
|
|
case 'k':
|
|
ui.Viewport.Y--
|
|
return nil
|
|
case 'l':
|
|
ui.Viewport.X++
|
|
return nil
|
|
}
|
|
case modeEx:
|
|
case modeFocus:
|
|
case modeSearch:
|
|
default:
|
|
panic("mode?")
|
|
}
|
|
return event
|
|
}
|
|
|
|
func (ui *UI) handleExInput(key tcell.Key) {
|
|
text := ui.ExInput.GetText()
|
|
ui.setMode(modeNormal)
|
|
|
|
if key != tcell.KeyEnter {
|
|
return
|
|
}
|
|
|
|
switch text {
|
|
case "q", "quit":
|
|
ui.quit()
|
|
}
|
|
|
|
ui.ExOutput.SetText("")
|
|
fmt.Fprintf(ui.ExOutput, "did not understand '%s', sorry", text)
|
|
}
|
|
|
|
/*
|
|
|
|
three coordinate systems:
|
|
|
|
- the infinite coordinate plane where nodes live. it is centered at 0,0 but nodes can be placed anywhere.
|
|
- the viewport representing what part of the infinite plane the user is looking at. the viewport has an origin in the infinite plane and a width and height
|
|
- the screen onto which characters are drawn. always rooted at 0,0; always has a width and a height that matches the width and height of the viewport
|
|
|
|
the viewport is the most pressing question. nodes exist in an XY plane and the field view is a viewport onto that plane.
|
|
|
|
nodes have a "real" X,Y coordinate pair and a width and a height. the viewport has a width, a height, and point A,B from which it originates.
|
|
|
|
Moving the viewport means changing A,B. To see what should be shown on the screen, compare a node's X,Y to A,B. If X+width > A and Y+height > B && X+width < A+width and Y+maxheight < B+maxheight then it should be shown. For each node, it also has coordinates H,J in the screen based on the viewport position.
|
|
|
|
But how to reconcile this with tview? can i add all of the nodes to the scene tree then update their H,J as the viewport changes and hope tview just does the right thing? I think I should start there.
|
|
|
|
*/
|
|
|
|
type viewport struct {
|
|
X int
|
|
Y int
|
|
W int
|
|
H int
|
|
}
|
|
|
|
type field struct {
|
|
Name string
|
|
Selected bool
|
|
}
|
|
|
|
type node struct {
|
|
Text string
|
|
Edges []*node
|
|
X int
|
|
Y int
|
|
widg *tview.TextView
|
|
// "screen" x and y are stored on corresponding tview widget as well as width/height
|
|
}
|
|
|
|
type Edge struct {
|
|
nodes []*node
|
|
}
|
|
|
|
func (e Edge) Draw(screen tcell.Screen) {
|
|
screen.SetContent(0, 0, tview.BoxDrawingsLightDiagonalUpperLeftToLowerRight, []rune{}, tcell.StyleDefault)
|
|
screen.SetContent(1, 1, tview.BoxDrawingsLightDiagonalUpperLeftToLowerRight, []rune{}, tcell.StyleDefault)
|
|
screen.SetContent(2, 2, tview.BoxDrawingsLightDiagonalUpperLeftToLowerRight, []rune{}, tcell.StyleDefault)
|
|
screen.SetContent(3, 3, tview.BoxDrawingsLightDiagonalUpperLeftToLowerRight, []rune{}, tcell.StyleDefault)
|
|
screen.SetContent(4, 4, tview.BoxDrawingsLightDiagonalUpperLeftToLowerRight, []rune{}, tcell.StyleDefault)
|
|
screen.SetContent(5, 5, tview.BoxDrawingsLightDiagonalUpperLeftToLowerRight, []rune{}, tcell.StyleDefault)
|
|
}
|
|
|
|
func (e Edge) GetRect() (int, int, int, int) {
|
|
return 0, 0, 0, 0
|
|
}
|
|
|
|
func (e Edge) SetRect(x, y, width, height int) {
|
|
}
|
|
|
|
func (e Edge) InputHandler() func(_ *tcell.EventKey, _ func(p tview.Primitive)) {
|
|
return func(event *tcell.EventKey, setFocus func(p tview.Primitive)) {}
|
|
}
|
|
|
|
func (e Edge) HasFocus() bool {
|
|
return false
|
|
}
|
|
|
|
func (e Edge) Focus(delegate func(p tview.Primitive)) {
|
|
}
|
|
|
|
func (e Edge) Blur() {
|
|
}
|
|
|
|
func (e Edge) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
|
|
return func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func NewUI() *UI {
|
|
app := tview.NewApplication()
|
|
ui := UI{
|
|
Mode: modeNormal,
|
|
App: app,
|
|
Fields: []field{{"scratch", true}, {"test", false}},
|
|
Nodes: []*node{},
|
|
Viewport: &viewport{},
|
|
Pages: tview.NewPages(),
|
|
TopFlex: tview.NewFlex(),
|
|
Field: tview.NewPages(),
|
|
FieldBar: tview.NewFlex(),
|
|
BottomBar: tview.NewPages(),
|
|
ExInput: tview.NewInputField(),
|
|
ExOutput: tview.NewTextView(),
|
|
}
|
|
|
|
app.SetInputCapture(ui.handleInput)
|
|
|
|
ui.TopFlex.SetDirection(tview.FlexRow)
|
|
ui.TopFlex.AddItem(ui.Field, 0, 20, true)
|
|
ui.TopFlex.AddItem(ui.FieldBar, 0, 1, false)
|
|
ui.TopFlex.AddItem(ui.BottomBar, 0, 1, false)
|
|
|
|
ui.Pages.AddPage("main", ui.TopFlex, true, true)
|
|
|
|
ui.BottomBar.AddPage("output", ui.ExOutput, true, true)
|
|
ui.BottomBar.AddPage("input", ui.ExInput, true, false)
|
|
|
|
ui.ExInput.SetLabel(":")
|
|
ui.ExInput.SetDoneFunc(ui.handleExInput)
|
|
|
|
ui.ExOutput.SetMaxLines(1)
|
|
fmt.Fprintf(ui.ExOutput, "porphyry has started. :q to quit")
|
|
|
|
ui.FieldBar.SetDirection(tview.FlexColumn)
|
|
|
|
ui.Nodes = append(ui.Nodes, &node{
|
|
Text: "foobar\nbaz\nquux",
|
|
})
|
|
ui.Nodes = append(ui.Nodes, &node{
|
|
Text: "the wild box",
|
|
})
|
|
ui.Nodes = append(ui.Nodes, &node{
|
|
Text: "cool stories bros",
|
|
})
|
|
ui.Nodes = append(ui.Nodes, &node{
|
|
Text: "why not",
|
|
})
|
|
ui.Nodes = append(ui.Nodes, &node{
|
|
Text: "hello\nthere\nhow",
|
|
})
|
|
|
|
x := 60
|
|
y := 0
|
|
|
|
rand.Seed(time.Now().Unix())
|
|
|
|
for _, n := range ui.Nodes {
|
|
b := tview.NewTextView()
|
|
b.SetText(n.Text)
|
|
b.SetBorder(true)
|
|
b.SetRect(x, y, 10, 5)
|
|
n.X = x
|
|
n.Y = y
|
|
x -= 20
|
|
ui.Field.AddPage(fmt.Sprintf("%d", rand.Intn(10000)), b, false, true)
|
|
n.widg = b
|
|
}
|
|
ui.Field.AddPage("edge", Edge{}, false, true)
|
|
|
|
app.SetBeforeDrawFunc(func(_ tcell.Screen) bool {
|
|
// Viewport
|
|
_, _, w, h := ui.Field.GetRect()
|
|
ui.Viewport.W = w
|
|
ui.Viewport.H = h
|
|
|
|
// Handle nodes
|
|
for _, n := range ui.Nodes {
|
|
_, _, w, h := n.widg.GetRect()
|
|
x := n.X - ui.Viewport.X
|
|
y := n.Y - ui.Viewport.Y
|
|
n.widg.SetRect(x, y, w, h)
|
|
}
|
|
|
|
// Handle field bar
|
|
ui.FieldBar.Clear()
|
|
|
|
for _, f := range ui.Fields {
|
|
t := tview.NewTextView().SetTextStyle(tcell.StyleDefault.Bold(f.Selected))
|
|
t.SetMaxLines(1)
|
|
t.SetBorder(true)
|
|
fmt.Fprintf(t, f.Name)
|
|
ui.FieldBar.AddItem(t, 0, 1, false)
|
|
}
|
|
|
|
// Handle ex mode prompt
|
|
|
|
if ui.Mode == modeEx {
|
|
ui.BottomBar.SwitchToPage("input")
|
|
} else {
|
|
ui.BottomBar.SwitchToPage("output")
|
|
}
|
|
return false
|
|
})
|
|
|
|
app.SetRoot(ui.Pages, true)
|
|
return &ui
|
|
}
|
|
|
|
func (ui *UI) quit() {
|
|
ui.App.Stop()
|
|
}
|
|
|
|
func _main() error {
|
|
cmd := &cobra.Command{
|
|
Use: "porphyry",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return NewUI().App.Run()
|
|
},
|
|
}
|
|
|
|
return cmd.Execute()
|
|
}
|
|
|
|
func main() {
|
|
if err := _main(); err != nil {
|
|
fmt.Fprintf(os.Stderr, err.Error()+"\n")
|
|
}
|
|
}
|