2023-03-16 12:22:56 -06:00
|
|
|
package containers
|
2023-01-19 11:07:27 -07:00
|
|
|
|
|
|
|
import "image"
|
2023-02-01 23:48:16 -07:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/input"
|
2023-01-19 11:07:27 -07:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/theme"
|
2023-02-07 22:22:40 -07:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/config"
|
2023-02-01 23:48:16 -07:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/elements"
|
2023-01-19 11:07:27 -07:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
2023-03-16 12:22:56 -06:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
|
2023-01-19 11:07:27 -07:00
|
|
|
|
|
|
|
// ScrollContainer is a container that is capable of holding a scrollable
|
|
|
|
// element.
|
|
|
|
type ScrollContainer struct {
|
|
|
|
*core.Core
|
2023-03-10 22:21:54 -07:00
|
|
|
*core.Propagator
|
2023-01-19 11:07:27 -07:00
|
|
|
core core.CoreControl
|
2023-01-20 13:35:43 -07:00
|
|
|
|
2023-03-10 22:21:54 -07:00
|
|
|
child elements.Scrollable
|
2023-03-16 12:22:56 -06:00
|
|
|
horizontal *basicElements.ScrollBar
|
|
|
|
vertical *basicElements.ScrollBar
|
2023-02-07 22:22:40 -07:00
|
|
|
|
2023-02-08 12:36:14 -07:00
|
|
|
config config.Wrapped
|
|
|
|
theme theme.Wrapped
|
2023-01-19 14:49:34 -07:00
|
|
|
|
2023-01-30 15:01:47 -07:00
|
|
|
onFocusRequest func () (granted bool)
|
2023-02-01 23:48:16 -07:00
|
|
|
onFocusMotionRequest func (input.KeynavDirection) (granted bool)
|
2023-01-19 11:07:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewScrollContainer creates a new scroll container with the specified scroll
|
|
|
|
// bars.
|
|
|
|
func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) {
|
2023-02-08 12:36:14 -07:00
|
|
|
element = &ScrollContainer { }
|
2023-03-16 12:22:56 -06:00
|
|
|
element.theme.Case = theme.C("containers", "scrollContainer")
|
2023-03-14 23:41:23 -06:00
|
|
|
element.Core, element.core = core.NewCore(element, element.redoAll)
|
|
|
|
element.Propagator = core.NewPropagator(element, element.core)
|
2023-03-10 22:21:54 -07:00
|
|
|
|
|
|
|
if horizontal {
|
2023-03-16 12:22:56 -06:00
|
|
|
element.horizontal = basicElements.NewScrollBar(false)
|
2023-03-14 23:41:23 -06:00
|
|
|
element.setUpChild(element.horizontal)
|
2023-03-10 22:21:54 -07:00
|
|
|
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 {
|
2023-03-16 12:22:56 -06:00
|
|
|
element.vertical = basicElements.NewScrollBar(true)
|
2023-03-14 23:41:23 -06:00
|
|
|
element.setUpChild(element.vertical)
|
2023-03-10 22:21:54 -07:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2023-01-19 11:07:27 -07:00
|
|
|
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.
|
2023-02-01 23:48:16 -07:00
|
|
|
func (element *ScrollContainer) Adopt (child elements.Scrollable) {
|
2023-01-19 11:07:27 -07:00
|
|
|
// disown previous child if it exists
|
|
|
|
if element.child != nil {
|
2023-03-14 23:41:23 -06:00
|
|
|
element.disownChild(child)
|
2023-01-19 11:07:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// adopt new child
|
|
|
|
element.child = child
|
|
|
|
if child != nil {
|
2023-03-14 23:41:23 -06:00
|
|
|
element.setUpChild(child)
|
2023-02-08 12:36:14 -07:00
|
|
|
}
|
2023-03-10 22:21:54 -07:00
|
|
|
|
|
|
|
element.updateEnabled()
|
|
|
|
element.updateMinimumSize()
|
2023-02-08 12:36:14 -07:00
|
|
|
if element.core.HasImage() {
|
2023-03-10 22:21:54 -07:00
|
|
|
element.redoAll()
|
|
|
|
element.core.DamageAll()
|
2023-02-08 12:36:14 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-14 23:41:23 -06:00
|
|
|
func (element *ScrollContainer) setUpChild (child elements.Element) {
|
|
|
|
child.SetParent(element)
|
|
|
|
if child, ok := child.(elements.Themeable); ok {
|
|
|
|
child.SetTheme(element.theme.Theme)
|
2023-02-08 12:36:14 -07:00
|
|
|
}
|
2023-03-14 23:41:23 -06:00
|
|
|
if child, ok := child.(elements.Configurable); ok {
|
|
|
|
child.SetConfig(element.config.Config)
|
2023-01-19 15:35:19 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-14 23:41:23 -06:00
|
|
|
func (element *ScrollContainer) disownChild (child elements.Scrollable) {
|
|
|
|
child.DrawTo(nil, image.Rectangle { }, nil)
|
|
|
|
child.SetParent(nil)
|
|
|
|
if child, ok := child.(elements.Focusable); ok {
|
|
|
|
if child.Focused() {
|
|
|
|
child.HandleUnfocus()
|
2023-01-21 19:05:51 -07:00
|
|
|
}
|
2023-03-10 22:21:54 -07:00
|
|
|
}
|
2023-01-19 16:03:50 -07:00
|
|
|
}
|
|
|
|
|
2023-03-14 23:41:23 -06:00
|
|
|
// NotifyMinimumSizeChange notifies the container that the minimum size of a
|
|
|
|
// child element has changed.
|
|
|
|
func (element *ScrollContainer) NotifyMinimumSizeChange (child elements.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 elements.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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-10 22:21:54 -07:00
|
|
|
// SetTheme sets the element's theme.
|
|
|
|
func (element *ScrollContainer) SetTheme (new theme.Theme) {
|
|
|
|
if new == element.theme.Theme { return }
|
|
|
|
element.theme.Theme = new
|
|
|
|
element.Propagator.SetTheme(new)
|
|
|
|
element.updateMinimumSize()
|
|
|
|
element.redoAll()
|
2023-01-19 16:03:50 -07:00
|
|
|
}
|
|
|
|
|
2023-03-10 22:21:54 -07:00
|
|
|
// SetConfig sets the element's configuration.
|
|
|
|
func (element *ScrollContainer) SetConfig (new config.Config) {
|
|
|
|
if new == element.config.Config { return }
|
|
|
|
element.Propagator.SetConfig(new)
|
|
|
|
element.updateMinimumSize()
|
|
|
|
element.redoAll()
|
2023-01-19 16:03:50 -07:00
|
|
|
}
|
|
|
|
|
2023-03-15 22:24:40 -06:00
|
|
|
func (element *ScrollContainer) HandleScroll (
|
2023-01-19 16:03:50 -07:00
|
|
|
x, y int,
|
|
|
|
deltaX, deltaY float64,
|
|
|
|
) {
|
2023-03-21 15:37:33 -06:00
|
|
|
horizontal, vertical := element.child.ScrollAxes()
|
|
|
|
if !horizontal { deltaX = 0 }
|
|
|
|
if !vertical { deltaY = 0 }
|
2023-01-21 19:05:51 -07:00
|
|
|
element.scrollChildBy(int(deltaX), int(deltaY))
|
|
|
|
}
|
|
|
|
|
2023-03-10 22:21:54 -07:00
|
|
|
// CountChildren returns the amount of children contained within this element.
|
|
|
|
func (element *ScrollContainer) CountChildren () (count int) {
|
|
|
|
return 3
|
2023-01-19 15:35:19 -07:00
|
|
|
}
|
|
|
|
|
2023-03-10 22:21:54 -07:00
|
|
|
// 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 elements.Element) {
|
|
|
|
switch index {
|
|
|
|
case 0: return element.child
|
|
|
|
case 1:
|
|
|
|
if element.horizontal == nil {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return element.horizontal
|
2023-01-19 14:49:34 -07:00
|
|
|
}
|
2023-03-10 22:21:54 -07:00
|
|
|
case 2:
|
|
|
|
if element.vertical == nil {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return element.vertical
|
|
|
|
}
|
|
|
|
default: return nil
|
2023-01-19 14:49:34 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-10 22:21:54 -07:00
|
|
|
func (element *ScrollContainer) redoAll () {
|
|
|
|
if !element.core.HasImage() { return }
|
2023-01-31 16:39:17 -07:00
|
|
|
|
2023-03-11 23:15:36 -07:00
|
|
|
zr := image.Rectangle { }
|
2023-03-14 23:41:23 -06:00
|
|
|
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) }
|
2023-02-07 22:22:40 -07:00
|
|
|
|
2023-03-10 22:21:54 -07:00
|
|
|
childBounds, horizontalBounds, verticalBounds := element.layout()
|
|
|
|
if element.child != nil {
|
2023-03-11 23:15:36 -07:00
|
|
|
element.child.DrawTo (
|
|
|
|
canvas.Cut(element.core, childBounds),
|
2023-03-14 23:41:23 -06:00
|
|
|
childBounds, element.childDamageCallback)
|
2023-01-19 11:07:27 -07:00
|
|
|
}
|
2023-03-10 22:21:54 -07:00
|
|
|
if element.horizontal != nil {
|
2023-03-11 23:15:36 -07:00
|
|
|
element.horizontal.DrawTo (
|
|
|
|
canvas.Cut(element.core, horizontalBounds),
|
2023-03-14 23:41:23 -06:00
|
|
|
horizontalBounds, element.childDamageCallback)
|
2023-01-20 13:35:43 -07:00
|
|
|
}
|
2023-03-10 22:21:54 -07:00
|
|
|
if element.vertical != nil {
|
2023-03-11 23:15:36 -07:00
|
|
|
element.vertical.DrawTo (
|
|
|
|
canvas.Cut(element.core, verticalBounds),
|
2023-03-14 23:41:23 -06:00
|
|
|
verticalBounds, element.childDamageCallback)
|
2023-01-20 13:35:43 -07:00
|
|
|
}
|
2023-03-10 22:21:54 -07:00
|
|
|
element.draw()
|
2023-01-19 11:07:27 -07:00
|
|
|
}
|
|
|
|
|
2023-03-10 22:21:54 -07:00
|
|
|
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)
|
2023-01-19 11:07:27 -07:00
|
|
|
}
|
|
|
|
|
2023-03-14 23:41:23 -06:00
|
|
|
func (element *ScrollContainer) childDamageCallback (region image.Rectangle) {
|
|
|
|
element.core.DamageRegion(region)
|
2023-01-19 11:07:27 -07:00
|
|
|
}
|
|
|
|
|
2023-03-10 22:21:54 -07:00
|
|
|
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
|
2023-02-07 22:22:40 -07:00
|
|
|
}
|
2023-01-28 23:49:01 -07:00
|
|
|
|
2023-03-10 22:21:54 -07:00
|
|
|
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
|
2023-01-20 23:15:30 -07:00
|
|
|
}
|
|
|
|
|
2023-03-10 22:21:54 -07:00
|
|
|
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 := theme.State { }
|
|
|
|
deadArea := element.theme.Pattern(theme.PatternDead, state)
|
|
|
|
deadArea.Draw(canvas.Cut(element.core, bounds), bounds)
|
|
|
|
}
|
2023-01-20 23:15:30 -07:00
|
|
|
}
|
|
|
|
|
2023-01-19 11:07:27 -07:00
|
|
|
func (element *ScrollContainer) updateMinimumSize () {
|
2023-03-10 22:21:54 -07:00
|
|
|
var width, height int
|
2023-01-29 23:30:13 -07:00
|
|
|
|
2023-01-19 11:07:27 -07:00
|
|
|
if element.child != nil {
|
2023-03-10 22:21:54 -07:00
|
|
|
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
|
|
|
|
}
|
2023-01-19 11:07:27 -07:00
|
|
|
}
|
|
|
|
element.core.SetMinimumSize(width, height)
|
|
|
|
}
|
2023-01-20 13:35:43 -07:00
|
|
|
|
2023-03-10 22:21:54 -07:00
|
|
|
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)
|
2023-01-20 13:35:43 -07:00
|
|
|
}
|
|
|
|
}
|