Compare commits
	
		
			8 Commits
		
	
	
		
			1efb946953
			...
			d0ee6c432c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| d0ee6c432c | |||
| b9f980e7fd | |||
| b7d1a0abdd | |||
| a38cee8437 | |||
| 48bfa05452 | |||
| e8a3a376ea | |||
| ae1e62c1f2 | |||
| 0b7e5392f4 | 
@ -47,8 +47,7 @@ func NewCalendar (tm time.Time) *Calendar {
 | 
			
		||||
	calendar.grid = tomo.NewContainerBox()
 | 
			
		||||
	calendar.grid.SetRole(tomo.R("objects", "CalendarGrid", ""))
 | 
			
		||||
	calendar.grid.SetLayout(layouts.NewGrid (
 | 
			
		||||
		[]bool { true, true, true, true, true, true, true },
 | 
			
		||||
		[]bool { }))
 | 
			
		||||
		true, true, true, true, true, true, true)())
 | 
			
		||||
	calendar.Add(NewInnerContainer (
 | 
			
		||||
		layouts.Row { false, true, false },
 | 
			
		||||
		prevButton, calendar.monthLabel, nextButton))
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										156
									
								
								colorpicker.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								colorpicker.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,156 @@
 | 
			
		||||
package objects
 | 
			
		||||
 | 
			
		||||
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 "git.tebibyte.media/tomo/objects/internal"
 | 
			
		||||
 | 
			
		||||
// ColorPicker allows the user to pick a color by controlling its HSBA
 | 
			
		||||
// parameters.
 | 
			
		||||
