2023-09-14 23:47:58 -06:00
|
|
|
package objects
|
|
|
|
|
|
|
|
import "image"
|
|
|
|
import "git.tebibyte.media/tomo/tomo"
|
2024-07-21 09:48:28 -06:00
|
|
|
import "git.tebibyte.media/tomo/tomo/input"
|
2023-09-14 23:47:58 -06:00
|
|
|
import "git.tebibyte.media/tomo/tomo/event"
|
2024-07-21 09:48:28 -06:00
|
|
|
import "git.tebibyte.media/tomo/objects/layouts"
|
2023-09-14 23:47:58 -06:00
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
root tomo.ContentObject
|
|
|
|
horizontal *Scrollbar
|
|
|
|
vertical *Scrollbar
|
|
|
|
|
2023-09-14 23:47:58 -06:00
|
|
|
horizontalCookie event.Cookie
|
|
|
|
verticalCookie event.Cookie
|
2024-06-27 12:01:14 -06:00
|
|
|
|
|
|
|
on struct {
|
|
|
|
valueChange event.FuncBroadcaster
|
|
|
|
}
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewScrollContainer creates a new scroll container.
|
|
|
|
func NewScrollContainer (sides ScrollSide) *ScrollContainer {
|
|
|
|
this := &ScrollContainer {
|
|
|
|
ContainerBox: tomo.NewContainerBox(),
|
|
|
|
}
|
|
|
|
if sides.Vertical() {
|
2024-07-21 09:48:28 -06:00
|
|
|
this.vertical = NewVerticalScrollbar()
|
|
|
|
this.vertical.OnValueChange(this.handleValueChange)
|
|
|
|
this.Add(this.vertical)
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
|
|
|
if sides.Horizontal() {
|
2024-07-21 09:48:28 -06:00
|
|
|
this.horizontal = NewHorizontalScrollbar()
|
|
|
|
this.horizontal.OnValueChange(this.handleValueChange)
|
|
|
|
this.Add(this.horizontal)
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
|
|
|
this.OnScroll(this.handleScroll)
|
2024-07-21 09:48:28 -06:00
|
|
|
this.OnKeyDown(this.handleKeyDown)
|
|
|
|
this.OnKeyUp(this.handleKeyUp)
|
|
|
|
this.SetRole(tomo.R("objects", "ScrollContainer"))
|
|
|
|
this.SetTag(sides.String(), true)
|
2024-07-26 15:56:29 -06:00
|
|
|
if sides == ScrollHorizontal {
|
2024-07-26 15:54:32 -06:00
|
|
|
this.SetAttr(tomo.ALayout(layouts.NewGrid(true)(true, false)))
|
|
|
|
} else {
|
|
|
|
this.SetAttr(tomo.ALayout(layouts.NewGrid(true, false)(true, false)))
|
|
|
|
}
|
2023-09-14 23:47:58 -06:00
|
|
|
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) {
|
2024-07-21 09:48:28 -06:00
|
|
|
if this.root != nil {
|
2024-05-26 15:21:58 -06:00
|
|
|
// remove root and close cookies
|
2024-07-21 09:48:28 -06:00
|
|
|
this.Remove(this.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
|
|
|
|
}
|
|
|
|
}
|
2024-07-21 09:48:28 -06:00
|
|
|
this.root = root
|
2023-09-14 23:47:58 -06:00
|
|
|
if root != nil {
|
|
|
|
// insert root at the beginning (for keynav)
|
|
|
|
switch {
|
2024-07-21 09:48:28 -06:00
|
|
|
case this.vertical != nil:
|
|
|
|
this.Insert(root, this.vertical)
|
|
|
|
case this.horizontal != nil:
|
|
|
|
this.Insert(root, this.horizontal)
|
2023-09-14 23:47:58 -06:00
|
|
|
default:
|
|
|
|
this.Add(root)
|
|
|
|
}
|
|
|
|
|
|
|
|
// link root and remember cookies
|
2024-07-21 09:48:28 -06:00
|
|
|
if this.horizontal != nil {
|
|
|
|
this.horizontalCookie = this.horizontal.Link(root)
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
2024-07-21 09:48:28 -06:00
|
|
|
if this.vertical != nil {
|
|
|
|
this.verticalCookie = this.vertical.Link(root)
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-27 12:01:14 -06:00
|
|
|
// Value returns the horizontal and vertical scrollbar values where 0 is all the
|
2024-05-16 23:08:39 -06:00
|
|
|
// way to the left/top, and 1 is all the way to the right/bottom.
|
2024-06-27 12:01:14 -06:00
|
|
|
func (this *ScrollContainer) Value () (x, y float64) {
|
2024-07-21 09:48:28 -06:00
|
|
|
if this.horizontal != nil {
|
|
|
|
x = this.horizontal.Value()
|
2024-05-16 23:08:39 -06:00
|
|
|
}
|
2024-07-21 09:48:28 -06:00
|
|
|
if this.vertical != nil {
|
|
|
|
y = this.vertical.Value()
|
2024-05-16 23:08:39 -06:00
|
|
|
}
|
2024-06-27 12:01:14 -06:00
|
|
|
return x, y
|
2024-05-16 23:08:39 -06:00
|
|
|
}
|
|
|
|
|
2024-06-27 12:01:14 -06:00
|
|
|
// SetValue sets the horizontal and vertical scrollbar values where 0 is all the
|
2024-05-16 23:08:39 -06:00
|
|
|
// way to the left/top, and 1 is all the way to the right/bottom.
|
2024-06-27 12:01:14 -06:00
|
|
|
func (this *ScrollContainer) SetValue (x, y float64) {
|
2024-07-21 09:48:28 -06:00
|
|
|
if this.horizontal != nil {
|
|
|
|
this.horizontal.SetValue(x)
|
2024-05-16 23:08:39 -06:00
|
|
|
}
|
2024-07-21 09:48:28 -06:00
|
|
|
if this.vertical != nil {
|
|
|
|
this.vertical.SetValue(y)
|
2024-05-16 23:08:39 -06:00
|
|
|
}
|
2024-06-27 12:01:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// OnValueChange specifies a function to be called when the user changes the
|
|
|
|
// position of the horizontal or vertical scrollbars.
|
|
|
|
func (this *ScrollContainer) OnValueChange (callback func ()) event.Cookie {
|
|
|
|
return this.on.valueChange.Connect(callback)
|
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
// PageSize returns the scroll distance of a page.
|
|
|
|
func (this *ScrollContainer) PageSize () image.Point {
|
|
|
|
page := image.Point { }
|
2023-09-14 23:47:58 -06:00
|
|
|
if this.horizontal != nil {
|
2024-07-21 09:48:28 -06:00
|
|
|
page.X = this.horizontal.PageSize()
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
|
|
|
if this.vertical != nil {
|
2024-07-21 09:48:28 -06:00
|
|
|
page.Y = this.vertical.PageSize()
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
2024-07-21 09:48:28 -06:00
|
|
|
return page
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
// StepSize returns the scroll distance of a step.
|
|
|
|
func (this *ScrollContainer) StepSize () image.Point {
|
|
|
|
page := image.Point { }
|
2023-09-14 23:47:58 -06:00
|
|
|
if this.horizontal != nil {
|
2024-07-21 09:48:28 -06:00
|
|
|
page.X = this.horizontal.StepSize()
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
|
|
|
if this.vertical != nil {
|
2024-07-21 09:48:28 -06:00
|
|
|
page.Y = this.vertical.StepSize()
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
2024-07-21 09:48:28 -06:00
|
|
|
return page
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (this *ScrollContainer) handleValueChange () {
|
|
|
|
this.on.valueChange.Broadcast()
|
2024-06-11 15:17:11 -06:00
|
|
|
}
|
|
|
|
|
2024-07-21 09:48:28 -06:00
|
|
|
func (this *ScrollContainer) scrollBy (vector image.Point) {
|
2024-07-25 10:58:38 -06:00
|
|
|
if this.root == nil { return }
|
|
|
|
if vector == (image.Point { }) { return }
|
2024-07-21 09:48:28 -06:00
|
|
|
this.root.ScrollTo (
|
|
|
|
this.root.ContentBounds().Min.
|
|
|
|
Sub(vector))
|
|
|
|
}
|
|
|
|
|
2024-07-25 10:58:38 -06:00
|
|
|
func (this *ScrollContainer) handleScroll (x, y float64) bool {
|
|
|
|
if this.root == nil { return false }
|
|
|
|
this.scrollBy(image.Pt(int(x), int(y)))
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *ScrollContainer) handleKeyDown (key input.Key, numpad bool) bool {
|
2024-07-21 09:48:28 -06:00
|
|
|
modifiers := this.Window().Modifiers()
|
|
|
|
vector := image.Point { }
|
|
|
|
switch key {
|
|
|
|
case input.KeyPageUp:
|
|
|
|
if modifiers.Shift {
|
|
|
|
vector.X -= this.PageSize().X
|
|
|
|
} else {
|
|
|
|
vector.Y -= this.PageSize().Y
|
|
|
|
}
|
2024-07-25 10:58:38 -06:00
|
|
|
return true
|
2024-07-21 09:48:28 -06:00
|
|
|
case input.KeyPageDown:
|
|
|
|
if modifiers.Shift {
|
|
|
|
vector.X += this.PageSize().X
|
|
|
|
} else {
|
|
|
|
vector.Y += this.PageSize().Y
|
|
|
|
}
|
2024-07-25 10:58:38 -06:00
|
|
|
return true
|
2024-07-21 09:48:28 -06:00
|
|
|
}
|
2024-07-25 10:58:38 -06:00
|
|
|
return false
|
2024-07-21 09:48:28 -06:00
|
|
|
}
|
|
|
|
|
2024-07-25 10:58:38 -06:00
|
|
|
func (this *ScrollContainer) handleKeyUp (key input.Key, numpad bool) bool {
|
2024-07-21 09:48:28 -06:00
|
|
|
switch key {
|
2024-07-25 10:58:38 -06:00
|
|
|
case input.KeyPageUp: return true
|
|
|
|
case input.KeyPageDown: return true
|
2024-07-21 09:48:28 -06:00
|
|
|
}
|
2024-07-25 10:58:38 -06:00
|
|
|
return false
|
2023-09-14 23:47:58 -06:00
|
|
|
}
|