diff options
Diffstat (limited to 'examples/go-dashboard/src/github.com/mum4k/termdash/container')
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 -} |