2023-04-16 15:30:13 -06:00
|
|
|
package elements
|
2023-04-16 01:37:28 -06:00
|
|
|
|
|
|
|
import "image"
|
|
|
|
import "git.tebibyte.media/sashakoshka/tomo"
|
2023-04-20 12:44:54 -06:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/input"
|
2023-04-16 01:37:28 -06:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
|
|
|
|
2023-04-19 23:37:06 -06:00
|
|
|
// ScrollMode specifies which sides of a Scroll have scroll bars.
|
2023-04-18 11:14:10 -06:00
|
|
|
type ScrollMode int; const (
|
2023-04-19 23:37:06 -06:00
|
|
|
ScrollNeither ScrollMode = 0
|
|
|
|
ScrollVertical ScrollMode = 1
|
|
|
|
ScrollHorizontal ScrollMode = 2
|
|
|
|
ScrollBoth ScrollMode = ScrollVertical | ScrollHorizontal
|
2023-04-18 11:14:10 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:37:06 -06:00
|
|
|
// Scroll adds scroll bars to any scrollable element. It also captures scroll
|
|
|
|
// wheel input.
|
2023-04-16 01:37:28 -06:00
|
|
|
type Scroll struct {
|
|
|
|
entity tomo.ContainerEntity
|
|
|
|
|
|
|
|
child tomo.Scrollable
|
2023-04-16 15:30:13 -06:00
|
|
|
horizontal *ScrollBar
|
|
|
|
vertical *ScrollBar
|
2023-04-16 01:37:28 -06:00
|
|
|
|
|
|
|
config config.Wrapped
|
|
|
|
theme theme.Wrapped
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:37:06 -06:00
|
|
|
// NewScroll creates a new scroll element.
|
2023-04-18 11:14:10 -06:00
|
|
|
func NewScroll (mode ScrollMode, child tomo.Scrollable) (element *Scroll) {
|
2023-04-16 01:37:28 -06:00
|
|
|
element = &Scroll { }
|
|
|
|
element.theme.Case = tomo.C("tomo", "scroll")
|
|
|
|
element.entity = tomo.NewEntity(element).(tomo.ContainerEntity)
|
|
|
|
|
2023-04-18 11:14:10 -06:00
|
|
|
if mode.Includes(ScrollHorizontal) {
|
2023-04-19 23:10:47 -06:00
|
|
|
element.horizontal = NewHScrollBar()
|
2023-04-16 01:37:28 -06:00
|
|
|
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)
|
|
|
|
}
|
2023-04-18 11:14:10 -06:00
|
|
|
if mode.Includes(ScrollVertical) {
|
2023-04-19 23:10:47 -06:00
|
|
|
element.vertical = NewVScrollBar()
|
2023-04-16 01:37:28 -06:00
|
|
|
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)
|
|
|
|
}
|
2023-04-17 00:13:21 -06:00
|
|
|
|
|
|
|
element.Adopt(child)
|
2023-04-16 01:37:28 -06:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:37:06 -06:00
|
|
|
// Entity returns this element's entity.
|
2023-04-16 01:37:28 -06:00
|
|
|
func (element *Scroll) Entity () tomo.Entity {
|
|
|
|
return element.entity
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:37:06 -06:00
|
|
|
// Draw causes the element to draw to the specified destination canvas.
|
2023-04-16 01:37:28 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:37:06 -06:00
|
|
|
// Layout causes this element to perform a layout operation.
|
2023-04-16 01:37:28 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:37:06 -06:00
|
|
|
// DrawBackground draws this element's background pattern to the specified
|
|
|
|
// destination canvas.
|
2023-04-16 01:37:28 -06:00
|
|
|
func (element *Scroll) DrawBackground (destination canvas.Canvas) {
|
|
|
|
element.entity.DrawBackground(destination)
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:37:06 -06:00
|
|
|
// Adopt sets this element's child. If nil is passed, any child is removed.
|
2023-04-16 01:37:28 -06:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:37:06 -06:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2023-04-16 01:37:28 -06:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-16 12:12:55 -06:00
|
|
|
func (element *Scroll) HandleScroll (
|
2023-04-20 12:44:54 -06:00
|
|
|
position image.Point,
|
2023-04-16 12:12:55 -06:00
|
|
|
deltaX, deltaY float64,
|
2023-04-20 12:44:54 -06:00
|
|
|
modifiers input.Modifiers,
|
2023-04-16 12:12:55 -06:00
|
|
|
) {
|
|
|
|
horizontal, vertical := element.child.ScrollAxes()
|
|
|
|
if !horizontal { deltaX = 0 }
|
|
|
|
if !vertical { deltaY = 0 }
|
|
|
|
element.scrollChildBy(int(deltaX), int(deltaY))
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:37:06 -06:00
|
|
|
// SetTheme sets the element's theme.
|
2023-04-16 01:37:28 -06:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2023-04-19 23:37:06 -06:00
|
|
|
// SetConfig sets the element's configuration.
|
2023-04-16 01:37:28 -06:00
|
|
|
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 () {
|
2023-04-18 01:23:51 -06:00
|
|
|
horizontal, vertical := false, false
|
|
|
|
if element.child != nil {
|
|
|
|
horizontal, vertical = element.child.ScrollAxes()
|
|
|
|
}
|
2023-04-16 01:37:28 -06:00
|
|
|
if element.horizontal != nil {
|
|
|
|
element.horizontal.SetEnabled(horizontal)
|
|
|
|
}
|
|
|
|
if element.vertical != nil {
|
|
|
|
element.vertical.SetEnabled(vertical)
|
|
|
|
}
|
|
|
|
}
|
2023-04-16 12:12:55 -06:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|