Scroll containers yay

This commit is contained in:
Sasha Koshka 2023-04-16 03:37:28 -04:00
parent ed6de3a36f
commit b9c8350677
9 changed files with 272 additions and 389 deletions

View File

@ -97,11 +97,13 @@ func (request *selectionRequest) convertSelection (
func (request *selectionRequest) die (err error) { func (request *selectionRequest) die (err error) {
request.callback(nil, err) request.callback(nil, err)
request.window.system.afterEvent()
request.state = selReqStateClosed request.state = selReqStateClosed
} }
func (request *selectionRequest) finalize (data data.Data) { func (request *selectionRequest) finalize (data data.Data) {
request.callback(data, nil) request.callback(data, nil)
request.window.system.afterEvent()
request.state = selReqStateClosed request.state = selReqStateClosed
} }

View File

@ -141,11 +141,13 @@ func (system *system) afterEvent () {
} }
func (system *system) layout (entity *entity, force bool) { func (system *system) layout (entity *entity, force bool) {
if entity == nil || !entity.isContainer { return } if entity == nil { return }
if entity.layoutInvalid == true || force { if entity.layoutInvalid == true || force {
entity.element.(tomo.Container).Layout() if element, ok := entity.element.(tomo.Layoutable); ok {
entity.layoutInvalid = false element.Layout()
force = true entity.layoutInvalid = false
force = true
}
} }
for _, child := range entity.children { for _, child := range entity.children {

View File

@ -15,12 +15,19 @@ type Element interface {
Entity () Entity Entity () Entity
} }
// Container is an element capable of containing child elements. // Layoutable represents an element that needs to perform layout calculations
// before it can draw itself.
type Layoutable interface {
Element
// Layout causes this element to perform a layout operation.
Layout ()
}
// Container represents an element capable of containing child elements.
type Container interface { type Container interface {
Element Element
Layoutable
// Layout causes this element to arrange its children.
Layout ()
// DrawBackground causes the element to draw its background pattern to // DrawBackground causes the element to draw its background pattern to
// the specified canvas. The bounds of this canvas specify the area that // the specified canvas. The bounds of this canvas specify the area that

View File

@ -98,7 +98,6 @@ func (element *Box) Layout () {
if element.margin { x += marginSize } if element.margin { x += marginSize }
} }
} }
} }
func (element *Box) Adopt (child tomo.Element, expand bool) { func (element *Box) Adopt (child tomo.Element, expand bool) {

View File

@ -1,332 +0,0 @@
package containers
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// ScrollContainer is a container that is capable of holding a scrollable
// element.
type ScrollContainer struct {
*core.Core
*core.Propagator
core core.CoreControl
child tomo.Scrollable
horizontal *elements.ScrollBar
vertical *elements.ScrollBar
config config.Wrapped
theme theme.Wrapped
onFocusRequest func () (granted bool)
onFocusMotionRequest func (input.KeynavDirection) (granted bool)
}
// NewScrollContainer creates a new scroll container with the specified scroll
// bars.
func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) {
element = &ScrollContainer { }
element.theme.Case = tomo.C("tomo", "scrollContainer")
element.Core, element.core = core.NewCore(element, element.redoAll)
element.Propagator = core.NewPropagator(element, element.core)
if horizontal {
element.horizontal = elements.NewScrollBar(false)
element.setUpChild(element.horizontal)
element.horizontal.OnScroll (func (viewport image.Point) {
if element.child != nil {
element.child.ScrollTo(viewport)
}
if element.vertical != nil {
element.vertical.SetBounds (
element.child.ScrollContentBounds(),
element.child.ScrollViewportBounds())
}
})
}
if vertical {
element.vertical = elements.NewScrollBar(true)
element.setUpChild(element.vertical)
element.vertical.OnScroll (func (viewport image.Point) {
if element.child != nil {
element.child.ScrollTo(viewport)
}
if element.horizontal != nil {
element.horizontal.SetBounds (
element.child.ScrollContentBounds(),
element.child.ScrollViewportBounds())
}
})
}
return
}
// 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.disownChild(child)
}
// adopt new child
element.child = child
if child != nil {
element.setUpChild(child)
}
element.updateEnabled()
element.updateMinimumSize()
if element.core.HasImage() {
element.redoAll()
element.core.DamageAll()
}
}
func (element *ScrollContainer) setUpChild (child tomo.Element) {
child.SetParent(element)
if child, ok := child.(tomo.Themeable); ok {
child.SetTheme(element.theme.Theme)
}
if child, ok := child.(tomo.Configurable); ok {
child.SetConfig(element.config.Config)
}
}
func (element *ScrollContainer) disownChild (child tomo.Scrollable) {
child.DrawTo(nil, image.Rectangle { }, nil)
child.SetParent(nil)
if child, ok := child.(tomo.Focusable); ok {
if child.Focused() {
child.HandleUnfocus()
}
}
}
func (element *ScrollContainer) Window () tomo.Window {
return element.core.Window()
}
// NotifyMinimumSizeChange notifies the container that the minimum size of a
// child element has changed.
func (element *ScrollContainer) NotifyMinimumSizeChange (child tomo.Element) {
element.redoAll()
element.core.DamageAll()
}
// NotifyScrollBoundsChange notifies the container that the scroll bounds or
// axes of a child have changed.
func (element *ScrollContainer) NotifyScrollBoundsChange (child tomo.Scrollable) {
element.updateEnabled()
viewportBounds := element.child.ScrollViewportBounds()
contentBounds := element.child.ScrollContentBounds()
if element.horizontal != nil {
element.horizontal.SetBounds(contentBounds, viewportBounds)
}
if element.vertical != nil {
element.vertical.SetBounds(contentBounds, viewportBounds)
}
}
// DrawBackground draws a portion of the container's background pattern within
// the specified bounds. The container will not push these changes.
func (element *ScrollContainer) DrawBackground (bounds image.Rectangle) {
element.core.DrawBackgroundBounds (
element.theme.Pattern(tomo.PatternBackground, tomo.State { }),
bounds)
}
// SetTheme sets the element's theme.
func (element *ScrollContainer) SetTheme (new tomo.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.Propagator.SetTheme(new)
element.updateMinimumSize()
element.redoAll()
}
// SetConfig sets the element's configuration.
func (element *ScrollContainer) SetConfig (new tomo.Config) {
if new == element.config.Config { return }
element.Propagator.SetConfig(new)
element.updateMinimumSize()
element.redoAll()
}
func (element *ScrollContainer) HandleScroll (
x, y int,
deltaX, deltaY float64,
) {
horizontal, vertical := element.child.ScrollAxes()
if !horizontal { deltaX = 0 }
if !vertical { deltaY = 0 }
element.scrollChildBy(int(deltaX), int(deltaY))
}
// HandleKeyDown is called when a key is pressed down or repeated while
// this element has keyboard focus. It is important to note that not
// every key down event is guaranteed to be paired with exactly one key
// up event. This is the reason a list of modifier keys held down at the
// time of the key press is given.
func (element *ScrollContainer) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
switch key {
case input.KeyPageUp:
viewport := element.child.ScrollViewportBounds()
element.HandleScroll(0, 0, 0, float64(-viewport.Dy()))
case input.KeyPageDown:
viewport := element.child.ScrollViewportBounds()
element.HandleScroll(0, 0, 0, float64(viewport.Dy()))
default:
element.Propagator.HandleKeyDown(key, modifiers)
}
}
// HandleKeyUp is called when a key is released while this element has
// keyboard focus.
func (element *ScrollContainer) HandleKeyUp (key input.Key, modifiers input.Modifiers) { }
// CountChildren returns the amount of children contained within this element.
func (element *ScrollContainer) CountChildren () (count int) {
return 3
}
// Child returns the child at the specified index. If the index is out of
// bounds, this method will return nil.
func (element *ScrollContainer) Child (index int) (child tomo.Element) {
switch index {
case 0: return element.child
case 1:
if element.horizontal == nil {
return nil
} else {
return element.horizontal
}
case 2:
if element.vertical == nil {
return nil
} else {
return element.vertical
}
default: return nil
}
}
func (element *ScrollContainer) redoAll () {
if !element.core.HasImage() { return }
zr := image.Rectangle { }
if element.child != nil { element.child.DrawTo(nil, zr, nil) }
if element.horizontal != nil { element.horizontal.DrawTo(nil, zr, nil) }
if element.vertical != nil { element.vertical.DrawTo(nil, zr, nil) }
childBounds, horizontalBounds, verticalBounds := element.layout()
if element.child != nil {
element.child.DrawTo (
canvas.Cut(element.core, childBounds),
childBounds, element.childDamageCallback)
}
if element.horizontal != nil {
element.horizontal.DrawTo (
canvas.Cut(element.core, horizontalBounds),
horizontalBounds, element.childDamageCallback)
}
if element.vertical != nil {
element.vertical.DrawTo (
canvas.Cut(element.core, verticalBounds),
verticalBounds, element.childDamageCallback)
}
element.draw()
}
func (element *ScrollContainer) scrollChildBy (x, y int) {
if element.child == nil { return }
scrollPoint :=
element.child.ScrollViewportBounds().Min.
Add(image.Pt(x, y))
element.child.ScrollTo(scrollPoint)
}
func (element *ScrollContainer) childDamageCallback (region image.Rectangle) {
element.core.DamageRegion(region)
}
func (element *ScrollContainer) layout () (
child image.Rectangle,
horizontal image.Rectangle,
vertical image.Rectangle,
) {
bounds := element.Bounds()
child = bounds
if element.horizontal != nil {
_, hMinHeight := element.horizontal.MinimumSize()
child.Max.Y -= hMinHeight
}
if element.vertical != nil {
vMinWidth, _ := element.vertical.MinimumSize()
child.Max.X -= vMinWidth
}
vertical.Min.X = child.Max.X
vertical.Max.X = bounds.Max.X
vertical.Min.Y = bounds.Min.Y
vertical.Max.Y = child.Max.Y
horizontal.Min.X = bounds.Min.X
horizontal.Max.X = child.Max.X
horizontal.Min.Y = child.Max.Y
horizontal.Max.Y = bounds.Max.Y
return
}
func (element *ScrollContainer) draw () {
if element.horizontal != nil && element.vertical != nil {
bounds := element.Bounds()
bounds.Min = image.Pt (
bounds.Max.X - element.vertical.Bounds().Dx(),
bounds.Max.Y - element.horizontal.Bounds().Dy())
state := tomo.State { }
deadArea := element.theme.Pattern(tomo.PatternDead, state)
deadArea.Draw(canvas.Cut(element.core, bounds), bounds)
}
}
func (element *ScrollContainer) updateMinimumSize () {
var width, height int
if element.child != nil {
width, height = element.child.MinimumSize()
}
if element.horizontal != nil {
hMinWidth, hMinHeight := element.horizontal.MinimumSize()
height += hMinHeight
if hMinWidth > width {
width = hMinWidth
}
}
if element.vertical != nil {
vMinWidth, vMinHeight := element.vertical.MinimumSize()
width += vMinWidth
if vMinHeight > height {
height = vMinHeight
}
}
element.core.SetMinimumSize(width, height)
}
func (element *ScrollContainer) updateEnabled () {
horizontal, vertical := element.child.ScrollAxes()
if element.horizontal != nil {
element.horizontal.SetEnabled(horizontal)
}
if element.vertical != nil {
element.vertical.SetEnabled(vertical)
}
}

