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) }