From 992c242ba2124675281364bf36aca89481972bc6 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sat, 12 Aug 2023 01:02:48 -0400 Subject: [PATCH] Add slider --- slider.go | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 176 insertions(+), 8 deletions(-) diff --git a/slider.go b/slider.go index fdb3488..0b1ad23 100644 --- a/slider.go +++ b/slider.go @@ -1,38 +1,206 @@ package objects +import "image" import "git.tebibyte.media/tomo/tomo" import "git.tebibyte.media/tomo/tomo/theme" +import "git.tebibyte.media/tomo/tomo/input" +import "git.tebibyte.media/tomo/tomo/event" +// UNDER CONSTRUCTION! type Slider struct { tomo.ContainerBox - handle *SliderHandle - vertical bool + handle *SliderHandle + layout sliderLayout + dragging bool + dragOffset image.Point + + on struct { + valueChange event.FuncBroadcaster + } } type SliderHandle struct { tomo.Box } -func newSlider (orient string) *Slider { +func newSlider (orient string, value float64) *Slider { this := &Slider { ContainerBox: tomo.NewContainerBox(), handle: &SliderHandle { Box: tomo.NewBox(), }, - vertical: orient == "vertical", + layout: sliderLayout { + vertical: orient == "vertical", + }, } this.Add(this.handle) + this.SetFocusable(true) + this.SetPropagateEvents(false) + this.SetValue(value) + this.OnKeyDown(this.handleKeyDown) + this.OnMouseDown(this.handleMouseDown) + this.OnMouseUp(this.handleMouseUp) + this.OnMouseMove(this.handleMouseMove) theme.Apply(this.handle, theme.R("objects", "SliderHandle", orient)) theme.Apply(this, theme.R("objects", "Slider", orient)) return this } -func NewVerticalSlider () *Slider { - return newSlider("vertical") +func NewVerticalSlider (value float64) *Slider { + return newSlider("vertical", value) } -func NewHorizontalSlider () *Slider { - return newSlider("horizontal") +func NewHorizontalSlider (value float64) *Slider { + return newSlider("horizontal", value) +} + +func (this *Slider) SetValue (value float64) { + if value < 0 { value = 0 } + if value > 1 { value = 1 } + if value == this.layout.value { return } + this.layout.value = value + this.SetLayout(this.layout) + this.on.valueChange.Broadcast() +} + +func (this *Slider) Value () float64 { + return this.layout.value +} + +func (this *Slider) OnValueChange (callback func ()) event.Cookie { + return this.on.valueChange.Connect(callback) +} + +func (this *Slider) handleKeyDown (key input.Key, numpad bool) { + switch key { + case input.KeyUp, input.KeyLeft: + if this.Modifiers().Alt { + this.SetValue(0) + } else { + this.SetValue(this.Value() - 0.05) + } + case input.KeyDown, input.KeyRight: + if this.Modifiers().Alt { + this.SetValue(1) + } else { + this.SetValue(this.Value() + 0.05) + } + case input.KeyHome: + this.SetValue(0) + case input.KeyEnd: + this.SetValue(1) + } +} + +func (this *Slider) handleMouseDown (button input.Button) { + pointer := this.MousePosition() + handle := this.handle.Bounds() + var above, within bool + + if pointer.In(handle) { + within = true + } else if this.layout.vertical { + above = pointer.Y < handle.Min.Y + } else { + above = pointer.X < handle.Min.X + } + + switch button { + case input.ButtonLeft: + if within { + this.dragging = true + this.dragOffset = + pointer.Sub(this.handle.Bounds().Min). + Add(this.InnerBounds().Min) + this.drag() + } else { + this.dragOffset = this.fallbackDragOffset() + this.dragging = true + this.drag() + } + case input.ButtonMiddle: + if above { + this.SetValue(0) + } else { + this.SetValue(1) + } + case input.ButtonRight: + if above { + this.SetValue(this.Value() - 0.05) + } else { + this.SetValue(this.Value() + 0.05) + } + } +} + +func (this *Slider) handleMouseUp (button input.Button) { + if button != input.ButtonLeft || !this.dragging { return } + this.dragging = false +} + +func (this *Slider) handleMouseMove () { + if !this.dragging { return } + this.drag() +} + +func (this *Slider) drag () { + pointer := this.MousePosition().Sub(this.dragOffset) + gutter := this.InnerBounds() + handle := this.handle.Bounds() + + if this.layout.vertical { + this.SetValue ( + float64(pointer.Y) / + float64(gutter.Dy() - handle.Dy())) + } else { + this.SetValue ( + float64(pointer.X) / + float64(gutter.Dx() - handle.Dx())) + } +} + +func (this *Slider) fallbackDragOffset () image.Point { + if this.layout.vertical { + return this.InnerBounds().Min. + Add(image.Pt(0, this.handle.Bounds().Dy() / 2)) + } else { + return this.InnerBounds().Min. + Add(image.Pt(this.handle.Bounds().Dx() / 2, 0)) + } +} + +type sliderLayout struct { + vertical bool + value float64 +} + +func (sliderLayout) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point { + if len(boxes) != 1 { return image.Pt(0, 0) } + return boxes[0].MinimumSize() +} + +func (this sliderLayout) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) { + if len(boxes) != 1 { return } + handle := image.Rectangle { Max: boxes[0].MinimumSize() } + gutter := hints.Bounds + + if this.vertical { + height := gutter.Dy() - handle.Dy() + offset := int(float64(height) * this.value) + gutter.Max.X = handle.Max.X + boxes[0].SetBounds ( + handle. + Add(image.Pt(0, offset)). + Add(gutter.Min)) + } else { + width := gutter.Dx() - handle.Dx() + offset := int(float64(width) * this.value) + gutter.Max.Y = handle.Max.Y + boxes[0].SetBounds ( + handle. + Add(image.Pt(offset, 0)). + Add(gutter.Min)) + } }