View File

@ -0,0 +1,197 @@
package containers
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
// import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
type Scroll struct {
entity tomo.ContainerEntity
child tomo.Scrollable
horizontal *elements.ScrollBar
vertical *elements.ScrollBar
config config.Wrapped
theme theme.Wrapped
}
func NewScroll (horizontal, vertical bool) (element *Scroll) {
element = &Scroll { }
element.theme.Case = tomo.C("tomo", "scroll")
element.entity = tomo.NewEntity(element).(tomo.ContainerEntity)
if horizontal {
element.horizontal = elements.NewScrollBar(false)
element.horizontal.OnScroll (func (viewport image.Point) {
if element.child != nil {
element.child.ScrollTo(viewport)
}
if element.vertical != nil {
element.vertical.SetBounds (
element.child.ScrollContentBounds(),
element.child.ScrollViewportBounds())
}
})
element.entity.Adopt(element.horizontal)
}
if vertical {
element.vertical = elements.NewScrollBar(true)
element.vertical.OnScroll (func (viewport image.Point) {
if element.child != nil {
element.child.ScrollTo(viewport)
}
if element.horizontal != nil {
element.horizontal.SetBounds (
element.child.ScrollContentBounds(),
element.child.ScrollViewportBounds())
}
})
element.entity.Adopt(element.vertical)
}
return
}
func (element *Scroll) Entity () tomo.Entity {
return element.entity
}
func (element *Scroll) Draw (destination canvas.Canvas) {
if element.horizontal != nil && element.vertical != nil {
bounds := element.entity.Bounds()
bounds.Min = image.Pt (
bounds.Max.X - element.vertical.Entity().Bounds().Dx(),
bounds.Max.Y - element.horizontal.Entity().Bounds().Dy())
state := tomo.State { }
deadArea := element.theme.Pattern(tomo.PatternDead, state)
deadArea.Draw(canvas.Cut(destination, bounds), bounds)
}
}
func (element *Scroll) Layout () {
bounds := element.entity.Bounds()
child := bounds
iHorizontal := element.entity.IndexOf(element.horizontal)
iVertical := element.entity.IndexOf(element.vertical)
iChild := element.entity.IndexOf(element.child)
var horizontal, vertical image.Rectangle
if element.horizontal != nil {
_, hMinHeight := element.entity.ChildMinimumSize(iHorizontal)
child.Max.Y -= hMinHeight
}
if element.vertical != nil {
vMinWidth, _ := element.entity.ChildMinimumSize(iVertical)
child.Max.X -= vMinWidth
}
horizontal.Min.X = bounds.Min.X
horizontal.Max.X = child.Max.X
horizontal.Min.Y = child.Max.Y
horizontal.Max.Y = bounds.Max.Y
vertical.Min.X = child.Max.X
vertical.Max.X = bounds.Max.X
vertical.Min.Y = bounds.Min.Y
vertical.Max.Y = child.Max.Y
if element.horizontal != nil {
element.entity.PlaceChild (iHorizontal, horizontal)
}
if element.vertical != nil {
element.entity.PlaceChild(iVertical, vertical)
}
if element.child != nil {
element.entity.PlaceChild(iChild, child)
}
}
func (element *Scroll) DrawBackground (destination canvas.Canvas) {
element.entity.DrawBackground(destination)
}
func (element *Scroll) Adopt (child tomo.Scrollable) {
if element.child != nil {
element.entity.Disown(element.entity.IndexOf(element.child))
}
if child != nil {
element.entity.Adopt(child)
}
element.child = child
element.updateEnabled()
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
}
func (element *Scroll) HandleChildMinimumSizeChange (tomo.Element) {
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
}
func (element *Scroll) HandleChildScrollBoundsChange (tomo.Scrollable) {
element.updateEnabled()
viewportBounds := element.child.ScrollViewportBounds()
contentBounds := element.child.ScrollContentBounds()
if element.horizontal != nil {
element.horizontal.SetBounds(contentBounds, viewportBounds)
}
if element.vertical != nil {
element.vertical.SetBounds(contentBounds, viewportBounds)
}
}
func (element *Scroll) SetTheme (theme tomo.Theme) {
if theme == element.theme.Theme { return }
element.theme.Theme = theme
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
}
func (element *Scroll) SetConfig (config tomo.Config) {
element.config.Config = config
}
func (element *Scroll) updateMinimumSize () {
var width, height int
if element.child != nil {
width, height = element.entity.ChildMinimumSize (
element.entity.IndexOf(element.child))
}
if element.horizontal != nil {
hMinWidth, hMinHeight := element.entity.ChildMinimumSize (
element.entity.IndexOf(element.horizontal))
height += hMinHeight
if hMinWidth > width {
width = hMinWidth
}
}
if element.vertical != nil {
vMinWidth, vMinHeight := element.entity.ChildMinimumSize (
element.entity.IndexOf(element.vertical))
width += vMinWidth
if vMinHeight > height {
height = vMinHeight
}
}
element.entity.SetMinimumSize(width, height)
}
func (element *Scroll) updateEnabled () {
horizontal, vertical := element.child.ScrollAxes()
if element.horizontal != nil {
element.horizontal.SetEnabled(horizontal)
}
if element.vertical != nil {
element.vertical.SetEnabled(vertical)
}
}

