zntg/ui/slider.go

174 lines
4.1 KiB
Go
Raw Normal View History

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.OnDragStart(func(start geom.PointF32) { s.handleDrag(start) })
s.handle.OnDragMove(func(_, pos geom.PointF32) { s.handleDrag(pos) })
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
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 {
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) {
s.handle.Handle(ctx, e)
s.ControlBase.Handle(ctx, e)
s.setValue(s.Value)
}
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 Image
}
func (h *sliderHandle) im(ctx Context) Image {
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 = CreateImageAlpha(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) {
h.ControlBase.Handle(ctx, e)
if h.IsOver() {
ctx.Renderer().SetMouseCursor(MouseCursorPointer)
}
}
func (h *sliderHandle) Render(ctx Context) {
color := ctx.Style().Palette.Primary
if h.IsOver() {
color = ctx.Style().Palette.PrimaryLight
}
ctx.Renderer().DrawImageOptions(h.im(ctx), h.Bounds().Min, DrawOptions{Tint: color})
}