objects/swatch.go

210 lines
5.3 KiB
Go
Raw Normal View History

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"
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
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)
}
// 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
}
// 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()
}
// OnConfirm specifies a function to be called when the user selects "OK" in the
2024-06-26 09:20:53 -06:00
// color picker.
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
}
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)
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 () {
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)
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 {
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 {
if !isClickingKey(key) { return false }
this.Choose()
2024-07-25 10:58:38 -06:00
return true
}
func (this *Swatch) handleKeyUp (key input.Key, numberPad bool) bool {
if !isClickingKey(key) { return false }
2024-07-25 10:58:38 -06:00
return true
}
func (this *Swatch) handleButtonDown (button input.Button) bool {
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 {
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
}