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 (hints tomo.LayoutHints, boxes []tomo.Box, width int) int { return this.MinimumSize(hints, boxes).X } func (this *scrollContainerLayout) RecommendedWidth (hints tomo.LayoutHints, boxes []tomo.Box, height int) int { return this.MinimumSize(hints, boxes).Y } func max (x, y int) int { if x > y { return x } return y }