package ui import ( "math" "github.com/llgcode/draw2d/draw2dimg" "opslag.de/schobers/geom" "opslag.de/schobers/zntg/allg5" ) var _ Control = &Scrollbar{} const ScrollbarWidth = 12 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 normal *allg5.Bitmap hover *allg5.Bitmap pressed *allg5.Bitmap } func (s *Scrollbar) Created(ctx Context, p Container) error { s.ControlBase.Created(ctx, p) var center = float64(ScrollbarWidth) * 1.5 var rad = float64(ScrollbarWidth) * .5 s.normal = drawBitmap(3*ScrollbarWidth, 3*ScrollbarWidth, func(gc *draw2dimg.GraphicContext) { gc.SetFillColor(ctx.Palette().Primary()) gc.MoveTo(center, center) gc.ArcTo(center, center, rad, rad, 0, 2*math.Pi) gc.Fill() }) s.hover = drawBitmap(3*ScrollbarWidth, 3*ScrollbarWidth, func(gc *draw2dimg.GraphicContext) { gc.SetFillColor(ctx.Palette().PrimaryTransparent()) gc.MoveTo(center, center) gc.ArcTo(center, center, center, center, 0, 2*math.Pi) gc.Fill() gc.SetFillColor(ctx.Palette().Primary()) gc.MoveTo(center, center) gc.ArcTo(center, center, rad, rad, 0, 2*math.Pi) gc.Fill() }) s.pressed = drawBitmap(3*ScrollbarWidth, 3*ScrollbarWidth, func(gc *draw2dimg.GraphicContext) { gc.SetFillColor(ctx.Palette().PrimaryTransparent()) for i := 0; i < 2; i++ { gc.MoveTo(center, center) gc.ArcTo(center, center, center, center, 0, 2*math.Pi) gc.Fill() } gc.SetFillColor(ctx.Palette().Primary()) gc.MoveTo(center, center) gc.ArcTo(center, center, rad, rad, 0, 2*math.Pi) gc.Fill() }) s.handle = &ControlBase{} s.handle.Created(ctx, nil) return nil } func (s *Scrollbar) Destroyed(ctx Context) { var d = func(b *allg5.Bitmap) { if nil != b { b.Destroy() } } d(s.normal) d(s.hover) d(s.pressed) s.handle.Destroyed(ctx) } func (s *Scrollbar) barViewRange() (float32, float32) { var small bool var min, max float32 switch s.Orientation { case OrientationHorizontal: min = s.Bounds.Min.X + ScrollbarWidth max = s.Bounds.Max.X - ScrollbarWidth small = min > max default: min = s.Bounds.Max.Y - ScrollbarWidth max = s.Bounds.Min.Y + ScrollbarWidth small = max > min } if small { var center = (min + max) * .5 min, max = center, center } return min, max } func (s *Scrollbar) barViewCenter() float32 { 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 float32) 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*float32(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(float32(x), float32(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 allg5.Event) { s.ControlBase.Handle(ctx, ev) s.handle.Handle(ctx, ev) switch e := ev.(type) { case *allg5.MouseMoveEvent: if s.handle.IsPressed { s.snapTo(e.X, e.Y) } if 0 != e.DeltaZ && s.IsOver { var d = e.DeltaZ if allg5.IsAnyKeyDown(allg5.KeyLShift, allg5.KeyRShift) { d *= 10 } s.increment(d) } case *allg5.MouseButtonDownEvent: if !s.handle.IsPressed && s.IsOver { s.snapTo(e.X, e.Y) } } } func (s *Scrollbar) DesiredSize(Context) geom.PointF32 { var width = float32(2 * ScrollbarWidth) switch s.Orientation { case OrientationHorizontal: return geom.PtF32(geom.NaN32(), width) } return geom.PtF32(width, geom.NaN32()) } func (s *Scrollbar) SetRect(rect geom.RectangleF32) { var width = float32(2 * ScrollbarWidth) switch s.Orientation { case OrientationHorizontal: if rect.Dy() > width { rect.Min.Y = rect.Max.Y - width } default: if rect.Dx() > width { rect.Min.X = rect.Max.X - width } } s.ControlBase.SetRect(rect) var min, max = s.barViewRange() var off = float32(s.Value-s.Minimum) / float32(s.Maximum-s.Minimum) var centerH = min + (max-min)*off var r = 0.5 * float32(s.normal.Width()) var center = s.barViewCenter() switch s.Orientation { case OrientationHorizontal: s.handle.SetRect(geom.RectF32(centerH-r, center-r, centerH+r, center+r)) default: s.handle.SetRect(geom.RectF32(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 centerH = s.handle.Bounds.Center() switch s.Orientation { case OrientationHorizontal: // Left line allg5.DrawLine(min, center, centerH.X, center, ctx.Palette().Primary(), 2) allg5.DrawLine(centerH.X, center, max, center, ctx.Palette().PrimaryTransparent(), 2) default: allg5.DrawLine(center, max, center, centerH.Y, ctx.Palette().PrimaryTransparent(), 2) allg5.DrawLine(center, centerH.Y, center, min, ctx.Palette().Primary(), 2) } var minH = s.handle.Bounds.Min var state = s.normal if s.handle.IsOver { if s.handle.IsPressed { state = s.pressed } else { state = s.hover } } state.Draw(minH.X, minH.Y) s.ControlBase.Render(ctx) }