type ColorPicker struct {
 | 
			
		||||
	tomo.ContainerBox
 | 
			
		||||
	value internal.HSVA
 | 
			
		||||
	
 | 
			
		||||
	pickerMap   *colorPickerMap
 | 
			
		||||
	hueSlider   *Slider
 | 
			
		||||
	alphaSlider *Slider
 | 
			
		||||
	
 | 
			
		||||
	on struct {
 | 
			
		||||
		valueChange event.FuncBroadcaster
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewColorPicker creates a new color picker with the specified color.
 | 
			
		||||
func NewColorPicker (value color.Color) *ColorPicker {
 | 
			
		||||
	picker := &ColorPicker {
 | 
			
		||||
		ContainerBox: tomo.NewContainerBox(),
 | 
			
		||||
	}
 | 
			
		||||
	picker.SetRole(tomo.R("objects", "ColorPicker", ""))
 | 
			
		||||
	picker.SetLayout(layouts.Row { true, false, false })
 | 
			
		||||
	picker.pickerMap = newColorPickerMap(picker)
 | 
			
		||||
	picker.Add(picker.pickerMap)
 | 
			
		||||
	
 | 
			
		||||
	picker.hueSlider = NewVerticalSlider(0.0)
 | 
			
		||||
	picker.Add(picker.hueSlider)
 | 
			
		||||
	picker.hueSlider.OnSlide(func () {
 | 
			
		||||
		picker.value.H = picker.hueSlider.Value()
 | 
			
		||||
		picker.on.valueChange.Broadcast()
 | 
			
		||||
		picker.pickerMap.Invalidate()
 | 
			
		||||
	})
 | 
			
		||||
	
 | 
			
		||||
	picker.alphaSlider = NewVerticalSlider(0.0)
 | 
			
		||||
	picker.Add(picker.alphaSlider)
 | 
			
		||||
	picker.alphaSlider.OnSlide(func () {
 | 
			
		||||
		picker.value.A = uint8(picker.alphaSlider.Value() * 255)
 | 
			
		||||
		picker.on.valueChange.Broadcast()
 | 
			
		||||
		picker.pickerMap.Invalidate()
 | 
			
		||||
	})
 | 
			
		||||
	
 | 
			
		||||
	if value == nil { value = color.Transparent }
 | 
			
		||||
	picker.SetValue(value)
 | 
			
		||||
	return picker
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetValue sets the color of the picker.
 | 
			
		||||
func (this *ColorPicker) SetValue (value color.Color) {
 | 
			
		||||
	if value == nil { value = color.Transparent }
 | 
			
		||||
	this.value = internal.RGBAToHSVA(value.RGBA())
 | 
			
		||||
	this.hueSlider.SetValue(this.value.H)
 | 
			
		||||
	this.alphaSlider.SetValue(float64(this.value.A) / 255)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Value returns the color of the picker.
 | 
			
		||||
func (this *ColorPicker) Value () color.Color {
 | 
			
		||||
	return this.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RGBA satisfies the color.Color interface
 | 
			
		||||
func (this *ColorPicker) RGBA () (r, g, b, a uint32) {
 | 
			
		||||
	return this.value.RGBA()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnValueChange specifies a function to be called when the swatch's color
 | 
			
		||||
// changes.
 | 
			
		||||
func (this *ColorPicker) OnValueChange (callback func ()) event.Cookie {
 | 
			
		||||
	return this.on.valueChange.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type colorPickerMap struct {
 | 
			
		||||
	tomo.CanvasBox
 | 
			
		||||
	dragging bool
 | 
			
		||||
	parent *ColorPicker
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newColorPickerMap (parent *ColorPicker) *colorPickerMap {
 | 
			
		||||
	picker := &colorPickerMap {
 | 
			
		||||
		CanvasBox: tomo.NewCanvasBox(),
 | 
			
		||||
		parent: parent,
 | 
			
		||||
	}
 | 
			
		||||
	picker.SetDrawer(picker)
 | 
			
		||||
	picker.SetRole(tomo.R("objects", "ColorPickerMap", ""))
 | 
			
		||||
	picker.OnMouseUp(picker.handleMouseUp)
 | 
			
		||||
	picker.OnMouseDown(picker.handleMouseDown)
 | 
			
		||||
	picker.OnMouseMove(picker.handleMouseMove)
 | 
			
		||||
	return picker
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *colorPickerMap) handleMouseDown (button input.Button) {
 | 
			
		||||
	if button != input.ButtonLeft { return }
 | 
			
		||||
	this.dragging = true
 | 
			
		||||
	this.drag()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *colorPickerMap) handleMouseUp (button input.Button) {
 | 
			
		||||
	if button != input.ButtonLeft || !this.dragging { return }
 | 
			
		||||
	this.dragging = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *colorPickerMap) handleMouseMove () {
 | 
			
		||||
	if !this.dragging { return }
 | 
			
		||||
	this.drag()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *colorPickerMap) drag () {
 | 
			
		||||
	pointer := this.MousePosition()
 | 
			
		||||
	bounds  := this.InnerBounds()
 | 
			
		||||
	this.parent.value.S =     float64(pointer.X - bounds.Min.X) / float64(bounds.Dx())
 | 
			
		||||
	this.parent.value.V = 1 - float64(pointer.Y - bounds.Min.Y) / float64(bounds.Dy())
 | 
			
		||||
	this.parent.value = this.parent.value.Canon()
 | 
			
		||||
	this.parent.on.valueChange.Broadcast()
 | 
			
		||||
	this.Invalidate()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *colorPickerMap) Draw (can canvas.Canvas) {
 | 
			
		||||
	bounds := can.Bounds()
 | 
			
		||||
	for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
 | 
			
		||||
	for x := bounds.Min.X; x < bounds.Max.X; x ++ {
 | 
			
		||||
		xx := x - bounds.Min.X
 | 
			
		||||
		yy := y - bounds.Min.Y
 | 
			
		||||
		
 | 
			
		||||
		pixel := internal.HSVA {
 | 
			
		||||
			H: this.parent.value.H,
 | 
			
		||||
			S:     float64(xx) / float64(bounds.Dx()),
 | 
			
		||||
			V: 1 - float64(yy) / float64(bounds.Dy()),
 | 
			
		||||
			A: 255,
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		sPos := int(     this.parent.value.S  * float64(bounds.Dx()))
 | 
			
		||||
		vPos := int((1 - this.parent.value.V) * float64(bounds.Dy()))
 | 
			
		||||
		sDist := sPos - xx
 | 
			
		||||
		vDist := vPos - yy
 | 
			
		||||
		crosshair :=
 | 
			
		||||
			(sDist == 0 || vDist == 0) &&
 | 
			
		||||
			-8 < sDist && sDist < 8 &&
 | 
			
		||||
			-8 < vDist && vDist < 8
 | 
			
		||||
		if crosshair {
 | 
			
		||||
			pixel.S = 1 - pixel.S
 | 
			
		||||
			pixel.V = 1 - pixel.V
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		can.Set(x, y, pixel)
 | 
			
		||||
	}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -66,7 +66,7 @@ func NewDialog (kind DialogKind, parent tomo.Window, title, message string, opti
 | 
			
		||||
	dialog.controlRow.SetAlign(tomo.AlignEnd, tomo.AlignEnd)
 | 
			
		||||
 | 
			
		||||
	dialog.SetRoot(NewOuterContainer (
 | 
			
		||||
		layouts.NewGrid([]bool { true }, []bool { true, false }),
 | 
			
		||||
		layouts.Column { true, false },
 | 
			
		||||
		NewInnerContainer(layouts.ContractHorizontal, icon, messageText),
 | 
			
		||||
		dialog.controlRow))
 | 
			
		||||
	return dialog, nil
 | 
			
		||||
 | 
			
		||||
@ -62,8 +62,6 @@ func RGBAToHSVA (r, g, b, a uint32) HSVA {
 | 
			
		||||
	// Adapted from:
 | 
			
		||||
	// https://www.cs.rit.edu/~ncs/color/t_convert.html
 | 
			
		||||
	
 | 
			
		||||
	// FIXME: this does not always work!
 | 
			
		||||
 | 
			
		||||
	component := func (x uint32) float64 {
 | 
			
		||||
		return clamp01(float64(x) / 0xFFFF)
 | 
			
		||||
	}
 | 
			
		||||
@ -86,7 +84,7 @@ func RGBAToHSVA (r, g, b, a uint32) HSVA {
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	delta := maxComponent - minComponent
 | 
			
		||||
	if maxComponent == 0 {
 | 
			
		||||
	if delta == 0 {
 | 
			
		||||
		// hsva.S is undefined, so hue doesn't matter
 | 
			
		||||
		return hsva
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ func NewLabelCheckbox (value bool, text string) *LabelCheckbox {
 | 
			
		||||
	box.label.SetAlign(tomo.AlignStart, tomo.AlignMiddle)
 | 
			
		||||
	box.Add(box.checkbox)
 | 
			
		||||
	box.Add(box.label)
 | 
			
		||||
	box.SetLayout(layouts.NewGrid([]bool { false, true }, []bool { false }))
 | 
			
		||||
	box.SetLayout(layouts.Row { false, true })
 | 
			
		||||
 | 
			
		||||
	box.OnMouseUp(box.handleMouseUp)
 | 
			
		||||
	box.label.OnMouseUp(box.handleMouseUp)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										69
									
								
								labelswatch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								labelswatch.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,69 @@
 | 
			
		||||
package objects
 | 
			
		||||
 | 
			
		||||
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/objects/layouts"
 | 
			
		||||
 | 
			
		||||
// LabelSwatch is a swatch with a label.
 | 
			
		||||
type LabelSwatch struct {
 | 
			
		||||
	tomo.ContainerBox
 | 
			
		||||
	swatch  *Swatch
 | 
			
		||||
	label   *Label
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewLabelSwatch creates a new labeled swatch with the specified color and
 | 
			
		||||
// label text.
 | 
			
		||||
func NewLabelSwatch (value color.Color, text string) *LabelSwatch {
 | 
			
		||||
	box := &LabelSwatch {
 | 
			
		||||
		ContainerBox: tomo.NewContainerBox(),
 | 
			
		||||
		swatch:       NewSwatch(value),
 | 
			
		||||
		label:        NewLabel(text),
 | 
			
		||||
	}
 | 
			
		||||
	box.SetRole(tomo.R("objects", "LabelSwatch", ""))
 | 
			
		||||
	box.swatch.label = text
 | 
			
		||||
	box.label.SetAlign(tomo.AlignStart, tomo.AlignMiddle)
 | 
			
		||||
	box.Add(box.swatch)
 | 
			
		||||
	box.Add(box.label)
 | 
			
		||||
	box.SetLayout(layouts.Row { false, true })
 | 
			
		||||
 | 
			
		||||
	box.OnMouseUp(box.handleMouseUp)
 | 
			
		||||
	box.label.OnMouseUp(box.handleMouseUp)
 | 
			
		||||
	return box
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetValue sets the color of the swatch.
 | 
			
		||||
func (this *LabelSwatch) SetValue (value color.Color) {
 | 
			
		||||
	this.swatch.SetValue(value)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Value returns the color of the swatch.
 | 
			
		||||
func (this *LabelSwatch) Value () color.Color {
 | 
			
		||||
	return this.swatch.Value()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RGBA satisfies the color.Color interface
 | 
			
		||||
func (this *LabelSwatch) RGBA () (r, g, b, a uint32) {
 | 
			
		||||
	return this.swatch.RGBA()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnValueChange specifies a function to be called when the swatch's color
 | 
			
		||||
// is changed by the user.
 | 
			
		||||
func (this *LabelSwatch) OnValueChange (callback func ()) event.Cookie {
 | 
			
		||||
	return this.swatch.OnValueChange(callback)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnEnter specifies a function to be called when the user selects "OK" in the
 | 
			
		||||
// color picker.
 | 
			
		||||
func (this *LabelSwatch) OnEnter (callback func ()) event.Cookie {
 | 
			
		||||
	return this.swatch.OnEnter(callback)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *LabelSwatch) handleMouseUp (button input.Button) {
 | 
			
		||||
	if button != input.ButtonLeft { return }
 | 
			
		||||
	if this.MousePosition().In(this.Bounds()) {
 | 
			
		||||
		this.swatch.SetFocused(true)
 | 
			
		||||
		this.swatch.Choose()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -18,12 +18,18 @@ type Grid struct {
 | 
			
		||||
// will contract. Boxes are laid out left to right, then top to bottom. Boxes
 | 
			
		||||
// that go beyond the lengh of rows will be laid out according to columns, but
 | 
			
		||||
// they will not expand vertically.
 | 
			
		||||
func NewGrid (columns, rows []bool) *Grid {
 | 
			
		||||
	this := &Grid {
 | 
			
		||||
//
 | 
			
		||||
// If you aren't sure how to use this constructor, here is an example:
 | 
			
		||||
//
 | 
			
		||||
//            X0     X1    X2     Y0    Y1    Y2
 | 
			
		||||
//   NewGrid(true, false, true)(false, true, true)
 | 
			
		||||
func NewGrid (columns ...bool) func (rows ...bool) *Grid {
 | 
			
		||||
	return func (rows ...bool) *Grid {
 | 
			
		||||
		return &Grid {
 | 
			
		||||
			xExpand: columns,
 | 
			
		||||
			yExpand: rows,
 | 
			
		||||
		}
 | 
			
		||||
	return this
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *Grid) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point {
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ import "git.tebibyte.media/tomo/tomo/input"
 | 
			
		||||
import "git.tebibyte.media/tomo/tomo/event"
 | 
			
		||||
import "git.tebibyte.media/tomo/objects/layouts"
 | 
			
		||||
 | 
			
		||||
// MenuItem is a clickable button.
 | 
			
		||||
// MenuItem is a selectable memu item.
 | 
			
		||||
type MenuItem struct {
 | 
			
		||||
	tomo.ContainerBox
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ func NewMenuItem (text string) *MenuItem {
 | 
			
		||||
	}
 | 
			
		||||
	box.SetRole(tomo.R("objects", "MenuItem", ""))
 | 
			
		||||
	box.label.SetAlign(tomo.AlignStart, tomo.AlignMiddle)
 | 
			
		||||
	box.SetLayout(layouts.NewGrid([]bool { false, true }, []bool { true }))
 | 
			
		||||
	box.SetLayout(layouts.Row { false, true })
 | 
			
		||||
	
 | 
			
		||||
	box.Add(box.icon)
 | 
			
		||||
	box.Add(box.label)
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ func NewNumberInput (value float64) *NumberInput {
 | 
			
		||||
	box.Add(box.input)
 | 
			
		||||
	box.Add(box.decrement)
 | 
			
		||||
	box.Add(box.increment)
 | 
			
		||||
	box.SetLayout(layouts.NewGrid([]bool { true, false, false }, []bool { true }))
 | 
			
		||||
	box.SetLayout(layouts.Row { true, false, false })
 | 
			
		||||
	box.increment.SetIcon(tomo.IconValueIncrement)
 | 
			
		||||
	box.decrement.SetIcon(tomo.IconValueDecrement)
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										13
									
								
								slider.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								slider.go
									
									
									
									
									
								
							@ -17,6 +17,7 @@ type Slider struct {
 | 
			
		||||
 | 
			
		||||
	on struct {
 | 
			
		||||
		slide event.FuncBroadcaster
 | 
			
		||||
		enter event.FuncBroadcaster
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -89,6 +90,12 @@ func (this *Slider) OnSlide (callback func ()) event.Cookie {
 | 
			
		||||
	return this.on.slide.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OnEnter specifies a function to be called when the user stops moving the
 | 
			
		||||
// slider.
 | 
			
		||||
func (this *Slider) OnEnter (callback func ()) event.Cookie {
 | 
			
		||||
	return this.on.enter.Connect(callback)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *Slider) handleKeyDown (key input.Key, numpad bool) {
 | 
			
		||||
	var increment float64; if this.layout.vertical {
 | 
			
		||||
		increment = -0.05
 | 
			
		||||
@ -148,17 +155,21 @@ func (this *Slider) handleMouseDown (button input.Button) {
 | 
			
		||||
		if above {
 | 
			
		||||
			this.SetValue(0)
 | 
			
		||||
			this.on.slide.Broadcast()
 | 
			
		||||
			this.on.enter.Broadcast()
 | 
			
		||||
		} else {
 | 
			
		||||
			this.SetValue(1)
 | 
			
		||||
			this.on.slide.Broadcast()
 | 
			
		||||
			this.on.enter.Broadcast()
 | 
			
		||||
		}
 | 
			
		||||
	case input.ButtonRight:
 | 
			
		||||
		if above {
 | 
			
		||||
			this.SetValue(this.Value() - this.step)
 | 
			
		||||
			this.on.slide.Broadcast()
 | 
			
		||||
			this.on.enter.Broadcast()
 | 
			
		||||
		} else {
 | 
			
		||||
			this.SetValue(this.Value() + this.step)
 | 
			
		||||
			this.on.slide.Broadcast()
 | 
			
		||||
			this.on.enter.Broadcast()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -166,6 +177,7 @@ func (this *Slider) handleMouseDown (button input.Button) {
 | 
			
		||||
func (this *Slider) handleMouseUp (button input.Button) {
 | 
			
		||||
	if button != input.ButtonLeft || !this.dragging { return }
 | 
			
		||||
	this.dragging = false
 | 
			
		||||
	this.on.enter.Broadcast()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *Slider) handleMouseMove () {
 | 
			
		||||
@ -177,6 +189,7 @@ func (this *Slider) handleScroll (x, y float64) {
 | 
			
		||||
	delta := (x + y) * 0.005
 | 
			
		||||
	this.SetValue(this.Value() + delta)
 | 
			
		||||
	this.on.slide.Broadcast()
 | 
			
		||||
	this.on.enter.Broadcast()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *Slider) drag () {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										168
									
								
								swatch.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								swatch.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,168 @@
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Value returns the color of the swatch.
 | 
			
		||||
func (this *Swatch) Value () color.Color {
 | 
			
		||||
	return this.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RGBA satisfies the color.Color interface
 | 
			
		||||
func (this *Swatch) RGBA () (r, g, b, a uint32) {
 | 
			
		||||
	if this.value == nil { return }
 | 
			
		||||
	return this.value.RGBA()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user