package objects import "log" import "image" 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 icolor "git.tebibyte.media/tomo/objects/internal/color" var _ tomo.Object = new(Swatch) // Swatch displays a color, allowing the user to edit it by clicking on it. type Swatch struct { box tomo.CanvasBox value color.Color editing bool label string on struct { valueChange event.FuncBroadcaster confirm event.FuncBroadcaster } } // NewSwatch creates a new swatch with the given color. func NewSwatch (value color.Color) *Swatch { swatch := &Swatch { box: tomo.NewCanvasBox(), } swatch.box.SetRole(tomo.R("objects", "Swatch")) swatch.box.SetDrawer(swatch) swatch.SetValue(value) swatch.box.OnButtonDown(swatch.handleButtonDown) swatch.box.OnButtonUp(swatch.handleButtonUp) swatch.box.OnKeyDown(swatch.handleKeyDown) swatch.box.OnKeyUp(swatch.handleKeyUp) swatch.box.SetFocusable(true) return swatch } // GetBox returns the underlying box. func (this *Swatch) GetBox () tomo.Box { return this.box } // SetFocused sets whether or not this swatch has keyboard focus. If set to // true, this method will steal focus away from whichever object currently has // focus. func (this *Swatch) SetFocused (focused bool) { this.box.SetFocused(focused) } // Value returns the color of the swatch. func (this *Swatch) Value () color.Color { return this.value } // SetValue sets the color of the swatch. func (this *Swatch) SetValue (value color.Color) { this.value = value if value == nil { value = color.Transparent } this.box.Invalidate() } // OnValueChange specifies a function to be called when the swatch's color // is changed by the user. func (this *Swatch) OnValueChange (callback func ()) event.Cookie { return this.on.valueChange.Connect(callback) } // RGBA satisfies the color.Color interface func (this *Swatch) RGBA () (r, g, b, a uint32) { if this.value == nil { return } return this.value.RGBA() } // OnConfirm specifies a function to be called when the user selects "OK" in the // color picker. func (this *Swatch) OnConfirm (callback func ()) event.Cookie { return this.on.confirm.Connect(callback) } // Choose creates a modal that allows the user to edit the color of the swatch. func (this *Swatch) Choose () { if this.editing { return } var err error var window tomo.Window if parent := this.box.Window(); parent != nil { window, err = parent.NewChild(image.Rectangle { }) } else { window, err = tomo.NewWindow(image.Rectangle { }) } if err != nil { log.Println("objects: could not create swatch modal:", err) return } if this.label == "" { window.SetTitle("Select Color") } else { window.SetTitle(this.label) } committed := false colorPicker := NewHSVAColorPicker(this.Value()) colorMemory := this.value hexInput := NewTextInput("") hexInput.SetFocused(true) cancelButton := NewButton("Cancel") cancelButton.SetIcon(tomo.IconDialogCancel) okButton := NewButton("OK") okButton.SetIcon(tomo.IconDialogOkay) updateHexInput := func () { nrgba := color.NRGBAModel.Convert(colorPicker.Value()).(color.NRGBA) hexInput.SetValue(icolor.FormatNRGBA(nrgba)) } updateHexInput() commit := func () { committed = true window.Close() } colorPicker.OnValueChange(func () { this.userSetValue(colorPicker.Value()) updateHexInput() }) hexInput.OnConfirm(commit) hexInput.OnValueChange(func () { nrgba := icolor.ParseNRGBA(hexInput.Value()) this.userSetValue(nrgba) colorPicker.SetValue(nrgba) }) cancelButton.OnClick(func () { window.Close() }) okButton.OnClick(commit) controlRow := NewInnerContainer ( layouts.ContractHorizontal, cancelButton, okButton) controlRow.SetAlign(tomo.AlignEnd, tomo.AlignMiddle) window.SetRoot(NewOuterContainer ( layouts.Column { true, false }, colorPicker, NewInnerContainer(layouts.Row { false, true }, NewLabel("Hex"), hexInput), controlRow)) window.OnClose(func () { if committed { this.on.confirm.Broadcast() } else { this.userSetValue(colorMemory) } this.editing = false }) this.editing = true window.SetVisible(true) } func (this *Swatch) Draw (can canvas.Canvas) { pen := can.Pen() // transparency slash pen.Stroke(color.RGBA { R: 255, A: 255 }) pen.StrokeWeight(1) pen.Path(this.box.Bounds().Min, this.box.Bounds().Max) // color if this.value != nil { pen.StrokeWeight(0) pen.Fill(this.value) pen.Rectangle(this.box.Bounds()) } } func (this *Swatch) userSetValue (value color.Color) { this.SetValue(value) this.on.valueChange.Broadcast() } func (this *Swatch) handleKeyDown (key input.Key, numberPad bool) bool { if !isClickingKey(key) { return false } this.Choose() return true } func (this *Swatch) handleKeyUp (key input.Key, numberPad bool) bool { if !isClickingKey(key) { return false } return true } func (this *Swatch) handleButtonDown (button input.Button) bool { if !isClickingButton(button) { return false } return true } func (this *Swatch) handleButtonUp (button input.Button) bool { if !isClickingButton(button) { return false } if this.box.Window().MousePosition().In(this.box.Bounds()) { this.Choose() } return true }