zntg/ui/scrollbar.go

214 lines
5.0 KiB
Go

package ui
import (
"math"
"opslag.de/schobers/galleg/allegro5"
"opslag.de/schobers/geom"
)
var _ Control = &Scrollbar{}
const ScrollbarWidth = 26
const ScrollbarHandleThickness = 4
const ScrollbarHandlePadding = 1
type ScrollbarValueChangedFn func(int)
type Scrollbar struct {
ControlBase
Minimum int
Maximum int
Value int
Orientation Orientation
OnChanged ScrollbarValueChangedFn
handle *ControlBase
handles []*allegro5.Bitmap
}
func (s *Scrollbar) Created(ctx Context, p Container) error {
s.ControlBase.Created(ctx, p)
s.handles = []*allegro5.Bitmap{
createCirle((ScrollbarWidth/2)-ScrollbarHandleThickness-2*ScrollbarHandlePadding, ScrollbarHandleThickness, 0, math.Pi*2, Blue500),
createCirle((ScrollbarWidth/2)-ScrollbarHandleThickness-2*ScrollbarHandlePadding, ScrollbarHandleThickness, 0, math.Pi*2, Blue300),
createCirle((ScrollbarWidth/2)-ScrollbarHandleThickness-2*ScrollbarHandlePadding, ScrollbarHandleThickness, 0, math.Pi*2, Blue700),
}
s.handle = &ControlBase{}
s.handle.Created(ctx, nil)
return nil
}
func (s *Scrollbar) Destroyed(ctx Context) {
if nil != s.handles {
for _, h := range s.handles {
h.Destroy()
}
}
s.handle.Destroyed(ctx)
}
func (s *Scrollbar) barViewRange() (float64, float64) {
var small bool
var min, max float64
switch s.Orientation {
case OrientationHorizontal:
min = s.Bounds.Min.X + ScrollbarWidth*.5
max = s.Bounds.Max.X - ScrollbarWidth*.5
small = min > max
default:
min = s.Bounds.Max.Y - ScrollbarWidth*.5
max = s.Bounds.Min.Y + ScrollbarWidth*.5
small = max > min
}
if small {
var center = (min + max) * .5
min, max = center, center
}
return min, max
}
func (s *Scrollbar) barViewCenter() float64 {
switch s.Orientation {
case OrientationHorizontal:
return (s.Bounds.Min.Y + s.Bounds.Max.Y) * .5
default:
return (s.Bounds.Min.X + s.Bounds.Max.X) * .5
}
}
func (s *Scrollbar) toValue(x, y float64) int {
var n = y
if OrientationHorizontal == s.Orientation {
n = x
}
var min, max = s.barViewRange()
if min == max {
return s.Minimum
}
var off = (n - min) / (max - min)
var v = s.Minimum + int(off*float64(s.Maximum-s.Minimum)+.5)
if v < s.Minimum {
v = s.Minimum
} else if v > s.Maximum {
v = s.Maximum
}
return v
}
func (s *Scrollbar) change(v int) {
if v != s.Value {
s.Value = v
var onChanged = s.OnChanged
if nil != onChanged {
onChanged(v)
}
}
}
func (s *Scrollbar) snapTo(x, y int) {
var val = s.toValue(float64(x), float64(y))
s.change(val)
}
func (s *Scrollbar) increment(d int) {
var val = s.Value + d
if val < s.Minimum {
val = s.Minimum
} else if val > s.Maximum {
val = s.Maximum
}
s.change(val)
}
func (s *Scrollbar) Handle(ctx Context, ev allegro5.Event) {
s.ControlBase.Handle(ctx, ev)
s.handle.Handle(ctx, ev)
switch e := ev.(type) {
case *allegro5.MouseMoveEvent:
if s.handle.IsPressed {
s.snapTo(e.X, e.Y)
}
if 0 != e.DeltaZ && s.IsOver {
var d = e.DeltaZ
if allegro5.IsAnyKeyDown(allegro5.KeyLShift, allegro5.KeyRShift) {
d *= 10
}
s.increment(d)
}
case *allegro5.MouseButtonDownEvent:
if !s.handle.IsPressed && s.IsOver {
s.snapTo(e.X, e.Y)
}
}
}
func (s *Scrollbar) DesiredSize(Context) geom.PointF {
switch s.Orientation {
case OrientationHorizontal:
return geom.PtF(math.NaN(), ScrollbarWidth)
}
return geom.PtF(ScrollbarWidth, math.NaN())
}
func (s *Scrollbar) SetRect(rect geom.RectangleF) {
switch s.Orientation {
case OrientationHorizontal:
if rect.Dy() > ScrollbarWidth {
rect.Min.Y = rect.Max.Y - ScrollbarWidth
}
default:
if rect.Dx() > ScrollbarWidth {
rect.Min.X = rect.Max.X - ScrollbarWidth
}
}
s.ControlBase.SetRect(rect)
var min, max = s.barViewRange()
var off = float64(s.Value-s.Minimum) / float64(s.Maximum-s.Minimum)
var centerH = min + (max-min)*off
var r = 0.5 * float64(s.handles[0].Width())
var center = s.barViewCenter()
switch s.Orientation {
case OrientationHorizontal:
s.handle.SetRect(geom.RectF(centerH-r, center-r, centerH+r, center+r))
default:
s.handle.SetRect(geom.RectF(center-r, centerH-r, center+r, centerH+r))
}
}
func (s *Scrollbar) Render(ctx Context) {
var center = float32(s.barViewCenter())
var min64, max64 = s.barViewRange()
var min, max = float32(min64), float32(max64)
var minH = s.handle.Bounds.Min.To32()
var stateH = 0
if s.handle.IsOver {
stateH = 1
if s.handle.IsPressed {
stateH = 2
}
}
s.handles[stateH].Draw(minH.X, minH.Y)
var maxH = s.handle.Bounds.Max.To32()
switch s.Orientation {
case OrientationHorizontal:
if min < minH.X-1 { // Top line
allegro5.DrawLine(min, center, minH.X-1, center, ctx.Palette().Black(), 1)
}
if max > maxH.X+1 { // Bottom line
allegro5.DrawLine(maxH.X+1, center, max, center, ctx.Palette().Black(), 1)
}
default:
if max < minH.Y-1 { // Top line
allegro5.DrawLine(center, max, center, minH.Y-1, ctx.Palette().Black(), 1)
}
if min > maxH.Y+1 { // Bottom line
allegro5.DrawLine(center, maxH.Y+1, center, min, ctx.Palette().Black(), 1)
}
}
s.ControlBase.Render(ctx)
}