Skip to content

Commit

Permalink
refactor(tui): Implement UI-pane creation for editors removed from ed…
Browse files Browse the repository at this point in the history
…itor logic

This seems much better to me (despite, for now, dropping the views
package).
There are multiple ways to decide on this sort of thing, and many
ways to get lost in the abstract what-if-edgecase sauce, but I find
that the editor should allow inspecting it sufficiently to build a
UI from it, rather than giving the editor all the necessities to
build its own UI.
Especially if we ever support something other than TUI, I think
it'll be much less weird this way, imagine having to implement for
each UI paradigm in every control-logic component...
  • Loading branch information
ja-he committed Jan 13, 2024
1 parent 3c6624a commit a6797a7
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 262 deletions.
26 changes: 14 additions & 12 deletions internal/control/cli/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,24 +472,26 @@ func NewController(
controller.data.TaskEditor = nil
return
}
taskEditorPane, err := controller.data.TaskEditor.GetPane(
ui.NewConstrainedRenderer(renderer, func() (x, y, w, h int) {
screenWidth, screenHeight := screenSize()
taskEditorBoxWidth := int(math.Min(80, float64(screenWidth)))
taskEditorBoxHeight := int(math.Min(20, float64(screenHeight)))
return (screenWidth / 2) - (taskEditorBoxWidth / 2), (screenHeight / 2) - (taskEditorBoxHeight / 2), taskEditorBoxWidth, taskEditorBoxHeight
}),

taskEditorRenderer := ui.NewConstrainedRenderer(renderer, func() (x, y, w, h int) {
screenWidth, screenHeight := screenSize()
taskEditorBoxWidth := int(math.Min(80, float64(screenWidth)))
taskEditorBoxHeight := int(math.Min(20, float64(screenHeight)))
return (screenWidth / 2) - (taskEditorBoxWidth / 2), (screenHeight / 2) - (taskEditorBoxHeight / 2), taskEditorBoxWidth, taskEditorBoxHeight
})

taskEditorPane, err := panes.NewCompositeEditorPane(
taskEditorRenderer,
cursorWrangler,
func() bool { return true },
inputConfig,
stylesheet,
cursorWrangler,
controller.data.TaskEditor,
)
if err != nil {
log.Error().Err(err).Msgf("could not construct task editor pane")
controller.data.TaskEditor = nil
return
log.Fatal().Err(err).Msg("could not construct task editor pane (this is likely a serious programming error / omission)")
}
log.Info().Str("info", taskEditorPane.(*panes.CompositeEditorPane).GetDebugInfo()).Msg("here is the debug info for the task editor pane")

controller.rootPane.PushSubpane(taskEditorPane)
taskEditorDone := make(chan struct{})
controller.data.TaskEditor.AddQuitCallback(func() {
Expand Down
31 changes: 12 additions & 19 deletions internal/control/edit/editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,13 @@
// user).
package edit

import (
"github.com/ja-he/dayplan/internal/input"
"github.com/ja-he/dayplan/internal/styling"
"github.com/ja-he/dayplan/internal/ui"
)

// Editor is an interface for editing of objects (by the user).
type Editor interface {
IsActiveAndFocussed() (bool, bool)

GetName() string

GetType() string
GetSummary() SummaryEntry

// Write the state of the editor.
Write()
Expand All @@ -25,18 +18,18 @@ type Editor interface {

// AddQuitCallback adds a callback that is called when the editor is quit.
AddQuitCallback(func())

// GetPane returns a pane that represents this editor.
GetPane(
renderer ui.ConstrainedRenderer,
visible func() bool,
inputConfig input.InputConfig,
stylesheet styling.Stylesheet,
cursorController ui.CursorLocationRequestHandler,
) (ui.Pane, error)
}

type SummaryEntry struct {
Representation any
Represents Editor
// View is an interface for viewing of objects (by the user).
type View interface {
IsActiveAndFocussed() (bool, bool)

GetName() string

// GetType reutrns the type of the view.
//
// Based on this information, a cast to a relevant type can be done.
// E.g., when GetType() returns "string", the view can be cast to a
// StringEditorView.
GetType() string
}
126 changes: 9 additions & 117 deletions internal/control/edit/editors/composite_editor.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package editors contains the editors for the different data types.
package editors

import (
Expand All @@ -12,9 +13,6 @@ import (
"github.com/ja-he/dayplan/internal/input"
"github.com/ja-he/dayplan/internal/input/processors"
"github.com/ja-he/dayplan/internal/model"
"github.com/ja-he/dayplan/internal/styling"
"github.com/ja-he/dayplan/internal/ui"
"github.com/ja-he/dayplan/internal/ui/panes"
)

// Composite implements Editor
Expand Down Expand Up @@ -207,58 +205,8 @@ func (e *Composite) Quit() {
}
}

// GetPane constructs a pane for this composite editor (including all subeditors).
func (e *Composite) GetPane(
renderer ui.ConstrainedRenderer,
visible func() bool,
inputConfig input.InputConfig,
stylesheet styling.Stylesheet,
cursorController ui.CursorLocationRequestHandler,
) (ui.Pane, error) {
subpanes := []ui.Pane{}

// TODO: this needs to compute an enriched version of the editor tree
editorSummary := e.GetSummary()
minX, minY, maxWidth, maxHeight := renderer.Dimensions()
uiBoxModel, err := translateToUIBoxModel(editorSummary, minX, minY, maxWidth, maxHeight)
if err != nil {
return nil, fmt.Errorf("error translating editor summary to UI box model (%s)", err.Error())
}
log.Debug().Msgf("have UI box model: %s", uiBoxModel.String())

for _, child := range uiBoxModel.Children {
childX, childY, childW, childH := child.X, child.Y, child.W, child.H
subRenderer := ui.NewConstrainedRenderer(renderer, func() (int, int, int, int) { return childX, childY, childW, childH })
subeditorPane, err := child.Represents.GetPane(
subRenderer,
visible,
inputConfig,
stylesheet,
cursorController,
)
if err != nil {
return nil, fmt.Errorf("error constructing subpane of '%s' for subeditor '%s' (%s)", e.name, child.Represents.GetName(), err.Error())
}
subpanes = append(subpanes, subeditorPane)
}

inputProcessor, err := e.createInputProcessor(inputConfig)
if err != nil {
return nil, fmt.Errorf("could not construct input processor (%s)", err.Error())
}
return panes.NewCompositeEditorPane(
renderer,
visible,
inputProcessor,
stylesheet,
subpanes,
func() int { return e.activeFieldIndex },
func() bool { return e.inField },
e,
), nil
}

func (e *Composite) createInputProcessor(cfg input.InputConfig) (input.ModalInputProcessor, error) {
// CreateInputProcessor creates an input processor for the editor.
func (e *Composite) CreateInputProcessor(cfg input.InputConfig) (input.ModalInputProcessor, error) {
actionspecToFunc := map[input.Actionspec]func(){
"next-field": e.SwitchToNextField,
"prev-field": e.SwitchToPrevField,
Expand All @@ -282,68 +230,12 @@ func (e *Composite) createInputProcessor(cfg input.InputConfig) (input.ModalInpu
return processors.NewModalInputProcessor(inputTree), nil
}

func (e *Composite) IsActiveAndFocussed() (bool, bool) { return e.activeAndFocussedFunc() }

func (e *Composite) GetSummary() edit.SummaryEntry {

result := edit.SummaryEntry{
Representation: []edit.SummaryEntry{},
Represents: e,
}
for _, subeditor := range e.fields {
log.Debug().Msgf("constructing subpane of '%s' for subeditor '%s'", e.name, subeditor.GetName())
result.Representation = append(result.Representation.([]edit.SummaryEntry), subeditor.GetSummary())
}

return result
}

func translateToUIBoxModel(summary edit.SummaryEntry, minX, minY, maxWidth, maxHeight int) (ui.BoxRepresentation[edit.Editor], error) {

switch repr := summary.Representation.(type) {
func (e *Composite) GetActiveFieldIndex() int { return e.activeFieldIndex }
func (e *Composite) IsInField() bool { return e.inField }

// a slice indicates a composite
case []edit.SummaryEntry:
var children []ui.BoxRepresentation[edit.Editor]
computedHeight := 1
rollingY := minY + 1
for _, child := range repr {
childBoxRepresentation, err := translateToUIBoxModel(child, minX+1, rollingY, maxWidth-2, maxHeight-2)
if err != nil {
return ui.BoxRepresentation[edit.Editor]{}, fmt.Errorf("error translating child '%s' (%s)", child.Represents.GetName(), err.Error())
}
rollingY += childBoxRepresentation.H + 1
children = append(children, childBoxRepresentation)
computedHeight += childBoxRepresentation.H + 1
}
return ui.BoxRepresentation[edit.Editor]{
X: minX,
Y: minY,
W: maxWidth,
H: computedHeight,
Represents: summary.Represents,
Children: children,
}, nil

// a string indicates a leaf, i.e., a concrete editor rather than a composite
case string:
switch repr {
case "string":
return ui.BoxRepresentation[edit.Editor]{
X: minX,
Y: minY,
W: maxWidth,
H: 1,
Represents: summary.Represents,
Children: nil,
}, nil
default:
return ui.BoxRepresentation[edit.Editor]{}, fmt.Errorf("unknown editor identification value '%s'", repr)
}

default:
return ui.BoxRepresentation[edit.Editor]{}, fmt.Errorf("for editor '%s' have unknown type '%t'", summary.Represents.GetName(), summary.Representation)

}
func (e *Composite) IsActiveAndFocussed() (bool, bool) { return e.activeAndFocussedFunc() }

// GetFields returns the subeditors of this composite editor.
func (e *Composite) GetFields() []edit.Editor {
return e.fields
}
37 changes: 2 additions & 35 deletions internal/control/edit/editors/string_editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@ import (
"strconv"

"github.com/ja-he/dayplan/internal/control/action"
"github.com/ja-he/dayplan/internal/control/edit"
"github.com/ja-he/dayplan/internal/input"
"github.com/ja-he/dayplan/internal/input/processors"
"github.com/ja-he/dayplan/internal/styling"
"github.com/ja-he/dayplan/internal/ui"
"github.com/ja-he/dayplan/internal/ui/panes"
"github.com/rs/zerolog/log"
)

Expand Down Expand Up @@ -245,30 +241,8 @@ func (e *StringEditor) AddQuitCallback(f func()) {
}
}

// GetPane returns a UI pane representing the editor.
func (e *StringEditor) GetPane(
renderer ui.ConstrainedRenderer,
visible func() bool,
inputConfig input.InputConfig,
stylesheet styling.Stylesheet,
cursorController ui.CursorLocationRequestHandler,
) (ui.Pane, error) {
inputProcessor, err := e.createInputProcessor(inputConfig)
if err != nil {
return nil, err
}
p := panes.NewStringEditorPane(
renderer,
visible,
inputProcessor,
e,
stylesheet,
cursorController,
)
return p, nil
}

func (e *StringEditor) createInputProcessor(cfg input.InputConfig) (input.ModalInputProcessor, error) {
// CreateInputProcessor creates an input processor for the editor.
func (e *StringEditor) CreateInputProcessor(cfg input.InputConfig) (input.ModalInputProcessor, error) {

var enterInsertMode func()
var exitInsertMode func()
Expand Down Expand Up @@ -325,10 +299,3 @@ func (e *StringEditor) createInputProcessor(cfg input.InputConfig) (input.ModalI

return p, nil
}

func (e *StringEditor) GetSummary() edit.SummaryEntry {
return edit.SummaryEntry{
Representation: "string",
Represents: e,
}
}
12 changes: 0 additions & 12 deletions internal/control/edit/views/composite_editor.go

This file was deleted.

24 changes: 0 additions & 24 deletions internal/control/edit/views/string_editor.go

This file was deleted.

Loading

0 comments on commit a6797a7

Please sign in to comment.