157 lines
4.2 KiB
Go
157 lines
4.2 KiB
Go
package objects
|
|
|
|
import "image/color"
|
|
import "git.tebibyte.media/tomo/tomo"
|
|
import "git.tebibyte.media/tomo/tomo/input"
|
|
import "git.tebibyte.media/tomo/tomo/event"
|
|
import "git.tebibyte.media/tomo/tomo/canvas"
|
|
import "git.tebibyte.media/tomo/objects/layouts"
|
|
import "git.tebibyte.media/tomo/objects/internal"
|
|
|
|
// ColorPicker allows the user to pick a color by controlling its HSBA
|
|
// parameters.
|
|
type ColorPicker struct {
|
|
tomo.ContainerBox
|
|
value internal.HSVA
|
|
|
|
pickerMap *colorPickerMap
|
|
hueSlider *Slider
|
|
alphaSlider *Slider
|
|
|
|
on struct {
|
|
valueChange event.FuncBroadcaster
|
|
}
|
|
}
|
|
|
|
// NewColorPicker creates a new color picker with the specified color.
|
|
func NewColorPicker (value color.Color) *ColorPicker {
|
|
picker := &ColorPicker {
|
|
ContainerBox: tomo.NewContainerBox(),
|
|
}
|
|
picker.SetRole(tomo.R("objects", "ColorPicker", ""))
|
|
picker.SetLayout(layouts.Row { true, false, false })
|
|
picker.pickerMap = newColorPickerMap(picker)
|
|
picker.Add(picker.pickerMap)
|
|
|
|
picker.hueSlider = NewVerticalSlider(0.0)
|
|
picker.Add(picker.hueSlider)
|
|
picker.hueSlider.OnSlide(func () {
|
|
picker.value.H = picker.hueSlider.Value()
|
|
picker.on.valueChange.Broadcast()
|
|
picker.pickerMap.Invalidate()
|
|
})
|
|
|
|
picker.alphaSlider = NewVerticalSlider(0.0)
|
|
picker.Add(picker.alphaSlider)
|
|
picker.alphaSlider.OnSlide(func () {
|
|
picker.value.A = uint8(picker.alphaSlider.Value() * 255)
|
|
picker.on.valueChange.Broadcast()
|
|
picker.pickerMap.Invalidate()
|
|
})
|
|
|
|
if value == nil { value = color.Transparent }
|
|
picker.SetValue(value)
|
|
return picker
|
|
}
|
|
|
|
// SetValue sets the color of the picker.
|
|
func (this *ColorPicker) SetValue (value color.Color) {
|
|
if value == nil { value = color.Transparent }
|
|
this.value = internal.RGBAToHSVA(value.RGBA())
|
|
this.hueSlider.SetValue(this.value.H)
|
|
this.alphaSlider.SetValue(float64(this.value.A) / 255)
|
|
}
|
|
|
|
// Value returns the color of the picker.
|
|
func (this *ColorPicker) Value () color.Color {
|
|
return this.value
|
|
}
|
|
|
|
// RGBA satisfies the color.Color interface
|
|
func (this *ColorPicker) RGBA () (r, g, b, a uint32) {
|
|
return this.value.RGBA()
|
|
}
|
|
|
|
// OnValueChange specifies a function to be called when the swatch's color
|
|
// changes.
|
|
func (this *ColorPicker) OnValueChange (callback func ()) event.Cookie {
|
|
return this.on.valueChange.Connect(callback)
|
|
}
|
|
|
|
type colorPickerMap struct {
|
|
tomo.CanvasBox
|
|
dragging bool
|
|
parent *ColorPicker
|
|
}
|
|
|
|
func newColorPickerMap (parent *ColorPicker) *colorPickerMap {
|
|
picker := &colorPickerMap {
|
|
CanvasBox: tomo.NewCanvasBox(),
|
|
parent: parent,
|
|
}
|
|
picker.SetDrawer(picker)
|
|
picker.SetRole(tomo.R("objects", "ColorPickerMap", ""))
|
|
picker.OnMouseUp(picker.handleMouseUp)
|
|
picker.OnMouseDown(picker.handleMouseDown)
|
|
picker.OnMouseMove(picker.handleMouseMove)
|
|
return picker
|
|
}
|
|
|
|
func (this *colorPickerMap) handleMouseDown (button input.Button) {
|
|
if button != input.ButtonLeft { return }
|
|
this.dragging = true
|
|
this.drag()
|
|
}
|
|
|
|
func (this *colorPickerMap) handleMouseUp (button input.Button) {
|
|
if button != input.ButtonLeft || !this.dragging { return }
|
|
this.dragging = false
|
|
}
|
|
|
|
func (this *colorPickerMap) handleMouseMove () {
|
|
if !this.dragging { return }
|
|
this.drag()
|
|
}
|
|
|
|
func (this *colorPickerMap) drag () {
|
|
pointer := this.MousePosition()
|
|
bounds := this.InnerBounds()
|
|
this.parent.value.S = float64(pointer.X - bounds.Min.X) / float64(bounds.Dx())
|
|
this.parent.value.V = 1 - float64(pointer.Y - bounds.Min.Y) / float64(bounds.Dy())
|
|
this.parent.value = this.parent.value.Canon()
|
|
this.parent.on.valueChange.Broadcast()
|
|
this.Invalidate()
|
|
}
|
|
|
|
func (this *colorPickerMap) Draw (can canvas.Canvas) {
|
|
bounds := can.Bounds()
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
|
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
|
xx := x - bounds.Min.X
|
|
yy := y - bounds.Min.Y
|
|
|
|
pixel := internal.HSVA {
|
|
H: this.parent.value.H,
|
|
S: float64(xx) / float64(bounds.Dx()),
|
|
V: 1 - float64(yy) / float64(bounds.Dy()),
|
|
A: 255,
|
|
}
|
|
|
|
sPos := int( this.parent.value.S * float64(bounds.Dx()))
|
|
vPos := int((1 - this.parent.value.V) * float64(bounds.Dy()))
|
|
sDist := sPos - xx
|
|
vDist := vPos - yy
|
|
crosshair :=
|
|
(sDist == 0 || vDist == 0) &&
|
|
-8 < sDist && sDist < 8 &&
|
|
-8 < vDist && vDist < 8
|
|
if crosshair {
|
|
pixel.S = 1 - pixel.S
|
|
pixel.V = 1 - pixel.V
|
|
}
|
|
|
|
can.Set(x, y, pixel)
|
|
}}
|
|
}
|
|
|