objects/scrollcontainer.go
2024-06-11 17:17:11 -04:00

199 lines
5.4 KiB
Go

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)
this.SetRole(tomo.R("objects", "ScrollContainer", sides.String()))
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.
func (this *ScrollContainer) SetRoot (root tomo.ContentObject) {
if this.layout.root != nil {
// remove root and close cookies
this.Remove(this.layout.root)
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)
}
}
}
// 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
}
func (this *ScrollContainer) handleScroll (x, y float64) {
if this.layout.root == nil { return }
this.layout.root.ScrollTo (
this.layout.root.ContentBounds().Min.
Sub(image.Pt(int(x), int(y))))
}
// TODO: remove this and replace it with something from the layouts package
type scrollContainerLayout struct {
root tomo.ContentObject
horizontal *Scrollbar
vertical *Scrollbar
}
func (this *scrollContainerLayout) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point {
var minimum image.Point; if this.root != nil {
minimum = this.root.GetBox().MinimumSize()
}
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 {
this.root.GetBox().SetBounds(rootBounds)
}
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 (this *scrollContainerLayout) RecommendedHeight (tomo.LayoutHints, []tomo.Box, int) int {
return 0
}
func (this *scrollContainerLayout) RecommendedWidth (tomo.LayoutHints, []tomo.Box, int) int {
return 0
}
func max (x, y int) int {
if x > y { return x }
return y
}