exp with vert layout

This commit is contained in:
Alex Goodman 2019-10-21 06:54:13 -04:00
parent 6dd7f7af6e
commit 6c2aac7340
No known key found for this signature in database
GPG key ID: 98AF011C5C78EB7E
12 changed files with 208 additions and 77 deletions

View file

@ -16,7 +16,7 @@ const debug = false
type app struct {
gui *gocui.Gui
controllers *Controller
layout *layoutManager
layout *LayoutManager
}
var (
@ -35,11 +35,11 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.TreeC
return
}
lm := newLayoutManager(theControls)
lm := NewLayoutManager(theControls)
gui.Cursor = false
//g.Mouse = true
gui.SetManagerFunc(lm.layout)
gui.SetManagerFunc(lm.Layout)
// var profileObj = profile.Start(profile.CPUProfile, profile.ProfilePath("."), profile.NoShutdownHook)
//
@ -77,7 +77,7 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.TreeC
return
}
theControls.Status.AddHelpKeys(globalHelpKeys...)
theControls.Help.AddHelpKeys(globalHelpKeys...)
// perform the first update and render now that all resources have been loaded
err = theControls.UpdateAndRender()
@ -106,7 +106,6 @@ func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.TreeC
// }
// }
var lastX, lastY int
// quit is the gocui callback invoked when the user hits Ctrl+C
func (a *app) quit() error {

View file

@ -14,10 +14,11 @@ type Controller struct {
gui *gocui.Gui
Tree *view.FileTree
Layer *view.Layer
Status *view.Status
Help *view.Help
Filter *view.Filter
Details *view.Details
lookup map[string]view.Renderer
lookup map[string]view.View
}
func NewCollection(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.TreeCache) (*Controller, error) {
@ -26,7 +27,7 @@ func NewCollection(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.
controller := &Controller{
gui: g,
}
controller.lookup = make(map[string]view.Renderer)
controller.lookup = make(map[string]view.View)
controller.Layer, err = view.NewLayerView("layers", g, analysis.Layers)
if err != nil {
@ -47,10 +48,10 @@ func NewCollection(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.
// layer view cursor down event should trigger an update in the file tree
controller.Layer.AddLayerChangeListener(controller.onLayerChange)
controller.Status = view.NewStatusView("status", g)
controller.lookup[controller.Status.Name()] = controller.Status
controller.Help = view.NewHelpView("status", g)
controller.lookup[controller.Help.Name()] = controller.Help
// set the layer view as the first selected view
controller.Status.SetCurrentView(controller.Layer)
controller.Help.SetCurrentView(controller.Layer)
// update the status pane when a filetree option is changed by the user
controller.Tree.AddViewOptionChangeListener(controller.onFileTreeViewOptionChange)
@ -79,11 +80,11 @@ func NewCollection(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.
}
func (c *Controller) onFileTreeViewOptionChange() error {
err := c.Status.Update()
err := c.Help.Update()
if err != nil {
return err
}
return c.Status.Render()
return c.Help.Render()
}
func (c *Controller) onFilterEdit(filter string) error {
@ -173,10 +174,10 @@ func (c *Controller) ToggleView() (err error) {
v := c.gui.CurrentView()
if v == nil || v.Name() == c.Layer.Name() {
_, err = c.gui.SetCurrentView(c.Tree.Name())
c.Status.SetCurrentView(c.Tree)
c.Help.SetCurrentView(c.Tree)
} else {
_, err = c.gui.SetCurrentView(c.Layer.Name())
c.Status.SetCurrentView(c.Layer)
c.Help.SetCurrentView(c.Layer)
}
if err != nil {

View file

@ -0,0 +1,58 @@
package layout
import (
"fmt"
"github.com/wagoodman/dive/runtime/ui/view"
)
type Vertical struct {
visible bool
width int
elements []View
}
// how does overrun work? which view gets precidence? how does max possible height work?
func NewVerticalLayout() *Vertical {
return &Vertical{
visible: true,
width: view.WidthFull,
elements: make([]View, 0),
}
}
func (v Vertical) SetWidth(w int) {
v.width = w
}
func (v *Vertical) AddView(sub View) error {
for _, element := range v.elements {
if element.Name() == sub.Name() {
return fmt.Errorf("view already added")
}
}
v.elements = append(v.elements, sub)
return nil
}
func (v *Vertical) Name() string {
return view.IdentityNone
}
func (v *Vertical) IsVisible() bool {
return v.visible
}
func (v *Vertical) Height() (height int) {
for _, element := range v.elements {
height += element.Height()
if height == view.HeightFull {
return view.HeightFull
}
}
return
}
func (v *Vertical) Width() int {
return v.width
}

10
runtime/ui/layout/view.go Normal file
View file

@ -0,0 +1,10 @@
package layout
import (
"github.com/wagoodman/dive/runtime/ui/view"
)
type View interface {
view.Identifiable
view.Dimensional
}

View file

@ -6,13 +6,15 @@ import (
"github.com/spf13/viper"
)
type layoutManager struct {
var lastY, lastX int
type LayoutManager struct {
fileTreeSplitRatio float64
controllers *Controller
controller *Controller
}
// todo: this needs a major refactor (derive layout from view obj info, which should not live here)
func newLayoutManager(c *Controller) *layoutManager {
func NewLayoutManager(c *Controller) *LayoutManager {
fileTreeSplitRatio := viper.GetFloat64("filetree.pane-width")
if fileTreeSplitRatio >= 1 || fileTreeSplitRatio <= 0 {
@ -20,14 +22,14 @@ func newLayoutManager(c *Controller) *layoutManager {
fileTreeSplitRatio = 0.5
}
return &layoutManager{
return &LayoutManager{
fileTreeSplitRatio: fileTreeSplitRatio,
controllers: c,
controller: c,
}
}
// IsNewView determines if a view has already been created based on the set of errors given (a bit hokie)
func IsNewView(errs ...error) bool {
// isNewView determines if a view has already been created based on the set of errors given (a bit hokie)
func isNewView(errs ...error) bool {
for _, err := range errs {
if err == nil {
return false
@ -41,7 +43,7 @@ func IsNewView(errs ...error) bool {
// layout defines the definition of the window pane size and placement relations to one another. This
// is invoked at application start and whenever the screen dimensions change.
func (lm *layoutManager) layout(g *gocui.Gui) error {
func (lm *LayoutManager) Layout(g *gocui.Gui) error {
// TODO: this logic should be refactored into an abstraction that takes care of the math for us
maxX, maxY := g.Size()
@ -64,12 +66,12 @@ func (lm *layoutManager) layout(g *gocui.Gui) error {
headerRows := 2
filterBarHeight := 1
statusBarHeight := 1
helpBarHeight := 1
statusBarIndex := 1
helpBarIndex := 1
filterBarIndex := 2
layersHeight := len(lm.controllers.Layer.Layers) + headerRows + 1 // layers + header + base image layer row
layersHeight := len(lm.controller.Layer.Layers) + headerRows + 1 // layers + header + base image layer row
maxLayerHeight := int(0.75 * float64(maxY))
if layersHeight > maxLayerHeight {
layersHeight = maxLayerHeight
@ -78,7 +80,7 @@ func (lm *layoutManager) layout(g *gocui.Gui) error {
var view, header *gocui.View
var viewErr, headerErr, err error
if !lm.controllers.Filter.IsVisible() {
if !lm.controller.Filter.IsVisible() {
bottomRows--
filterBarHeight = 0
}
@ -93,21 +95,21 @@ func (lm *layoutManager) layout(g *gocui.Gui) error {
}
// Layers
view, viewErr = g.SetView(lm.controllers.Layer.Name(), -1, -1+headerRows, splitCols, layersHeight)
header, headerErr = g.SetView(lm.controllers.Layer.Name()+"header", -1, -1, splitCols, headerRows)
if IsNewView(viewErr, headerErr) {
err = lm.controllers.Layer.Setup(view, header)
view, viewErr = g.SetView(lm.controller.Layer.Name(), -1, -1+headerRows, splitCols, layersHeight)
header, headerErr = g.SetView(lm.controller.Layer.Name()+"header", -1, -1, splitCols, headerRows)
if isNewView(viewErr, headerErr) {
err = lm.controller.Layer.Setup(view, header)
if err != nil {
logrus.Error("unable to setup layer controller", err)
return err
}
if _, err = g.SetCurrentView(lm.controllers.Layer.Name()); err != nil {
if _, err = g.SetCurrentView(lm.controller.Layer.Name()); err != nil {
logrus.Error("unable to set view to layer", err)
return err
}
// since we are selecting the view, we should rerender to indicate it is selected
err = lm.controllers.Layer.Render()
err = lm.controller.Layer.Render()
if err != nil {
logrus.Error("unable to render layer view", err)
return err
@ -115,10 +117,10 @@ func (lm *layoutManager) layout(g *gocui.Gui) error {
}
// Details
view, viewErr = g.SetView(lm.controllers.Details.Name(), -1, -1+layersHeight+headerRows, splitCols, maxY-bottomRows)
header, headerErr = g.SetView(lm.controllers.Details.Name()+"header", -1, -1+layersHeight, splitCols, layersHeight+headerRows)
if IsNewView(viewErr, headerErr) {
err = lm.controllers.Details.Setup(view, header)
view, viewErr = g.SetView(lm.controller.Details.Name(), -1, -1+layersHeight+headerRows, splitCols, maxY-bottomRows)
header, headerErr = g.SetView(lm.controller.Details.Name()+"header", -1, -1+layersHeight, splitCols, layersHeight+headerRows)
if isNewView(viewErr, headerErr) {
err = lm.controller.Details.Setup(view, header)
if err != nil {
return err
}
@ -126,39 +128,39 @@ func (lm *layoutManager) layout(g *gocui.Gui) error {
// Filetree
offset := 0
if !lm.controllers.Tree.AreAttributesVisible() {
if !lm.controller.Tree.AreAttributesVisible() {
offset = 1
}
view, viewErr = g.SetView(lm.controllers.Tree.Name(), splitCols, -1+headerRows-offset, debugCols, maxY-bottomRows)
header, headerErr = g.SetView(lm.controllers.Tree.Name()+"header", splitCols, -1, debugCols, headerRows-offset)
if IsNewView(viewErr, headerErr) {
err = lm.controllers.Tree.Setup(view, header)
view, viewErr = g.SetView(lm.controller.Tree.Name(), splitCols, -1+headerRows-offset, debugCols, maxY-bottomRows)
header, headerErr = g.SetView(lm.controller.Tree.Name()+"header", splitCols, -1, debugCols, headerRows-offset)
if isNewView(viewErr, headerErr) {
err = lm.controller.Tree.Setup(view, header)
if err != nil {
logrus.Error("unable to setup tree controller", err)
return err
}
}
err = lm.controllers.Tree.OnLayoutChange(resized)
err = lm.controller.Tree.OnLayoutChange(resized)
if err != nil {
logrus.Error("unable to setup layer controller onLayoutChange", err)
return err
}
// Status Bar
view, viewErr = g.SetView(lm.controllers.Status.Name(), -1, maxY-statusBarHeight-statusBarIndex, maxX, maxY-(statusBarIndex-1))
if IsNewView(viewErr, headerErr) {
err = lm.controllers.Status.Setup(view, nil)
// Help Bar
view, viewErr = g.SetView(lm.controller.Help.Name(), -1, maxY-helpBarHeight-helpBarIndex, maxX, maxY-(helpBarIndex-1))
if isNewView(viewErr, headerErr) {
err = lm.controller.Help.Setup(view, nil)
if err != nil {
logrus.Error("unable to setup status controller", err)
logrus.Error("unable to setup help controller", err)
return err
}
}
// Filter Bar
view, viewErr = g.SetView(lm.controllers.Filter.Name(), len(lm.controllers.Filter.HeaderStr())-1, maxY-filterBarHeight-filterBarIndex, maxX, maxY-(filterBarIndex-1))
header, headerErr = g.SetView(lm.controllers.Filter.Name()+"header", -1, maxY-filterBarHeight-filterBarIndex, len(lm.controllers.Filter.HeaderStr()), maxY-(filterBarIndex-1))
if IsNewView(viewErr, headerErr) {
err = lm.controllers.Filter.Setup(view, header)
view, viewErr = g.SetView(lm.controller.Filter.Name(), len(lm.controller.Filter.HeaderStr())-1, maxY-filterBarHeight-filterBarIndex, maxX, maxY-(filterBarIndex-1))
header, headerErr = g.SetView(lm.controller.Filter.Name()+"header", -1, maxY-filterBarHeight-filterBarIndex, len(lm.controller.Filter.HeaderStr()), maxY-(filterBarIndex-1))
if isNewView(viewErr, headerErr) {
err = lm.controller.Filter.Setup(view, header)
if err != nil {
logrus.Error("unable to setup filter controller", err)
return err

View file

@ -43,6 +43,14 @@ func NewDetailsView(name string, gui *gocui.Gui, efficiency float64, inefficienc
return controller
}
func (c *Details) Height() int {
return HeightFull
}
func (c *Details) Width() int {
return WidthFull
}
func (c *Details) Name() string {
return c.name
}

View file

@ -175,6 +175,14 @@ func (c *FileTree) Setup(v *gocui.View, header *gocui.View) error {
return nil
}
func (c *FileTree) Height() int {
return HeightFull
}
func (c *FileTree) Width() int {
return WidthFull
}
// IsVisible indicates if the file tree view pane is currently initialized
func (c *FileTree) IsVisible() bool {
return c != nil

View file

@ -43,6 +43,15 @@ func (c *Filter) AddFilterEditListener(listener ...FilterEditListener) {
c.filterEditListeners = append(c.filterEditListeners, listener...)
}
func (c *Filter) Height() int {
return 1
}
func (c *Filter) Width() int {
return WidthFull
}
func (c *Filter) Name() string {
return c.name
}

View file

@ -10,21 +10,21 @@ import (
"github.com/jroimartin/gocui"
)
// Status holds the UI objects and data models for populating the bottom-most pane. Specifically the panel
// Help holds the UI objects and data models for populating the bottom-most pane. Specifically the panel
// shows the user a set of possible actions to take in the window and currently selected pane.
type Status struct {
type Help struct {
name string
gui *gocui.Gui
view *gocui.View
selectedView Renderer
selectedView View
helpKeys []*key.Binding
}
// NewStatusView creates a new view object attached the the global [gocui] screen object.
func NewStatusView(name string, gui *gocui.Gui) (controller *Status) {
controller = new(Status)
// NewHelpView creates a new view object attached the the global [gocui] screen object.
func NewHelpView(name string, gui *gocui.Gui) (controller *Help) {
controller = new(Help)
// populate main fields
controller.name = name
@ -34,20 +34,28 @@ func NewStatusView(name string, gui *gocui.Gui) (controller *Status) {
return controller
}
func (c *Status) SetCurrentView(r Renderer) {
func (c *Help) SetCurrentView(r View) {
c.selectedView = r
}
func (c *Status) Name() string {
func (c *Help) Height() int {
return 1
}
func (c *Help) Width() int {
return WidthFull
}
func (c *Help) Name() string {
return c.name
}
func (c *Status) AddHelpKeys(keys ...*key.Binding) {
func (c *Help) AddHelpKeys(keys ...*key.Binding) {
c.helpKeys = append(c.helpKeys, keys...)
}
// Setup initializes the UI concerns within the context of a global [gocui] view object.
func (c *Status) Setup(v *gocui.View, header *gocui.View) error {
func (c *Help) Setup(v *gocui.View, header *gocui.View) error {
// set controller options
c.view = v
@ -57,27 +65,27 @@ func (c *Status) Setup(v *gocui.View, header *gocui.View) error {
}
// IsVisible indicates if the status view pane is currently initialized.
func (c *Status) IsVisible() bool {
func (c *Help) IsVisible() bool {
return c != nil
}
// CursorDown moves the cursor down in the details pane (currently indicates nothing).
func (c *Status) CursorDown() error {
func (c *Help) CursorDown() error {
return nil
}
// CursorUp moves the cursor up in the details pane (currently indicates nothing).
func (c *Status) CursorUp() error {
func (c *Help) CursorUp() error {
return nil
}
// Update refreshes the state objects for future rendering (currently does nothing).
func (c *Status) Update() error {
func (c *Help) Update() error {
return nil
}
// Render flushes the state objects to the screen.
func (c *Status) Render() error {
func (c *Help) Render() error {
c.gui.Update(func(g *gocui.Gui) error {
c.view.Clear()
@ -97,7 +105,7 @@ func (c *Status) Render() error {
}
// KeyHelp indicates all the possible global actions a user can take when any pane is selected.
func (c *Status) KeyHelp() string {
func (c *Help) KeyHelp() string {
var help string
for _, binding := range c.helpKeys {
help += binding.RenderKeyHelp()

View file

@ -79,6 +79,15 @@ func (c *Layer) notifyLayerChangeListeners() error {
return nil
}
func (c *Layer) Height() int {
return HeightFull
}
func (c *Layer) Width() int {
return WidthFull
}
func (c *Layer) Name() string {
return c.name
}

View file

@ -1,9 +0,0 @@
package view
// Controller defines the a renderable terminal screen pane.
type Renderer interface {
Update() error
Render() error
IsVisible() bool
KeyHelp() string
}

28
runtime/ui/view/view.go Normal file
View file

@ -0,0 +1,28 @@
package view
const (
HeightFull = -1
WidthFull = -1
IdentityNone = ""
)
type Identifiable interface {
Name() string
}
type Dimensional interface {
IsVisible() bool
Height() int
Width() int
}
// View defines the an element with state that can be updated, queried if visible, and render elements to the screen
type View interface {
Identifiable
Dimensional
Update() error
Render() error
KeyHelp() string
}