objects/swatch.go

210 lines
5.3 KiB
Go

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
}