2024-06-22 13:44:37 -06:00
|
|
|
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"
|
2024-09-05 20:46:58 -06:00
|
|
|
import icolor "git.tebibyte.media/tomo/objects/internal/color"
|
2024-06-22 13:44:37 -06:00
|
|
|
|
2024-08-24 20:12:43 -06:00
|
|
|
var _ tomo.Object = new(Swatch)
|
|
|
|
|
2024-06-22 13:44:37 -06:00
|
|
|
// Swatch displays a color, allowing the user to edit it by clicking on it.
|
|
|
|
type Swatch struct {
|
2024-08-24 20:12:43 -06:00
|
|
|
box tomo.CanvasBox
|
|
|
|
value color.Color
|
2024-06-22 13:44:37 -06:00
|
|
|
editing bool
|
2024-08-24 20:12:43 -06:00
|
|
|
label string
|
2024-06-22 13:44:37 -06:00
|
|
|
on struct {
|
|
|
|
valueChange event.FuncBroadcaster
|
2024-06-27 12:09:58 -06:00
|
|
|
confirm event.FuncBroadcaster
|
2024-06-22 13:44:37 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewSwatch creates a new swatch with the given color.
|
|
|
|
func NewSwatch (value color.Color) *Swatch {
|
|
|
|
swatch := &Swatch {
|
2024-08-24 20:12:43 -06:00
|
|
|
box: tomo.NewCanvasBox(),
|
2024-06-22 13:44:37 -06:00
|
|
|
}
|
2024-08-24 20:12:43 -06:00
|
|
|
swatch.box.SetRole(tomo.R("objects", "Swatch"))
|
|
|
|
swatch.box.SetDrawer(swatch)
|
2024-06-22 13:44:37 -06:00
|
|
|
swatch.SetValue(value)
|
|
|
|
|
2024-08-24 20:12:43 -06:00
|
|
|
swatch.box.OnButtonDown(swatch.handleButtonDown)
|
|
|
|
swatch.box.OnButtonUp(swatch.handleButtonUp)
|
|
|
|
swatch.box.OnKeyDown(swatch.handleKeyDown)
|
|
|
|
swatch.box.OnKeyUp(swatch.handleKeyUp)
|
|
|
|
swatch.box.SetFocusable(true)
|
2024-06-22 13:44:37 -06:00
|
|
|
return swatch
|
|
|
|
}
|
|
|
|
|
2024-08-24 20:12:43 -06:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2024-06-27 12:01:14 -06:00
|
|
|
// Value returns the color of the swatch.
|
|
|
|
func (this *Swatch) Value () color.Color {
|
|
|
|
return this.value
|
|
|
|
}
|
|
|
|
|
2024-06-22 13:44:37 -06:00
|
|
|
// SetValue sets the color of the swatch.
|
|
|
|
func (this *Swatch) SetValue (value color.Color) {
|
|
|
|
this.value = value
|
|
|
|
if value == nil { value = color.Transparent }
|
2024-08-24 20:12:43 -06:00
|
|
|
this.box.Invalidate()
|
2024-06-22 13:44:37 -06:00
|
|
|
}
|
|
|
|
|
2024-06-27 12:01:14 -06:00
|
|
|
// 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)
|
2024-06-22 13:44:37 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// RGBA satisfies the color.Color interface
|
|
|
|
func (this *Swatch) RGBA () (r, g, b, a uint32) {
|
2024-06-26 09:20:53 -06:00
|
|
|
if this.value == nil { return }
|
2024-06-22 13:44:37 -06:00
|
|
|
return this.value.RGBA()
|
|
|
|
}
|
|
|
|
|
2024-06-27 12:09:58 -06:00
|
|
|
// OnConfirm specifies a function to be called when the user selects "OK" in the
|
2024-06-26 09:20:53 -06:00
|
|
|
// color picker.
|
2024-06-27 12:09:58 -06:00
|
|
|
func (this *Swatch) OnConfirm (callback func ()) event.Cookie {
|
|
|
|
return this.on.confirm.Connect(callback)
|
2024-06-26 09:20:53 -06:00
|
|
|
}
|
|
|
|
|
2024-06-22 13:44:37 -06:00
|
|
|
// 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
|
2024-08-24 20:12:43 -06:00
|
|
|
if parent := this.box.Window(); parent != nil {
|
2024-06-26 09:20:53 -06:00
|
|
|
window, err = parent.NewChild(image.Rectangle { })
|
2024-06-22 13:44:37 -06:00
|
|
|
} else {
|
|
|
|
window, err = tomo.NewWindow(image.Rectangle { })
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
log.Println("objects: could not create swatch modal:", err)
|
|
|
|
return
|
|
|
|
}
|
2024-06-22 16:48:54 -06:00
|
|
|
if this.label == "" {
|
|
|
|
window.SetTitle("Select Color")
|
|
|
|
} else {
|
|
|
|
window.SetTitle(this.label)
|
|
|
|
}
|
2024-06-22 13:44:37 -06:00
|
|
|
|
2024-06-26 09:20:53 -06:00
|
|
|
committed := false
|
|
|
|
|
2024-08-16 13:17:44 -06:00
|
|
|
colorPicker := NewHSVAColorPicker(this.Value())
|
2024-06-22 13:44:37 -06:00
|
|
|
colorMemory := this.value
|
2024-08-16 14:28:47 -06:00
|
|
|
hexInput := NewTextInput("")
|
2024-08-16 13:17:44 -06:00
|
|
|
hexInput.SetFocused(true)
|
2024-06-22 13:44:37 -06:00
|
|
|
cancelButton := NewButton("Cancel")
|
|
|
|
cancelButton.SetIcon(tomo.IconDialogCancel)
|
2024-08-16 13:17:44 -06:00
|
|
|
okButton := NewButton("OK")
|
2024-06-22 13:44:37 -06:00
|
|
|
okButton.SetIcon(tomo.IconDialogOkay)
|
2024-08-16 13:17:44 -06:00
|
|
|
|
|
|
|
updateHexInput := func () {
|
|
|
|
nrgba := color.NRGBAModel.Convert(colorPicker.Value()).(color.NRGBA)
|
2024-09-05 20:46:58 -06:00
|
|
|
hexInput.SetValue(icolor.FormatNRGBA(nrgba))
|
2024-08-16 13:17:44 -06:00
|
|
|
}
|
|
|
|
updateHexInput()
|
|
|
|
commit := func () {
|
2024-06-26 09:20:53 -06:00
|
|
|
committed = true
|
2024-06-22 13:44:37 -06:00
|
|
|
window.Close()
|
2024-08-16 13:17:44 -06:00
|
|
|
}
|
|
|
|
colorPicker.OnValueChange(func () {
|
|
|
|
this.userSetValue(colorPicker.Value())
|
|
|
|
updateHexInput()
|
|
|
|
})
|
|
|
|
hexInput.OnConfirm(commit)
|
|
|
|
hexInput.OnValueChange(func () {
|
2024-09-05 20:46:58 -06:00
|
|
|
nrgba := icolor.ParseNRGBA(hexInput.Value())
|
2024-08-16 13:17:44 -06:00
|
|
|
this.userSetValue(nrgba)
|
|
|
|
colorPicker.SetValue(nrgba)
|
|
|
|
})
|
|
|
|
cancelButton.OnClick(func () {
|
|
|
|
window.Close()
|
2024-06-22 13:44:37 -06:00
|
|
|
})
|
2024-08-16 13:17:44 -06:00
|
|
|
okButton.OnClick(commit)
|
2024-06-22 13:44:37 -06:00
|
|
|
|
|
|
|
controlRow := NewInnerContainer (
|
|
|
|
layouts.ContractHorizontal,
|
|
|
|
cancelButton,
|
|
|
|
okButton)
|
2024-08-24 13:02:17 -06:00
|
|
|
controlRow.SetAlign(tomo.AlignEnd, tomo.AlignMiddle)
|
2024-06-22 13:44:37 -06:00
|
|
|
window.SetRoot(NewOuterContainer (
|
|
|
|
layouts.Column { true, false },
|
|
|
|
colorPicker,
|
2024-06-26 09:20:53 -06:00
|
|
|
NewInnerContainer(layouts.Row { false, true },
|
|
|
|
NewLabel("Hex"),
|
|
|
|
hexInput),
|
2024-06-22 13:44:37 -06:00
|
|
|
controlRow))
|
|
|
|
window.OnClose(func () {
|
2024-06-26 09:20:53 -06:00
|
|
|
if committed {
|
2024-06-27 12:09:58 -06:00
|
|
|
this.on.confirm.Broadcast()
|
2024-06-26 09:20:53 -06:00
|
|
|
} else {
|
|
|
|
this.userSetValue(colorMemory)
|
|
|
|
}
|
2024-06-22 13:44:37 -06:00
|
|
|
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)
|
2024-08-24 20:12:43 -06:00
|
|
|
pen.Path(this.box.Bounds().Min, this.box.Bounds().Max)
|
2024-06-22 13:44:37 -06:00
|
|
|
|
|
|
|
// color
|
|
|
|
if this.value != nil {
|
|
|
|
pen.StrokeWeight(0)
|
|
|
|
pen.Fill(this.value)
|
2024-08-24 20:12:43 -06:00
|
|
|
pen.Rectangle(this.box.Bounds())
|
2024-06-22 13:44:37 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *Swatch) userSetValue (value color.Color) {
|
|
|
|
this.SetValue(value)
|
|
|
|
this.on.valueChange.Broadcast()
|
|
|
|
}
|
|
|
|
|
2024-07-25 10:58:38 -06:00
|
|
|
func (this *Swatch) handleKeyDown (key input.Key, numberPad bool) bool {
|
2024-08-16 14:15:52 -06:00
|
|
|
if !isClickingKey(key) { return false }
|
2024-08-16 13:31:48 -06:00
|
|
|
this.Choose()
|
2024-07-25 10:58:38 -06:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *Swatch) handleKeyUp (key input.Key, numberPad bool) bool {
|
2024-08-16 14:15:52 -06:00
|
|
|
if !isClickingKey(key) { return false }
|
2024-07-25 10:58:38 -06:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (this *Swatch) handleButtonDown (button input.Button) bool {
|
2024-08-16 14:15:52 -06:00
|
|
|
if !isClickingButton(button) { return false }
|
2024-07-25 10:58:38 -06:00
|
|
|
return true
|
2024-06-22 13:44:37 -06:00
|
|
|
}
|
|
|
|
|
2024-07-25 10:58:38 -06:00
|
|
|
func (this *Swatch) handleButtonUp (button input.Button) bool {
|
2024-08-16 14:15:52 -06:00
|
|
|
if !isClickingButton(button) { return false }
|
2024-08-24 20:12:43 -06:00
|
|
|
if this.box.Window().MousePosition().In(this.box.Bounds()) {
|
2024-06-22 13:44:37 -06:00
|
|
|
this.Choose()
|
|
|
|
}
|
2024-07-25 10:58:38 -06:00
|
|
|
return true
|
2024-06-22 13:44:37 -06:00
|
|
|
}
|