objects/scrollcontainer.go

249 lines
6.6 KiB
Go
Raw Normal View History

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"
}
}
var _ tomo.Object = new(ScrollContainer)
2023-09-14 23:47:58 -06:00
// ScrollContainer couples a ContentBox with one or two Scrollbars.
type ScrollContainer struct {
box tomo.ContainerBox
2023-09-14 23:47:58 -06:00
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
on struct {
valueChange event.FuncBroadcaster
}
2023-09-14 23:47:58 -06:00
}
// NewScrollContainer creates a new scroll container.
func NewScrollContainer (sides ScrollSide) *ScrollContainer {
scrollContainer := &ScrollContainer {
box: tomo.NewContainerBox(),
2023-09-14 23:47:58 -06:00
}
if sides.Vertical() {
scrollContainer.vertical = NewVerticalScrollbar()
scrollContainer.vertical.OnValueChange(scrollContainer.handleValueChange)
scrollContainer.box.Add(scrollContainer.vertical)
2023-09-14 23:47:58 -06:00
}
if sides.Horizontal() {
scrollContainer.horizontal = NewHorizontalScrollbar()
scrollContainer.horizontal.OnValueChange(scrollContainer.handleValueChange)
scrollContainer.box.Add(scrollContainer.horizontal)
}
scrollContainer.box.OnScroll(scrollContainer.handleScroll)
scrollContainer.box.OnKeyDown(scrollContainer.handleKeyDown)
scrollContainer.box.OnKeyUp(scrollContainer.handleKeyUp)
scrollContainer.box.SetRole(tomo.R("objects", "ScrollContainer"))
scrollContainer.box.SetTag(sides.String(), true)
2024-07-26 15:56:29 -06:00
if sides == ScrollHorizontal {
scrollContainer.box.SetAttr(tomo.ALayout(layouts.NewGrid(true)(true, false)))
2024-07-26 15:54:32 -06:00
} else {
scrollContainer.box.SetAttr(tomo.ALayout(layouts.NewGrid(true, false)(true, false)))
2024-07-26 15:54:32 -06:00
}
return scrollContainer
}
// GetBox returns the underlying box.
func (this *ScrollContainer) GetBox () tomo.Box {
return this.box
2023-09-14 23:47:58 -06:00
}
// 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) {
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
this.box.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.box.Insert(root, this.vertical)
2024-07-21 09:48:28 -06:00
case this.horizontal != nil:
this.box.Insert(root, this.horizontal)
2023-09-14 23:47:58 -06:00
default:
this.box.Add(root)
2023-09-14 23:47:58 -06:00
}
// 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
}
}
}
// 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) {
2024-07-21 09:48:28 -06:00
if this.horizontal != nil {
x = this.horizontal.Value()
}
2024-07-21 09:48:28 -06:00
if this.vertical != nil {
y = this.vertical.Value()
}
return x, y
}
// 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) {
2024-07-21 09:48:28 -06:00
if this.horizontal != nil {
this.horizontal.SetValue(x)
}
2024-07-21 09:48:28 -06:00
if this.vertical != nil {
this.vertical.SetValue(y)
}
}
// 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 {
modifiers := this.box.Window().Modifiers()
2024-07-21 09:48:28 -06:00
vector := image.Point { }
switch key {
case input.KeyPageUp:
2024-09-12 00:34:28 -06:00
if modifiers.Shift() {
2024-07-21 09:48:28 -06:00
vector.X -= this.PageSize().X
} else {
vector.Y -= this.PageSize().Y
}
this.scrollBy(vector)
2024-07-25 10:58:38 -06:00
return true
2024-07-21 09:48:28 -06:00
case input.KeyPageDown:
2024-09-12 00:34:28 -06:00
if modifiers.Shift() {
2024-07-21 09:48:28 -06:00
vector.X += this.PageSize().X
} else {
vector.Y += this.PageSize().Y
}
this.scrollBy(vector)
2024-07-25 10:58:38 -06:00
return true
case input.KeyUp:
2024-09-12 00:34:28 -06:00
if modifiers.Shift() {
vector.X -= this.StepSize().X
} else {
vector.Y -= this.StepSize().Y
}
this.scrollBy(vector)
return true
case input.KeyDown:
2024-09-12 00:34:28 -06:00
if modifiers.Shift() {
vector.X += this.StepSize().X
} else {
vector.Y += this.StepSize().Y
}
this.scrollBy(vector)
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-09-12 00:34:28 -06:00
case input.KeyUp: return true
case input.KeyDown: 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
}