2018-08-03 06:46:10 +00:00
|
|
|
package ui
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math"
|
|
|
|
|
2018-08-07 05:03:52 +00:00
|
|
|
"github.com/llgcode/draw2d/draw2dimg"
|
|
|
|
|
2018-08-03 06:46:10 +00:00
|
|
|
"opslag.de/schobers/galleg/allegro5"
|
|
|
|
"opslag.de/schobers/geom"
|
|
|
|
)
|
|
|
|
|
|
|
|
var _ Control = &Scrollbar{}
|
|
|
|
|
2018-08-07 05:03:52 +00:00
|
|
|
const ScrollbarWidth = 12
|
2018-08-03 06:46:10 +00:00
|
|
|
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
|
2018-08-07 05:03:52 +00:00
|
|
|
normal *allegro5.Bitmap
|
|
|
|
hover *allegro5.Bitmap
|
|
|
|
pressed *allegro5.Bitmap
|
2018-08-03 06:46:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Scrollbar) Created(ctx Context, p Container) error {
|
|
|
|
s.ControlBase.Created(ctx, p)
|
2018-08-07 05:03:52 +00:00
|
|
|
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()
|
|
|
|
})
|
2018-08-03 06:46:10 +00:00
|
|
|
s.handle = &ControlBase{}
|
|
|
|
s.handle.Created(ctx, nil)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Scrollbar) Destroyed(ctx Context) {
|
2018-08-07 05:03:52 +00:00
|
|
|
var d = func(b *allegro5.Bitmap) {
|
|
|
|
if nil != b {
|
|
|
|
b.Destroy()
|
2018-08-03 06:46:10 +00:00
|
|
|
}
|
|
|
|
}
|
2018-08-07 05:03:52 +00:00
|
|
|
d(s.normal)
|
|
|
|
d(s.hover)
|
|
|
|
d(s.pressed)
|
2018-08-03 06:46:10 +00:00
|
|
|
s.handle.Destroyed(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Scrollbar) barViewRange() (float64, float64) {
|
|
|
|
var small bool
|
|
|
|
var min, max float64
|
|
|
|
switch s.Orientation {
|
|
|
|
case OrientationHorizontal:
|
2018-08-07 05:03:52 +00:00
|
|
|
min = s.Bounds.Min.X + ScrollbarWidth
|
|
|
|
max = s.Bounds.Max.X - ScrollbarWidth
|
2018-08-03 06:46:10 +00:00
|
|
|
small = min > max
|
|
|
|
default:
|
2018-08-07 05:03:52 +00:00
|
|
|
min = s.Bounds.Max.Y - ScrollbarWidth
|
|
|
|
max = s.Bounds.Min.Y + ScrollbarWidth
|
2018-08-03 06:46:10 +00:00
|
|
|
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 {
|
2018-08-07 05:03:52 +00:00
|
|
|
var width = float64(2 * ScrollbarWidth)
|
2018-08-03 06:46:10 +00:00
|
|
|
switch s.Orientation {
|
|
|
|
case OrientationHorizontal:
|
2018-08-07 05:03:52 +00:00
|
|
|
return geom.PtF(math.NaN(), width)
|
2018-08-03 06:46:10 +00:00
|
|
|
}
|
2018-08-07 05:03:52 +00:00
|
|
|
return geom.PtF(width, math.NaN())
|
2018-08-03 06:46:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Scrollbar) SetRect(rect geom.RectangleF) {
|
2018-08-07 05:03:52 +00:00
|
|
|
var width = float64(2 * ScrollbarWidth)
|
2018-08-03 06:46:10 +00:00
|
|
|
switch s.Orientation {
|
|
|
|
case OrientationHorizontal:
|
2018-08-07 05:03:52 +00:00
|
|
|
if rect.Dy() > width {
|
|
|
|
rect.Min.Y = rect.Max.Y - width
|
2018-08-03 06:46:10 +00:00
|
|
|
}
|
|
|
|
default:
|
2018-08-07 05:03:52 +00:00
|
|
|
if rect.Dx() > width {
|
|
|
|
rect.Min.X = rect.Max.X - width
|
2018-08-03 06:46:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
2018-08-07 05:03:52 +00:00
|
|
|
var r = 0.5 * float64(s.normal.Width())
|
2018-08-03 06:46:10 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2018-08-07 05:03:52 +00:00
|
|
|
var centerH = s.handle.Bounds.Center().To32()
|
2018-08-03 06:46:10 +00:00
|
|
|
|
|
|
|
switch s.Orientation {
|
|
|
|
case OrientationHorizontal:
|
2018-08-07 05:03:52 +00:00
|
|
|
// Left line
|
|
|
|
allegro5.DrawLine(min, center, centerH.X, center, ctx.Palette().Primary(), 2)
|
|
|
|
allegro5.DrawLine(centerH.X, center, max, center, ctx.Palette().PrimaryTransparent(), 2)
|
2018-08-03 06:46:10 +00:00
|
|
|
default:
|
2018-08-07 05:03:52 +00:00
|
|
|
allegro5.DrawLine(center, max, center, centerH.Y, ctx.Palette().PrimaryTransparent(), 2)
|
|
|
|
allegro5.DrawLine(center, centerH.Y, center, min, ctx.Palette().Primary(), 2)
|
|
|
|
}
|
|
|
|
|
|
|
|
var minH = s.handle.Bounds.Min.To32()
|
|
|
|
var state = s.normal
|
|
|
|
if s.handle.IsOver {
|
|
|
|
if s.handle.IsPressed {
|
|
|
|
state = s.pressed
|
|
|
|
} else {
|
|
|
|
state = s.hover
|
2018-08-03 06:46:10 +00:00
|
|
|
}
|
|
|
|
}
|
2018-08-07 05:03:52 +00:00
|
|
|
state.Draw(minH.X, minH.Y)
|
|
|
|
|
2018-08-03 06:46:10 +00:00
|
|
|
s.ControlBase.Render(ctx)
|
|
|
|
}
|