package objects import "image" import "git.tebibyte.media/tomo/tomo" import "git.tebibyte.media/tomo/tomo/input" import "git.tebibyte.media/tomo/tomo/event" import "git.tebibyte.media/tomo/objects/layouts" // 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) // ScrollContainer couples a ContentBox with one or two Scrollbars. type ScrollContainer struct { box tomo.ContainerBox root tomo.ContentObject horizontal *Scrollbar vertical *Scrollbar horizontalCookie event.Cookie verticalCookie event.Cookie on struct { valueChange event.FuncBroadcaster } } // NewScrollContainer creates a new scroll container. func NewScrollContainer (sides ScrollSide) *ScrollContainer { scrollContainer := &ScrollContainer { box: tomo.NewContainerBox(), } if sides.Vertical() { scrollContainer.vertical = NewVerticalScrollbar() scrollContainer.vertical.OnValueChange(scrollContainer.handleValueChange) scrollContainer.box.Add(scrollContainer.vertical) } 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) if sides == ScrollHorizontal { scrollContainer.box.SetAttr(tomo.ALayout(layouts.NewGrid(true)(true, false))) } else { scrollContainer.box.SetAttr(tomo.ALayout(layouts.NewGrid(true, false)(true, false))) } return scrollContainer } // GetBox returns the underlying box. func (this *ScrollContainer) GetBox () tomo.Box { return this.box } // 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.root != nil { // remove root and close cookies this.box.Remove(this.root) if this.horizontalCookie != nil { this.horizontalCookie.Close() this.horizontalCookie = nil } if this.verticalCookie != nil { this.verticalCookie.Close() this.verticalCookie = nil } } this.root = root if root != nil { // insert root at the beginning (for keynav) switch { case this.vertical != nil: this.box.Insert(root, this.vertical) case this.horizontal != nil: this.box.Insert(root, this.horizontal) default: this.box.Add(root) } // link root and remember cookies if this.horizontal != nil { this.horizontalCookie = this.horizontal.Link(root) } if this.vertical != nil { this.verticalCookie = this.vertical.Link(root) } } } // 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.horizontal != nil { x = this.horizontal.Value() } 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) { if this.horizontal != nil { this.horizontal.SetValue(x) } 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) } // PageSize returns the scroll distance of a page. func (this *ScrollContainer) PageSize () image.Point { page := image.Point { } if this.horizontal != nil { page.X = this.horizontal.PageSize() } if this.vertical != nil { page.Y = this.vertical.PageSize() } return page } // StepSize returns the scroll distance of a step. func (this *ScrollContainer) StepSize () image.Point { page := image.Point { } if this.horizontal != nil { page.X = this.horizontal.StepSize() } if this.vertical != nil { page.Y = this.vertical.StepSize() } return page } func (this *ScrollContainer) handleValueChange () { this.on.valueChange.Broadcast() } func (this *ScrollContainer) scrollBy (vector image.Point) { if this.root == nil { return } if vector == (image.Point { }) { return } this.root.ScrollTo ( this.root.ContentBounds().Min. Sub(vector)) } 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() vector := image.Point { } switch key { case input.KeyPageUp: if modifiers.Shift { vector.X -= this.PageSize().X } else { vector.Y -= this.PageSize().Y } this.scrollBy(vector) return true case input.KeyPageDown: if modifiers.Shift { vector.X += this.PageSize().X } else { vector.Y += this.PageSize().Y } this.scrollBy(vector) return true case input.KeyUp: if modifiers.Shift { vector.X -= this.StepSize().X } else { vector.Y -= this.StepSize().Y } this.scrollBy(vector) return true case input.KeyDown: if modifiers.Shift { vector.X += this.StepSize().X } else { vector.Y += this.StepSize().Y } this.scrollBy(vector) return true } return false } func (this *ScrollContainer) handleKeyUp (key input.Key, numpad bool) bool { switch key { case input.KeyPageUp: return true case input.KeyPageDown: return true } return false }