ScrollContainer uses ScrollBar for scrolling
This commit is contained in:
parent
9cc9e78504
commit
677dca1dbf
@ -274,8 +274,8 @@ func (element *ScrollBar) recalculateHorizontal () {
|
|||||||
element.bar.Max.Y = element.track.Max.Y
|
element.bar.Max.Y = element.track.Max.Y
|
||||||
|
|
||||||
ratio :=
|
ratio :=
|
||||||
float64(element.track.Dy()) /
|
float64(element.track.Dx()) /
|
||||||
float64(contentBounds.Dy())
|
float64(contentBounds.Dx())
|
||||||
element.bar.Min.X = int(float64(viewportBounds.Min.X) * ratio)
|
element.bar.Min.X = int(float64(viewportBounds.Min.X) * ratio)
|
||||||
element.bar.Max.X = int(float64(viewportBounds.Max.X) * ratio)
|
element.bar.Max.X = int(float64(viewportBounds.Max.X) * ratio)
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import "git.tebibyte.media/sashakoshka/tomo/input"
|
|||||||
import "git.tebibyte.media/sashakoshka/tomo/theme"
|
import "git.tebibyte.media/sashakoshka/tomo/theme"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/config"
|
import "git.tebibyte.media/sashakoshka/tomo/config"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements"
|
import "git.tebibyte.media/sashakoshka/tomo/elements"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
||||||
|
|
||||||
@ -13,33 +12,12 @@ import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
|||||||
// element.
|
// element.
|
||||||
type ScrollContainer struct {
|
type ScrollContainer struct {
|
||||||
*core.Core
|
*core.Core
|
||||||
|
*core.Propagator
|
||||||
core core.CoreControl
|
core core.CoreControl
|
||||||
focused bool
|
|
||||||
|
|
||||||
child elements.Scrollable
|
child elements.Scrollable
|
||||||
childWidth, childHeight int
|
horizontal *ScrollBar
|
||||||
|
vertical *ScrollBar
|
||||||
horizontal struct {
|
|
||||||
theme theme.Wrapped
|
|
||||||
exists bool
|
|
||||||
enabled bool
|
|
||||||
dragging bool
|
|
||||||
dragOffset int
|
|
||||||
gutter image.Rectangle
|
|
||||||
track image.Rectangle
|
|
||||||
bar image.Rectangle
|
|
||||||
}
|
|
||||||
|
|
||||||
vertical struct {
|
|
||||||
theme theme.Wrapped
|
|
||||||
exists bool
|
|
||||||
enabled bool
|
|
||||||
dragging bool
|
|
||||||
dragOffset int
|
|
||||||
gutter image.Rectangle
|
|
||||||
track image.Rectangle
|
|
||||||
bar image.Rectangle
|
|
||||||
}
|
|
||||||
|
|
||||||
config config.Wrapped
|
config config.Wrapped
|
||||||
theme theme.Wrapped
|
theme theme.Wrapped
|
||||||
@ -53,20 +31,40 @@ type ScrollContainer struct {
|
|||||||
func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) {
|
func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) {
|
||||||
element = &ScrollContainer { }
|
element = &ScrollContainer { }
|
||||||
element.theme.Case = theme.C("basic", "scrollContainer")
|
element.theme.Case = theme.C("basic", "scrollContainer")
|
||||||
element.horizontal.theme.Case = theme.C("basic", "scrollBarHorizontal")
|
element.Core, element.core = core.NewCore(element.redoAll)
|
||||||
element.vertical.theme.Case = theme.C("basic", "scrollBarVertical")
|
element.Propagator = core.NewPropagator(element)
|
||||||
|
|
||||||
element.Core, element.core = core.NewCore(element.handleResize)
|
if horizontal {
|
||||||
element.horizontal.exists = horizontal
|
element.horizontal = NewScrollBar(false)
|
||||||
element.vertical.exists = vertical
|
element.setChildEventHandlers(element.horizontal)
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if vertical {
|
||||||
|
element.vertical = NewScrollBar(true)
|
||||||
|
element.setChildEventHandlers(element.vertical)
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *ScrollContainer) handleResize () {
|
|
||||||
element.recalculate()
|
|
||||||
element.resizeChildToFit()
|
|
||||||
element.draw()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adopt adds a scrollable element to the scroll container. The container can
|
// Adopt adds a scrollable element to the scroll container. The container can
|
||||||
// only contain one scrollable element at a time, and when a new one is adopted
|
// only contain one scrollable element at a time, and when a new one is adopted
|
||||||
@ -80,247 +78,45 @@ func (element *ScrollContainer) Adopt (child elements.Scrollable) {
|
|||||||
// adopt new child
|
// adopt new child
|
||||||
element.child = child
|
element.child = child
|
||||||
if child != nil {
|
if child != nil {
|
||||||
|
element.setChildEventHandlers(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
element.updateEnabled()
|
||||||
|
element.updateMinimumSize()
|
||||||
|
if element.core.HasImage() {
|
||||||
|
element.redoAll()
|
||||||
|
element.core.DamageAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *ScrollContainer) setChildEventHandlers (child elements.Element) {
|
||||||
if child0, ok := child.(elements.Themeable); ok {
|
if child0, ok := child.(elements.Themeable); ok {
|
||||||
child0.SetTheme(element.theme.Theme)
|
child0.SetTheme(element.theme.Theme)
|
||||||
}
|
}
|
||||||
if child0, ok := child.(elements.Configurable); ok {
|
if child0, ok := child.(elements.Configurable); ok {
|
||||||
child0.SetConfig(element.config.Config)
|
child0.SetConfig(element.config.Config)
|
||||||
}
|
}
|
||||||
child.OnDamage(element.childDamageCallback)
|
child.OnDamage (func (region canvas.Canvas) {
|
||||||
child.OnMinimumSizeChange(element.updateMinimumSize)
|
|
||||||
child.OnScrollBoundsChange(element.childScrollBoundsChangeCallback)
|
|
||||||
if newChild, ok := child.(elements.Focusable); ok {
|
|
||||||
newChild.OnFocusRequest (
|
|
||||||
element.childFocusRequestCallback)
|
|
||||||
newChild.OnFocusMotionRequest (
|
|
||||||
element.childFocusMotionRequestCallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
element.updateMinimumSize()
|
|
||||||
|
|
||||||
element.horizontal.enabled,
|
|
||||||
element.vertical.enabled = element.child.ScrollAxes()
|
|
||||||
|
|
||||||
if element.core.HasImage() {
|
|
||||||
element.resizeChildToFit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTheme sets the element's theme.
|
|
||||||
func (element *ScrollContainer) SetTheme (new theme.Theme) {
|
|
||||||
if new == element.theme.Theme { return }
|
|
||||||
element.theme.Theme = new
|
|
||||||
if child, ok := element.child.(elements.Themeable); ok {
|
|
||||||
child.SetTheme(element.theme.Theme)
|
|
||||||
}
|
|
||||||
if element.core.HasImage() {
|
|
||||||
element.recalculate()
|
|
||||||
element.resizeChildToFit()
|
|
||||||
element.draw()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetConfig sets the element's configuration.
|
|
||||||
func (element *ScrollContainer) SetConfig (new config.Config) {
|
|
||||||
if new == element.config.Config { return }
|
|
||||||
element.config.Config = new
|
|
||||||
if child, ok := element.child.(elements.Configurable); ok {
|
|
||||||
child.SetConfig(element.config.Config)
|
|
||||||
}
|
|
||||||
if element.core.HasImage() {
|
|
||||||
element.recalculate()
|
|
||||||
element.resizeChildToFit()
|
|
||||||
element.draw()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
|
|
||||||
if child, ok := element.child.(elements.KeyboardTarget); ok {
|
|
||||||
child.HandleKeyDown(key, modifiers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
|
|
||||||
if child, ok := element.child.(elements.KeyboardTarget); ok {
|
|
||||||
child.HandleKeyUp(key, modifiers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) HandleMouseDown (x, y int, button input.Button) {
|
|
||||||
velocity := element.config.ScrollVelocity()
|
|
||||||
point := image.Pt(x, y)
|
|
||||||
if point.In(element.horizontal.bar) {
|
|
||||||
element.horizontal.dragging = true
|
|
||||||
element.horizontal.dragOffset =
|
|
||||||
x - element.horizontal.bar.Min.X +
|
|
||||||
element.Bounds().Min.X
|
|
||||||
element.dragHorizontalBar(point)
|
|
||||||
|
|
||||||
} else if point.In(element.horizontal.gutter) {
|
|
||||||
switch button {
|
|
||||||
case input.ButtonLeft:
|
|
||||||
element.horizontal.dragging = true
|
|
||||||
element.horizontal.dragOffset =
|
|
||||||
element.horizontal.bar.Dx() / 2 +
|
|
||||||
element.Bounds().Min.X
|
|
||||||
element.dragHorizontalBar(point)
|
|
||||||
case input.ButtonMiddle:
|
|
||||||
viewport := element.child.ScrollViewportBounds().Dx()
|
|
||||||
if x > element.horizontal.bar.Min.X {
|
|
||||||
element.scrollChildBy(viewport, 0)
|
|
||||||
} else {
|
|
||||||
element.scrollChildBy(-viewport, 0)
|
|
||||||
}
|
|
||||||
case input.ButtonRight:
|
|
||||||
if x > element.horizontal.bar.Min.X {
|
|
||||||
element.scrollChildBy(velocity, 0)
|
|
||||||
} else {
|
|
||||||
element.scrollChildBy(-velocity, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if point.In(element.vertical.bar) {
|
|
||||||
element.vertical.dragging = true
|
|
||||||
element.vertical.dragOffset =
|
|
||||||
y - element.vertical.bar.Min.Y +
|
|
||||||
element.Bounds().Min.Y
|
|
||||||
element.dragVerticalBar(point)
|
|
||||||
|
|
||||||
} else if point.In(element.vertical.gutter) {
|
|
||||||
switch button {
|
|
||||||
case input.ButtonLeft:
|
|
||||||
element.vertical.dragging = true
|
|
||||||
element.vertical.dragOffset =
|
|
||||||
element.vertical.bar.Dy() / 2 +
|
|
||||||
element.Bounds().Min.Y
|
|
||||||
element.dragVerticalBar(point)
|
|
||||||
case input.ButtonMiddle:
|
|
||||||
viewport := element.child.ScrollViewportBounds().Dy()
|
|
||||||
if y > element.vertical.bar.Min.Y {
|
|
||||||
element.scrollChildBy(0, viewport)
|
|
||||||
} else {
|
|
||||||
element.scrollChildBy(0, -viewport)
|
|
||||||
}
|
|
||||||
case input.ButtonRight:
|
|
||||||
if y > element.vertical.bar.Min.Y {
|
|
||||||
element.scrollChildBy(0, velocity)
|
|
||||||
} else {
|
|
||||||
element.scrollChildBy(0, -velocity)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if child, ok := element.child.(elements.MouseTarget); ok {
|
|
||||||
child.HandleMouseDown(x, y, button)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) HandleMouseUp (x, y int, button input.Button) {
|
|
||||||
if element.horizontal.dragging {
|
|
||||||
element.horizontal.dragging = false
|
|
||||||
element.drawHorizontalBar()
|
|
||||||
element.core.DamageRegion(element.horizontal.bar)
|
|
||||||
|
|
||||||
} else if element.vertical.dragging {
|
|
||||||
element.vertical.dragging = false
|
|
||||||
element.drawVerticalBar()
|
|
||||||
element.core.DamageRegion(element.vertical.bar)
|
|
||||||
|
|
||||||
} else if child, ok := element.child.(elements.MouseTarget); ok {
|
|
||||||
child.HandleMouseUp(x, y, button)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) HandleMouseMove (x, y int) {
|
|
||||||
if element.horizontal.dragging {
|
|
||||||
element.dragHorizontalBar(image.Pt(x, y))
|
|
||||||
|
|
||||||
} else if element.vertical.dragging {
|
|
||||||
element.dragVerticalBar(image.Pt(x, y))
|
|
||||||
|
|
||||||
} else if child, ok := element.child.(elements.MouseTarget); ok {
|
|
||||||
child.HandleMouseMove(x, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) HandleMouseScroll (
|
|
||||||
x, y int,
|
|
||||||
deltaX, deltaY float64,
|
|
||||||
) {
|
|
||||||
element.scrollChildBy(int(deltaX), int(deltaY))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) scrollChildBy (x, y int) {
|
|
||||||
if element.child == nil { return }
|
|
||||||
scrollPoint :=
|
|
||||||
element.child.ScrollViewportBounds().Min.
|
|
||||||
Add(image.Pt(x, y))
|
|
||||||
element.child.ScrollTo(scrollPoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) Focused () (focused bool) {
|
|
||||||
return element.focused
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) Focus () {
|
|
||||||
if element.onFocusRequest != nil {
|
|
||||||
if element.onFocusRequest() {
|
|
||||||
element.focused = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) HandleFocus (
|
|
||||||
direction input.KeynavDirection,
|
|
||||||
) (
|
|
||||||
accepted bool,
|
|
||||||
) {
|
|
||||||
if child, ok := element.child.(elements.Focusable); ok {
|
|
||||||
element.focused = child.HandleFocus(direction)
|
|
||||||
return element.focused
|
|
||||||
} else {
|
|
||||||
element.focused = false
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) HandleUnfocus () {
|
|
||||||
if child, ok := element.child.(elements.Focusable); ok {
|
|
||||||
child.HandleUnfocus()
|
|
||||||
}
|
|
||||||
element.focused = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) OnFocusRequest (callback func () (granted bool)) {
|
|
||||||
element.onFocusRequest = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) OnFocusMotionRequest (
|
|
||||||
callback func (direction input.KeynavDirection) (granted bool),
|
|
||||||
) {
|
|
||||||
element.onFocusMotionRequest = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) childDamageCallback (region canvas.Canvas) {
|
|
||||||
element.core.DamageRegion(region.Bounds())
|
element.core.DamageRegion(region.Bounds())
|
||||||
}
|
})
|
||||||
|
child.OnMinimumSizeChange (func () {
|
||||||
func (element *ScrollContainer) childFocusRequestCallback () (granted bool) {
|
element.updateMinimumSize()
|
||||||
if element.onFocusRequest != nil {
|
element.redoAll()
|
||||||
element.focused = element.onFocusRequest()
|
element.core.DamageAll()
|
||||||
return element.focused
|
})
|
||||||
} else {
|
if child0, ok := child.(elements.Focusable); ok {
|
||||||
return false
|
child0.OnFocusRequest (func () (granted bool) {
|
||||||
}
|
return element.childFocusRequestCallback(child0)
|
||||||
}
|
})
|
||||||
|
child0.OnFocusMotionRequest (
|
||||||
func (element *ScrollContainer) childFocusMotionRequestCallback (
|
func (direction input.KeynavDirection) (granted bool) {
|
||||||
direction input.KeynavDirection,
|
|
||||||
) (
|
|
||||||
granted bool,
|
|
||||||
) {
|
|
||||||
if element.onFocusMotionRequest == nil { return }
|
if element.onFocusMotionRequest == nil { return }
|
||||||
return element.onFocusMotionRequest(direction)
|
return element.onFocusMotionRequest(direction)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if child0, ok := child.(elements.Scrollable); ok {
|
||||||
|
child0.OnScrollBoundsChange(element.childScrollBoundsChangeCallback)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollable) {
|
func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollable) {
|
||||||
@ -340,189 +136,193 @@ func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollab
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *ScrollContainer) resizeChildToFit () {
|
// SetTheme sets the element's theme.
|
||||||
childBounds := image.Rect (
|
func (element *ScrollContainer) SetTheme (new theme.Theme) {
|
||||||
0, 0,
|
if new == element.theme.Theme { return }
|
||||||
element.childWidth,
|
element.theme.Theme = new
|
||||||
element.childHeight).Add(element.Bounds().Min)
|
element.Propagator.SetTheme(new)
|
||||||
element.child.DrawTo(canvas.Cut(element.core, childBounds))
|
element.updateMinimumSize()
|
||||||
|
element.redoAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *ScrollContainer) recalculate () {
|
// SetConfig sets the element's configuration.
|
||||||
horizontal := &element.horizontal
|
func (element *ScrollContainer) SetConfig (new config.Config) {
|
||||||
vertical := &element.vertical
|
if new == element.config.Config { return }
|
||||||
|
element.Propagator.SetConfig(new)
|
||||||
|
element.updateMinimumSize()
|
||||||
|
element.redoAll()
|
||||||
|
}
|
||||||
|
|
||||||
gutterInsetHorizontal := horizontal.theme.Padding(theme.PatternGutter)
|
func (element *ScrollContainer) HandleMouseScroll (
|
||||||
gutterInsetVertical := vertical.theme.Padding(theme.PatternGutter)
|
x, y int,
|
||||||
|
deltaX, deltaY float64,
|
||||||
|
) {
|
||||||
|
element.scrollChildBy(int(deltaX), int(deltaY))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *ScrollContainer) OnFocusRequest (callback func () (granted bool)) {
|
||||||
|
element.onFocusRequest = callback
|
||||||
|
element.Propagator.OnFocusRequest(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *ScrollContainer) OnFocusMotionRequest (
|
||||||
|
callback func (direction input.KeynavDirection) (granted bool),
|
||||||
|
) {
|
||||||
|
element.onFocusMotionRequest = callback
|
||||||
|
element.Propagator.OnFocusMotionRequest(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountChildren returns the amount of children contained within this element.
|
||||||
|
func (element *ScrollContainer) CountChildren () (count int) {
|
||||||
|
return 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// Child returns the child at the specified index. If the index is out of
|
||||||
|
// bounds, this method will return nil.
|
||||||
|
func (element *ScrollContainer) Child (index int) (child elements.Element) {
|
||||||
|
switch index {
|
||||||
|
case 0: return element.child
|
||||||
|
case 1:
|
||||||
|
if element.horizontal == nil {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return element.horizontal
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
if element.vertical == nil {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return element.vertical
|
||||||
|
}
|
||||||
|
default: return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *ScrollContainer) redoAll () {
|
||||||
|
if !element.core.HasImage() { return }
|
||||||
|
|
||||||
|
if element.child != nil { element.child.DrawTo(nil) }
|
||||||
|
if element.horizontal != nil { element.horizontal.DrawTo(nil) }
|
||||||
|
if element.vertical != nil { element.vertical.DrawTo(nil) }
|
||||||
|
|
||||||
|
childBounds, horizontalBounds, verticalBounds := element.layout()
|
||||||
|
if element.child != nil {
|
||||||
|
element.child.DrawTo(canvas.Cut(element.core, childBounds))
|
||||||
|
}
|
||||||
|
if element.horizontal != nil {
|
||||||
|
element.horizontal.DrawTo(canvas.Cut(element.core, horizontalBounds))
|
||||||
|
}
|
||||||
|
if element.vertical != nil {
|
||||||
|
element.vertical.DrawTo(canvas.Cut(element.core, verticalBounds))
|
||||||
|
}
|
||||||
|
element.draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *ScrollContainer) scrollChildBy (x, y int) {
|
||||||
|
if element.child == nil { return }
|
||||||
|
scrollPoint :=
|
||||||
|
element.child.ScrollViewportBounds().Min.
|
||||||
|
Add(image.Pt(x, y))
|
||||||
|
element.child.ScrollTo(scrollPoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *ScrollContainer) childFocusRequestCallback (
|
||||||
|
child elements.Focusable,
|
||||||
|
) (
|
||||||
|
granted bool,
|
||||||
|
) {
|
||||||
|
if element.onFocusRequest != nil && element.onFocusRequest() {
|
||||||
|
element.Propagator.HandleUnfocus()
|
||||||
|
element.Propagator.HandleFocus(input.KeynavDirectionNeutral)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *ScrollContainer) layout () (
|
||||||
|
child image.Rectangle,
|
||||||
|
horizontal image.Rectangle,
|
||||||
|
vertical image.Rectangle,
|
||||||
|
) {
|
||||||
bounds := element.Bounds()
|
bounds := element.Bounds()
|
||||||
thicknessHorizontal :=
|
child = bounds
|
||||||
element.config.HandleWidth() +
|
|
||||||
gutterInsetHorizontal[3] +
|
|
||||||
gutterInsetHorizontal[1]
|
|
||||||
thicknessVertical :=
|
|
||||||
element.config.HandleWidth() +
|
|
||||||
gutterInsetVertical[3] +
|
|
||||||
gutterInsetVertical[1]
|
|
||||||
|
|
||||||
// calculate child size
|
if element.horizontal != nil {
|
||||||
element.childWidth = bounds.Dx()
|
_, hMinHeight := element.horizontal.MinimumSize()
|
||||||
element.childHeight = bounds.Dy()
|
child.Max.Y -= hMinHeight
|
||||||
|
|
||||||
// reset bounds
|
|
||||||
horizontal.gutter = image.Rectangle { }
|
|
||||||
vertical.gutter = image.Rectangle { }
|
|
||||||
horizontal.bar = image.Rectangle { }
|
|
||||||
vertical.bar = image.Rectangle { }
|
|
||||||
|
|
||||||
// if enabled, give substance to the gutters
|
|
||||||
if horizontal.exists {
|
|
||||||
horizontal.gutter.Min.X = bounds.Min.X
|
|
||||||
horizontal.gutter.Min.Y = bounds.Max.Y - thicknessHorizontal
|
|
||||||
horizontal.gutter.Max.X = bounds.Max.X
|
|
||||||
horizontal.gutter.Max.Y = bounds.Max.Y
|
|
||||||
if vertical.exists {
|
|
||||||
horizontal.gutter.Max.X -= thicknessVertical
|
|
||||||
}
|
}
|
||||||
element.childHeight -= thicknessHorizontal
|
if element.vertical != nil {
|
||||||
horizontal.track = gutterInsetHorizontal.Apply(horizontal.gutter)
|
vMinWidth, _ := element.vertical.MinimumSize()
|
||||||
}
|
child.Max.X -= vMinWidth
|
||||||
if vertical.exists {
|
|
||||||
vertical.gutter.Min.X = bounds.Max.X - thicknessVertical
|
|
||||||
vertical.gutter.Max.X = bounds.Max.X
|
|
||||||
vertical.gutter.Min.Y = bounds.Min.Y
|
|
||||||
vertical.gutter.Max.Y = bounds.Max.Y
|
|
||||||
if horizontal.exists {
|
|
||||||
vertical.gutter.Max.Y -= thicknessHorizontal
|
|
||||||
}
|
|
||||||
element.childWidth -= thicknessVertical
|
|
||||||
vertical.track = gutterInsetVertical.Apply(vertical.gutter)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if enabled, calculate the positions of the bars
|
vertical.Min.X = child.Max.X
|
||||||
contentBounds := element.child.ScrollContentBounds()
|
vertical.Max.X = bounds.Max.X
|
||||||
viewportBounds := element.child.ScrollViewportBounds()
|
vertical.Min.Y = bounds.Min.Y
|
||||||
if horizontal.exists && horizontal.enabled {
|
vertical.Max.Y = child.Max.Y
|
||||||
horizontal.bar.Min.Y = horizontal.track.Min.Y
|
|
||||||
horizontal.bar.Max.Y = horizontal.track.Max.Y
|
|
||||||
|
|
||||||
scale := float64(horizontal.track.Dx()) /
|
horizontal.Min.X = bounds.Min.X
|
||||||
float64(contentBounds.Dx())
|
horizontal.Max.X = child.Max.X
|
||||||
horizontal.bar.Min.X = int(float64(viewportBounds.Min.X) * scale)
|
horizontal.Min.Y = child.Max.Y
|
||||||
horizontal.bar.Max.X = int(float64(viewportBounds.Max.X) * scale)
|
horizontal.Max.Y = bounds.Max.Y
|
||||||
|
return
|
||||||
horizontal.bar.Min.X += horizontal.track.Min.X
|
|
||||||
horizontal.bar.Max.X += horizontal.track.Min.X
|
|
||||||
}
|
|
||||||
if vertical.exists && vertical.enabled {
|
|
||||||
vertical.bar.Min.X = vertical.track.Min.X
|
|
||||||
vertical.bar.Max.X = vertical.track.Max.X
|
|
||||||
|
|
||||||
scale := float64(vertical.track.Dy()) /
|
|
||||||
float64(contentBounds.Dy())
|
|
||||||
vertical.bar.Min.Y = int(float64(viewportBounds.Min.Y) * scale)
|
|
||||||
vertical.bar.Max.Y = int(float64(viewportBounds.Max.Y) * scale)
|
|
||||||
|
|
||||||
vertical.bar.Min.Y += vertical.track.Min.Y
|
|
||||||
vertical.bar.Max.Y += vertical.track.Min.Y
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the scroll bars are out of bounds, don't display them.
|
|
||||||
if horizontal.bar.Dx() >= horizontal.track.Dx() {
|
|
||||||
horizontal.bar = image.Rectangle { }
|
|
||||||
}
|
|
||||||
if vertical.bar.Dy() >= vertical.track.Dy() {
|
|
||||||
vertical.bar = image.Rectangle { }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *ScrollContainer) draw () {
|
func (element *ScrollContainer) draw () {
|
||||||
deadPattern := element.theme.Pattern (
|
// XOR
|
||||||
theme.PatternDead, theme.State { })
|
if element.horizontal != nil && element.vertical != nil {
|
||||||
artist.DrawBounds (
|
bounds := element.Bounds()
|
||||||
element.core, deadPattern,
|
bounds.Min = image.Pt (
|
||||||
image.Rect (
|
bounds.Max.X - element.vertical.Bounds().Dx(),
|
||||||
element.vertical.gutter.Min.X,
|
bounds.Max.Y - element.horizontal.Bounds().Dy())
|
||||||
element.horizontal.gutter.Min.Y,
|
state := theme.State { }
|
||||||
element.vertical.gutter.Max.X,
|
deadArea := element.theme.Pattern(theme.PatternDead, state)
|
||||||
element.horizontal.gutter.Max.Y))
|
deadArea.Draw(canvas.Cut(element.core, bounds), bounds)
|
||||||
element.drawHorizontalBar()
|
|
||||||
element.drawVerticalBar()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) drawHorizontalBar () {
|
|
||||||
state := theme.State {
|
|
||||||
Disabled: !element.horizontal.enabled,
|
|
||||||
Pressed: element.horizontal.dragging,
|
|
||||||
}
|
}
|
||||||
gutterPattern := element.horizontal.theme.Pattern(theme.PatternGutter, state)
|
|
||||||
artist.DrawBounds(element.core, gutterPattern, element.horizontal.gutter)
|
|
||||||
|
|
||||||
handlePattern := element.horizontal.theme.Pattern(theme.PatternHandle, state)
|
|
||||||
artist.DrawBounds(element.core, handlePattern, element.horizontal.bar)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) drawVerticalBar () {
|
|
||||||
state := theme.State {
|
|
||||||
Disabled: !element.vertical.enabled,
|
|
||||||
Pressed: element.vertical.dragging,
|
|
||||||
}
|
|
||||||
gutterPattern := element.vertical.theme.Pattern(theme.PatternGutter, state)
|
|
||||||
artist.DrawBounds(element.core, gutterPattern, element.vertical.gutter)
|
|
||||||
|
|
||||||
handlePattern := element.vertical.theme.Pattern(theme.PatternHandle, state)
|
|
||||||
artist.DrawBounds(element.core, handlePattern, element.vertical.bar)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) dragHorizontalBar (mousePosition image.Point) {
|
|
||||||
scrollX :=
|
|
||||||
float64(element.child.ScrollContentBounds().Dx()) /
|
|
||||||
float64(element.horizontal.track.Dx()) *
|
|
||||||
float64(mousePosition.X - element.horizontal.dragOffset)
|
|
||||||
scrollY := element.child.ScrollViewportBounds().Min.Y
|
|
||||||
element.child.ScrollTo(image.Pt(int(scrollX), scrollY))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *ScrollContainer) dragVerticalBar (mousePosition image.Point) {
|
|
||||||
scrollY :=
|
|
||||||
float64(element.child.ScrollContentBounds().Dy()) /
|
|
||||||
float64(element.vertical.track.Dy()) *
|
|
||||||
float64(mousePosition.Y - element.vertical.dragOffset)
|
|
||||||
scrollX := element.child.ScrollViewportBounds().Min.X
|
|
||||||
element.child.ScrollTo(image.Pt(scrollX, int(scrollY)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *ScrollContainer) updateMinimumSize () {
|
func (element *ScrollContainer) updateMinimumSize () {
|
||||||
gutterInsetHorizontal := element.horizontal.theme.Padding(theme.PatternGutter)
|
var width, height int
|
||||||
gutterInsetVertical := element.vertical.theme.Padding(theme.PatternGutter)
|
|
||||||
|
|
||||||
thicknessHorizontal :=
|
|
||||||
element.config.HandleWidth() +
|
|
||||||
gutterInsetHorizontal[3] +
|
|
||||||
gutterInsetHorizontal[1]
|
|
||||||
thicknessVertical :=
|
|
||||||
element.config.HandleWidth() +
|
|
||||||
gutterInsetVertical[3] +
|
|
||||||
gutterInsetVertical[1]
|
|
||||||
|
|
||||||
width := thicknessHorizontal
|
|
||||||
height := thicknessVertical
|
|
||||||
if element.child != nil {
|
if element.child != nil {
|
||||||
childWidth, childHeight := element.child.MinimumSize()
|
width, height = element.child.MinimumSize()
|
||||||
width += childWidth
|
}
|
||||||
height += childHeight
|
if element.horizontal != nil {
|
||||||
|
hMinWidth, hMinHeight := element.horizontal.MinimumSize()
|
||||||
|
height += hMinHeight
|
||||||
|
if hMinWidth > width {
|
||||||
|
width = hMinWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if element.vertical != nil {
|
||||||
|
vMinWidth, vMinHeight := element.vertical.MinimumSize()
|
||||||
|
width += vMinWidth
|
||||||
|
if vMinHeight > height {
|
||||||
|
height = vMinHeight
|
||||||
|
}
|
||||||
}
|
}
|
||||||
element.core.SetMinimumSize(width, height)
|
element.core.SetMinimumSize(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *ScrollContainer) childScrollBoundsChangeCallback () {
|
func (element *ScrollContainer) childScrollBoundsChangeCallback () {
|
||||||
element.horizontal.enabled,
|
element.updateEnabled()
|
||||||
element.vertical.enabled = element.child.ScrollAxes()
|
viewportBounds := element.child.ScrollViewportBounds()
|
||||||
if element.core.HasImage() {
|
contentBounds := element.child.ScrollContentBounds()
|
||||||
element.recalculate()
|
if element.horizontal != nil {
|
||||||
element.drawHorizontalBar()
|
element.horizontal.SetBounds(contentBounds, viewportBounds)
|
||||||
element.drawVerticalBar()
|
}
|
||||||
element.core.DamageRegion(element.horizontal.gutter)
|
if element.vertical != nil {
|
||||||
element.core.DamageRegion(element.vertical.gutter)
|
element.vertical.SetBounds(contentBounds, viewportBounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *ScrollContainer) updateEnabled () {
|
||||||
|
horizontal, vertical := element.child.ScrollAxes()
|
||||||
|
if element.horizontal != nil {
|
||||||
|
element.horizontal.SetEnabled(horizontal)
|
||||||
|
}
|
||||||
|
if element.vertical != nil {
|
||||||
|
element.vertical.SetEnabled(vertical)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user