diff --git a/elements/basic/container.go b/elements/basic/container.go index 008a1d8..0f4ff6e 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -207,6 +207,13 @@ func (element *Container) ChildAt (point image.Point) (child elements.Element) { func (element *Container) redoAll () { if !element.core.HasImage() { return } + + // remove child canvasses so that any operations done in here will not + // cause a child to draw to a wack ass canvas. + for _, entry := range element.children { + entry.DrawTo(nil) + } + // do a layout element.doLayout() diff --git a/elements/basic/scrollbar.go b/elements/basic/scrollbar.go index f2714cd..a820aaa 100644 --- a/elements/basic/scrollbar.go +++ b/elements/basic/scrollbar.go @@ -15,6 +15,9 @@ import "git.tebibyte.media/sashakoshka/tomo/elements/core" // the user is trying to move the handle to. A program can check to see if this // value is valid, move the viewport, and give the scroll bar the new viewport // bounds (which will then cause it to move the handle). +// +// Typically, you wont't want to use a ScrollBar by itself. A ScrollContainer is +// better for most cases. type ScrollBar struct { *core.Core core core.CoreControl @@ -47,11 +50,18 @@ func NewScrollBar (vertical bool) (element *ScrollBar) { } else { element.theme.Case = theme.C("basic", "scrollBarVertical") } - element.Core, element.core = core.NewCore(element.draw) + element.Core, element.core = core.NewCore(element.handleResize) element.updateMinimumSize() return } +func (element *ScrollBar) handleResize () { + if element.core.HasImage() { + element.recalculate() + element.draw() + } +} + func (element *ScrollBar) HandleMouseDown (x, y int, button input.Button) { velocity := element.config.ScrollVelocity() point := image.Pt(x, y) @@ -59,7 +69,7 @@ func (element *ScrollBar) HandleMouseDown (x, y int, button input.Button) { if point.In(element.bar) { // the mouse is pressed down within the bar's handle element.dragging = true - element.redo() + element.drawAndPush() element.dragOffset = point element.dragTo(point) } else { @@ -100,7 +110,7 @@ func (element *ScrollBar) HandleMouseDown (x, y int, button input.Button) { func (element *ScrollBar) HandleMouseUp (x, y int, button input.Button) { if element.dragging { element.dragging = false - element.redo() + element.drawAndPush() } } @@ -118,7 +128,7 @@ func (element *ScrollBar) HandleMouseScroll (x, y int, deltaX, deltaY float64) { func (element *ScrollBar) SetEnabled (enabled bool) { if element.enabled == enabled { return } element.enabled = enabled - element.redo() + element.drawAndPush() } // Enabled returns whether or not the element is enabled. @@ -130,7 +140,8 @@ func (element *ScrollBar) Enabled () (enabled bool) { func (element *ScrollBar) SetBounds (content, viewport image.Rectangle) { element.contentBounds = content element.viewportBounds = viewport - element.redo() + element.recalculate() + element.drawAndPush() } // OnScroll sets a function to be called when the user tries to move the scroll @@ -146,7 +157,7 @@ func (element *ScrollBar) OnScroll (callback func (viewport image.Point)) { func (element *ScrollBar) SetTheme (new theme.Theme) { if new == element.theme.Theme { return } element.theme.Theme = new - element.redo() + element.drawAndPush() } // SetConfig sets the element's configuration. @@ -154,7 +165,7 @@ func (element *ScrollBar) SetConfig (new config.Config) { if new == element.config.Config { return } element.config.Config = new element.updateMinimumSize() - element.redo() + element.drawAndPush() } func (element *ScrollBar) isAfterHandle (point image.Point) bool { @@ -227,10 +238,11 @@ func (element *ScrollBar) recalculateVertical () { element.bar.Min.X = element.track.Min.X element.bar.Max.X = element.track.Max.X - scale := float64(element.track.Dy()) / + ratio := + float64(element.track.Dy()) / float64(contentBounds.Dy()) - element.bar.Min.Y = int(float64(viewportBounds.Min.Y) * scale) - element.bar.Max.Y = int(float64(viewportBounds.Max.Y) * scale) + element.bar.Min.Y = int(float64(viewportBounds.Min.Y) * ratio) + element.bar.Max.Y = int(float64(viewportBounds.Max.Y) * ratio) element.bar.Min.Y += element.track.Min.Y element.bar.Max.Y += element.track.Min.Y @@ -243,7 +255,30 @@ func (element *ScrollBar) recalculateVertical () { } func (element *ScrollBar) recalculateHorizontal () { - + bounds := element.Bounds() + padding := element.theme.Padding(theme.PatternGutter) + element.track = padding.Apply(bounds) + + contentBounds := element.contentBounds + viewportBounds := element.viewportBounds + if element.Enabled() { + element.bar.Min.Y = element.track.Min.Y + element.bar.Max.Y = element.track.Max.Y + + ratio := + float64(element.track.Dy()) / + float64(contentBounds.Dy()) + element.bar.Min.X = int(float64(viewportBounds.Min.X) * ratio) + element.bar.Max.X = int(float64(viewportBounds.Max.X) * ratio) + + element.bar.Min.X += element.track.Min.X + element.bar.Max.X += element.track.Min.X + } + + // if the handle is out of bounds, don't display it + if element.bar.Dx() >= element.track.Dx() { + element.bar = image.Rectangle { } + } } func (element *ScrollBar) updateMinimumSize () { @@ -259,7 +294,7 @@ func (element *ScrollBar) updateMinimumSize () { } } -func (element *ScrollBar) redo () { +func (element *ScrollBar) drawAndPush () { if element.core.HasImage () { element.draw() element.core.DamageAll() diff --git a/elements/core/core.go b/elements/core/core.go index b900a60..4b98dca 100644 --- a/elements/core/core.go +++ b/elements/core/core.go @@ -50,7 +50,7 @@ func (core *Core) MinimumSize () (width, height int) { // overridden. func (core *Core) DrawTo (canvas canvas.Canvas) { core.canvas = canvas - if core.drawSizeChange != nil { + if core.drawSizeChange != nil && core.canvas != nil { core.drawSizeChange() } } diff --git a/examples/scroll/main.go b/examples/scroll/main.go index 749ed39..e17c7d2 100644 --- a/examples/scroll/main.go +++ b/examples/scroll/main.go @@ -1,5 +1,6 @@ package main +import "image" import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/layouts/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic" @@ -10,18 +11,52 @@ func main () { } func run () { - window, _ := tomo.NewWindow(2, 2) + window, _ := tomo.NewWindow(480, 360) window.SetTitle("Scroll") container := basicElements.NewContainer(basicLayouts.Vertical { true, true }) window.Adopt(container) - container.Adopt(basicElements.NewLabel("look at this non sense", false), false) - textBox := basicElements.NewTextBox("", "sample text sample text") + textBox := basicElements.NewTextBox("", copypasta) scrollContainer := basicElements.NewScrollContainer(true, false) + + disconnectedContainer := basicElements.NewContainer (basicLayouts.Horizontal { + Gap: true, + }) + list := basicElements.NewList ( + basicElements.NewListEntry("something", nil), + basicElements.NewListEntry("something", nil), + basicElements.NewListEntry("something", nil), + basicElements.NewListEntry("something", nil), + basicElements.NewListEntry("something", nil), + basicElements.NewListEntry("something", nil), + basicElements.NewListEntry("something", nil), + basicElements.NewListEntry("something", nil), + basicElements.NewListEntry("something", nil), + basicElements.NewListEntry("something", nil), + basicElements.NewListEntry("something", nil)) + list.Collapse(0, 32) + scrollBar := basicElements.NewScrollBar(true) + list.OnScrollBoundsChange (func () { + scrollBar.SetBounds ( + list.ScrollContentBounds(), + list.ScrollViewportBounds()) + }) + scrollBar.OnScroll (func (viewport image.Point) { + list.ScrollTo(viewport) + }) + + container.Adopt(basicElements.NewLabel("look at this non sense", false), false) scrollContainer.Adopt(textBox) - container.Adopt(scrollContainer, true) + container.Adopt(scrollContainer, false) + container.Adopt(basicElements.NewLabel("what does that scrollbar do?", false), false) + disconnectedContainer.Adopt(list, false) + disconnectedContainer.Adopt(basicElements.NewSpacer(true), true) + disconnectedContainer.Adopt(scrollBar, false) + container.Adopt(disconnectedContainer, true) window.OnClose(tomo.Stop) window.Show() } + +const copypasta = `"I use Linux as my operating system," I state proudly to the unkempt, bearded man. He swivels around in his desk chair with a devilish gleam in his eyes, ready to mansplain with extreme precision. "Actually", he says with a grin, "Linux is just the kernel. You use GNU+Linux!' I don't miss a beat and reply with a smirk, "I use Alpine, a distro that doesn't include the GNU Coreutils, or any other GNU code. It's Linux, but it's not GNU+Linux." The smile quickly drops from the man's face. His body begins convulsing and he foams at the mouth and drops to the floor with a sickly thud. As he writhes around he screams "I-IT WAS COMPILED WITH GCC! THAT MEANS IT'S STILL GNU!" Coolly, I reply "If windows were compiled with GCC, would that make it GNU?" I interrupt his response with "-and work is being made on the kernel to make it more compiler-agnostic. Even if you were correct, you won't be for long." With a sickly wheeze, the last of the man's life is ejected from his body. He lies on the floor, cold and limp. I've womansplained him to death.`