Sasha Koshka
c7cd944ae2
The handle width can be specified by themes with padding values. This also allows for far more granularity of the handle width adjustment as it can depend on context.
241 lines
6.0 KiB
Go
241 lines
6.0 KiB
Go
package elements
|
|
|
|
import "image"
|
|
import "git.tebibyte.media/sashakoshka/tomo"
|
|
import "git.tebibyte.media/sashakoshka/tomo/input"
|
|
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
|
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
|
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
|
|
|
// Slider is a slider control with a floating point value between zero and one.
|
|
type Slider struct {
|
|
*core.Core
|
|
*core.FocusableCore
|
|
core core.CoreControl
|
|
focusableControl core.FocusableCoreControl
|
|
|
|
value float64
|
|
vertical bool
|
|
dragging bool
|
|
dragOffset int
|
|
track image.Rectangle
|
|
bar image.Rectangle
|
|
|
|
config config.Wrapped
|
|
theme theme.Wrapped
|
|
|
|
onSlide func ()
|
|
onRelease func ()
|
|
}
|
|
|
|
// NewSlider creates a new slider with the specified value. If vertical is set
|
|
// to true,
|
|
func NewSlider (value float64, vertical bool) (element *Slider) {
|
|
element = &Slider {
|
|
value: value,
|
|
vertical: vertical,
|
|
}
|
|
if vertical {
|
|
element.theme.Case = tomo.C("tomo", "sliderVertical")
|
|
} else {
|
|
element.theme.Case = tomo.C("tomo", "sliderHorizontal")
|
|
}
|
|
element.Core, element.core = core.NewCore(element, element.draw)
|
|
element.FocusableCore,
|
|
element.focusableControl = core.NewFocusableCore(element.core, element.redo)
|
|
element.updateMinimumSize()
|
|
return
|
|
}
|
|
|
|
func (element *Slider) HandleMouseDown (x, y int, button input.Button) {
|
|
if !element.Enabled() { return }
|
|
element.Focus()
|
|
if button == input.ButtonLeft {
|
|
element.dragging = true
|
|
element.value = element.valueFor(x, y)
|
|
if element.onSlide != nil {
|
|
element.onSlide()
|
|
}
|
|
element.redo()
|
|
}
|
|
}
|
|
|
|
func (element *Slider) HandleMouseUp (x, y int, button input.Button) {
|
|
if button != input.ButtonLeft || !element.dragging { return }
|
|
element.dragging = false
|
|
if element.onRelease != nil {
|
|
element.onRelease()
|
|
}
|
|
element.redo()
|
|
}
|
|
|
|
func (element *Slider) HandleMotion (x, y int) {
|
|
if element.dragging {
|
|
element.dragging = true
|
|
element.value = element.valueFor(x, y)
|
|
if element.onSlide != nil {
|
|
element.onSlide()
|
|
}
|
|
element.redo()
|
|
}
|
|
}
|
|
|
|
func (element *Slider) HandleScroll (x, y int, deltaX, deltaY float64) { }
|
|
|
|
func (element *Slider) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
|
|
switch key {
|
|
case input.KeyUp:
|
|
element.changeValue(0.1)
|
|
case input.KeyDown:
|
|
element.changeValue(-0.1)
|
|
case input.KeyRight:
|
|
if element.vertical {
|
|
element.changeValue(-0.1)
|
|
} else {
|
|
element.changeValue(0.1)
|
|
}
|
|
case input.KeyLeft:
|
|
if element.vertical {
|
|
element.changeValue(0.1)
|
|
} else {
|
|
element.changeValue(-0.1)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (element *Slider) HandleKeyUp (key input.Key, modifiers input.Modifiers) { }
|
|
|
|
// Value returns the slider's value.
|
|
func (element *Slider) Value () (value float64) {
|
|
return element.value
|
|
}
|
|
|
|
// SetEnabled sets whether or not the slider can be interacted with.
|
|
func (element *Slider) SetEnabled (enabled bool) {
|
|
element.focusableControl.SetEnabled(enabled)
|
|
}
|
|
|
|
// SetValue sets the slider's value.
|
|
func (element *Slider) SetValue (value float64) {
|
|
if value < 0 { value = 0 }
|
|
if value > 1 { value = 1 }
|
|
|
|
if element.value == value { return }
|
|
|
|
element.value = value
|
|
if element.onRelease != nil {
|
|
element.onRelease()
|
|
}
|
|
element.redo()
|
|
}
|
|
|
|
// OnSlide sets a function to be called every time the slider handle changes
|
|
// position while being dragged.
|
|
func (element *Slider) OnSlide (callback func ()) {
|
|
element.onSlide = callback
|
|
}
|
|
|
|
// OnRelease sets a function to be called when the handle stops being dragged.
|
|
func (element *Slider) OnRelease (callback func ()) {
|
|
element.onRelease = callback
|
|
}
|
|
|
|
// SetTheme sets the element's theme.
|
|
func (element *Slider) SetTheme (new tomo.Theme) {
|
|
if new == element.theme.Theme { return }
|
|
element.theme.Theme = new
|
|
element.redo()
|
|
}
|
|
|
|
// SetConfig sets the element's configuration.
|
|
func (element *Slider) SetConfig (new tomo.Config) {
|
|
if new == element.config.Config { return }
|
|
element.config.Config = new
|
|
element.updateMinimumSize()
|
|
element.redo()
|
|
}
|
|
|
|
func (element *Slider) changeValue (delta float64) {
|
|
element.value += delta
|
|
if element.value < 0 {
|
|
element.value = 0
|
|
}
|
|
if element.value > 1 {
|
|
element.value = 1
|
|
}
|
|
if element.onRelease != nil {
|
|
element.onRelease()
|
|
}
|
|
element.redo()
|
|
}
|
|
|
|
func (element *Slider) valueFor (x, y int) (value float64) {
|
|
if element.vertical {
|
|
value =
|
|
float64(y - element.track.Min.Y - element.bar.Dy() / 2) /
|
|
float64(element.track.Dy() - element.bar.Dy())
|
|
value = 1 - value
|
|
} else {
|
|
value =
|
|
float64(x - element.track.Min.X - element.bar.Dx() / 2) /
|
|
float64(element.track.Dx() - element.bar.Dx())
|
|
}
|
|
|
|
if value < 0 { value = 0 }
|
|
if value > 1 { value = 1 }
|
|
return
|
|
}
|
|
|
|
func (element *Slider) updateMinimumSize () {
|
|
gutterPadding := element.theme.Padding(tomo.PatternGutter)
|
|
handlePadding := element.theme.Padding(tomo.PatternHandle)
|
|
if element.vertical {
|
|
element.core.SetMinimumSize (
|
|
gutterPadding.Horizontal() + handlePadding.Horizontal(),
|
|
gutterPadding.Vertical() + handlePadding.Vertical() * 2)
|
|
} else {
|
|
element.core.SetMinimumSize (
|
|
gutterPadding.Horizontal() + handlePadding.Horizontal() * 2,
|
|
gutterPadding.Vertical() + handlePadding.Vertical())
|
|
}
|
|
}
|
|
|
|
func (element *Slider) redo () {
|
|
if element.core.HasImage () {
|
|
element.draw()
|
|
element.core.DamageAll()
|
|
}
|
|
}
|
|
|
|
func (element *Slider) draw () {
|
|
bounds := element.Bounds()
|
|
element.track = element.theme.Padding(tomo.PatternGutter).Apply(bounds)
|
|
if element.vertical {
|
|
barSize := element.track.Dx()
|
|
element.bar = image.Rect(0, 0, barSize, barSize).Add(bounds.Min)
|
|
barOffset :=
|
|
float64(element.track.Dy() - barSize) *
|
|
(1 - element.value)
|
|
element.bar = element.bar.Add(image.Pt(0, int(barOffset)))
|
|
} else {
|
|
barSize := element.track.Dy()
|
|
element.bar = image.Rect(0, 0, barSize, barSize).Add(bounds.Min)
|
|
barOffset :=
|
|
float64(element.track.Dx() - barSize) *
|
|
element.value
|
|
element.bar = element.bar.Add(image.Pt(int(barOffset), 0))
|
|
}
|
|
|
|
state := tomo.State {
|
|
Focused: element.Focused(),
|
|
Disabled: !element.Enabled(),
|
|
Pressed: element.dragging,
|
|
}
|
|
element.theme.Pattern(tomo.PatternGutter, state).Draw (
|
|
element.core,
|
|
bounds)
|
|
element.theme.Pattern(tomo.PatternHandle, state).Draw (
|
|
element.core,
|
|
element.bar)
|
|
}
|