still working on the layout tool

- started doing it in a more adhoc way and will engineer it once I have
a clear vision
This commit is contained in:
2026-02-12 23:03:57 +00:00
parent 97caeafd58
commit f2d92c80ee
9 changed files with 678 additions and 154 deletions
+65 -113
View File
@@ -3,10 +3,8 @@ package views
import (
"esdi/tui/internal/dom"
"esdi/tui/internal/events"
"esdi/tui/internal/models"
"esdi/tui/internal/ui"
"fmt"
"strconv"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
@@ -47,41 +45,64 @@ func BindWindowEvents(
// I recon I have to change this for some type os event system that
// triggers directly on the UINodes I want them to be triggered on
bus.On(ui.WindowCreatedEv{}, func(e any) {
bus.Emit(ui.LogEv{Log: "Received a window created event\n"})
if tree == nil {
bus.Emit(ui.LogEv{Log: "tree view is nil"})
return
}
go func() {
bus.Emit(ui.LogEv{Log: "Received a window created event\n"})
if tree == nil {
bus.Emit(ui.LogEv{Log: "tree view is nil"})
return
}
root := tree.GetRoot()
if root == nil {
bus.Emit(ui.LogEv{Log: "unable to get root of tree view"})
return
}
root := tree.GetRoot()
if root == nil {
bus.Emit(ui.LogEv{Log: "unable to get root of tree view"})
return
}
newWindow := tview.NewTreeNode(e.(ui.WindowCreatedEv).Title).
SetReference(e.(ui.WindowCreatedEv).ID)
root.AddChild(newWindow)
newWindow := tview.NewTreeNode(e.(ui.WindowCreatedEv).Title).
SetReference(e.(ui.WindowCreatedEv).ID)
root.AddChild(newWindow)
}()
})
bus.On(ui.WindowDestroyedEv{}, func(e any) {
go func() {
root := tree.GetRoot()
if root == nil {
bus.Emit(ui.LogEv{Log: "unable to get current tree node"})
}
node := FindNodeByReference(root, e.(ui.WindowDestroyedEv).ID)
if node != nil {
root.RemoveChild(node)
}
// NODE: add a log here in case it fails so we know whats going on
bus.Emit(ui.ForceRedraw{})
}()
})
bus.On(ui.RegisterLoadedLayout{}, func(e any) {
root := tree.GetRoot()
if root == nil {
bus.Emit(ui.LogEv{Log: "unable to get current tree node"})
return
}
node := FindNodeByReference(root, e.(ui.WindowDestroyedEv).ID)
if node != nil {
root.RemoveChild(node)
layout := e.(ui.RegisterLoadedLayout)
for idx, w := range layout.Layout.Windows {
newWindow := tview.NewTreeNode(w.Title.String()).SetReference(idx)
root.AddChild(newWindow)
}
// NODE: add a log here in case it fails so we know whats going on
// bus.Emit(ui.ForceRedraw{})
})
bus.On(ui.ErrorCreateWindowEv{}, func(e any) {
bus.Emit(ui.LogEv{
Log: fmt.Sprintf(
"Error performing action: %s\n", e.(ui.ErrorCreateWindowEv).Error.Error(),
)})
go func() {
bus.Emit(ui.LogEv{
Log: fmt.Sprintf(
"Error performing action: %s\n", e.(ui.ErrorCreateWindowEv).Error.Error(),
)})
}()
})
}
@@ -121,8 +142,28 @@ func layoutToolTreeViewEvents(bus *events.Bus, doc *dom.DOM,
bus.Emit(ui.DestroyWindowEv{ID: wID})
case 'm':
// Go into move mode
curNode := tree.GetCurrentNode()
if curNode == nil {
bus.Emit(ui.LogEv{Log: "unable to get current tree node"})
break
}
ref := curNode.GetReference()
wID, ok := ref.(int16)
if !ok {
// Whatever, do something better here
break
}
windowManipulationTool(bus, doc, wID)
case 'e':
// Go into edit mode
case 's':
// Save the current layout
bus.Emit(ui.SaveLayoutEv{})
case 'l':
// Load the layout
bus.Emit(ui.LoadLayoutEv{})
}
return event
@@ -218,92 +259,3 @@ func buildLayoutFlexComponent(bus *events.Bus, doc *dom.DOM) (*dom.UINode, error
return layoutToolFlexNode, nil
}
// Layout tool actions
// Validate the form inputs
func validateFormInputs(x, y, w, h, title string) (models.Window, error) {
xValue, err := strconv.ParseUint(x, 10, 64)
if err != nil {
return models.Window{}, err
}
yValue, err := strconv.ParseUint(y, 10, 64)
if err != nil {
return models.Window{}, err
}
widthValue, err := strconv.ParseUint(w, 10, 64)
if err != nil {
return models.Window{}, err
}
heightValue, err := strconv.ParseUint(h, 10, 64)
if err != nil {
return models.Window{}, err
}
return models.Window{
X: uint16(xValue),
Y: uint16(yValue),
Width: uint16(widthValue),
Height: uint16(heightValue),
Title: title,
}, nil
}
func createNewWindowForm(bus *events.Bus, doc *dom.DOM) {
var err error
x0 := tview.NewInputField().SetLabel("x")
y0 := tview.NewInputField().SetLabel("y")
width := tview.NewInputField().SetLabel("width")
height := tview.NewInputField().SetLabel("height")
title := tview.NewInputField().SetLabel("title")
form := tview.NewForm().
AddFormItem(x0).
AddFormItem(y0).
AddFormItem(width).
AddFormItem(height).
AddFormItem(title).
AddButton("Create", func() {
// Validate the inputs
window, err := validateFormInputs(
x0.GetText(),
y0.GetText(),
width.GetText(),
height.GetText(),
title.GetText(),
)
if err != nil {
bus.Emit(ui.LogEv{Log: fmt.Sprintf("failed to parse form: %s\n", err.Error())})
return
}
bus.Emit(ui.CreateWindowEv{Window: window})
})
form.SetBorder(true).
SetTitle("new window form").
SetTitleAlign(tview.AlignLeft).
SetInputCapture(func(ev *tcell.EventKey) *tcell.EventKey {
switch ev.Key() {
case tcell.KeyEscape:
bus.Emit(ui.ChangeFocusEv{Target: doc.GetElemByID(layoutToolFlexID)})
}
return ev
})
var formNode *dom.UINode
formNode = doc.GetNodeByID("new-window-form")
if formNode == nil {
formNode, err = doc.NewUINode("new-window-form",
doc.GetElemByID(layoutToolActionPagesID), form)
if err != nil {
panic("failed to create UI node for the new window form: " + err.Error())
}
}
AddAndShowPage(bus, doc, doc.GetElemByID(layoutToolActionPagesID).(*tview.Pages),
formNode)
}
+101
View File
@@ -0,0 +1,101 @@
package views
import (
"fmt"
"strconv"
"esdi/tui/internal/dom"
"esdi/tui/internal/events"
"esdi/tui/internal/models"
"esdi/tui/internal/ui"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
// Validate the form inputs
func validateFormInputs(x, y, w, h, title string) (models.Window, error) {
xValue, err := strconv.ParseUint(x, 10, 64)
if err != nil {
return models.Window{}, err
}
yValue, err := strconv.ParseUint(y, 10, 64)
if err != nil {
return models.Window{}, err
}
widthValue, err := strconv.ParseUint(w, 10, 64)
if err != nil {
return models.Window{}, err
}
heightValue, err := strconv.ParseUint(h, 10, 64)
if err != nil {
return models.Window{}, err
}
return models.Window{
X: uint16(xValue),
Y: uint16(yValue),
Width: uint16(widthValue),
Height: uint16(heightValue),
Title: title,
}, nil
}
func createNewWindowForm(bus *events.Bus, doc *dom.DOM) {
var err error
x0 := tview.NewInputField().SetLabel("x")
y0 := tview.NewInputField().SetLabel("y")
width := tview.NewInputField().SetLabel("width")
height := tview.NewInputField().SetLabel("height")
title := tview.NewInputField().SetLabel("title")
form := tview.NewForm().
AddFormItem(x0).
AddFormItem(y0).
AddFormItem(width).
AddFormItem(height).
AddFormItem(title).
AddButton("Create", func() {
// Validate the inputs
window, err := validateFormInputs(
x0.GetText(),
y0.GetText(),
width.GetText(),
height.GetText(),
title.GetText(),
)
if err != nil {
bus.Emit(ui.LogEv{Log: fmt.Sprintf("failed to parse form: %s\n", err.Error())})
return
}
bus.Emit(ui.CreateWindowEv{Window: window})
})
form.SetBorder(true).
SetTitle("new window form").
SetTitleAlign(tview.AlignLeft).
SetInputCapture(func(ev *tcell.EventKey) *tcell.EventKey {
switch ev.Key() {
case tcell.KeyEscape:
bus.Emit(ui.ChangeFocusEv{Target: doc.GetElemByID(layoutToolFlexID)})
}
return ev
})
var formNode *dom.UINode
formNode = doc.GetNodeByID("new-window-form")
if formNode == nil {
formNode, err = doc.NewUINode("new-window-form",
doc.GetElemByID(layoutToolActionPagesID), form)
if err != nil {
panic("failed to create UI node for the new window form: " + err.Error())
}
}
AddAndShowPage(bus, doc, doc.GetElemByID(layoutToolActionPagesID).(*tview.Pages),
formNode)
}
@@ -0,0 +1,172 @@
package views
import (
"fmt"
helper "esdi/helpers"
"esdi/tui/internal/dom"
"esdi/tui/internal/events"
"esdi/tui/internal/ui"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
type ManipMode int
const (
moveMode ManipMode = iota
resizeMode
)
type windowManipState struct {
Mode ManipMode
}
func keyToVector(r rune) (helper.Vector, bool) {
var mult uint16 = 1
// Is uppercase? For fast movement
if r >= 'A' && r <= 'Z' {
mult = 10
r += 'a' - 'A'
}
switch r {
case 'h':
return helper.Vector{DX: -mult, DY: 0}, true
case 'j':
return helper.Vector{DX: 0, DY: mult}, true
case 'k':
return helper.Vector{DX: 0, DY: -mult}, true
case 'l':
return helper.Vector{DX: mult, DY: 0}, true
}
return helper.Vector{}, false
}
func handleMovementCapture(bus *events.Bus, id int16, ev *tcell.EventKey) *tcell.EventKey {
vec, ok := keyToVector(ev.Rune())
if !ok {
return ev
}
bus.Emit(ui.MoveWindowEv{
WindowID: id,
Delta: vec,
})
return ev
}
func handleResizeCapture(bus *events.Bus, id int16, ev *tcell.EventKey) *tcell.EventKey {
vec, ok := keyToVector(ev.Rune())
if !ok {
return ev
}
bus.Emit(ui.ResizeWindowEv{
WindowID: id,
Delta: vec,
})
return ev
}
type modeHandler func(*tcell.EventKey) *tcell.EventKey
func (s *windowManipState) CurrentHandler(
bus *events.Bus,
id int16,
) modeHandler {
switch s.Mode {
case moveMode:
return func(ev *tcell.EventKey) *tcell.EventKey {
return handleMovementCapture(bus, id, ev)
}
case resizeMode:
return func(ev *tcell.EventKey) *tcell.EventKey {
return handleResizeCapture(bus, id, ev)
}
}
return nil
}
func windowManipulationEvCapture(
bus *events.Bus,
doc *dom.DOM,
id int16,
state *windowManipState,
) func(ev *tcell.EventKey) *tcell.EventKey {
return func(ev *tcell.EventKey) *tcell.EventKey {
switch ev.Key() {
case tcell.KeyEscape:
bus.Emit(ui.ChangeFocusEv{
Target: doc.GetElemByID(layoutToolFlexID),
})
return ev
}
bus.Emit(ui.PrintLogEv{
Log: fmt.Sprintf("-> IDX: %d\n", id),
})
// Mode switching
switch ev.Rune() {
case 'r':
state.Mode = resizeMode
bus.Emit(ui.PrintLogEv{Log: "switched to resizeMode"})
case 'm':
state.Mode = moveMode
bus.Emit(ui.PrintLogEv{Log: "switched to moveMode"})
}
// Delegate the current handler input
handler := state.CurrentHandler(bus, id)
if handler != nil {
return handler(ev)
}
return ev
}
}
func windowManipulationTool(bus *events.Bus, doc *dom.DOM, idx int16) {
bus.Emit(ui.PrintLogEv{Log: fmt.Sprintf("WID: %d\n", idx)})
// Get the ActionPages parent
box := tview.NewBox().SetTitle("MOVE WINDOW TOOL")
box.SetBorder(true)
var err error
var boxNode *dom.UINode
// delete the currently existing move-window-box
actionPages := doc.GetElemByID(layoutToolActionPagesID).(*tview.Pages)
actionPages.RemovePage("move-window-box")
boxNode = doc.GetNodeByID("move-window-box")
if boxNode != nil {
doc.DeleteElem(boxNode)
}
boxNode, err = doc.NewUINode(
"move-window-box",
doc.GetElemByID(layoutToolActionPagesID),
box,
)
if err != nil {
return
}
// Create the state for this tool
state := windowManipState{
Mode: moveMode,
}
box.SetInputCapture(windowManipulationEvCapture(bus, doc, idx, &state))
AddAndShowPage(bus, doc, actionPages, boxNode)
}