From dcc7fcb2513588baab847d304fc5b9def94fe0cd Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Thu, 19 Jan 2023 13:07:27 -0500 Subject: [PATCH] Ok thats it next commit im getting rid of parent hooks --- backends/x/window.go | 2 +- element.go | 8 ++ elements/basic/scrollcontainer.go | 137 ++++++++++++++++++++++++++++++ elements/basic/textbox.go | 6 +- elements/core/core.go | 6 ++ examples/scroll/main.go | 27 ++++++ 6 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 elements/basic/scrollcontainer.go create mode 100644 examples/scroll/main.go diff --git a/backends/x/window.go b/backends/x/window.go index 58fa4df..d065357 100644 --- a/backends/x/window.go +++ b/backends/x/window.go @@ -77,7 +77,7 @@ func (backend *Backend) NewWindow ( func (window *Window) Adopt (child tomo.Element) { if window.child != nil { - child.SetParentHooks (tomo.ParentHooks { }) + window.child.SetParentHooks (tomo.ParentHooks { }) if previousChild, ok := window.child.(tomo.Selectable); ok { if previousChild.Selected() { previousChild.HandleDeselection() diff --git a/element.go b/element.go index 2ac7faa..c2a3397 100644 --- a/element.go +++ b/element.go @@ -84,6 +84,14 @@ func (hooks ParentHooks) RunSelectionMotionRequest ( return } +// RunContentBoundsChange runs the ContentBoundsChange hook if it is not nil. If +// it is nil, it does nothing. +func (hooks ParentHooks) RunContentBoundsChange () { + if hooks.ContentBoundsChange != nil { + hooks.ContentBoundsChange() + } +} + // Element represents a basic on-screen object. type Element interface { // Element must implement the Canvas interface. Elements should start diff --git a/elements/basic/scrollcontainer.go b/elements/basic/scrollcontainer.go new file mode 100644 index 0000000..a48adab --- /dev/null +++ b/elements/basic/scrollcontainer.go @@ -0,0 +1,137 @@ +package basic + +import "image" +import "git.tebibyte.media/sashakoshka/tomo" +import "git.tebibyte.media/sashakoshka/tomo/theme" +import "git.tebibyte.media/sashakoshka/tomo/artist" +import "git.tebibyte.media/sashakoshka/tomo/elements/core" + +// ScrollContainer is a container that is capable of holding a scrollable +// element. +type ScrollContainer struct { + *core.Core + core core.CoreControl + selected bool + child tomo.Scrollable + + horizontal struct { + enabled bool + bounds image.Rectangle + } + + vertical struct { + enabled bool + bounds image.Rectangle + } +} + +// NewScrollContainer creates a new scroll container with the specified scroll +// bars. +func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) { + element = &ScrollContainer { } + element.Core, element.core = core.NewCore(element) + element.updateMinimumSize() + element.horizontal.enabled = horizontal + element.vertical.enabled = vertical + return +} + +// Resize resizes the scroll box. +func (element *ScrollContainer) Resize (width, height int) { + element.core.AllocateCanvas(width, height) + element.recalculate() + element.child.Resize ( + element.vertical.bounds.Min.X, + element.horizontal.bounds.Min.Y) + element.draw() +} + +// Adopt adds a scrollable element to the scroll container. The container can +// only contain one scrollable element at a time, and when a new one is adopted +// it replaces the last one. +func (element *ScrollContainer) Adopt (child tomo.Scrollable) { + // disown previous child if it exists + if element.child != nil { + element.child.SetParentHooks (tomo.ParentHooks { }) + if previousChild, ok := element.child.(tomo.Selectable); ok { + if previousChild.Selected() { + previousChild.HandleDeselection() + } + } + } + + // adopt new child + element.child = child + if child != nil { + child.SetParentHooks (tomo.ParentHooks { + // Draw: window.childDrawCallback, + // MinimumSizeChange: window.childMinimumSizeChangeCallback, + // FlexibleHeightChange: window.resizeChildToFit, + // SelectionRequest: window.childSelectionRequestCallback, + }) + + // TODO: somehow inform the core that we do not in fact want to + // redraw the element. + element.updateMinimumSize() + + if element.core.HasImage() { + element.recalculate() + element.child.Resize ( + element.horizontal.bounds.Min.X, + element.vertical.bounds.Min.X) + element.draw() + } + } +} + +func (element *ScrollContainer) recalculate () { + horizontal := &element.horizontal + vertical := &element.vertical + bounds := element.Bounds() + thickness := theme.Padding() * 2 + + // reset bounds + horizontal.bounds = image.Rectangle { } + vertical.bounds = image.Rectangle { } + + // if enabled, give substance to the bars + if horizontal.enabled { + horizontal.bounds.Max.X = bounds.Max.X - thickness + horizontal.bounds.Max.Y = thickness + } + if vertical.enabled { + vertical.bounds.Max.X = thickness + vertical.bounds.Max.Y = bounds.Max.Y - thickness + } + + // move the bars to the edge of the element + horizontal.bounds = horizontal.bounds.Add ( + bounds.Max.Sub(horizontal.bounds.Max)) + vertical.bounds = vertical.bounds.Add ( + bounds.Max.Sub(vertical.bounds.Max)) +} + +func (element *ScrollContainer) draw () { + artist.Paste(element.core, element.child, image.Point { }) + element.drawHorizontalBar() + element.drawVerticalBar() +} + +func (element *ScrollContainer) drawHorizontalBar () { + +} + +func (element *ScrollContainer) drawVerticalBar () { + +} + +func (element *ScrollContainer) updateMinimumSize () { + width := theme.Padding() * 2 + height := theme.Padding() * 2 + if element.child != nil { + childWidth, childHeight := element.child.MinimumSize() + width += childWidth + height += childHeight + } + element.core.SetMinimumSize(width, height) +} diff --git a/elements/basic/textbox.go b/elements/basic/textbox.go index d6e5478..722dc76 100644 --- a/elements/basic/textbox.go +++ b/elements/basic/textbox.go @@ -247,7 +247,9 @@ func (element *TextBox) ScrollTo (position image.Point) { if element.core.HasImage () { element.draw() element.core.PushAll() - }} + } + element.core.NotifyContentBoundsChange() +} // ScrollAxes returns the supported axes for scrolling. func (element *TextBox) ScrollAxes () (horizontal, vertical bool) { @@ -284,6 +286,7 @@ func (element *TextBox) scrollToCursor () { element.scroll -= minX - cursorPosition.X if element.scroll < 0 { element.scroll = 0 } } + element.core.NotifyContentBoundsChange() } func (element *TextBox) draw () { @@ -311,7 +314,6 @@ func (element *TextBox) draw () { } else { // draw input value textBounds := element.valueDrawer.LayoutBounds() - println(textBounds.String()) offset := image.Point { X: theme.Padding() - element.scroll, Y: theme.Padding(), diff --git a/elements/core/core.go b/elements/core/core.go index 91ceac9..5bf81e9 100644 --- a/elements/core/core.go +++ b/elements/core/core.go @@ -149,6 +149,12 @@ func (control CoreControl) NotifyFlexibleHeightChange () { control.core.hooks.RunFlexibleHeightChange() } +// NotifyContentBoundsChange notifies the parent element that this element's +// inner content bounds or scroll position have changed. +func (control CoreControl) NotifyContentBoundsChange () { + control.core.hooks.RunContentBoundsChange() +} + // ConstrainSize contstrains the specified width and height to the minimum width // and height, and returns wether or not anything ended up being constrained. func (control CoreControl) ConstrainSize ( diff --git a/examples/scroll/main.go b/examples/scroll/main.go new file mode 100644 index 0000000..e3cb55c --- /dev/null +++ b/examples/scroll/main.go @@ -0,0 +1,27 @@ +package main + +import "git.tebibyte.media/sashakoshka/tomo" +import "git.tebibyte.media/sashakoshka/tomo/layouts" +import "git.tebibyte.media/sashakoshka/tomo/elements/basic" +import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" + +func main () { + tomo.Run(run) +} + +func run () { + window, _ := tomo.NewWindow(2, 2) + window.SetTitle("Scroll") + container := basic.NewContainer(layouts.Vertical { true, true }) + window.Adopt(container) + + container.Adopt(basic.NewLabel("look at this non sense", false), false) + + textBox := basic.NewTextBox("", "sample text sample text") + scrollContainer := basic.NewScrollContainer(true, true) + scrollContainer.Adopt(textBox) + container.Adopt(scrollContainer, false) + + window.OnClose(tomo.Stop) + window.Show() +}