From cdf23c9b13f0b099ffddd8cb1838cba873a3373b Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 12 Sep 2024 14:07:54 -0400 Subject: [PATCH] Overhaul collection of containers --- abstractcontainer.go | 93 +++++++++++++++++++++++++++++++ container.go | 128 +++---------------------------------------- pegboard.go | 22 ++++++++ root.go | 20 +++++++ segment.go | 100 +++++++++++++++++++++++++++++++++ 5 files changed, 243 insertions(+), 120 deletions(-) create mode 100644 abstractcontainer.go create mode 100644 pegboard.go create mode 100644 root.go create mode 100644 segment.go diff --git a/abstractcontainer.go b/abstractcontainer.go new file mode 100644 index 0000000..b265ebc --- /dev/null +++ b/abstractcontainer.go @@ -0,0 +1,93 @@ +package objects + +import "image" +import "git.tebibyte.media/tomo/tomo" +import "git.tebibyte.media/tomo/tomo/event" + +type abstractContainer struct { + box tomo.ContainerBox +} + +func (this *abstractContainer) init (layout tomo.Layout, children ...tomo.Object) { + this.box = tomo.NewContainerBox() + this.SetLayout(layout) + for _, child := range children { + this.Add(child) + } +} + +// GetBox returns the underlying box. +func (this *abstractContainer) GetBox () tomo.Box { + return this.box +} + +// ContentBounds returns the bounds of the inner content of the container +// relative to the container's InnerBounds. +func (this *abstractContainer) ContentBounds () image.Rectangle { + return this.box.ContentBounds() +} + +// ScrollTo shifts the origin of the container's content to the origin of the +// container's InnerBounds, offset by the given point. +func (this *abstractContainer) ScrollTo (position image.Point) { + this.box.ScrollTo(position) +} + +// OnContentBoundsChange specifies a function to be called when the container's +// ContentBounds or InnerBounds changes. +func (this *abstractContainer) OnContentBoundsChange (callback func ()) event.Cookie { + return this.box.OnContentBoundsChange(callback) +} + +// SetLayout sets the layout of the container. +func (this *abstractContainer) SetLayout (layout tomo.Layout) { + if layout == nil { + this.box.UnsetAttr(tomo.AttrKindLayout) + } else { + this.box.SetAttr(tomo.ALayout(layout)) + } +} + +// SetAlign sets the X and Y alignment of the container. +func (this *abstractContainer) SetAlign (x, y tomo.Align) { + this.box.SetAttr(tomo.AAlign(x, y)) +} + +// SetOverflow sets the X and Y overflow of the container. +func (this *abstractContainer) SetOverflow (x, y bool) { + this.box.SetAttr(tomo.AOverflow(x, y)) +} + +// Add appends a child object. If the object is already a child of another +// object, it will be removed from that object first. +func (this *abstractContainer) Add (object tomo.Object) { + this.box.Add(object) +} + +// Remove removes a child object, if it is a child of this container. +func (this *abstractContainer) Remove (object tomo.Object) { + this.box.Remove(object) +} + +// Insert inserts a child object before a specified object. If the before object +// is nil or is not contained within this container, the inserted object is +// appended. If the inserted object is already a child of another object, it +// will be removed from that object first. +func (this *abstractContainer) Insert (child tomo.Object, before tomo.Object) { + this.box.Insert(child, before) +} + +// Clear removes all child objects. +func (this *abstractContainer) Clear () { + this.box.Clear() +} + +// Len returns hte amount of child objects. +func (this *abstractContainer) Len () int { + return this.box.Len() +} + +// At returns the child object at the specified index. +func (this *abstractContainer) At (index int) tomo.Object { + return this.box.At(index) +} diff --git a/container.go b/container.go index 5b75025..49124ff 100644 --- a/container.go +++ b/container.go @@ -1,133 +1,21 @@ package objects -import "image" import "git.tebibyte.media/tomo/tomo" -import "git.tebibyte.media/tomo/tomo/event" var _ tomo.ContentObject = new(Container) -// Container is an object that can contain other objects. It can be used as a -// primitive for building more complex layouts. It has two main variants: an -// outer container, and an inner container. The outer container has padding -// around its edges, whereas the inner container does not. It also has a -// "sunken" variation designed to hold a scrolled list of items. -// -// Tags: -// - [outer] The container is the root of a window. -// - [inner] The container is within another container, and is part of a -// larger layout. -// - [sunken] The container holds a visually grouped, usually scrolled, list -// of items. +// Container is an object that can contain other objects. It is plain looking, +// and is intended to be used within other containers as a primitive for +// building more complex layouts. type Container struct { - box tomo.ContainerBox + abstractContainer } -func newContainer (layout tomo.Layout, children ...tomo.Object) *Container { - this := &Container { - box: tomo.NewContainerBox(), - } - this.box.SetAttr(tomo.ALayout(layout)) - for _, child := range children { - this.Add(child) - } - return this -} - -// NewOuterContainer creates a new container that has padding around it, as well -// as a solid background color. It is meant to be used as a root container for a -// window, tab pane, etc. -func NewOuterContainer (layout tomo.Layout, children ...tomo.Object) *Container { - this := newContainer(layout, children...) +// NewContainer creates a new container. +func NewContainer (layout tomo.Layout, children ...tomo.Object) *Container { + this := &Container { } + this.init(layout, children...) this.box.SetRole(tomo.R("objects", "Container")) this.box.SetTag("outer", true) return this } - -// NewSunkenContainer creates a new container with a sunken style and padding -// around it. It is meant to be used as a root container for a ScrollContainer. -func NewSunkenContainer (layout tomo.Layout, children ...tomo.Object) *Container { - this := newContainer(layout, children...) - this.box.SetRole(tomo.R("objects", "Container")) - this.box.SetTag("sunken", true) - return this -} - -// NewInnerContainer creates a new container that has no padding around it. -func NewInnerContainer (layout tomo.Layout, children ...tomo.Object) *Container { - this := newContainer(layout, children...) - this.box.SetRole(tomo.R("objects", "Container")) - this.box.SetTag("inner", true) - return this -} - -// GetBox returns the underlying box. -func (this *Container) GetBox () tomo.Box { - return this.box -} - -// ContentBounds returns the bounds of the inner content of the container -// relative to the container's InnerBounds. -func (this *Container) ContentBounds () image.Rectangle { - return this.box.ContentBounds() -} - -// ScrollTo shifts the origin of the container's content to the origin of the -// container's InnerBounds, offset by the given point. -func (this *Container) ScrollTo (position image.Point) { - this.box.ScrollTo(position) -} - -// OnContentBoundsChange specifies a function to be called when the container's -// ContentBounds or InnerBounds changes. -func (this *Container) OnContentBoundsChange (callback func ()) event.Cookie { - return this.box.OnContentBoundsChange(callback) -} - -// SetLayout sets the layout of the container. -func (this *Container) SetLayout (layout tomo.Layout) { - this.box.SetAttr(tomo.ALayout(layout)) -} - -// SetAlign sets the X and Y alignment of the container. -func (this *Container) SetAlign (x, y tomo.Align) { - this.box.SetAttr(tomo.AAlign(x, y)) -} - -// SetOverflow sets the X and Y overflow of the container. -func (this *Container) SetOverflow (x, y bool) { - this.box.SetAttr(tomo.AOverflow(x, y)) -} - -// Add appends a child object. If the object is already a child of another -// object, it will be removed from that object first. -func (this *Container) Add (object tomo.Object) { - this.box.Add(object) -} - -// Remove removes a child object, if it is a child of this container. -func (this *Container) Remove (object tomo.Object) { - this.box.Remove(object) -} - -// Insert inserts a child object before a specified object. If the before object -// is nil or is not contained within this container, the inserted object is -// appended. If the inserted object is already a child of another object, it -// will be removed from that object first. -func (this *Container) Insert (child tomo.Object, before tomo.Object) { - this.box.Insert(child, before) -} - -// Clear removes all child objects. -func (this *Container) Clear () { - this.box.Clear() -} - -// Len returns hte amount of child objects. -func (this *Container) Len () int { - return this.box.Len() -} - -// At returns the child object at the specified index. -func (this *Container) At (index int) tomo.Object { - return this.box.At(index) -} diff --git a/pegboard.go b/pegboard.go new file mode 100644 index 0000000..1643df8 --- /dev/null +++ b/pegboard.go @@ -0,0 +1,22 @@ +package objects + +import "git.tebibyte.media/tomo/tomo" +import "git.tebibyte.media/tomo/objects/layouts" + +var _ tomo.ContentObject = new(Pegboard) + +// Pegboard is an object that can contain other objects. It is intended to +// contain a flowed list of objects which represent some data, such as files. +type Pegboard struct { + abstractContainer +} + +// NewPegboard creates a new pegboard. If the provided layout is nil, it will +// use a FlowHorizontal layout. +func NewPegboard (layout tomo.Layout, children ...tomo.Object) *Pegboard { + if layout == nil { layout = layouts.FlowHorizontal } + pegboard := &Pegboard { } + pegboard.init(layout, children...) + pegboard.box.SetRole(tomo.R("objects", "Pegboard")) + return pegboard +} diff --git a/root.go b/root.go new file mode 100644 index 0000000..22acb2f --- /dev/null +++ b/root.go @@ -0,0 +1,20 @@ +package objects + +import "git.tebibyte.media/tomo/tomo" + +var _ tomo.ContentObject = new(Root) + +// Root is an object that can contain other objects. It is intended to be used +// as the root of a window in order to contain its segments. +type Root struct { + abstractContainer +} + +// NewRoot creates a new container. +func NewRoot (layout tomo.Layout, children ...tomo.Object) *Container { + this := &Container { } + this.init(layout, children...) + this.box.SetRole(tomo.R("objects", "Container")) + this.box.SetTag("outer", true) + return this +} diff --git a/segment.go b/segment.go new file mode 100644 index 0000000..864ade5 --- /dev/null +++ b/segment.go @@ -0,0 +1,100 @@ +package objects + +import "git.tebibyte.media/tomo/tomo" +import "git.tebibyte.media/tomo/objects/layouts" + +var _ tomo.ContentObject = new(Segment) + +// Segment is an object that can contain other objects, and provides a way to +// categorize the functionality of a window. Segments are typically laid out +// vertically in a window. There are several variants, the main one being +// the content segment. They can all be created using specialized constructors. +// +// The following is a brief visual discription of how they are typically used, +// and in what order: +// +// ┌──────────────────────────┐ +// │ ┌──┐ ┌──┐ ┌────────────┐ ├─ command +// │ │◄─│ │─►│ │/foo/bar/baz│ │ +// │ └──┘ └──┘ └────────────┘ │ +// ├──────────────────────────┤ +// │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ├─ content +// │ │ │ │ │ │ │ │ │ │ +// │ └───┘ └───┘ └───┘ └───┘ │ +// │ ┌───┐ │ +// │ │ │ │ +// │ └───┘ │ +// ├──────────────────────────┤ +// │ 5 items, 4KiB ├─ status +// └──────────────────────────┘ +// ┌─────────────────┐ +// │ ┌───┐ │ +// │ │ ! │ Continue? │ +// │ └───┘ │ +// ├─────────────────┤ +// │ ┌──┐ ┌───┐ ├────────── option +// │ │No│ │Yes│ │ +// │ └──┘ └───┘ │ +// └─────────────────┘ +// +// Tags: +// - [command] The segment is a command segment. +// - [content] The segment is a content segment. +// - [status] The segment is a status segment. +// - [option] The segment is an option segment. +type Segment struct { + abstractContainer +} + +func newSegment (kind string, layout tomo.Layout, children ...tomo.Object) *Segment { + segment := &Segment { } + segment.init(layout, children...) + segment.box.SetRole(tomo.R("objects", "Segment")) + segment.box.SetTag(kind, true) + return segment +} + +// NewCommandSegment creates a new segment intended to hold the window's +// navigational controls and command functionality. If the provided layout is +// nil, it will use a ContractHorizontal layout. +func NewCommandSegment (layout tomo.Layout, children ...tomo.Object) *Segment { + if layout == nil { layout = layouts.ContractHorizontal } + return newSegment("command", layout, children...) +} + +// NewContentSegment creates a new segment intended to hold the window's main +// content. If the provided layout is nil, it will use a ContractVertical +// layout. +func NewContentSegment (layout tomo.Layout, children ...tomo.Object) *Segment { + if layout == nil { layout = layouts.ContractVertical } + return newSegment("content", layout, children...) +} + +// NewStatusSegment creates a new segment intended to display the window's +// status. If the provided layout is nil, it will use a ContractHorizontal +// layout. +func NewStatusSegment (layout tomo.Layout, children ...tomo.Object) *Segment { + if layout == nil { layout = layouts.ContractHorizontal } + return newSegment("status", layout, children...) +} + +// NewOptionSegment creates a new segment intended to hold the window's options. +// This is typically used for dialog boxes. If the provided layout is nil, it +// will use a ContractHorizontal layout. By default, it is end-aligned. +func NewOptionSegment (layout tomo.Layout, children ...tomo.Object) *Segment { + if layout == nil { layout = layouts.ContractHorizontal } + segment := newSegment("option", layout, children...) + segment.GetBox().SetAttr(tomo.AAlign(tomo.AlignEnd, tomo.AlignMiddle)) + return segment +} + +// TODO create constructors somewhere that make a window with segments and +// automatically applied layout +// +// window, content, err := NewContentWindow +// window, nav, content, status, err := NewNavContentStatusWindow +// window, content, control, err := NewContentControlWindow +// +// alternatively: +// (the constructor will create a column from the types of the segments) +// window, err := NewSegmentedWindow(NewNavigationSegment(), NewContentSegment(), NewStatusSegment())