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