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 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) bool { defer s.setValue(s.Value) if s.handle.Handle(ctx, e) { return true } if s.ControlBase.Handle(ctx, e) { return true } 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() { ctx.Renderer().SetMouseCursor(MouseCursorPointer) } return true } func (h *sliderHandle) Render(ctx Context) { color := ctx.Style().Palette.Primary if h.IsOver() { color = ctx.Style().Palette.PrimaryLight } ctx.Renderer().DrawTextureOptions(h.texture(ctx), h.Bounds().Min, DrawOptions{Tint: color}) }