View File

@ -18,6 +18,7 @@ import "git.tebibyte.media/sashakoshka/tomo/default/config"
type textBoxEntity interface { type textBoxEntity interface {
tomo.FocusableEntity tomo.FocusableEntity
tomo.ScrollableEntity tomo.ScrollableEntity
tomo.LayoutEntity
} }
// TextBox is a single-line text input. // TextBox is a single-line text input.
@ -72,7 +73,6 @@ func (element *TextBox) Entity () tomo.Entity {
// Draw causes the element to draw to the specified destination canvas. // Draw causes the element to draw to the specified destination canvas.
func (element *TextBox) Draw (destination canvas.Canvas) { func (element *TextBox) Draw (destination canvas.Canvas) {
bounds := element.entity.Bounds() bounds := element.entity.Bounds()
element.scrollToCursor()
state := element.state() state := element.state()
pattern := element.theme.Pattern(tomo.PatternInput, state) pattern := element.theme.Pattern(tomo.PatternInput, state)
@ -134,6 +134,11 @@ func (element *TextBox) Draw (destination canvas.Canvas) {
} }
} }
// Layout causes the element to perform a layout operation.
func (element *TextBox) Layout () {
element.scrollToCursor()
}
func (element *TextBox) HandleFocusChange () { func (element *TextBox) HandleFocusChange () {
element.entity.Invalidate() element.entity.Invalidate()
} }
@ -497,8 +502,8 @@ func (element *TextBox) scrollToCursor () {
} else if cursorPosition.X < minX { } else if cursorPosition.X < minX {
element.scroll -= minX - cursorPosition.X element.scroll -= minX - cursorPosition.X
if element.scroll < 0 { element.scroll = 0 } if element.scroll < 0 { element.scroll = 0 }
element.entity.Invalidate()
element.entity.NotifyScrollBoundsChange() element.entity.NotifyScrollBoundsChange()
element.entity.Invalidate()
} }
} }

