This repository has been archived on 2023-08-08. You can view files and clone it, but cannot push or open issues or pull requests.
tomo-old/elements/scroll.go
Sasha Koshka 53f78cb0e7 Overhauled mouse events
Everything gets an image.Point instead of an x y pair, and most
things now get modifiers.
2023-04-20 14:44:54 -04:00

252 lines
6.9 KiB
Go

package elements
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
import "git.tebibyte.media/sashakoshka/tomo/default/config"
// ScrollMode specifies which sides of a Scroll have scroll bars.
type ScrollMode int; const (
ScrollNeither ScrollMode = 0
ScrollVertical ScrollMode = 1
ScrollHorizontal ScrollMode = 2
ScrollBoth ScrollMode = ScrollVertical | ScrollHorizontal
)
// Includes returns whether a scroll mode has been or'd with another scroll
// mode.
func (mode ScrollMode) Includes (sub ScrollMode) bool {
return (mode & sub) > 0
}
// Scroll adds scroll bars to any scrollable element. It also captures scroll
// wheel input.
type Scroll struct {
entity tomo.ContainerEntity
child tomo.Scrollable
horizontal *ScrollBar
vertical *ScrollBar
config config.Wrapped
theme theme.Wrapped
}
// NewScroll creates a new scroll element.
func NewScroll (mode ScrollMode, child tomo.Scrollable) (element *Scroll) {
element = &Scroll { }
element.theme.Case = tomo.C("tomo", "scroll")
element.entity = tomo.NewEntity(element).(tomo.ContainerEntity)
if mode.Includes(ScrollHorizontal) {
element.horizontal = NewHScrollBar()
element.horizontal.OnScroll (func (viewport image.Point) {
if element.child != nil {
element.child.ScrollTo(viewport)
}
if element.vertical != nil {
element.vertical.SetBounds (
element.child.ScrollContentBounds(),
element.child.ScrollViewportBounds())
}
})
element.entity.Adopt(element.horizontal)
}
if mode.Includes(ScrollVertical) {
element.vertical = NewVScrollBar()
element.vertical.OnScroll (func (viewport image.Point) {
if element.child != nil {
element.child.ScrollTo(viewport)
}
if element.horizontal != nil {
element.horizontal.SetBounds (
element.child.ScrollContentBounds(),
element.child.ScrollViewportBounds())
}
})
element.entity.Adopt(element.vertical)
}
element.Adopt(child)
return
}
// Entity returns this element's entity.
func (element *Scroll) Entity () tomo.Entity {
return element.entity
}
// Draw causes the element to draw to the specified destination canvas.
func (element *Scroll) Draw (destination canvas.Canvas) {
if element.horizontal != nil && element.vertical != nil {
bounds := element.entity.Bounds()
bounds.Min = image.Pt (
bounds.Max.X - element.vertical.Entity().Bounds().Dx(),
bounds.Max.Y - element.horizontal.Entity().Bounds().Dy())
state := tomo.State { }
deadArea := element.theme.Pattern(tomo.PatternDead, state)
deadArea.Draw(canvas.Cut(destination, bounds), bounds)
}
}
// Layout causes this element to perform a layout operation.
func (element *Scroll) Layout () {
bounds := element.entity.Bounds()
child := bounds
iHorizontal := element.entity.IndexOf(element.horizontal)
iVertical := element.entity.IndexOf(element.vertical)
iChild := element.entity.IndexOf(element.child)
var horizontal, vertical image.Rectangle
if element.horizontal != nil {
_, hMinHeight := element.entity.ChildMinimumSize(iHorizontal)
child.Max.Y -= hMinHeight
}
if element.vertical != nil {
vMinWidth, _ := element.entity.ChildMinimumSize(iVertical)
child.Max.X -= vMinWidth
}
horizontal.Min.X = bounds.Min.X
horizontal.Max.X = child.Max.X
horizontal.Min.Y = child.Max.Y
horizontal.Max.Y = bounds.Max.Y
vertical.Min.X = child.Max.X
vertical.Max.X = bounds.Max.X
vertical.Min.Y = bounds.Min.Y
vertical.Max.Y = child.Max.Y
if element.horizontal != nil {
element.entity.PlaceChild (iHorizontal, horizontal)
}
if element.vertical != nil {
element.entity.PlaceChild(iVertical, vertical)
}
if element.child != nil {
element.entity.PlaceChild(iChild, child)
}
}
// DrawBackground draws this element's background pattern to the specified
// destination canvas.
func (element *Scroll) DrawBackground (destination canvas.Canvas) {
element.entity.DrawBackground(destination)
}
// Adopt sets this element's child. If nil is passed, any child is removed.
func (element *Scroll) Adopt (child tomo.Scrollable) {
if element.child != nil {
element.entity.Disown(element.entity.IndexOf(element.child))
}
if child != nil {
element.entity.Adopt(child)
}
element.child = child
element.updateEnabled()
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
}
// Child returns this element's child. If there is no child, this method will
// return nil.
func (element *Scroll) Child () tomo.Scrollable {
return element.child
}
func (element *Scroll) HandleChildMinimumSizeChange (tomo.Element) {
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
}
func (element *Scroll) HandleChildScrollBoundsChange (tomo.Scrollable) {
element.updateEnabled()
viewportBounds := element.child.ScrollViewportBounds()
contentBounds := element.child.ScrollContentBounds()
if element.horizontal != nil {
element.horizontal.SetBounds(contentBounds, viewportBounds)
}
if element.vertical != nil {
element.vertical.SetBounds(contentBounds, viewportBounds)
}
}
func (element *Scroll) HandleScroll (
position image.Point,
deltaX, deltaY float64,
modifiers input.Modifiers,
) {
horizontal, vertical := element.child.ScrollAxes()
if !horizontal { deltaX = 0 }
if !vertical { deltaY = 0 }
element.scrollChildBy(int(deltaX), int(deltaY))
}
// SetTheme sets the element's theme.
func (element *Scroll) SetTheme (theme tomo.Theme) {
if theme == element.theme.Theme { return }
element.theme.Theme = theme
element.updateMinimumSize()
element.entity.Invalidate()
element.entity.InvalidateLayout()
}
// SetConfig sets the element's configuration.
func (element *Scroll) SetConfig (config tomo.Config) {
element.config.Config = config
}
func (element *Scroll) updateMinimumSize () {
var width, height int
if element.child != nil {
width, height = element.entity.ChildMinimumSize (
element.entity.IndexOf(element.child))
}
if element.horizontal != nil {
hMinWidth, hMinHeight := element.entity.ChildMinimumSize (
element.entity.IndexOf(element.horizontal))
height += hMinHeight
if hMinWidth > width {
width = hMinWidth
}
}
if element.vertical != nil {
vMinWidth, vMinHeight := element.entity.ChildMinimumSize (
element.entity.IndexOf(element.vertical))
width += vMinWidth
if vMinHeight > height {
height = vMinHeight
}
}
element.entity.SetMinimumSize(width, height)
}
func (element *Scroll) updateEnabled () {
horizontal, vertical := false, false
if element.child != nil {
horizontal, vertical = element.child.ScrollAxes()
}
if element.horizontal != nil {
element.horizontal.SetEnabled(horizontal)
}
if element.vertical != nil {
element.vertical.SetEnabled(vertical)
}
}
func (element *Scroll) scrollChildBy (x, y int) {
if element.child == nil { return }
scrollPoint :=
element.child.ScrollViewportBounds().Min.
Add(image.Pt(x, y))
element.child.ScrollTo(scrollPoint)
}