Started working on TUI
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
// Package dom will be used to handle the multiple elements of an UI
|
||||
package dom
|
||||
|
||||
import "github.com/rivo/tview"
|
||||
|
||||
// ErrElementAlreadyRegistered defines the error scenario of the user trying
|
||||
// to register an element to the DOM with an already registered ID
|
||||
type ErrElementAlreadyRegistered struct{}
|
||||
|
||||
func (e *ErrElementAlreadyRegistered) Error() string {
|
||||
return "an element with that ID has already been registered in the DOM"
|
||||
}
|
||||
|
||||
type DOM struct {
|
||||
Root *UINode
|
||||
elements map[string]*UINode
|
||||
}
|
||||
|
||||
// NewDOM constructs a new DOM
|
||||
func NewDOM() *DOM {
|
||||
return &DOM{
|
||||
Root: nil,
|
||||
elements: make(map[string]*UINode),
|
||||
}
|
||||
}
|
||||
|
||||
// Private
|
||||
func (d *DOM) idIsRegisterd(ID string) bool {
|
||||
_, ok := d.elements[ID]
|
||||
return ok
|
||||
}
|
||||
|
||||
// registerElem register a new element to the DOM
|
||||
// should verify that the ID doesn't yet exist and thats it so far
|
||||
func (d *DOM) registerElem(elem *UINode) error {
|
||||
if d.idIsRegisterd(elem.ID) {
|
||||
return &ErrElementAlreadyRegistered{}
|
||||
}
|
||||
|
||||
d.elements[elem.ID] = elem
|
||||
return nil
|
||||
}
|
||||
|
||||
// Public
|
||||
|
||||
// SetRoot defines the root element for the DOM
|
||||
func (d *DOM) SetRoot(r *UINode) *DOM {
|
||||
d.Root = r
|
||||
return d
|
||||
}
|
||||
|
||||
// NewUINode creates and registers a UI Node
|
||||
func (d *DOM) NewUINode(ID string, parent, root tview.Primitive) (*UINode, error) {
|
||||
newNode := &UINode{
|
||||
ID: ID,
|
||||
Self: root,
|
||||
Children: make(map[string]*UINode),
|
||||
}
|
||||
|
||||
err := d.registerElem(newNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newNode, nil
|
||||
}
|
||||
|
||||
// GetRoot fetches the root UINode of the DOM
|
||||
func (d *DOM) GetRoot(r *UINode) *UINode {
|
||||
return d.Root
|
||||
}
|
||||
|
||||
// GetRootElem fetches the root primitive of the dom
|
||||
func (d *DOM) GetRootElem() tview.Primitive {
|
||||
return d.Root.Self
|
||||
}
|
||||
|
||||
// GetElemByID returns the primitive of a UINode given its ID
|
||||
// returns nil if the Node isn't registered
|
||||
func (d *DOM) GetElemByID(ID string) tview.Primitive {
|
||||
if !d.idIsRegisterd(ID) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return d.elements[ID].Self
|
||||
}
|
||||
|
||||
// GetNodeByID returns a UINode given its ID
|
||||
// returns nil if the Node isn't registered
|
||||
func (d *DOM) GetNodeByID(ID string) *UINode {
|
||||
if !d.idIsRegisterd(ID) {
|
||||
return d.elements[ID]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AppendElem adds a new UINode to a given UINode
|
||||
func (d *DOM) AppendElem(root *UINode, elem *UINode) error {
|
||||
return root.AppendItem(elem)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Package dom
|
||||
package dom
|
||||
|
||||
import (
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
type UINode struct {
|
||||
ID string
|
||||
Self tview.Primitive
|
||||
Parent tview.Primitive
|
||||
Children map[string]*UINode
|
||||
}
|
||||
|
||||
func newUINode(ID string, parent, root tview.Primitive) *UINode {
|
||||
return &UINode{
|
||||
ID: ID,
|
||||
Self: root,
|
||||
Children: make(map[string]*UINode),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *UINode) AppendItem(item *UINode) error {
|
||||
// Its not necessary to verify if this ID is already a child of this Node
|
||||
// because we do that at the UI level
|
||||
l.Children[item.ID] = item
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Package tui
|
||||
package tui
|
||||
|
||||
import (
|
||||
dom "esdi/t/internal/dom"
|
||||
"esdi/t/internal/views"
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
type TUI struct {
|
||||
App *tview.Application
|
||||
Dom *dom.DOM
|
||||
}
|
||||
|
||||
var (
|
||||
application *TUI
|
||||
initiated = false
|
||||
)
|
||||
|
||||
func NewTUI() *TUI {
|
||||
return &TUI{
|
||||
App: tview.NewApplication(),
|
||||
Dom: dom.NewDOM(),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TUI) Start() error {
|
||||
// The app starts running here!
|
||||
//
|
||||
// Set the event capture for the global app itself here
|
||||
t.App.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyCtrlC || event.Rune() == 'q' {
|
||||
t.App.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
return event
|
||||
})
|
||||
|
||||
ctx := &views.UIContext{
|
||||
Redraw: func() {
|
||||
t.App.QueueUpdateDraw(func() {})
|
||||
},
|
||||
}
|
||||
|
||||
// Set the main view
|
||||
mainUINode, err := views.BuildMainFlex(t.Dom, ctx)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to build main flex - %s", err.Error()))
|
||||
}
|
||||
|
||||
rootNode, err := t.Dom.NewUINode("root", nil, mainUINode)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to create root node - %s", err.Error()))
|
||||
}
|
||||
|
||||
t.Dom.SetRoot(rootNode)
|
||||
firstFocus := t.Dom.GetElemByID("device-api-list")
|
||||
if firstFocus == nil {
|
||||
panic(fmt.Errorf("`list-window` isn't registered"))
|
||||
}
|
||||
|
||||
// Star the app
|
||||
err = t.App.SetRoot(t.Dom.GetRootElem(), true).SetFocus(firstFocus).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Init() {
|
||||
initiated = true
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package views
|
||||
|
||||
type UIContext struct {
|
||||
Redraw func()
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"esdi/t/internal/dom"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
func buildLayoutToolUINode(root *dom.DOM) (*dom.UINode, error) {
|
||||
// Want a TreeView at the top
|
||||
layoutTree := tview.NewTreeView()
|
||||
layoutTree.SetBorder(true).SetTitle("Layout Tree")
|
||||
_, err := root.NewUINode("layout-tree",
|
||||
root.GetElemByID("right-flex"), layoutTree)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Want pages below to run actions
|
||||
actionPages := tview.NewPages()
|
||||
actionPages.SetBorder(true).SetTitle("Action Page")
|
||||
_, err = root.NewUINode("layout-action-pages",
|
||||
root.GetElemByID("right-flex"), actionPages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
layoutToolFlex := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
layoutToolFlexNode, err := root.NewUINode("layout-tool-flex", root.GetElemByID("api-tool-pages"),
|
||||
layoutToolFlex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return layoutToolFlexNode, nil
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// Package views
|
||||
package views
|
||||
|
||||
import (
|
||||
"esdi/t/internal/dom"
|
||||
"fmt"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
)
|
||||
|
||||
func buildRightSidePages(root *dom.DOM, ctx *UIContext) (*dom.UINode, error) {
|
||||
// We need to build a set of pages with an empty page
|
||||
emptyPage := tview.NewTextView().
|
||||
SetTextAlign(tview.AlignCenter).
|
||||
SetChangedFunc(func() {
|
||||
ctx.Redraw()
|
||||
})
|
||||
emptyPage.SetBorder(true).SetTitle("-- Tool Area --")
|
||||
|
||||
fmt.Fprintf(emptyPage, "No Tool Selected")
|
||||
|
||||
apiToolPages := tview.NewPages().
|
||||
AddPage("emptypage", emptyPage, true, true)
|
||||
|
||||
apiToolPagesNode, err := root.NewUINode("api-tool-pages", root.GetElemByID("right-flex"),
|
||||
apiToolPages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return apiToolPagesNode, nil
|
||||
}
|
||||
|
||||
func buildRightSideFlex(root *dom.DOM, ctx *UIContext) (*dom.UINode, error) {
|
||||
apiToolPagesNode, err := buildRightSidePages(root, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This will be the right side flex
|
||||
flex := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
flexNode, err := root.NewUINode("right-flex", root.GetElemByID("main-flex"), flex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make the output window
|
||||
outputWin := tview.NewTextView().SetBorder(true).SetTitle("DebugWindow")
|
||||
outputWinNode, err := root.NewUINode("debug-window", root.GetRootElem(),
|
||||
outputWin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
flex.
|
||||
AddItem(apiToolPagesNode.Self, 0, 5, false).
|
||||
AddItem(outputWinNode.Self, 0, 2, false)
|
||||
|
||||
return flexNode, nil
|
||||
}
|
||||
|
||||
func BuildMainFlex(root *dom.DOM, ctx *UIContext) (*tview.Flex, error) {
|
||||
// To build the main view we must set the DOM root
|
||||
mainFlex := tview.NewFlex().SetDirection(tview.FlexColumn)
|
||||
mainFlexUINode, err := root.NewUINode("main-flex", nil, mainFlex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
root.SetRoot(mainFlexUINode)
|
||||
|
||||
deviceAPIList := tview.NewList().
|
||||
AddItem("layout", "build a layout for CDashDisplay", 0, func() {
|
||||
// When clicked we want to replace the right side pages with this
|
||||
})
|
||||
deviceAPIList.SetBorder(true).SetTitle("list")
|
||||
|
||||
apiListWindowUINode, err := root.NewUINode("device-api-list", root.GetRootElem(),
|
||||
deviceAPIList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rightSideFlex, err := buildRightSideFlex(root, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mainFlex.
|
||||
AddItem(apiListWindowUINode.Self, 0, 1, false).
|
||||
AddItem(rightSideFlex.Self, 0, 4, false)
|
||||
|
||||
return mainFlex, nil
|
||||
}
|
||||
Reference in New Issue
Block a user