View File

@ -31,14 +31,20 @@ type Entity interface {
DrawBackground (canvas.Canvas) DrawBackground (canvas.Canvas)
} }
// LayoutEntity is given to elements that support the Layoutable interface.
type LayoutEntity interface {
Entity
// InvalidateLayout marks the element's layout as invalid. At the end of
// every event, the backend will ask all invalid elements to recalculate
// their layouts.
InvalidateLayout ()
}
// ContainerEntity is given to elements that support the Container interface. // ContainerEntity is given to elements that support the Container interface.
type ContainerEntity interface { type ContainerEntity interface {
Entity Entity
LayoutEntity
// InvalidateLayout marks the element's layout as invalid. At the end of
// every event, the backend will ask all invalid containers to
// recalculate their layouts.
InvalidateLayout ()
// Adopt adds an element as a child. // Adopt adds an element as a child.
Adopt (child Element) Adopt (child Element)

View File

@ -1,8 +1,7 @@
package main package main
import "image" // import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/layouts"
import "git.tebibyte.media/sashakoshka/tomo/elements" import "git.tebibyte.media/sashakoshka/tomo/elements"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/all" import _ "git.tebibyte.media/sashakoshka/tomo/backends/all"
import "git.tebibyte.media/sashakoshka/tomo/elements/containers" import "git.tebibyte.media/sashakoshka/tomo/elements/containers"
@ -14,58 +13,56 @@ func main () {
func run () { func run () {
window, _ := tomo.NewWindow(tomo.Bounds(0, 0, 360, 240)) window, _ := tomo.NewWindow(tomo.Bounds(0, 0, 360, 240))
window.SetTitle("Scroll") window.SetTitle("Scroll")
container := containers.NewContainer(layouts.Vertical { true, true }) container := containers.NewVBox(true, true)
window.Adopt(container) window.Adopt(container)
textBox := elements.NewTextBox("", copypasta) textBox := elements.NewTextBox("", copypasta)
scrollContainer := containers.NewScrollContainer(true, false) scrollContainer := containers.NewScroll(true, false)
disconnectedContainer := containers.NewContainer (layouts.Horizontal { disconnectedContainer := containers.NewHBox(false, true)
Gap: true, // list := elements.NewList (
}) // elements.NewListEntry("This is list item 0", nil),
list := elements.NewList ( // elements.NewListEntry("This is list item 1", nil),
elements.NewListEntry("This is list item 0", nil), // elements.NewListEntry("This is list item 2", nil),
elements.NewListEntry("This is list item 1", nil), // elements.NewListEntry("This is list item 3", nil),
elements.NewListEntry("This is list item 2", nil), // elements.NewListEntry("This is list item 4", nil),
elements.NewListEntry("This is list item 3", nil), // elements.NewListEntry("This is list item 5", nil),
elements.NewListEntry("This is list item 4", nil), // elements.NewListEntry("This is list item 6", nil),
elements.NewListEntry("This is list item 5", nil), // elements.NewListEntry("This is list item 7", nil),
elements.NewListEntry("This is list item 6", nil), // elements.NewListEntry("This is list item 8", nil),
elements.NewListEntry("This is list item 7", nil), // elements.NewListEntry("This is list item 9", nil),
elements.NewListEntry("This is list item 8", nil), // elements.NewListEntry("This is list item 10", nil),
elements.NewListEntry("This is list item 9", nil), // elements.NewListEntry("This is list item 11", nil),
elements.NewListEntry("This is list item 10", nil), // elements.NewListEntry("This is list item 12", nil),
elements.NewListEntry("This is list item 11", nil), // elements.NewListEntry("This is list item 13", nil),
elements.NewListEntry("This is list item 12", nil), // elements.NewListEntry("This is list item 14", nil),
elements.NewListEntry("This is list item 13", nil), // elements.NewListEntry("This is list item 15", nil),
elements.NewListEntry("This is list item 14", nil), // elements.NewListEntry("This is list item 16", nil),
elements.NewListEntry("This is list item 15", nil), // elements.NewListEntry("This is list item 17", nil),
elements.NewListEntry("This is list item 16", nil), // elements.NewListEntry("This is list item 18", nil),
elements.NewListEntry("This is list item 17", nil), // elements.NewListEntry("This is list item 19", nil),
elements.NewListEntry("This is list item 18", nil), // elements.NewListEntry("This is list item 20", nil))
elements.NewListEntry("This is list item 19", nil), // list.Collapse(0, 32)
elements.NewListEntry("This is list item 20", nil)) // scrollBar := elements.NewScrollBar(true)
list.Collapse(0, 32) // list.OnScrollBoundsChange (func () {
scrollBar := elements.NewScrollBar(true) // scrollBar.SetBounds (
list.OnScrollBoundsChange (func () { // list.ScrollContentBounds(),
scrollBar.SetBounds ( // list.ScrollViewportBounds())
list.ScrollContentBounds(), // })
list.ScrollViewportBounds()) // scrollBar.OnScroll (func (viewport image.Point) {
}) // list.ScrollTo(viewport)
scrollBar.OnScroll (func (viewport image.Point) { // })
list.ScrollTo(viewport)
})
scrollContainer.Adopt(textBox) scrollContainer.Adopt(textBox)
container.Adopt(elements.NewLabel("A ScrollContainer:", false), false) container.Adopt(elements.NewLabel("A ScrollContainer:", false), false)
container.Adopt(scrollContainer, false) container.Adopt(scrollContainer, false)
disconnectedContainer.Adopt(list, false) // disconnectedContainer.Adopt(list, false)
disconnectedContainer.Adopt (elements.NewLabel ( disconnectedContainer.Adopt (elements.NewLabel (
"Notice how the scroll bar to the right can be used to " + "Notice how the scroll bar to the right can be used to " +
"control the list, despite not even touching it. It is " + "control the list, despite not even touching it. It is " +
"indeed a thing you can do. It is also terrible UI design so " + "indeed a thing you can do. It is also terrible UI design so " +
"don't do it.", true), true) "don't do it.", true), true)
disconnectedContainer.Adopt(scrollBar, false) // disconnectedContainer.Adopt(scrollBar, false)
container.Adopt(disconnectedContainer, true) container.Adopt(disconnectedContainer, true)
window.OnClose(tomo.Stop) window.OnClose(tomo.Stop)