summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Goodman <wagoodman@gmail.com>2020-02-09 14:33:52 -0500
committerAlex Goodman <wagoodman@gmail.com>2020-02-09 14:33:52 -0500
commit91d58ab09da0d062203c10a3aeb839e2a6724797 (patch)
treeb6646999cdf7fe6fd3c70012c2fc474a6bfd8d05
parentb46180936e979a9080c9d670053fb0355b2c8112 (diff)
basic pane swap workingimage-details-pane
-rw-r--r--cmd/root.go1
-rw-r--r--runtime/ui/app.go66
-rw-r--r--runtime/ui/controller.go3
-rw-r--r--runtime/ui/format/format.go10
-rw-r--r--runtime/ui/layout/manager.go17
-rw-r--r--runtime/ui/view/debug.go30
-rw-r--r--runtime/ui/view/details.go7
-rw-r--r--runtime/ui/view/filetree.go188
-rw-r--r--runtime/ui/view/filter.go13
-rw-r--r--runtime/ui/view/layer.go2
-rw-r--r--runtime/ui/view/status.go9
11 files changed, 247 insertions, 99 deletions
diff --git a/cmd/root.go b/cmd/root.go
index cdbc60f..f354ee3 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -73,6 +73,7 @@ func initConfig() {
viper.SetDefault("keybinding.quit", "ctrl+c")
viper.SetDefault("keybinding.toggle-view", "tab")
viper.SetDefault("keybinding.filter-files", "ctrl+f, ctrl+slash")
+ viper.SetDefault("keybinding.toggle-details", "ctrl+d")
// keybindings: layer view
viper.SetDefault("keybinding.compare-all", "ctrl+a")
viper.SetDefault("keybinding.compare-layer", "ctrl+l")
diff --git a/runtime/ui/app.go b/runtime/ui/app.go
index c1b48a7..4365cd6 100644
--- a/runtime/ui/app.go
+++ b/runtime/ui/app.go
@@ -16,9 +16,10 @@ const debug = false
// type global
type app struct {
- gui *gocui.Gui
- controllers *Controller
- layout *layout.Manager
+ gui *gocui.Gui
+ controller *Controller
+ layout *layout.Manager
+ detailedMode bool
}
var (
@@ -32,7 +33,7 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Compa
var controller *Controller
var globalHelpKeys []*key.Binding
- controller, err = NewCollection(gui, analysis, cache)
+ controller, err = NewController(gui, analysis, cache)
if err != nil {
return
}
@@ -59,9 +60,10 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Compa
// }
appSingleton = &app{
- gui: gui,
- controllers: controller,
- layout: lm,
+ gui: gui,
+ controller: controller,
+ layout: lm,
+ detailedMode: false,
}
var infos = []key.BindingInfo{
@@ -90,6 +92,19 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Compa
controller.views.Status.AddHelpKeys(globalHelpKeys...)
+ // dont show these key bindings on the status pane
+ quietKeys := []key.BindingInfo{
+ {
+ ConfigKeys: []string{"keybinding.toggle-details"},
+ OnAction: appSingleton.toggleDetails,
+ Display: "",
+ },
+ }
+ _, err = key.GenerateBindings(gui, "", quietKeys)
+ if err != nil {
+ return
+ }
+
// perform the first update and render now that all resources have been loaded
err = controller.UpdateAndRender()
if err != nil {
@@ -106,8 +121,8 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Compa
// debugPrint writes the given string to the debug pane (if the debug pane is enabled)
// func debugPrint(s string) {
-// if controllers.Tree != nil && controllers.Tree.gui != nil {
-// v, _ := controllers.Tree.gui.View("debug")
+// if controller.Tree != nil && controller.Tree.gui != nil {
+// v, _ := controller.Tree.gui.View("debug")
// if v != nil {
// if len(v.BufferLines()) > 20 {
// v.Clear()
@@ -126,6 +141,39 @@ func (a *app) quit() error {
return gocui.ErrQuit
}
+func (a *app) toggleDetails() error {
+ if a.detailedMode {
+ err := a.layout.Remove(a.controller.views.Debug)
+ if err != nil {
+ logrus.Errorf("could not remove DEBUG pane")
+ }
+ a.layout.Add(a.controller.views.Tree, layout.LocationColumn)
+ a.controller.views.Debug.ToggleHide()
+ a.controller.views.Tree.ToggleHide()
+
+ a.controller.views.Status.Retop()
+ a.controller.views.Filter.Retop()
+
+ a.detailedMode = false
+ } else {
+ err := a.layout.Remove(a.controller.views.Tree)
+ if err != nil {
+ logrus.Errorf("could not remove TREE pane")
+ }
+ a.layout.Add(a.controller.views.Debug, layout.LocationColumn)
+ a.controller.views.Debug.ToggleHide()
+ a.controller.views.Tree.ToggleHide()
+
+ a.controller.views.Status.Retop()
+ a.controller.views.Filter.Retop()
+
+ a.detailedMode = true
+ }
+
+ return nil
+}
+
+
// Run is the UI entrypoint.
func Run(analysis *image.AnalysisResult, treeStack filetree.Comparer) error {
var err error
diff --git a/runtime/ui/controller.go b/runtime/ui/controller.go
index d9ca330..59dbdd0 100644
--- a/runtime/ui/controller.go
+++ b/runtime/ui/controller.go
@@ -13,9 +13,10 @@ import (
type Controller struct {
gui *gocui.Gui
views *view.Views
+
}
-func NewCollection(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Comparer) (*Controller, error) {
+func NewController(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Comparer) (*Controller, error) {
views, err := view.NewViews(g, analysis, cache)
if err != nil {
return nil, err
diff --git a/runtime/ui/format/format.go b/runtime/ui/format/format.go
index 6a7c671..b0bc97e 100644
--- a/runtime/ui/format/format.go
+++ b/runtime/ui/format/format.go
@@ -65,7 +65,11 @@ func RenderNoHeader(width int, selected bool) string {
return strings.Repeat(fillStr, width)
}
-func RenderHeader(title string, width int, selected bool) string {
+func RenderHeader(title string, width int, selected bool, nl bool) string {
+ newLine := "\n"
+ if !nl{
+ newLine = ""
+ }
if selected {
body := Header(fmt.Sprintf("%s%s ", selectStr, title))
bodyLen := len(vtclean.Clean(body, false))
@@ -73,7 +77,7 @@ func RenderHeader(title string, width int, selected bool) string {
if repeatCount < 0 {
repeatCount = 0
}
- return fmt.Sprintf("%s%s%s%s\n", selectedLeftBracketStr, body, selectedRightBracketStr, strings.Repeat(selectedFillStr, repeatCount))
+ return fmt.Sprintf("%s%s%s%s%s", selectedLeftBracketStr, body, selectedRightBracketStr, strings.Repeat(selectedFillStr, repeatCount), newLine)
//return fmt.Sprintf("%s%s%s%s\n", Selected(selectedLeftBracketStr), body, Selected(selectedRightBracketStr), Selected(strings.Repeat(selectedFillStr, width-bodyLen-2)))
//return fmt.Sprintf("%s%s%s%s\n", Selected(selectedLeftBracketStr), body, Selected(selectedRightBracketStr), strings.Repeat(selectedFillStr, width-bodyLen-2))
}
@@ -83,7 +87,7 @@ func RenderHeader(title string, width int, selected bool) string {
if repeatCount < 0 {
repeatCount = 0
}
- return fmt.Sprintf("%s%s%s%s\n", leftBracketStr, body, rightBracketStr, strings.Repeat(fillStr, repeatCount))
+ return fmt.Sprintf("%s%s%s%s%s", leftBracketStr, body, rightBracketStr, strings.Repeat(fillStr, repeatCount), newLine)
}
func RenderHelpKey(control, title string, selected bool) string {
diff --git a/runtime/ui/layout/manager.go b/runtime/ui/layout/manager.go
index 460cbea..33e04d5 100644
--- a/runtime/ui/layout/manager.go
+++ b/runtime/ui/layout/manager.go
@@ -1,6 +1,7 @@
package layout
import (
+ "fmt"
"github.com/jroimartin/gocui"
"github.com/sirupsen/logrus"
)
@@ -26,6 +27,22 @@ func (lm *Manager) Add(element Layout, location Location) {
lm.elements[location] = append(lm.elements[location], element)
}
+func (lm *Manager) Remove(element Layout) error {
+ for location, elements := range lm.elements {
+ idx := -1
+ for i, el := range elements {
+ if el == element {
+ idx = i
+ }
+ }
+ if idx >= 0 {
+ lm.elements[location] = append(elements[:idx], elements[idx+1:]...)
+ return nil
+ }
+ }
+ return fmt.Errorf("could not remove element from layout manager")
+}
+
func (lm *Manager) planAndLayoutHeaders(g *gocui.Gui, area Area) (Area, error) {
// layout headers top down
if elements, exists := lm.elements[LocationHeader]; exists {
diff --git a/runtime/ui/view/debug.go b/runtime/ui/view/debug.go
index aab9d06..e78340e 100644
--- a/runtime/ui/view/debug.go
+++ b/runtime/ui/view/debug.go
@@ -14,6 +14,8 @@ type Debug struct {
gui *gocui.Gui
view *gocui.View
header *gocui.View
+ //once sync.Once
+ hidden bool
selectedView Helper
}
@@ -25,6 +27,7 @@ func newDebugView(gui *gocui.Gui) (controller *Debug) {
// populate main fields
controller.name = "debug"
controller.gui = gui
+ controller.hidden = true
return controller
}
@@ -37,6 +40,29 @@ func (v *Debug) Name() string {
return v.name
}
+func (v *Debug) ToggleHide() error {
+ v.hidden = !v.hidden
+ if v.hidden {
+ logrus.Trace("hiding debug view...")
+
+ // take note: deleting a view will invoke layout again, so ensure this call is protected from an infinite loop
+ err := v.gui.DeleteView(v.Name())
+ if err != nil {
+ return err
+ }
+ // take note: deleting a view will invoke layout again, so ensure this call is protected from an infinite loop
+ err = v.gui.DeleteView(v.Name() + "header")
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (v *Debug) IsHidden() bool {
+ return v.hidden
+}
+
// Setup initializes the UI concerns within the context of a global [gocui] view object.
func (v *Debug) Setup(view *gocui.View, header *gocui.View) error {
logrus.Tracef("view.Setup() %s", v.Name())
@@ -82,7 +108,7 @@ func (v *Debug) Render() error {
// update header...
v.header.Clear()
width, _ := g.Size()
- headerStr := format.RenderHeader("Debug", width, false)
+ headerStr := format.RenderHeader("Debug", width, false, false)
_, _ = fmt.Fprintln(v.header, headerStr)
// update view...
@@ -98,7 +124,7 @@ func (v *Debug) Render() error {
}
func (v *Debug) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error {
- logrus.Tracef("view.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d) %s", minX, minY, maxX, maxY, v.Name())
+ logrus.Tracef("view.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d, hidden: %v) %s", minX, minY, maxX, maxY, v.hidden, v.Name())
// header
headerSize := 1
diff --git a/runtime/ui/view/details.go b/runtime/ui/view/details.go
index 95ee962..d8a0112 100644
--- a/runtime/ui/view/details.go
+++ b/runtime/ui/view/details.go
@@ -157,8 +157,8 @@ func (v *Details) Render() error {
v.header.Clear()
width, _ := v.view.Size()
- layerHeaderStr := format.RenderHeader("Layer Details", width, false)
- imageHeaderStr := format.RenderHeader("Image Details", width, false)
+ layerHeaderStr := format.RenderHeader("Layer Details", width, false, false)
+ imageHeaderStr := format.RenderHeader("Image", width, false, false)
_, err := fmt.Fprintln(v.header, layerHeaderStr)
if err != nil {
@@ -182,7 +182,8 @@ func (v *Details) Render() error {
lines = append(lines, imageSizeStr)
lines = append(lines, wastedSpaceStr)
lines = append(lines, effStr+"\n")
- lines = append(lines, inefficiencyReport)
+ lines = append(lines, format.Header("[^D for detailed image information]"))
+ //lines = append(lines, inefficiencyReport)
_, err = fmt.Fprintln(v.view, strings.Join(lines, "\n"))
if err != nil {
diff --git a/runtime/ui/view/filetree.go b/runtime/ui/view/filetree.go
index 97e6f7b..0eabfd7 100644
--- a/runtime/ui/view/filetree.go
+++ b/runtime/ui/view/filetree.go
@@ -11,6 +11,7 @@ import (
"github.com/wagoodman/dive/runtime/ui/viewmodel"
"github.com/wagoodman/dive/utils"
"regexp"
+ "sync"
)
type ViewOptionChangeListener func() error
@@ -22,6 +23,8 @@ type FileTree struct {
gui *gocui.Gui
view *gocui.View
header *gocui.View
+ once sync.Once
+ hidden bool
vm *viewmodel.FileTree
title string
@@ -73,6 +76,7 @@ func (v *FileTree) Name() string {
// Setup initializes the UI concerns within the context of a global [gocui] view object.
func (v *FileTree) Setup(view *gocui.View, header *gocui.View) error {
logrus.Tracef("view.Setup() %s", v.Name())
+ var err error
// set controller options
v.view = view
@@ -85,89 +89,90 @@ func (v *FileTree) Setup(view *gocui.View, header *gocui.View) error {
v.header.Wrap = false
v.header.Frame = false
- var infos = []key.BindingInfo{
- {
- ConfigKeys: []string{"keybinding.toggle-collapse-dir"},
- OnAction: v.toggleCollapse,
- Display: "Collapse dir",
- },
- {
- ConfigKeys: []string{"keybinding.toggle-collapse-all-dir"},
- OnAction: v.toggleCollapseAll,
- Display: "Collapse all dir",
- },
- {
- ConfigKeys: []string{"keybinding.toggle-added-files"},
- OnAction: func() error { return v.toggleShowDiffType(filetree.Added) },
- IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Added] },
- Display: "Added",
- },
- {
- ConfigKeys: []string{"keybinding.toggle-removed-files"},
- OnAction: func() error { return v.toggleShowDiffType(filetree.Removed) },
- IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Removed] },
- Display: "Removed",
- },
- {
- ConfigKeys: []string{"keybinding.toggle-modified-files"},
- OnAction: func() error { return v.toggleShowDiffType(filetree.Modified) },
- IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Modified] },
- Display: "Modified",
- },
- {
- ConfigKeys: []string{"keybinding.toggle-unchanged-files", "keybinding.toggle-unmodified-files"},
- OnAction: func() error { return v.toggleShowDiffType(filetree.Unmodified) },
- IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Unmodified] },
- Display: "Unmodified",
- },
- {
- ConfigKeys: []string{"keybinding.toggle-filetree-attributes"},
- OnAction: v.toggleAttributes,
- IsSelected: func() bool { return v.vm.ShowAttributes },
- Display: "Attributes",
- },
- {
- ConfigKeys: []string{"keybinding.page-up"},
- OnAction: v.PageUp,
- },
- {
- ConfigKeys: []string{"keybinding.page-down"},
- OnAction: v.PageDown,
- },
- {
- Key: gocui.KeyArrowDown,
- Modifier: gocui.ModNone,
- OnAction: v.CursorDown,
- },
- {
- Key: gocui.KeyArrowUp,
- Modifier: gocui.ModNone,
- OnAction: v.CursorUp,
- },
- {
- Key: gocui.KeyArrowLeft,
- Modifier: gocui.ModNone,
- OnAction: v.CursorLeft,
- },
- {
- Key: gocui.KeyArrowRight,
- Modifier: gocui.ModNone,
- OnAction: v.CursorRight,
- },
- }
+ v.once.Do(func() {
+ var infos = []key.BindingInfo{
+ {
+ ConfigKeys: []string{"keybinding.toggle-collapse-dir"},
+ OnAction: v.toggleCollapse,
+ Display: "Collapse dir",
+ },
+ {
+ ConfigKeys: []string{"keybinding.toggle-collapse-all-dir"},
+ OnAction: v.toggleCollapseAll,
+ Display: "Collapse all dir",
+ },
+ {
+ ConfigKeys: []string{"keybinding.toggle-added-files"},
+ OnAction: func() error { return v.toggleShowDiffType(filetree.Added) },
+ IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Added] },
+ Display: "Added",
+ },
+ {
+ ConfigKeys: []string{"keybinding.toggle-removed-files"},
+ OnAction: func() error { return v.toggleShowDiffType(filetree.Removed) },
+ IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Removed] },
+ Display: "Removed",
+ },
+ {
+ ConfigKeys: []string{"keybinding.toggle-modified-files"},
+ OnAction: func() error { return v.toggleShowDiffType(filetree.Modified) },
+ IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Modified] },
+ Display: "Modified",
+ },
+ {
+ ConfigKeys: []string{"keybinding.toggle-unchanged-files", "keybinding.toggle-unmodified-files"},
+ OnAction: func() error { return v.toggleShowDiffType(filetree.Unmodified) },
+ IsSelected: func() bool { return !v.vm.HiddenDiffTypes[filetree.Unmodified] },
+ Display: "Unmodified",
+ },
+ {
+ ConfigKeys: []string{"keybinding.toggle-filetree-attributes"},
+ OnAction: v.toggleAttributes,
+ IsSelected: func() bool { return v.vm.ShowAttributes },
+ Display: "Attributes",
+ },
+ {
+ ConfigKeys: []string{"keybinding.page-up"},
+ OnAction: v.PageUp,
+ },
+ {
+ ConfigKeys: []string{"keybinding.page-down"},
+ OnAction: v.PageDown,
+ },
+ {
+ Key: gocui.KeyArrowDown,
+ Modifier: gocui.ModNone,
+ OnAction: v.CursorDown,
+ },
+ {
+ Key: gocui.KeyArrowUp,
+ Modifier: gocui.ModNone,
+ OnAction: v.CursorUp,
+ },
+ {
+ Key: gocui.KeyArrowLeft,
+ Modifier: gocui.ModNone,
+ OnAction: v.CursorLeft,
+ },
+ {
+ Key: gocui.KeyArrowRight,
+ Modifier: gocui.ModNone,
+ OnAction: v.CursorRight,
+ },
+ }
- helpKeys, err := key.GenerateBindings(v.gui, v.name, infos)
- if err != nil {
- return err
- }
- v.helpKeys = helpKeys
+ helpKeys, err := key.GenerateBindings(v.gui, v.name, infos)
+ if err != nil {
+ return
+ }
+ v.helpKeys = helpKeys
+ })
_, height := v.view.Size()
v.vm.Setup(0, height)
_ = v.Update()
_ = v.Render()
-
- return nil
+ return err
}
// IsVisible indicates if the file tree view pane is currently initialized
@@ -328,6 +333,30 @@ func (v *FileTree) toggleShowDiffType(diffType filetree.DiffType) error {
return v.notifyOnViewOptionChangeListeners()
}
+func (v *FileTree) ToggleHide() error {
+ v.hidden = !v.hidden
+
+ if v.hidden {
+ logrus.Trace("hiding filetree view...")
+
+ // take note: deleting a view will invoke layout again, so ensure this call is protected from an infinite loop
+ err := v.gui.DeleteView(v.Name())
+ if err != nil {
+ return err
+ }
+ // take note: deleting a view will invoke layout again, so ensure this call is protected from an infinite loop
+ err = v.gui.DeleteView(v.Name() + "header")
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (v *FileTree) IsHidden() bool {
+ return v.hidden
+}
+
// OnLayoutChange is called by the UI framework to inform the view-model of the new screen dimensions
func (v *FileTree) OnLayoutChange() error {
err := v.Update()
@@ -362,7 +391,7 @@ func (v *FileTree) Render() error {
// update the header
v.header.Clear()
width, _ := g.Size()
- headerStr := format.RenderHeader(title, width, isSelected)
+ headerStr := format.RenderHeader(title, width, isSelected, true)
if v.vm.ShowAttributes {
headerStr += fmt.Sprintf(filetree.AttributeFormat+" %s", "P", "ermission", "UID:GID", "Size", "Filetree")
}
@@ -391,7 +420,7 @@ func (v *FileTree) KeyHelp() string {
}
func (v *FileTree) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error {
- logrus.Tracef("view.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d) %s", minX, minY, maxX, maxY, v.Name())
+ logrus.Tracef("view.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d, hidden: %v) %s", minX, minY, maxX, maxY, v.hidden, v.Name())
attributeRowSize := 0
// make the layout responsive to the available realestate. Make more room for the main content by hiding auxillary
@@ -408,7 +437,6 @@ func (v *FileTree) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error {
// header + attribute header
headerSize := 1 + attributeRowSize
- // note: maxY needs to account for the (invisible) border, thus a +1
header, headerErr := g.SetView(v.Name()+"header", minX, minY, maxX, minY+headerSize+1)
// we are going to overlap the view over the (invisible) border (so minY will be one less than expected).
// additionally, maxY will be bumped by one to include the border
diff --git a/runtime/ui/view/filter.go b/runtime/ui/view/filter.go
index e8f911b..38759e5 100644
--- a/runtime/ui/view/filter.go
+++ b/runtime/ui/view/filter.go
@@ -169,6 +169,19 @@ func (v *Filter) OnLayoutChange() error {
return v.Render()
}
+func (v *Filter) Retop() {
+ logrus.Trace("asserting filter on top...")
+ // take note: deleting a view will invoke layout again, so ensure this call is protected from an infinite loop
+ err := v.gui.DeleteView(v.Name()+"label")
+ if err != nil {
+ logrus.Errorf("could not put filter label on top:", err)
+ }
+ err = v.gui.DeleteView(v.Name())
+ if err != nil {
+ logrus.Errorf("could not put filter on top:", err)
+ }
+}
+
func (v *Filter) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error {
logrus.Tracef("view.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d) %s", minX, minY, maxX, maxY, v.Name())
diff --git a/runtime/ui/view/layer.go b/runtime/ui/view/layer.go
index 08472fa..08405ab 100644
--- a/runtime/ui/view/layer.go
+++ b/runtime/ui/view/layer.go
@@ -307,7 +307,7 @@ func (v *Layer) Render() error {
return err
}
} else {
- headerStr := format.RenderHeader(title, width, isSelected)
+ headerStr := format.RenderHeader(title, width, isSelected, true)
headerStr += fmt.Sprintf("Cmp"+image.LayerFormat, "Size", "Command")
_, err := fmt.Fprintln(v.header, headerStr)
if err != nil {
diff --git a/runtime/ui/view/status.go b/runtime/ui/view/status.go
index 87a4b46..59d2af4 100644
--- a/runtime/ui/view/status.go
+++ b/runtime/ui/view/status.go
@@ -110,6 +110,15 @@ func (v *Status) KeyHelp() string {
return help
}
+func (v *Status) Retop() {
+ logrus.Trace("asserting status on top...")
+ // take note: deleting a view will invoke layout again, so ensure this call is protected from an infinite loop
+ err := v.gui.DeleteView(v.Name())
+ if err != nil {
+ logrus.Errorf("could not put status on top:", err)
+ }
+}
+
func (v *Status) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error {
logrus.Tracef("view.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d) %s", minX, minY, maxX, maxY, v.Name())