We now have an untested lone scrollbar element

This commit is contained in:
Sasha Koshka 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/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" 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 { type ScrollBar struct {
*core.Core *core.Core
core core.CoreControl core core.CoreControl
@ -14,7 +22,7 @@ type ScrollBar struct {
vertical bool vertical bool
enabled bool enabled bool
dragging bool dragging bool
dragOffset int dragOffset image.Point
track image.Rectangle track image.Rectangle
bar image.Rectangle bar image.Rectangle
@ -24,10 +32,11 @@ type ScrollBar struct {
config config.Wrapped config config.Wrapped
theme theme.Wrapped theme theme.Wrapped
onSlide func () onScroll func (viewport image.Point)
onRelease func ()
} }
// 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) { func NewScrollBar (vertical bool) (element *ScrollBar) {
element = &ScrollBar { element = &ScrollBar {
vertical: vertical, vertical: vertical,
@ -44,15 +53,61 @@ func NewScrollBar (vertical bool) (element *ScrollBar) {
} }
func (element *ScrollBar) HandleMouseDown (x, y int, button input.Button) { 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) { 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) { 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) { func (element *ScrollBar) HandleMouseScroll (x, y int, deltaX, deltaY float64) {
@ -78,6 +133,15 @@ func (element *ScrollBar) SetBounds (content, viewport image.Rectangle) {
element.redo() 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. // SetTheme sets the element's theme.
func (element *ScrollBar) SetTheme (new theme.Theme) { func (element *ScrollBar) SetTheme (new theme.Theme) {
if new == element.theme.Theme { return } if new == element.theme.Theme { return }
@ -93,6 +157,57 @@ func (element *ScrollBar) SetConfig (new config.Config) {
element.redo() 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 () { func (element *ScrollBar) recalculate () {
if element.vertical { if element.vertical {
element.recalculateVertical() element.recalculateVertical()

2
go.mod
View File

@ -3,7 +3,7 @@ module git.tebibyte.media/sashakoshka/tomo
go 1.19 go 1.19
require ( require (
git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309013201-fc0de8121523 git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309044548-401cba83602b
github.com/faiface/beep v1.1.0 github.com/faiface/beep v1.1.0
github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66 github.com/jezek/xgbutil v0.0.0-20210302171758-530099784e66
golang.org/x/image v0.3.0 golang.org/x/image v0.3.0

2
go.sum
View File

@ -4,6 +4,8 @@ git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309013013-f7ee80c8f908 h1:kFdc
git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309013013-f7ee80c8f908/go.mod h1:cpXX8SAUDEvZX5m7scoyruavUhEqQ1SByfWzPFHkTbg= git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309013013-f7ee80c8f908/go.mod h1:cpXX8SAUDEvZX5m7scoyruavUhEqQ1SByfWzPFHkTbg=
git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309013201-fc0de8121523 h1:1KaoiGetWYIDQKts6yas1hW+4ObkuTm6+TkFpl6jZxg= git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309013201-fc0de8121523 h1:1KaoiGetWYIDQKts6yas1hW+4ObkuTm6+TkFpl6jZxg=
git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309013201-fc0de8121523/go.mod h1:cpXX8SAUDEvZX5m7scoyruavUhEqQ1SByfWzPFHkTbg= git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309013201-fc0de8121523/go.mod h1:cpXX8SAUDEvZX5m7scoyruavUhEqQ1SByfWzPFHkTbg=
git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309044548-401cba83602b h1:vPFKR7vjN1VrMdMtpATMrKQobz/cqbPiRrA1EbtG6PM=
git.tebibyte.media/sashakoshka/ezprof v0.0.0-20230309044548-401cba83602b/go.mod h1:cpXX8SAUDEvZX5m7scoyruavUhEqQ1SByfWzPFHkTbg=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA= github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298 h1:1qlsVAQJXZHsaM8b6OLVo6muQUQd4CwkH/D3fnnbHXA=
github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ= github.com/BurntSushi/freetype-go v0.0.0-20160129220410-b763ddbfe298/go.mod h1:D+QujdIlUNfa0igpNMk6UIvlb6C252URs4yupRUV4lQ=
github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g= github.com/BurntSushi/graphics-go v0.0.0-20160129215708-b43f31a4a966 h1:lTG4HQym5oPKjL7nGs+csTgiDna685ZXjxijkne828g=

BIN
xcf/large.xcf Normal file

Binary file not shown.

BIN
xcf/small.xcf Normal file

Binary file not shown.