We now have an untested lone scrollbar element

This commit is contained in:
2023-03-09 18:15:52 -05:00
parent cf672824a6
commit aff9aca835
6 changed files with 124 additions and 7 deletions

View File

@@ -7,6 +7,14 @@ import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
// ScrollBar is an element similar to Slider, but it has special behavior that
// makes it well suited for controlling the viewport position on one axis of a
// scrollable element. Instead of having a value from zero to one, it stores
// viewport and content boundaries. When the user drags the scroll bar handle,
// the scroll bar calls the OnScroll callback assigned to it with the position
// the user is trying to move the handle to. A program can check to see if this
// value is valid, move the viewport, and give the scroll bar the new viewport
// bounds (which will then cause it to move the handle).
type ScrollBar struct {
*core.Core
core core.CoreControl
@@ -14,7 +22,7 @@ type ScrollBar struct {
vertical bool
enabled bool
dragging bool
dragOffset int
dragOffset image.Point
track image.Rectangle
bar image.Rectangle
@@ -24,10 +32,11 @@ type ScrollBar struct {
config config.Wrapped
theme theme.Wrapped
onSlide func ()
onRelease func ()
onScroll func (viewport image.Point)
}
// NewScrollBar creates a new scroll bar. If vertical is set to true, the scroll
// bar will be vertical instead of horizontal.
func NewScrollBar (vertical bool) (element *ScrollBar) {
element = &ScrollBar {
vertical: vertical,
@@ -44,15 +53,61 @@ func NewScrollBar (vertical bool) (element *ScrollBar) {
}
func (element *ScrollBar) HandleMouseDown (x, y int, button input.Button) {
velocity := element.config.ScrollVelocity()
point := image.Pt(x, y)
if point.In(element.bar) {
// the mouse is pressed down within the bar's handle
element.dragging = true
element.redo()
element.dragOffset = point
element.dragTo(point)
} else {
// the mouse is pressed down within the bar's gutter
switch button {
case input.ButtonLeft:
// start scrolling at this point, but set the offset to
// the middle of the handle
element.dragging = true
element.dragOffset = element.fallbackDragOffset()
element.dragTo(point)
case input.ButtonMiddle:
// page up/down on middle click
viewport := 0
if element.vertical {
viewport = element.viewportBounds.Dy()
} else {
viewport = element.viewportBounds.Dx()
}
if element.isAfterHandle(point) {
element.scrollBy(viewport)
} else {
element.scrollBy(-viewport)
}
case input.ButtonRight:
// inch up/down on right click
if element.isAfterHandle(point) {
element.scrollBy(velocity)
} else {
element.scrollBy(-velocity)
}
}
}
}
func (element *ScrollBar) HandleMouseUp (x, y int, button input.Button) {
if element.dragging {
element.dragging = false
element.redo()
}
}
func (element *ScrollBar) HandleMouseMove (x, y int) {
if element.dragging {
element.dragTo(image.Pt(x, y))
}
}
func (element *ScrollBar) HandleMouseScroll (x, y int, deltaX, deltaY float64) {
@@ -78,6 +133,15 @@ func (element *ScrollBar) SetBounds (content, viewport image.Rectangle) {
element.redo()
}
// OnScroll sets a function to be called when the user tries to move the scroll
// bar's handle. The callback is passed a point representing the new viewport
// position. For the scroll bar's position to visually update, the callback must
// check if the position is valid and call ScrollBar.SetBounds with the new
// viewport bounds.
func (element *ScrollBar) OnScroll (callback func (viewport image.Point)) {
element.onScroll = callback
}
// SetTheme sets the element's theme.
func (element *ScrollBar) SetTheme (new theme.Theme) {
if new == element.theme.Theme { return }
@@ -93,6 +157,57 @@ func (element *ScrollBar) SetConfig (new config.Config) {
element.redo()
}
func (element *ScrollBar) isAfterHandle (point image.Point) bool {
if element.vertical {
return point.Y > element.bar.Min.Y
} else {
return point.X > element.bar.Min.X
}
}
func (element *ScrollBar) fallbackDragOffset () image.Point {
if element.vertical {
return element.bar.Min.Add(image.Pt(0, element.bar.Dy() / 2))
} else {
return element.bar.Min.Add(image.Pt(element.bar.Dx() / 2, 0))
}
}
func (element *ScrollBar) scrollBy (delta int) {
deltaPoint := image.Point { }
if element.vertical {
deltaPoint.Y = delta
} else {
deltaPoint.X = delta
}
if element.onScroll != nil {
element.onScroll(element.viewportBounds.Min.Add(deltaPoint))
}
}
func (element *ScrollBar) dragTo (point image.Point) {
point = point.Sub(element.dragOffset)
var scrollX, scrollY float64
if element.vertical {
ratio :=
float64(element.contentBounds.Dy()) /
float64(element.track.Dy())
scrollX = float64(element.viewportBounds.Min.X)
scrollY = float64(point.Y) * ratio
} else {
ratio :=
float64(element.contentBounds.Dx()) /
float64(element.track.Dx())
scrollX = float64(point.X) * ratio
scrollY = float64(element.viewportBounds.Min.Y)
}
if element.onScroll != nil {
element.onScroll(image.Pt(int(scrollX), int(scrollY)))
}
}
func (element *ScrollBar) recalculate () {
if element.vertical {
element.recalculateVertical()