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" // Swatch displays a color, allowing the user to edit it by clicking on it. type Swatch struct { tomo.CanvasBox value color.Color editing bool label string on struct { valueChange event.FuncBroadcaster enter event.FuncBroadcaster } } // NewSwatch creates a new swatch with the given color. func NewSwatch (value color.Color) *Swatch { swatch := &Swatch { CanvasBox: tomo.NewCanvasBox(), } swatch.SetRole(tomo.R("objects", "Swatch", "")) swatch.SetDrawer(swatch) swatch.SetValue(value) swatch.OnMouseUp(swatch.handleMouseUp) swatch.OnKeyUp(swatch.handleKeyUp) swatch.SetFocusable(true) return swatch } // 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.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() } // OnEnter specifies a function to be called when the user selects "OK" in the // color picker. func (this *Swatch) OnEnter (callback func ()) event.Cookie { return this.on.enter.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.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 := NewColorPicker(this.Value()) colorPicker.OnValueChange(func () { this.userSetValue(colorPicker.Value()) }) hexInput := NewTextInput("TODO") colorMemory := this.value cancelButton := NewButton("Cancel") cancelButton.SetIcon(tomo.IconDialogCancel) cancelButton.OnClick(func () { window.Close() }) okButton := NewButton("OK") okButton.SetFocused(true) okButton.SetIcon(tomo.IconDialogOkay) okButton.OnClick(func () { committed = true window.Close() }) 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.enter.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.Bounds().Min, this.Bounds().Max) // color if this.value != nil { pen.StrokeWeight(0) pen.Fill(this.value) pen.Rectangle(this.Bounds()) } } func (this *Swatch) userSetValue (value color.Color) { this.SetValue(value) this.on.valueChange.Broadcast() } func (this *Swatch) handleKeyUp (key input.Key, numberPad bool) { if key != input.KeyEnter && key != input.Key(' ') { return } this.Choose() } func (this *Swatch) handleMouseUp (button input.Button) { if button != input.ButtonLeft { return } if this.MousePosition().In(this.Bounds()) { this.Choose() } }