Sander Schobers
4cff23cd37
- With shift pressed it moves the value 10. - With control pressed it moves the value with 0.1 (does nothing when the Integer flag is enabled).
205 lines
4.8 KiB
Go
205 lines
4.8 KiB
Go
package ui
|
|
|
|
import (
|
|
"opslag.de/schobers/geom"
|
|
)
|
|
|
|
type Slider struct {
|
|
ControlBase
|
|
|
|
Orientation Orientation
|
|
Minimum float32
|
|
Maximum float32
|
|
Value float32
|
|
Integer bool
|
|
|
|
valueChanged func(float32)
|
|
handleWidth float32
|
|
handle sliderHandle
|
|
}
|
|
|
|
func BuildSlider(o Orientation, fn func(s *Slider)) *Slider {
|
|
s := &Slider{Orientation: o}
|
|
s.handle.DragStarted().AddHandler(func(_ Context, args DragStartedArgs) { s.handleDrag(args.Start) })
|
|
s.handle.DragMoved().AddHandler(func(_ Context, args DragMovedArgs) { s.handleDrag(args.Current) })
|
|
if fn != nil {
|
|
fn(s)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (s *Slider) handleDrag(pos geom.PointF32) {
|
|
start, length, _ := s.offsets()
|
|
var offset float32
|
|
if s.Orientation == OrientationHorizontal {
|
|
offset = pos.X
|
|
} else {
|
|
offset = pos.Y
|
|
}
|
|
s.setValue(s.Minimum + ((offset-start)/length)*(s.Maximum-s.Minimum))
|
|
}
|
|
|
|
func (s *Slider) setValue(v float32) {
|
|
if s.Minimum < s.Maximum {
|
|
v = geom.Min32(s.Maximum, geom.Max32(s.Minimum, v))
|
|
} else {
|
|
v = geom.Min32(s.Minimum, geom.Max32(s.Maximum, v))
|
|
}
|
|
if s.Integer {
|
|
v = geom.Round32(v)
|
|
}
|
|
if s.Value != v {
|
|
s.Value = v
|
|
valueChanged := s.valueChanged
|
|
if valueChanged != nil {
|
|
valueChanged(s.Value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Slider) offsets() (start, length, center float32) {
|
|
bounds := s.Bounds()
|
|
if s.Orientation == OrientationHorizontal {
|
|
start = bounds.Min.X
|
|
length = bounds.Dx()
|
|
center = .5 * (bounds.Min.Y + bounds.Max.Y)
|
|
} else {
|
|
start = bounds.Max.Y
|
|
length = -bounds.Dy()
|
|
center = .5 * (bounds.Min.X + bounds.Max.X)
|
|
}
|
|
if geom.Abs32(length) < s.handleWidth {
|
|
start += .5 * length
|
|
length = 0
|
|
} else {
|
|
if length < 0 {
|
|
start -= .5 * s.handleWidth
|
|
length += s.handleWidth
|
|
} else {
|
|
start += .5 * s.handleWidth
|
|
length -= s.handleWidth
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (s *Slider) Arrange(ctx Context, bounds geom.RectangleF32, offset geom.PointF32, parent Control) {
|
|
s.ControlBase.Arrange(ctx, bounds, offset, parent)
|
|
|
|
s.handleWidth = ctx.Style().Dimensions.ScrollbarWidth
|
|
start, length, center := s.offsets()
|
|
w := s.handleWidth
|
|
w05 := .5 * w
|
|
var handleOffset float32
|
|
if s.Maximum > s.Minimum {
|
|
handleOffset = (s.Value - s.Minimum) / (s.Maximum - s.Minimum)
|
|
}
|
|
if !geom.IsNaN32(handleOffset) {
|
|
start += handleOffset * length
|
|
}
|
|
if s.Orientation == OrientationHorizontal {
|
|
s.handle.Arrange(ctx, geom.RectF32(start-w05, center-w05, start+w05, center+w05), offset, s)
|
|
} else {
|
|
s.handle.Arrange(ctx, geom.RectF32(center-w05, start-w05, center+w05, start+w05), offset, s)
|
|
}
|
|
}
|
|
|
|
func (s *Slider) DesiredSize(ctx Context, _ geom.PointF32) geom.PointF32 {
|
|
w := ctx.Style().Dimensions.ScrollbarWidth
|
|
if s.Orientation == OrientationHorizontal {
|
|
return geom.PtF32(geom.NaN32(), w)
|
|
}
|
|
return geom.PtF32(w, geom.NaN32())
|
|
}
|
|
|
|
func (s *Slider) Handle(ctx Context, e Event) bool {
|
|
if s.handle.Handle(ctx, e) {
|
|
return true
|
|
}
|
|
if s.ControlBase.Handle(ctx, e) {
|
|
return true
|
|
}
|
|
if !s.IsOver() {
|
|
return false
|
|
}
|
|
switch e := e.(type) {
|
|
case *MouseMoveEvent:
|
|
if e.MouseWheel != 0 {
|
|
var speed float32 = 1
|
|
mods := ctx.KeyModifiers()
|
|
if mods&KeyModifierShift == KeyModifierShift {
|
|
speed = 10
|
|
} else if mods&KeyModifierControl == KeyModifierControl {
|
|
speed = 0.1
|
|
}
|
|
if e.MouseWheel > 0 {
|
|
s.setValue(s.Value + speed)
|
|
} else {
|
|
s.setValue(s.Value - speed)
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *Slider) Render(ctx Context) {
|
|
s.RenderBackground(ctx)
|
|
prim := ctx.Style().Palette.Primary
|
|
start, length, center := s.offsets()
|
|
if s.Orientation == OrientationHorizontal {
|
|
ctx.Renderer().FillRectangle(geom.RectF32(start, center-1, start+length, center+1), prim)
|
|
} else {
|
|
ctx.Renderer().FillRectangle(geom.RectF32(center-1, start, center+1, start+length), prim)
|
|
}
|
|
s.handle.Render(ctx)
|
|
}
|
|
|
|
func (s *Slider) OnValueChanged(fn func(float32)) {
|
|
s.valueChanged = fn
|
|
}
|
|
|
|
type sliderHandle struct {
|
|
ControlBase
|
|
|
|
handle Texture
|
|
}
|
|
|
|
func (h *sliderHandle) texture(ctx Context) Texture {
|
|
if h.handle != nil {
|
|
return h.handle
|
|
}
|
|
size := h.Bounds().Size()
|
|
center := geom.PtF32(.5*size.X-.5, .5*size.Y-.5)
|
|
radius := 0.5 * geom.Min32(size.X, size.Y)
|
|
h.handle = CreateTextureAlpha(ctx, geom.Pt(int(size.X), int(size.Y)), func(p geom.PointF32) uint8 {
|
|
dist := center.Distance(p)
|
|
if dist < radius {
|
|
if dist < (radius - 1) {
|
|
return 255
|
|
}
|
|
return uint8(255 * (radius - dist))
|
|
}
|
|
return 0
|
|
})
|
|
return h.handle
|
|
}
|
|
|
|
func (h *sliderHandle) Handle(ctx Context, e Event) bool {
|
|
h.ControlBase.Handle(ctx, e)
|
|
if h.IsOver() {
|
|
if h.Disabled {
|
|
return false
|
|
}
|
|
ctx.Renderer().SetMouseCursor(MouseCursorPointer)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (h *sliderHandle) Render(ctx Context) {
|
|
color := ctx.Style().Palette.Primary
|
|
if h.IsOver() {
|
|
color = ctx.Style().Palette.PrimaryLight
|
|
}
|
|
ctx.Renderer().DrawTexturePointOptions(h.texture(ctx), h.Bounds().Min, DrawOptions{Tint: color})
|
|
}
|