2023-09-14 23:47:58 -06:00
|
|
|
package objects
|
|
|
|
|
|
|
|
import "image"
|
|
|
|
import "git.tebibyte.media/tomo/tomo"
|
|
|
|
import "git.tebibyte.media/tomo/tomo/event"
|
|
|
|
|
|
|
|
// ScrollSide determines which Scrollbars are active in a ScrollContainer.
|
|
|
|
type ScrollSide int; const (
|
|
|
|
ScrollVertical ScrollSide = 1 << iota
|
|
|
|
ScrollHorizontal
|
|
|
|
ScrollBoth = ScrollVertical | ScrollHorizontal
|
|
|
|
)
|
|
|
|
|
|
|
|
// Horizontal returns true if the side includes a horizontal bar.
|
|
|
|
func (sides ScrollSide) Horizontal () bool {
|
|
|
|
return sides & ScrollHorizontal > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// Vertical returns true if the side includes a vertical bar.
|
|
|
|
func (sides ScrollSide) Vertical () bool {
|
|
|
|
return sides & ScrollVertical > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns one of:
|
|
|
|
// - both
|
|
|
|
// - horizontal
|
|
|
|
// - vertical
|
|
|
|
// - none
|
|
|
|
func (sides ScrollSide) String () string {
|
|
|
|
switch {
|
|
|
|
case sides.Horizontal() && sides.Vertical(): return "both"
|
|
|
|
case sides.Horizontal(): return "horizontal"
|
|
|
|
case sides.Vertical(): return "vertical"
|
|
|
|
default: return "none"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ScrollContainer couples a ContentBox with one or two Scrollbars.
|
|
|
|
type ScrollContainer struct {
|
|
|
|
tomo.ContainerBox
|
|
|
|
layout *scrollContainerLayout
|
|
|
|
|
|
|
|
horizontalCookie event.Cookie
|
|
|
|
verticalCookie event.Cookie
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewScrollContainer creates a new scroll container.
|
|
|
|
func NewScrollContainer (sides ScrollSide) *ScrollContainer {
|
|
|
|
this := &ScrollContainer {
|
|
|
|
ContainerBox: tomo.NewContainerBox(),
|
|
|
|
layout: &scrollContainerLayout { },
|
|
|
|
}
|
|
|
|
if sides.Vertical() {
|
|
|
|
this.layout.vertical = NewVerticalScrollbar()
|
|
|
|
this.Add(this.layout.vertical)
|
|
|
|
}
|
|
|
|
if sides.Horizontal() {
|
|
|
|
this.layout.horizontal = NewHorizontalScrollbar()
|
|
|
|
this.Add(this.layout.horizontal)
|
|
|
|
}
|
|
|
|
this.CaptureScroll(true)
|
|
|
|
this.OnScroll(this.handleScroll)
|
2024-06-03 19:13:18 -06:00
|
|
|
this.SetRole(tomo.R("objects", "ScrollContainer", sides.String()))
|
2023-09-14 23:47:58 -06:00
|
|
|
this.SetLayout(this.layout)
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetRoot sets the root child of the ScrollContainer. There can only be one at
|
|
|
|
// a time, and setting it will remove and unlink the current child if there is
|
|
|
|
// one.
|
2024-05-27 14:28:48 -06:00
|
|
|
func (this *ScrollContainer) SetRoot (root tomo.ContentObject) {
|
2023-09-14 23:47:58 -06:00
|
|
|
if this.layout.root != nil {
|
2024-05-26 15:21:58 -06:00
|
|
|
// remove root and close cookies
|
|
|
|
this.Remove(this.layout.root)
|
2023-09-14 23:47:58 -06:00
|
|
|
if this.horizontalCookie != nil {
|
|
|
|
this.horizontalCookie.Close()
|
|
|
|
this.horizontalCookie = nil
|
|
|
|
}
|
|
|
|
if this.verticalCookie != nil {
|
|
|
|
this.verticalCookie.Close()
|
|
|
|
this.verticalCookie = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.layout.root = root
|
|
|
|
if root != nil {
|
|
|
|
// insert root at the beginning (for keynav)
|
|
|
|
switch {
|
|
|
|
case this.layout.vertical != nil:
|
|
|
|
this.Insert(root, this.layout.vertical)
|
|
|
|
case this.layout.horizontal != nil:
|
|
|
|
this.Insert(root, this.layout.horizontal)
|
|
|
|
default:
|
|
|
|
this.Add(root)
|
|
|
|
}
|
|
|
|
|
|
|
|
// link root and remember cookies
|
|
|
|
if this.layout.horizontal != nil {
|
|
|
|
this.horizontalCookie = this.layout.horizontal.Link(root)
|
|
|
|
}
|
|
|
|
if this.layout.vertical != nil {
|
|
|
|
this.verticalCookie = this.layout.vertical.Link(root)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-16 23:08:39 -06:00
|
|
|
// SetValue sets the horizontal and vertical scrollbar values where 0 is all the
|
|
|
|
// way to the left/top, and 1 is all the way to the right/bottom.
|
|
|
|
func (this *ScrollContainer) SetValue (x, y float64) {
|
|
|
|
if this.layout.horizontal != nil {
|
|
|
|
this.layout.horizontal.SetValue(x)
|
|
|
|
}
|
|
|
|
if this.layout.vertical != nil {
|
|
|
|
this.layout.vertical.SetValue(y)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Value returns the horizontal and vertical scrollbar values where 0 is all the
|
|
|
|
// way to the left/top, and 1 is all the way to the right/bottom.
|
|
|
|
func (this *ScrollContainer) Value () (x, y float64) {
|
|
|
|
if this.layout.horizontal != nil {
|
|
|
|
x = this.layout.horizontal.Value()
|
|
|
|
}
|
|
|
|
if this.layout.vertical != nil {
|
|
|
|
y = this.layout.vertical.Value()
|
|
|
|
}
|
|
|
|
return x, y
|
|
|
|
}
|
|
|
|
|
2023-09-14 23:47:58 -06:00
|
|
|
func (this *ScrollContainer) handleScroll (x, y float64) {
|
|
|
|
if this.layout.root == nil { return }
|
|
|
|
this.layout.root.ScrollTo (
|
|
|
|
this.layout.root.ContentBounds().Min.
|
2024-05-13 17:45:18 -06:00
|
|
|
Sub(image.Pt(int(x), int(y))))
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type scrollContainerLayout struct {
|
2024-05-27 14:28:48 -06:00
|
|
|
root tomo.ContentObject
|
2023-09-14 23:47:58 -06:00
|
|
|
horizontal *Scrollbar
|
|
|
|
vertical *Scrollbar
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *scrollContainerLayout) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point {
|
|
|
|
var minimum image.Point; if this.root != nil {
|
2024-05-27 14:28:48 -06:00
|
|
|
minimum = this.root.GetBox().MinimumSize()
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
|
|
|
if this.horizontal != nil {
|
|
|
|
minimum.Y += this.horizontal.MinimumSize().Y
|
|
|
|
}
|
|
|
|
if this.vertical != nil {
|
|
|
|
minimum.X += this.vertical.MinimumSize().X
|
|
|
|
minimum.Y = max(minimum.Y, this.vertical.MinimumSize().Y)
|
|
|
|
}
|
|
|
|
if this.horizontal != nil {
|
|
|
|
minimum.X = max(minimum.X, this.horizontal.MinimumSize().X)
|
|
|
|
}
|
|
|
|
return minimum
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *scrollContainerLayout) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) {
|
|
|
|
rootBounds := hints.Bounds
|
|
|
|
if this.horizontal != nil {
|
|
|
|
rootBounds.Max.Y -= this.horizontal.MinimumSize().Y
|
|
|
|
}
|
|
|
|
if this.vertical != nil {
|
|
|
|
rootBounds.Max.X -= this.vertical.MinimumSize().X
|
|
|
|
}
|
|
|
|
if this.root != nil {
|
2024-05-27 14:28:48 -06:00
|
|
|
this.root.GetBox().SetBounds(rootBounds)
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
|
|
|
if this.horizontal != nil {
|
|
|
|
this.horizontal.SetBounds(image.Rect (
|
|
|
|
hints.Bounds.Min.X,
|
|
|
|
rootBounds.Max.Y,
|
|
|
|
rootBounds.Max.X,
|
|
|
|
hints.Bounds.Max.Y))
|
|
|
|
}
|
|
|
|
if this.vertical != nil {
|
|
|
|
this.vertical.SetBounds(image.Rect (
|
|
|
|
rootBounds.Max.X,
|
|
|
|
hints.Bounds.Min.Y,
|
|
|
|
hints.Bounds.Max.X,
|
|
|
|
rootBounds.Max.Y))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func max (x, y int) int {
|
|
|
|
if x > y { return x }
|
|
|
|
return y
|
|
|
|
}
|