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) }