summaryrefslogtreecommitdiff
path: root/examples/go-dashboard/src/github.com/mum4k/termdash/container
diff options
context:
space:
mode:
Diffstat (limited to 'examples/go-dashboard/src/github.com/mum4k/termdash/container')
-rw-r--r--examples/go-dashboard/src/github.com/mum4k/termdash/container/container.go471
-rw-r--r--examples/go-dashboard/src/github.com/mum4k/termdash/container/draw.go175
-rw-r--r--examples/go-dashboard/src/github.com/mum4k/termdash/container/focus.go116
-rw-r--r--examples/go-dashboard/src/github.com/mum4k/termdash/container/options.go817
-rw-r--r--examples/go-dashboard/src/github.com/mum4k/termdash/container/traversal.go86
5 files changed, 0 insertions, 1665 deletions
diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/container/container.go b/examples/go-dashboard/src/github.com/mum4k/termdash/container/container.go
deleted file mode 100644
index 54cef7856..000000000
--- a/examples/go-dashboard/src/github.com/mum4k/termdash/container/container.go
+++ /dev/null
@@ -1,471 +0,0 @@
-// Copyright 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/*
-Package container defines a type that wraps other containers or widgets.
-
-The container supports splitting container into sub containers, defining
-container styles and placing widgets. The container also creates and manages
-canvases assigned to the placed widgets.
-*/
-package container
-
-import (
- "errors"
- "fmt"
- "image"
- "sync"
-
- "github.com/mum4k/termdash/linestyle"
- "github.com/mum4k/termdash/private/alignfor"
- "github.com/mum4k/termdash/private/area"
- "github.com/mum4k/termdash/private/event"
- "github.com/mum4k/termdash/terminal/terminalapi"
- "github.com/mum4k/termdash/widgetapi"
-)
-
-// Container wraps either sub containers or widgets and positions them on the
-// terminal.
-// This is thread-safe.
-type Container struct {
- // parent is the parent container, nil if this is the root container.
- parent *Container
- // The sub containers, if these aren't nil, the widget must be.
- first *Container
- second *Container
-
- // term is the terminal this container is placed on.
- // All containers in the tree share the same terminal.
- term terminalapi.Terminal
-
- // focusTracker tracks the active (focused) container.
- // All containers in the tree share the same tracker.
- focusTracker *focusTracker
-
- // area is the area of the terminal this container has access to.
- // Initialized the first time Draw is called.
- area image.Rectangle
-
- // opts are the options provided to the container.
- opts *options
-
- // clearNeeded indicates if the terminal needs to be cleared next time we
- // are clearNeeded the container.
- // This is required if the container was updated and thus the layout might
- // have changed.
- clearNeeded bool
-
- // mu protects the container tree.
- // All containers in the tree share the same lock.
- mu *sync.Mutex
-}
-
-// String represents the container metadata in a human readable format.
-// Implements fmt.Stringer.
-func (c *Container) String() string {
- return fmt.Sprintf("Container@%p{parent:%p, first:%p, second:%p, area:%+v}", c, c.parent, c.first, c.second, c.area)
-}
-
-// New returns a new root container that will use the provided terminal and
-// applies the provided options.
-func New(t terminalapi.Terminal, opts ...Option) (*Container, error) {
- root := &Container{
- term: t,
- opts: newOptions( /* parent = */ nil),
- mu: &sync.Mutex{},
- }
-
- // Initially the root is focused.
- root.focusTracker = newFocusTracker(root)
- if err := applyOptions(root, opts...); err != nil {
- return nil, err
- }
- if err := validateOptions(root); err != nil {
- return nil, err
- }
- return root, nil
-}
-
-// newChild creates a new child container of the given parent.
-func newChild(parent *Container, opts []Option) (*Container, error) {
- child := &Container{
- parent: parent,
- term: parent.term,
- focusTracker: parent.focusTracker,
- opts: newOptions(parent.opts),
- mu: parent.mu,
- }
- if err := applyOptions(child, opts...); err != nil {
- return nil, err
- }
- return child, nil
-}
-
-// hasBorder determines if this container has a border.
-func (c *Container) hasBorder() bool {
- return c.opts.border != linestyle.None
-}
-
-// hasWidget determines if this container has a widget.
-func (c *Container) hasWidget() bool {
- return c.opts.widget != nil
-}
-
-// usable returns the usable area in this container.
-// This depends on whether the container has a border, etc.
-func (c *Container) usable() image.Rectangle {
- if c.hasBorder() {
- return area.ExcludeBorder(c.area)
- }
- return c.area
-}
-
-// widgetArea returns the area in the container that is available for the
-// widget's canvas. Takes the container border, widget's requested maximum size
-// and ratio and container's alignment into account.
-// Returns a zero area if the container has no widget.
-func (c *Container) widgetArea() (image.Rectangle, error) {
- if !c.hasWidget() {
- return image.ZR, nil
- }
-
- padded, err := c.opts.padding.apply(c.usable())
- if err != nil {
- return image.ZR, err
- }
- wOpts := c.opts.widget.Options()
-
- adjusted := padded
- if maxX := wOpts.MaximumSize.X; maxX > 0 && adjusted.Dx() > maxX {
- adjusted.Max.X -= adjusted.Dx() - maxX
- }
- if maxY := wOpts.MaximumSize.Y; maxY > 0 && adjusted.Dy() > maxY {
- adjusted.Max.Y -= adjusted.Dy() - maxY
- }
-
- if wOpts.Ratio.X > 0 && wOpts.Ratio.Y > 0 {
- adjusted = area.WithRatio(adjusted, wOpts.Ratio)
- }
- aligned, err := alignfor.Rectangle(padded, adjusted, c.opts.hAlign, c.opts.vAlign)
- if err != nil {
- return image.ZR, err
- }
- return aligned, nil
-}
-
-// split splits the container's usable area into child areas.
-// Panics if the container isn't configured for a split.
-func (c *Container) split() (image.Rectangle, image.Rectangle, error) {
- ar, err := c.opts.padding.apply(c.usable())
- if err != nil {
- return image.ZR, image.ZR, err
- }
- if c.opts.splitFixed > DefaultSplitFixed {
- if c.opts.split == splitTypeVertical {
- return area.VSplitCells(ar, c.opts.splitFixed)
- }
- return area.HSplitCells(ar, c.opts.splitFixed)
- }
-
- if c.opts.split == splitTypeVertical {
- return area.VSplit(ar, c.opts.splitPercent)
- }
- return area.HSplit(ar, c.opts.splitPercent)
-}
-
-// createFirst creates and returns the first sub container of this container.
-func (c *Container) createFirst(opts []Option) error {
- first, err := newChild(c, opts)
- if err != nil {
- return err
- }
- c.first = first
- return nil
-}
-
-// createSecond creates and returns the second sub container of this container.
-func (c *Container) createSecond(opts []Option) error {
- second, err := newChild(c, opts)
- if err != nil {
- return err
- }
- c.second = second
- return nil
-}
-
-// Draw draws this container and all of its sub containers.
-func (c *Container) Draw() error {
- c.mu.Lock()
- defer c.mu.Unlock()
-
- if c.clearNeeded {
- if err := c.term.Clear(); err != nil {
- return fmt.Errorf("term.Clear => error: %v", err)
- }
- c.clearNeeded = false
- }
-
- // Update the area we are tracking for focus in case the terminal size
- // changed.
- ar, err := area.FromSize(c.term.Size())
- if err != nil {
- return err
- }
- c.focusTracker.updateArea(ar)
- return drawTree(c)
-}
-
-// Update updates container with the specified id by setting the provided
-// options. This can be used to perform dynamic layout changes, i.e. anything
-// between replacing the widget in the container and completely changing the
-// layout and splits.
-// The argument id must match exactly one container with that was created with
-// matching ID() option. The argument id must not be an empty string.
-func (c *Container) Update(id string, opts ...Option) error {
- c.mu.Lock()
- defer c.mu.Unlock()
-
- target, err := findID(c, id)
- if err != nil {
- return err
- }
- c.clearNeeded = true
-
- if err := applyOptions(target, opts...); err != nil {
- return err
- }
- if err := validateOptions(c); err != nil {
- return err
- }
-
- // The currently focused container might not be reachable anymore, because
- // it was under the target. If that is so, move the focus up to the target.
- if !c.focusTracker.reachableFrom(c) {
- c.focusTracker.setActive(target)
- }
- return nil
-}
-
-// updateFocus processes the mouse event and determines if it changes the
-// focused container.
-// Caller must hold c.mu.
-func (c *Container) updateFocus(m *terminalapi.Mouse) {
- target := pointCont(c, m.Position)
- if target == nil { // Ignore mouse clicks where no containers are.
- return
- }
- c.focusTracker.mouse(target, m)
-}
-
-// processEvent processes events delivered to the container.
-func (c *Container) processEvent(ev terminalapi.Event) error {
- // This is done in two stages.
- // 1) under lock we traverse the container and identify all targets
- // (widgets) that should receive the event.
- // 2) lock is released and events are delivered to the widgets. Widgets
- // themselves are thread-safe. Lock must be releases when delivering,
- // because some widgets might try to mutate the container when they
- // receive the event, like dynamically change the layout.
- c.mu.Lock()
- sendFn, err := c.prepareEvTargets(ev)
- c.mu.Unlock()
- if err != nil {
- return err
- }
- return sendFn()
-}
-
-// prepareEvTargets returns a closure, that when called delivers the event to
-// widgets that registered for it.
-// Also processes the event on behalf of the container (tracks keyboard focus).
-// Caller must hold c.mu.
-func (c *Container) prepareEvTargets(ev terminalapi.Event) (func() error, error) {
- switch e := ev.(type) {
- case *terminalapi.Mouse:
- c.updateFocus(ev.(*terminalapi.Mouse))
-
- targets, err := c.mouseEvTargets(e)
- if err != nil {
- return nil, err
- }
- return func() error {
- for _, mt := range targets {
- if err := mt.widget.Mouse(mt.ev); err != nil {
- return err
- }
- }
- return nil
- }, nil
-
- case *terminalapi.Keyboard:
- targets := c.keyEvTargets()
- return func() error {
- for _, w := range targets {
- if err := w.Keyboard(e); err != nil {
- return err
- }
- }
- return nil
- }, nil
-
- default:
- return nil, fmt.Errorf("container received an unsupported event type %T", ev)
- }
-}
-
-// keyEvTargets returns those widgets found in the container that should
-// receive this keyboard event.
-// Caller must hold c.mu.
-func (c *Container) keyEvTargets() []widgetapi.Widget {
- var (
- errStr string
- widgets []widgetapi.Widget
- )
-
- // All the widgets that should receive this event.
- // For now stable ordering (preOrder).
- preOrder(c, &errStr, visitFunc(func(cur *Container) error {
- if !cur.hasWidget() {
- return nil
- }
-
- wOpt := cur.opts.widget.Options()
- switch wOpt.WantKeyboard {
- case widgetapi.KeyScopeNone:
- // Widget doesn't want any keyboard events.
- return nil
-
- case widgetapi.KeyScopeFocused:
- if cur.focusTracker.isActive(cur) {
- widgets = append(widgets, cur.opts.widget)
- }
-
- case widgetapi.KeyScopeGlobal:
- widgets = append(widgets, cur.opts.widget)
- }
- return nil
- }))
- return widgets
-}
-
-// mouseEvTarget contains a mouse event adjusted relative to the widget's area
-// and the widget that should receive it.
-type mouseEvTarget struct {
- // widget is the widget that should receive the mouse event.
- widget widgetapi.Widget
- // ev is the adjusted mouse event.
- ev *terminalapi.Mouse
-}
-
-// newMouseEvTarget returns a new newMouseEvTarget.
-func newMouseEvTarget(w widgetapi.Widget, wArea image.Rectangle, ev *terminalapi.Mouse) *mouseEvTarget {
- return &mouseEvTarget{
- widget: w,
- ev: adjustMouseEv(ev, wArea),
- }
-}
-
-// mouseEvTargets returns those widgets found in the container that should
-// receive this mouse event.
-// Caller must hold c.mu.
-func (c *Container) mouseEvTargets(m *terminalapi.Mouse) ([]*mouseEvTarget, error) {
- var (
- errStr string
- widgets []*mouseEvTarget
- )
-
- // All the widgets that should receive this event.
- // For now stable ordering (preOrder).
- preOrder(c, &errStr, visitFunc(func(cur *Container) error {
- if !cur.hasWidget() {
- return nil
- }
-
- wOpts := cur.opts.widget.Options()
- wa, err := cur.widgetArea()
- if err != nil {
- return err
- }
-
- switch wOpts.WantMouse {
- case widgetapi.MouseScopeNone:
- // Widget doesn't want any mouse events.
- return nil
-
- case widgetapi.MouseScopeWidget:
- // Only if the event falls inside of the widget's canvas.
- if m.Position.In(wa) {
- widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m))
- }
-
- case widgetapi.MouseScopeContainer:
- // Only if the event falls inside the widget's parent container.
- if m.Position.In(cur.area) {
- widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m))
- }
-
- case widgetapi.MouseScopeGlobal:
- // Widget wants all mouse events.
- widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m))
- }
- return nil
- }))
-
- if errStr != "" {
- return nil, errors.New(errStr)
- }
- return widgets, nil
-}
-
-// Subscribe tells the container to subscribe itself and widgets to the
-// provided event distribution system.
-// This method is private to termdash, stability isn't guaranteed and changes
-// won't be backward compatible.
-func (c *Container) Subscribe(eds *event.DistributionSystem) {
- c.mu.Lock()
- defer c.mu.Unlock()
-
- // maxReps is the maximum number of repetitive events towards widgets
- // before we throttle them.
- const maxReps = 10
-
- // Subscriber the container itself in order to track keyboard focus.
- want := []terminalapi.Event{
- &terminalapi.Keyboard{},
- &terminalapi.Mouse{},
- }
- eds.Subscribe(want, func(ev terminalapi.Event) {
- if err := c.processEvent(ev); err != nil {
- eds.Event(terminalapi.NewErrorf("failed to process event %v: %v", ev, err))
- }
- }, event.MaxRepetitive(maxReps))
-}
-
-// adjustMouseEv adjusts the mouse event relative to the widget area.
-func adjustMouseEv(m *terminalapi.Mouse, wArea image.Rectangle) *terminalapi.Mouse {
- // The sent mouse coordinate is relative to the widget canvas, i.e. zero
- // based, even though the widget might not be in the top left corner on the
- // terminal.
- offset := wArea.Min
- if m.Position.In(wArea) {
- return &terminalapi.Mouse{
- Position: m.Position.Sub(offset),
- Button: m.Button,
- }
- }
- return &terminalapi.Mouse{
- Position: image.Point{-1, -1},
- Button: m.Button,
- }
-}
diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/container/draw.go b/examples/go-dashboard/src/github.com/mum4k/termdash/container/draw.go
deleted file mode 100644
index d186b1272..000000000
--- a/examples/go-dashboard/src/github.com/mum4k/termdash/container/draw.go
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package container
-
-// draw.go contains logic to draw containers and the contained widgets.
-
-import (
- "errors"
- "fmt"
- "image"
-
- "github.com/mum4k/termdash/cell"
- "github.com/mum4k/termdash/private/area"
- "github.com/mum4k/termdash/private/canvas"
- "github.com/mum4k/termdash/private/draw"
- "github.com/mum4k/termdash/widgetapi"
-)
-
-// drawTree draws this container and all of its sub containers.
-func drawTree(c *Container) error {
- var errStr string
-
- root := rootCont(c)
- size := root.term.Size()
- ar, err := root.opts.margin.apply(image.Rect(0, 0, size.X, size.Y))
- if err != nil {
- return err
- }
- root.area = ar
-
- preOrder(root, &errStr, visitFunc(func(c *Container) error {
- first, second, err := c.split()
- if err != nil {
- return err
- }
- if c.first != nil {
- ar, err := c.first.opts.margin.apply(first)
- if err != nil {
- return err
- }
- c.first.area = ar
- }
-
- if c.second != nil {
- ar, err := c.second.opts.margin.apply(second)
- if err != nil {
- return err
- }
- c.second.area = ar
- }
- return drawCont(c)
- }))
- if errStr != "" {
- return errors.New(errStr)
- }
- return nil
-}
-
-// drawBorder draws the border around the container if requested.
-func drawBorder(c *Container) error {
- if !c.hasBorder() {
- return nil
- }
-
- cvs, err := canvas.New(c.area)
- if err != nil {
- return err
- }
-
- ar, err := area.FromSize(cvs.Size())
- if err != nil {
- return err
- }
-
- var cOpts []cell.Option
- if c.focusTracker.isActive(c) {
- cOpts = append(cOpts, cell.FgColor(c.opts.inherited.focusedColor))
- } else {
- cOpts = append(cOpts, cell.FgColor(c.opts.inherited.borderColor))
- }
-
- if err := draw.Border(cvs, ar,
- draw.BorderLineStyle(c.opts.border),
- draw.BorderTitle(c.opts.borderTitle, draw.OverrunModeThreeDot, cOpts...),
- draw.BorderTitleAlign(c.opts.borderTitleHAlign),
- draw.BorderCellOpts(cOpts...),
- ); err != nil {
- return err
- }
- return cvs.Apply(c.term)
-}
-
-// drawWidget requests the widget to draw on the canvas.
-func drawWidget(c *Container) error {
- widgetArea, err := c.widgetArea()
- if err != nil {
- return err
- }
- if widgetArea == image.ZR {
- return nil
- }
-
- if !c.hasWidget() {
- return nil
- }
-
- needSize := image.Point{1, 1}
- wOpts := c.opts.widget.Options()
- if wOpts.MinimumSize.X > 0 && wOpts.MinimumSize.Y > 0 {
- needSize = wOpts.MinimumSize
- }
-
- if widgetArea.Dx() < needSize.X || widgetArea.Dy() < needSize.Y {
- return drawResize(c, c.usable())
- }
-
- cvs, err := canvas.New(widgetArea)
- if err != nil {
- return err
- }
-
- meta := &widgetapi.Meta{
- Focused: c.focusTracker.isActive(c),
- }
-
- if err := c.opts.widget.Draw(cvs, meta); err != nil {
- return err
- }
- return cvs.Apply(c.term)
-}
-
-// drawResize draws an unicode character indicating that the size is too small to draw this container.
-// Does nothing if the size is smaller than one cell, leaving no space for the character.
-func drawResize(c *Container, area image.Rectangle) error {
- if area.Dx() < 1 || area.Dy() < 1 {
- return nil
- }
-
- cvs, err := canvas.New(area)
- if err != nil {
- return err
- }
- if err := draw.ResizeNeeded(cvs); err != nil {
- return err
- }
- return cvs.Apply(c.term)
-}
-
-// drawCont draws the container and its widget.
-func drawCont(c *Container) error {
- if us := c.usable(); us.Dx() <= 0 || us.Dy() <= 0 {
- return drawResize(c, c.area)
- }
-
- if err := drawBorder(c); err != nil {
- return fmt.Errorf("unable to draw container border: %v", err)
- }
-
- if err := drawWidget(c); err != nil {
- return fmt.Errorf("unable to draw widget %T: %v", c.opts.widget, err)
- }
- return nil
-}
diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/container/focus.go b/examples/go-dashboard/src/github.com/mum4k/termdash/container/focus.go
deleted file mode 100644
index 4320eea73..000000000
--- a/examples/go-dashboard/src/github.com/mum4k/termdash/container/focus.go
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package container
-
-// focus.go contains code that tracks the focused container.
-
-import (
- "image"
-
- "github.com/mum4k/termdash/mouse"
- "github.com/mum4k/termdash/private/button"
- "github.com/mum4k/termdash/terminal/terminalapi"
-)
-
-// pointCont finds the top-most (on the screen) container whose area contains
-// the given point. Returns nil if none of the containers in the tree contain
-// this point.
-func pointCont(c *Container, p image.Point) *Container {
- var (
- errStr string
- cont *Container
- )
- postOrder(rootCont(c), &errStr, visitFunc(func(c *Container) error {
- if p.In(c.area) && cont == nil {
- cont = c
- }
- return nil
- }))
- return cont
-}
-
-// focusTracker tracks the active (focused) container.
-// This is not thread-safe, the implementation assumes that the owner of
-// focusTracker performs locking.
-type focusTracker struct {
- // container is the currently focused container.
- container *Container
-
- // candidate is the container that might become focused next. I.e. we got
- // a mouse click and now waiting for a release or a timeout.
- candidate *Container
-
- // buttonFSM is a state machine tracking mouse clicks in containers and
- // moving focus from one container to the next.
- buttonFSM *button.FSM
-}
-
-// newFocusTracker returns a new focus tracker with focus set at the provided
-// container.
-func newFocusTracker(c *Container) *focusTracker {
- return &focusTracker{
- container: c,
- // Mouse FSM tracking clicks inside the entire area for the root
- // container.
- buttonFSM: button.NewFSM(mouse.ButtonLeft, c.area),
- }
-}
-
-// isActive determines if the provided container is the currently active container.
-func (ft *focusTracker) isActive(c *Container) bool {
- return ft.container == c
-}
-
-// setActive sets the currently active container to the one provided.
-func (ft *focusTracker) setActive(c *Container) {
- ft.container = c
-}
-
-// mouse identifies mouse events that change the focused container and track
-// the focused container in the tree.
-// The argument c is the container onto which the mouse event landed.
-func (ft *focusTracker) mouse(target *Container, m *terminalapi.Mouse) {
- clicked, bs := ft.buttonFSM.Event(m)
- switch {
- case bs == button.Down:
- ft.candidate = target
- case bs == button.Up && clicked:
- if target == ft.candidate {
- ft.container = target
- }
- }
-}
-
-// updateArea updates the area that the focus tracker considers active for
-// mouse clicks.
-func (ft *focusTracker) updateArea(ar image.Rectangle) {
- ft.buttonFSM.UpdateArea(ar)
-}
-
-// reachableFrom asserts whether the currently focused container is reachable
-// from the provided node in the tree.
-func (ft *focusTracker) reachableFrom(node *Container) bool {
- var (
- errStr string
- reachable bool
- )
- preOrder(node, &errStr, visitFunc(func(c *Container) error {
- if c == ft.container {
- reachable = true
- }
- return nil
- }))
- return reachable
-}
diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/container/options.go b/examples/go-dashboard/src/github.com/mum4k/termdash/container/options.go
deleted file mode 100644
index 2d34af4db..000000000
--- a/examples/go-dashboard/src/github.com/mum4k/termdash/container/options.go
+++ /dev/null
@@ -1,817 +0,0 @@
-// Copyright 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package container
-
-// options.go defines container options.
-
-import (
- "errors"
- "fmt"
- "image"
-
- "github.com/mum4k/termdash/align"
- "github.com/mum4k/termdash/cell"
- "github.com/mum4k/termdash/linestyle"
- "github.com/mum4k/termdash/private/area"
- "github.com/mum4k/termdash/widgetapi"
-)
-
-// applyOptions applies the options to the container and validates them.
-func applyOptions(c *Container, opts ...Option) error {
- for _, opt := range opts {
- if err := opt.set(c); err != nil {
- return err
- }
- }
- return nil
-}
-
-// ensure all the container identifiers are either empty or unique.
-func validateIds(c *Container, seen map[string]bool) error {
- if c.opts.id == "" {
- return nil
- } else if seen[c.opts.id] {
- return fmt.Errorf("duplicate container ID %q", c.opts.id)
- }
- seen[c.opts.id] = true
-
- return nil
-}
-
-// ensure all the container only have one split modifier.
-func validateSplits(c *Container) error {
- if c.opts.splitFixed > DefaultSplitFixed && c.opts.splitPercent != DefaultSplitPercent {
- return fmt.Errorf(
- "only one of splitFixed `%v` and splitPercent `%v` is allowed to be set per container",
- c.opts.splitFixed,
- c.opts.splitPercent,
- )
- }
-
- return nil
-}
-
-// validateOptions validates options set in the container tree.
-func validateOptions(c *Container) error {
- var errStr string
- seenID := map[string]bool{}
- preOrder(c, &errStr, func(c *Container) error {
- if err := validateIds(c, seenID); err != nil {
- return err
- }
- if err := validateSplits(c); err != nil {
- return err
- }
-
- return nil
- })
- if errStr != "" {
- return errors.New(errStr)
- }
-
- return nil
-}
-
-// Option is used to provide options to a container.
-type Option interface {
- // set sets the provided option.
- set(*Container) error
-}
-
-// options stores the options provided to the container.
-type options struct {
- // id is the identifier provided by the user.
- id string
-
- // inherited are options that are inherited by child containers.
- inherited inherited
-
- // split identifies how is this container split.
- split splitType
- splitPercent int
- splitFixed int
-
- // widget is the widget in the container.
- // A container can have either two sub containers (left and right) or a
- // widget. But not both.
- widget widgetapi.Widget
-
- // Alignment of the widget if present.
- hAlign align.Horizontal
- vAlign align.Vertical
-
- // border is the border around the container.
- border linestyle.LineStyle
- borderTitle string
- borderTitleHAlign align.Horizontal
-
- // padding is a space reserved between the outer edge of the container and
- // its content (the widget or other sub-containers).
- padding padding
-
- // margin is a space reserved on the outside of the container.
- margin margin
-}
-
-// margin stores the configured margin for the container.
-// For each margin direction, only one of the percentage or cells is set.
-type margin struct {
- topCells int
- topPerc int
- rightCells int
- rightPerc int
- bottomCells int
- bottomPerc int
- leftCells int
- leftPerc int
-}
-
-// apply applies the configured margin to the area.
-func (p *margin) apply(ar image.Rectangle) (image.Rectangle, error) {
- switch {
- case p.topCells != 0 || p.rightCells != 0 || p.bottomCells != 0 || p.leftCells != 0:
- return area.Shrink(ar, p.topCells, p.rightCells, p.bottomCells, p.leftCells)
- case p.topPerc != 0 || p.rightPerc != 0 || p.bottomPerc != 0 || p.leftPerc != 0:
- return area.ShrinkPercent(ar, p.topPerc, p.rightPerc, p.bottomPerc, p.leftPerc)
- }
- return ar, nil
-}
-
-// padding stores the configured padding for the container.
-// For each padding direction, only one of the percentage or cells is set.
-type padding struct {
- topCells int
- topPerc int
- rightCells int
- rightPerc int
- bottomCells int
- bottomPerc int
- leftCells int
- leftPerc int
-}
-
-// apply applies the configured padding to the area.
-func (p *padding) apply(ar image.Rectangle) (image.Rectangle, error) {
- switch {
- case p.topCells != 0 || p.rightCells != 0 || p.bottomCells != 0 || p.leftCells != 0:
- return area.Shrink(ar, p.topCells, p.rightCells, p.bottomCells, p.leftCells)
- case p.topPerc != 0 || p.rightPerc != 0 || p.bottomPerc != 0 || p.leftPerc != 0:
- return area.ShrinkPercent(ar, p.topPerc, p.rightPerc, p.bottomPerc, p.leftPerc)
- }
- return ar, nil
-}
-
-// inherited contains options that are inherited by child containers.
-type inherited struct {
- // borderColor is the color used for the border.
- borderColor cell.Color
- // focusedColor is the color used for the border when focused.
- focusedColor cell.Color
-}
-
-// newOptions returns a new options instance with the default values.
-// Parent are the inherited options from the parent container or nil if these
-// options are for a container with no parent (the root).
-func newOptions(parent *options) *options {
- opts := &options{
- inherited: inherited{
- focusedColor: cell.ColorYellow,
- },
- hAlign: align.HorizontalCenter,
- vAlign: align.VerticalMiddle,
- splitPercent: DefaultSplitPercent,
- splitFixed: DefaultSplitFixed,
- }
- if parent != nil {
- opts.inherited = parent.inherited
- }
- return opts
-}
-
-// option implements Option.
-type option func(*Container) error
-
-// set implements Option.set.
-func (o option) set(c *Container) error {
- return o(c)
-}
-
-// SplitOption is used when splitting containers.
-type SplitOption interface {
- // setSplit sets the provided split option.
- setSplit(*options) error
-}
-
-// splitOption implements SplitOption.
-type splitOption func(*options) error
-
-// setSplit implements SplitOption.setSplit.
-func (so splitOption) setSplit(opts *options) error {
- return so(opts)
-}
-
-// DefaultSplitPercent is the default value for the SplitPercent option.
-const DefaultSplitPercent = 50
-
-// DefaultSplitFixed is the default value for the SplitFixed option.
-const DefaultSplitFixed = -1
-
-// SplitPercent sets the relative size of the split as percentage of the available space.
-// When using SplitVertical, the provided size is applied to the new left
-// container, the new right container gets the reminder of the size.
-// When using SplitHorizontal, the provided size is applied to the new top
-// container, the new bottom container gets the reminder of the size.
-// The provided value must be a positive number in the range 0 < p < 100.
-// If not provided, defaults to DefaultSplitPercent.
-func SplitPercent(p int) SplitOption {
- return splitOption(func(opts *options) error {
- if min, max := 0, 100; p <= min || p >= max {
- return fmt.Errorf("invalid split percentage %d, must be in range %d < p < %d", p, min, max)
- }
- opts.splitPercent = p
- return nil
- })
-}
-
-// SplitFixed sets the size of the first container to be a fixed value
-// and makes the second container take up the remaining space.
-// When using SplitVertical, the provided size is applied to the new left
-// container, the new right container gets the reminder of the size.
-// When using SplitHorizontal, the provided size is applied to the new top
-// container, the new bottom container gets the reminder of the size.
-// The provided value must be a positive number in the range 0 <= cells.
-// If SplitFixed() is not specified, it defaults to SplitPercent() and its given value.
-// Only one of SplitFixed() and SplitPercent() can be specified per container.
-func SplitFixed(cells int) SplitOption {
- return splitOption(func(opts *options) error {
- if cells < 0 {
- return fmt.Errorf("invalid fixed value %d, must be in range %d <= cells", cells, 0)
- }
- opts.splitFixed = cells
- return nil
- })
-}
-
-// SplitVertical splits the container along the vertical axis into two sub
-// containers. The use of this option removes any widget placed at this
-// container, containers with sub containers cannot contain widgets.
-func SplitVertical(l LeftOption, r RightOption, opts ...SplitOption) Option {
- return option(func(c *Container) error {
- c.opts.split = splitTypeVertical
- c.opts.widget = nil
- for _, opt := range opts {
- if err := opt.setSplit(c.opts); err != nil {
- return err
- }
- }
-
- if err := c.createFirst(l.lOpts()); err != nil {
- return err
- }
- return c.createSecond(r.rOpts())
- })
-}
-
-// SplitHorizontal splits the container along the horizontal axis into two sub
-// containers. The use of this option removes any widget placed at this
-// container, containers with sub containers cannot contain widgets.
-func SplitHorizontal(t TopOption, b BottomOption, opts ...SplitOption) Option {
- return option(func(c *Container) error {
- c.opts.split = splitTypeHorizontal
- c.opts.widget = nil
- for _, opt := range opts {
- if err := opt.setSplit(c.opts); err != nil {
- return err
- }
- }
-
- if err := c.createFirst(t.tOpts()); err != nil {
- return err
- }
-
- return c.createSecond(b.bOpts())
- })
-}
-
-// ID sets an identifier for this container.
-// This ID can be later used to perform dynamic layout changes by passing new
-// options to this container. When provided, it must be a non-empty string that
-// is unique among all the containers.
-func ID(id string) Option {
- return option(func(c *Container) error {
- if id == "" {
- return errors.New("the ID cannot be an empty string")
- }
- c.opts.id = id
- return nil
- })
-}
-
-// Clear clears this container.
-// If the container contains a widget, the widget is removed.
-// If the container had any sub containers or splits, they are removed.
-func Clear() Option {
- return option(func(c *Container) error {
- c.opts.widget = nil
- c.first = nil
- c.second = nil
- return nil
- })
-}
-
-// PlaceWidget places the provided widget into the container.
-// The use of this option removes any sub containers. Containers with sub
-// containers cannot have widgets.
-func PlaceWidget(w widgetapi.Widget) Option {
- return option(func(c *Container) error {
- c.opts.widget = w
- c.first = nil
- c.second = nil
- return nil
- })
-}
-
-// MarginTop sets reserved space outside of the container at its top.
-// The provided number is the absolute margin in cells and must be zero or a
-// positive integer. Only one of MarginTop or MarginTopPercent can be specified.
-func MarginTop(cells int) Option {
- return option(func(c *Container) error {
- if min := 0; cells < min {
- return fmt.Errorf("invalid MarginTop(%d), must be in range %d <= value", cells, min)
- }
- if c.opts.margin.topPerc > 0 {
- return fmt.Errorf("cannot specify both MarginTop(%d) and MarginTopPercent(%d)", cells, c.opts.margin.topPerc)
- }
- c.opts.margin.topCells = cells
- return nil
- })
-}
-
-// MarginRight sets reserved space outside of the container at its right.
-// The provided number is the absolute margin in cells and must be zero or a
-// positive integer. Only one of MarginRight or MarginRightPercent can be specified.
-func MarginRight(cells int) Option {
- return option(func(c *Container) error {
- if min := 0; cells < min {
- return fmt.Errorf("invalid MarginRight(%d), must be in range %d <= value", cells, min)
- }
- if c.opts.margin.rightPerc > 0 {
- return fmt.Errorf("cannot specify both MarginRight(%d) and MarginRightPercent(%d)", cells, c.opts.margin.rightPerc)
- }
- c.opts.margin.rightCells = cells
- return nil
- })
-}
-
-// MarginBottom sets reserved space outside of the container at its bottom.
-// The provided number is the absolute margin in cells and must be zero or a
-// positive integer. Only one of MarginBottom or MarginBottomPercent can be specified.
-func MarginBottom(cells int) Option {
- return option(func(c *Container) error {
- if min := 0; cells < min {
- return fmt.Errorf("invalid MarginBottom(%d), must be in range %d <= value", cells, min)
- }
- if c.opts.margin.bottomPerc > 0 {
- return fmt.Errorf("cannot specify both MarginBottom(%d) and MarginBottomPercent(%d)", cells, c.opts.margin.bottomPerc)
- }
- c.opts.margin.bottomCells = cells
- return nil
- })
-}
-
-// MarginLeft sets reserved space outside of the container at its left.
-// The provided number is the absolute margin in cells and must be zero or a
-// positive integer. Only one of MarginLeft or MarginLeftPercent can be specified.
-func MarginLeft(cells int) Option {
- return option(func(c *Container) error {
- if min := 0; cells < min {
- return fmt.Errorf("invalid MarginLeft(%d), must be in range %d <= value", cells, min)
- }
- if c.opts.margin.leftPerc > 0 {
- return fmt.Errorf("cannot specify both MarginLeft(%d) and MarginLeftPercent(%d)", cells, c.opts.margin.leftPerc)
- }
- c.opts.margin.leftCells = cells
- return nil
- })
-}
-
-// MarginTopPercent sets reserved space outside of the container at its top.
-// The provided number is a relative margin defined as percentage of the container's height.
-// Only one of MarginTop or MarginTopPercent can be specified.
-// The value must be in range 0 <= value <= 100.
-func MarginTopPercent(perc int) Option {
- return option(func(c *Container) error {
- if min, max := 0, 100; perc < min || perc > max {
- return fmt.Errorf("invalid MarginTopPercent(%d), must be in range %d <= value <= %d", perc, min, max)
- }
- if c.opts.margin.topCells > 0 {
- return fmt.Errorf("cannot specify both MarginTopPercent(%d) and MarginTop(%d)", perc, c.opts.margin.topCells)
- }
- c.opts.margin.topPerc = perc
- return nil
- })
-}
-
-// MarginRightPercent sets reserved space outside of the container at its right.
-// The provided number is a relative margin defined as percentage of the container's height.
-// Only one of MarginRight or MarginRightPercent can be specified.
-// The value must be in range 0 <= value <= 100.
-func MarginRightPercent(perc int) Option {
- return option(func(c *Container) error {
- if min, max := 0, 100; perc < min || perc > max {
- return fmt.Errorf("invalid MarginRightPercent(%d), must be in range %d <= value <= %d", perc, min, max)
- }
- if c.opts.margin.rightCells > 0 {
- return fmt.Errorf("cannot specify both MarginRightPercent(%d) and MarginRight(%d)", perc, c.opts.margin.rightCells)
- }
- c.opts.margin.rightPerc = perc
- return nil
- })
-}
-
-// MarginBottomPercent sets reserved space outside of the container at its bottom.
-// The provided number is a relative margin defined as percentage of the container's height.
-// Only one of MarginBottom or MarginBottomPercent can be specified.
-// The value must be in range 0 <= value <= 100.
-func MarginBottomPercent(perc int) Option {
- return option(func(c *Container) error {
- if min, max := 0, 100; perc < min || perc > max {
- return fmt.Errorf("invalid MarginBottomPercent(%d), must be in range %d <= value <= %d", perc, min, max)
- }
- if c.opts.margin.bottomCells > 0 {
- return fmt.Errorf("cannot specify both MarginBottomPercent(%d) and MarginBottom(%d)", perc, c.opts.margin.bottomCells)
- }
- c.opts.margin.bottomPerc = perc
- return nil
- })
-}
-
-// MarginLeftPercent sets reserved space outside of the container at its left.
-// The provided number is a relative margin defined as percentage of the container's height.
-// Only one of MarginLeft or MarginLeftPercent can be specified.
-// The value must be in range 0 <= value <= 100.
-func MarginLeftPercent(perc int) Option {
- return option(func(c *Container) error {
- if min, max := 0, 100; perc < min || perc > max {
- return fmt.Errorf("invalid MarginLeftPercent(%d), must be in range %d <= value <= %d", perc, min, max)
- }
- if c.opts.margin.leftCells > 0 {
- return fmt.Errorf("cannot specify both MarginLeftPercent(%d) and MarginLeft(%d)", perc, c.opts.margin.leftCells)
- }
- c.opts.margin.leftPerc = perc
- return nil
- })
-}
-
-// PaddingTop sets reserved space between container and the top side of its widget.
-// The widget's area size is decreased to accommodate the padding.
-// The provided number is the absolute padding in cells and must be zero or a
-// positive integer. Only one of PaddingTop or PaddingTopPercent can be specified.
-func PaddingTop(cells int) Option {
- return option(func(c *Container) error {
- if min := 0; cells < min {
- return fmt.Errorf("invalid PaddingTop(%d), must be in range %d <= value", cells, min)
- }
- if c.opts.padding.topPerc > 0 {
- return fmt.Errorf("cannot specify both PaddingTop(%d) and PaddingTopPercent(%d)", cells, c.opts.padding.topPerc)
- }
- c.opts.padding.topCells = cells
- return nil
- })
-}
-
-// PaddingRight sets reserved space between container and the right side of its widget.
-// The widget's area size is decreased to accommodate the padding.
-// The provided number is the absolute padding in cells and must be zero or a
-// positive integer. Only one of PaddingRight or PaddingRightPercent can be specified.
-func PaddingRight(cells int) Option {
- return option(func(c *Container) error {
- if min := 0; cells < min {
- return fmt.Errorf("invalid PaddingRight(%d), must be in range %d <= value", cells, min)
- }
- if c.opts.padding.rightPerc > 0 {
- return fmt.Errorf("cannot specify both PaddingRight(%d) and PaddingRightPercent(%d)", cells, c.opts.padding.rightPerc)
- }
- c.opts.padding.rightCells = cells
- return nil
- })
-}
-
-// PaddingBottom sets reserved space between container and the bottom side of its widget.
-// The widget's area size is decreased to accommodate the padding.
-// The provided number is the absolute padding in cells and must be zero or a
-// positive integer. Only one of PaddingBottom or PaddingBottomPercent can be specified.
-func PaddingBottom(cells int) Option {
- return option(func(c *Container) error {
- if min := 0; cells < min {
- return fmt.Errorf("invalid PaddingBottom(%d), must be in range %d <= value", cells, min)
- }
- if c.opts.padding.bottomPerc > 0 {
- return fmt.Errorf("cannot specify both PaddingBottom(%d) and PaddingBottomPercent(%d)", cells, c.opts.padding.bottomPerc)
- }
- c.opts.padding.bottomCells = cells
- return nil
- })
-}
-
-// PaddingLeft sets reserved space between container and the left side of its widget.
-// The widget's area size is decreased to accommodate the padding.
-// The provided number is the absolute padding in cells and must be zero or a
-// positive integer. Only one of PaddingLeft or PaddingLeftPercent can be specified.
-func PaddingLeft(cells int) Option {
- return option(func(c *Container) error {
- if min := 0; cells < min {
- return fmt.Errorf("invalid PaddingLeft(%d), must be in range %d <= value", cells, min)
- }
- if c.opts.padding.leftPerc > 0 {
- return fmt.Errorf("cannot specify both PaddingLeft(%d) and PaddingLeftPercent(%d)", cells, c.opts.padding.leftPerc)
- }
- c.opts.padding.leftCells = cells
- return nil
- })
-}
-
-// PaddingTopPercent sets reserved space between container and the top side of
-// its widget. The widget's area size is decreased to accommodate the padding.
-// The provided number is a relative padding defined as percentage of the
-// container's height. The value must be in range 0 <= value <= 100.
-// Only one of PaddingTop or PaddingTopPercent can be specified.
-func PaddingTopPercent(perc int) Option {
- return option(func(c *Container) error {
- if min, max := 0, 100; perc < min || perc > max {
- return fmt.Errorf("invalid PaddingTopPercent(%d), must be in range %d <= value <= %d", perc, min, max)
- }
- if c.opts.padding.topCells > 0 {
- return fmt.Errorf("cannot specify both PaddingTopPercent(%d) and PaddingTop(%d)", perc, c.opts.padding.topCells)
- }
- c.opts.padding.topPerc = perc
- return nil
- })
-}
-
-// PaddingRightPercent sets reserved space between container and the right side of
-// its widget. The widget's area size is decreased to accommodate the padding.
-// The provided number is a relative padding defined as percentage of the
-// container's width. The value must be in range 0 <= value <= 100.
-// Only one of PaddingRight or PaddingRightPercent can be specified.
-func PaddingRightPercent(perc int) Option {
- return option(func(c *Container) error {
- if min, max := 0, 100; perc < min || perc > max {
- return fmt.Errorf("invalid PaddingRightPercent(%d), must be in range %d <= value <= %d", perc, min, max)
- }
- if c.opts.padding.rightCells > 0 {
- return fmt.Errorf("cannot specify both PaddingRightPercent(%d) and PaddingRight(%d)", perc, c.opts.padding.rightCells)
- }
- c.opts.padding.rightPerc = perc
- return nil
- })
-}
-
-// PaddingBottomPercent sets reserved space between container and the bottom side of
-// its widget. The widget's area size is decreased to accommodate the padding.
-// The provided number is a relative padding defined as percentage of the
-// container's height. The value must be in range 0 <= value <= 100.
-// Only one of PaddingBottom or PaddingBottomPercent can be specified.
-func PaddingBottomPercent(perc int) Option {
- return option(func(c *Container) error {
- if min, max := 0, 100; perc < min || perc > max {
- return fmt.Errorf("invalid PaddingBottomPercent(%d), must be in range %d <= value <= %d", perc, min, max)
- }
- if c.opts.padding.bottomCells > 0 {
- return fmt.Errorf("cannot specify both PaddingBottomPercent(%d) and PaddingBottom(%d)", perc, c.opts.padding.bottomCells)
- }
- c.opts.padding.bottomPerc = perc
- return nil
- })
-}
-
-// PaddingLeftPercent sets reserved space between container and the left side of
-// its widget. The widget's area size is decreased to accommodate the padding.
-// The provided number is a relative padding defined as percentage of the
-// container's width. The value must be in range 0 <= value <= 100.
-// Only one of PaddingLeft or PaddingLeftPercent can be specified.
-func PaddingLeftPercent(perc int) Option {
- return option(func(c *Container) error {
- if min, max := 0, 100; perc < min || perc > max {
- return fmt.Errorf("invalid PaddingLeftPercent(%d), must be in range %d <= value <= %d", perc, min, max)
- }
- if c.opts.padding.leftCells > 0 {
- return fmt.Errorf("cannot specify both PaddingLeftPercent(%d) and PaddingLeft(%d)", perc, c.opts.padding.leftCells)
- }
- c.opts.padding.leftPerc = perc
- return nil
- })
-}
-
-// AlignHorizontal sets the horizontal alignment for the widget placed in the
-// container. Has no effect if the container contains no widget.
-// Defaults to alignment in the center.
-func AlignHorizontal(h align.Horizontal) Option {
- return option(func(c *Container) error {
- c.opts.hAlign = h
- return nil
- })
-}
-
-// AlignVertical sets the vertical alignment for the widget placed in the container.
-// Has no effect if the container contains no widget.
-// Defaults to alignment in the middle.
-func AlignVertical(v align.Vertical) Option {
- return option(func(c *Container) error {
- c.opts.vAlign = v
- return nil
- })
-}
-
-// Border configures the container to have a border of the specified style.
-func Border(ls linestyle.LineStyle) Option {
- return option(func(c *Container) error {
- c.opts.border = ls
- return nil
- })
-}
-
-// BorderTitle sets a text title within the border.
-func BorderTitle(title string) Option {
- return option(func(c *Container) error {
- c.opts.borderTitle = title
- return nil
- })
-}
-
-// BorderTitleAlignLeft aligns the border title on the left.
-func BorderTitleAlignLeft() Option {
- return option(func(c *Container) error {
- c.opts.borderTitleHAlign = align.HorizontalLeft
- return nil
- })
-}
-
-// BorderTitleAlignCenter aligns the border title in the center.
-func BorderTitleAlignCenter() Option {
- return option(func(c *Container) error {
- c.opts.borderTitleHAlign = align.HorizontalCenter
- return nil
- })
-}
-
-// BorderTitleAlignRight aligns the border title on the right.
-func BorderTitleAlignRight() Option {
- return option(func(c *Container) error {
- c.opts.borderTitleHAlign = align.HorizontalRight
- return nil
- })
-}
-
-// BorderColor sets the color of the border around the container.
-// This option is inherited to sub containers created by container splits.
-func BorderColor(color cell.Color) Option {
- return option(func(c *Container) error {
- c.opts.inherited.borderColor = color
- return nil
- })
-}
-
-// FocusedColor sets the color of the border around the container when it has
-// keyboard focus.
-// This option is inherited to sub containers created by container splits.
-func FocusedColor(color cell.Color) Option {
- return option(func(c *Container) error {
- c.opts.inherited.focusedColor = color
- return nil
- })
-}
-
-// splitType identifies how a container is split.
-type splitType int
-
-// String implements fmt.Stringer()
-func (st splitType) String() string {
- if n, ok := splitTypeNames[st]; ok {
- return n
- }
- return "splitTypeUnknown"
-}
-
-// splitTypeNames maps splitType values to human readable names.
-var splitTypeNames = map[splitType]string{
- splitTypeVertical: "splitTypeVertical",
- splitTypeHorizontal: "splitTypeHorizontal",
-}
-
-const (
- splitTypeVertical splitType = iota
- splitTypeHorizontal
-)
-
-// LeftOption is used to provide options to the left sub container after a
-// vertical split of the parent.
-type LeftOption interface {
- // lOpts returns the options.
- lOpts() []Option
-}
-
-// leftOption implements LeftOption.
-type leftOption func() []Option
-
-// lOpts implements LeftOption.lOpts.
-func (lo leftOption) lOpts() []Option {
- if lo == nil {
- return nil
- }
- return lo()
-}
-
-// Left applies options to the left sub container after a vertical split of the parent.
-func Left(opts ...Option) LeftOption {
- return leftOption(func() []Option {
- return opts
- })
-}
-
-// RightOption is used to provide options to the right sub container after a
-// vertical split of the parent.
-type RightOption interface {
- // rOpts returns the options.
- rOpts() []Option
-}
-
-// rightOption implements RightOption.
-type rightOption func() []Option
-
-// rOpts implements RightOption.rOpts.
-func (lo rightOption) rOpts() []Option {
- if lo == nil {
- return nil
- }
- return lo()
-}
-
-// Right applies options to the right sub container after a vertical split of the parent.
-func Right(opts ...Option) RightOption {
- return rightOption(func() []Option {
- return opts
- })
-}
-
-// TopOption is used to provide options to the top sub container after a
-// horizontal split of the parent.
-type TopOption interface {
- // tOpts returns the options.
- tOpts() []Option
-}
-
-// topOption implements TopOption.
-type topOption func() []Option
-
-// tOpts implements TopOption.tOpts.
-func (lo topOption) tOpts() []Option {
- if lo == nil {
- return nil
- }
- return lo()
-}
-
-// Top applies options to the top sub container after a horizontal split of the parent.
-func Top(opts ...Option) TopOption {
- return topOption(func() []Option {
- return opts
- })
-}
-
-// BottomOption is used to provide options to the bottom sub container after a
-// horizontal split of the parent.
-type BottomOption interface {
- // bOpts returns the options.
- bOpts() []Option
-}
-
-// bottomOption implements BottomOption.
-type bottomOption func() []Option
-
-// bOpts implements BottomOption.bOpts.
-func (lo bottomOption) bOpts() []Option {
- if lo == nil {
- return nil
- }
- return lo()
-}
-
-// Bottom applies options to the bottom sub container after a horizontal split of the parent.
-func Bottom(opts ...Option) BottomOption {
- return bottomOption(func() []Option {
- return opts
- })
-}
diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/container/traversal.go b/examples/go-dashboard/src/github.com/mum4k/termdash/container/traversal.go
deleted file mode 100644
index f728b50ba..000000000
--- a/examples/go-dashboard/src/github.com/mum4k/termdash/container/traversal.go
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package container
-
-import (
- "errors"
- "fmt"
-)
-
-// traversal.go provides functions that navigate the container tree.
-
-// rootCont returns the root container.
-func rootCont(c *Container) *Container {
- for p := c.parent; p != nil; p = c.parent {
- c = p
- }
- return c
-}
-
-// visitFunc is executed during traversals when node is visited.
-// If the visit function returns an error, the traversal terminates and the
-// errStr is set to the text of the returned error.
-type visitFunc func(*Container) error
-
-// preOrder performs pre-order DFS traversal on the container tree.
-func preOrder(c *Container, errStr *string, visit visitFunc) {
- if c == nil || *errStr != "" {
- return
- }
-
- if err := visit(c); err != nil {
- *errStr = err.Error()
- return
- }
- preOrder(c.first, errStr, visit)
- preOrder(c.second, errStr, visit)
-}
-
-// postOrder performs post-order DFS traversal on the container tree.
-func postOrder(c *Container, errStr *string, visit visitFunc) {
- if c == nil || *errStr != "" {
- return
- }
-
- postOrder(c.first, errStr, visit)
- postOrder(c.second, errStr, visit)
- if err := visit(c); err != nil {
- *errStr = err.Error()
- return
- }
-}
-
-// findID finds container with the provided ID.
-// Returns an error of there is no container with the specified ID.
-func findID(root *Container, id string) (*Container, error) {
- if id == "" {
- return nil, errors.New("the container ID must not be empty")
- }
-
- var (
- errStr string
- cont *Container
- )
- preOrder(root, &errStr, visitFunc(func(c *Container) error {
- if c.opts.id == id {
- cont = c
- }
- return nil
- }))
- if cont == nil {
- return nil, fmt.Errorf("cannot find container with ID %q", id)
- }
- return cont, nil
-}