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 layout sliderLayout dragging bool dragOffset image.Point on struct { valueChange event.FuncBroadcaster } } type SliderHandle struct { tomo.Box } func newSlider (orient string, value float64) *Slider { this := &Slider { ContainerBox: tomo.NewContainerBox(), handle: &SliderHandle { Box: tomo.NewBox(), }, 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 (value float64) *Slider { return newSlider("vertical", value) } 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) handle.Max.X = gutter.Dx() boxes[0].SetBounds ( handle. Add(image.Pt(0, offset)). Add(gutter.Min)) } else { width := gutter.Dx() - handle.Dx() offset := int(float64(width) * this.value) handle.Max.Y = gutter.Dy() boxes[0].SetBounds ( handle. Add(image.Pt(offset, 0)). Add(gutter.Min)) } }