ecs #15
@ -5,18 +5,15 @@ import "math"
|
|||||||
import "image"
|
import "image"
|
||||||
import "image/color"
|
import "image/color"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
import "git.tebibyte.media/sashakoshka/tomo"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
|
import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
||||||
|
|
||||||
// AnalogClock can display the time of day in an analog format.
|
// AnalogClock can display the time of day in an analog format.
|
||||||
type AnalogClock struct {
|
type AnalogClock struct {
|
||||||
*core.Core
|
entity tomo.Entity
|
||||||
core core.CoreControl
|
|
||||||
time time.Time
|
time time.Time
|
||||||
|
|
||||||
config config.Wrapped
|
|
||||||
theme theme.Wrapped
|
theme theme.Wrapped
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,40 +21,18 @@ type AnalogClock struct {
|
|||||||
func NewAnalogClock (newTime time.Time) (element *AnalogClock) {
|
func NewAnalogClock (newTime time.Time) (element *AnalogClock) {
|
||||||
element = &AnalogClock { }
|
element = &AnalogClock { }
|
||||||
element.theme.Case = tomo.C("tomo", "clock")
|
element.theme.Case = tomo.C("tomo", "clock")
|
||||||
element.Core, element.core = core.NewCore(element, element.draw)
|
element.entity = tomo.NewEntity(element)
|
||||||
element.core.SetMinimumSize(64, 64)
|
element.core.SetMinimumSize(64, 64)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTime changes the time that the clock displays.
|
// Entity returns this element's entity.
|
||||||
func (element *AnalogClock) SetTime (newTime time.Time) {
|
func (element *AnalogClock) Entity () tomo.Entity {
|
||||||
if newTime == element.time { return }
|
return element.entity
|
||||||
element.time = newTime
|
|
||||||
element.redo()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTheme sets the element's theme.
|
// Draw causes the element to draw to the specified destination canvas.
|
||||||
func (element *AnalogClock) SetTheme (new tomo.Theme) {
|
func (element *AnalogClock) Draw (destination canvas.Canvas) {
|
||||||
if new == element.theme.Theme { return }
|
|
||||||
element.theme.Theme = new
|
|
||||||
element.redo()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetConfig sets the element's configuration.
|
|
||||||
func (element *AnalogClock) SetConfig (new tomo.Config) {
|
|
||||||
if new == element.config.Config { return }
|
|
||||||
element.config.Config = new
|
|
||||||
element.redo()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *AnalogClock) redo () {
|
|
||||||
if element.core.HasImage() {
|
|
||||||
element.draw()
|
|
||||||
element.core.DamageAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *AnalogClock) draw () {
|
|
||||||
bounds := element.Bounds()
|
bounds := element.Bounds()
|
||||||
|
|
||||||
state := tomo.State { }
|
state := tomo.State { }
|
||||||
@ -80,24 +55,39 @@ func (element *AnalogClock) draw () {
|
|||||||
minute := float64(element.time.Minute()) + second / 60
|
minute := float64(element.time.Minute()) + second / 60
|
||||||
hour := float64(element.time.Hour()) + minute / 60
|
hour := float64(element.time.Hour()) + minute / 60
|
||||||
|
|
||||||
element.radialLine(foreground, 0, 0.5, (hour - 3) / 6 * math.Pi)
|
element.radialLine(destination, foreground, 0, 0.5, (hour - 3) / 6 * math.Pi)
|
||||||
element.radialLine(foreground, 0, 0.7, (minute - 15) / 30 * math.Pi)
|
element.radialLine(destination, foreground, 0, 0.7, (minute - 15) / 30 * math.Pi)
|
||||||
element.radialLine(accent, 0, 0.7, (second - 15) / 30 * math.Pi)
|
element.radialLine(destination, accent, 0, 0.7, (second - 15) / 30 * math.Pi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTime changes the time that the clock displays.
|
||||||
|
func (element *AnalogClock) SetTime (newTime time.Time) {
|
||||||
|
if newTime == element.time { return }
|
||||||
|
element.time = newTime
|
||||||
|
element.entity.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTheme sets the element's theme.
|
||||||
|
func (element *AnalogClock) SetTheme (new tomo.Theme) {
|
||||||
|
if new == element.theme.Theme { return }
|
||||||
|
element.theme.Theme = new
|
||||||
|
element.entity.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *AnalogClock) radialLine (
|
func (element *AnalogClock) radialLine (
|
||||||
|
destination canvas.Canvas,
|
||||||
source color.RGBA,
|
source color.RGBA,
|
||||||
inner float64,
|
inner float64,
|
||||||
outer float64,
|
outer float64,
|
||||||
radian float64,
|
radian float64,
|
||||||
) {
|
) {
|
||||||
bounds := element.Bounds()
|
bounds := element.entity.Bounds()
|
||||||
width := float64(bounds.Dx()) / 2
|
width := float64(bounds.Dx()) / 2
|
||||||
height := float64(bounds.Dy()) / 2
|
height := float64(bounds.Dy()) / 2
|
||||||
min := element.Bounds().Min.Add(image.Pt (
|
min := bounds.Min.Add(image.Pt (
|
||||||
int(math.Cos(radian) * inner * width + width),
|
int(math.Cos(radian) * inner * width + width),
|
||||||
int(math.Sin(radian) * inner * height + height)))
|
int(math.Sin(radian) * inner * height + height)))
|
||||||
max := element.Bounds().Min.Add(image.Pt (
|
max := bounds.Min.Add(image.Pt (
|
||||||
int(math.Cos(radian) * outer * width + width),
|
int(math.Cos(radian) * outer * width + width),
|
||||||
int(math.Sin(radian) * outer * height + height)))
|
int(math.Sin(radian) * outer * height + height)))
|
||||||
shapes.ColorLine(element.core, source, 1, min, max)
|
shapes.ColorLine(element.core, source, 1, min, max)
|
||||||
|
@ -3,8 +3,8 @@ package fun
|
|||||||
import "image"
|
import "image"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
import "git.tebibyte.media/sashakoshka/tomo"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/input"
|
import "git.tebibyte.media/sashakoshka/tomo/input"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
import "git.tebibyte.media/sashakoshka/tomo/default/theme"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
import "git.tebibyte.media/sashakoshka/tomo/default/config"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/fun/music"
|
import "git.tebibyte.media/sashakoshka/tomo/elements/fun/music"
|
||||||
@ -18,21 +18,19 @@ type pianoKey struct {
|
|||||||
|
|
||||||
// Piano is an element that can be used to input midi notes.
|
// Piano is an element that can be used to input midi notes.
|
||||||
type Piano struct {
|
type Piano struct {
|
||||||
*core.Core
|
entity tomo.FocusableEntity
|
||||||
*core.FocusableCore
|
|
||||||
core core.CoreControl
|
|
||||||
focusableControl core.FocusableCoreControl
|
|
||||||
low, high music.Octave
|
|
||||||
|
|
||||||
config config.Wrapped
|
config config.Wrapped
|
||||||
theme theme.Wrapped
|
theme theme.Wrapped
|
||||||
flatTheme theme.Wrapped
|
flatTheme theme.Wrapped
|
||||||
sharpTheme theme.Wrapped
|
sharpTheme theme.Wrapped
|
||||||
|
|
||||||
|
low, high music.Octave
|
||||||
flatKeys []pianoKey
|
flatKeys []pianoKey
|
||||||
sharpKeys []pianoKey
|
sharpKeys []pianoKey
|
||||||
contentBounds image.Rectangle
|
contentBounds image.Rectangle
|
||||||
|
|
||||||
|
enabled bool
|
||||||
pressed *pianoKey
|
pressed *pianoKey
|
||||||
keynavPressed map[music.Note] bool
|
keynavPressed map[music.Note] bool
|
||||||
|
|
||||||
@ -43,11 +41,7 @@ type Piano struct {
|
|||||||
// NewPiano returns a new piano element with a lowest and highest octave,
|
// NewPiano returns a new piano element with a lowest and highest octave,
|
||||||
// inclusive. If low is greater than high, they will be swapped.
|
// inclusive. If low is greater than high, they will be swapped.
|
||||||
func NewPiano (low, high music.Octave) (element *Piano) {
|
func NewPiano (low, high music.Octave) (element *Piano) {
|
||||||
if low > high {
|
if low > high { low, high = high, low }
|
||||||
temp := low
|
|
||||||
low = high
|
|
||||||
high = temp
|
|
||||||
}
|
|
||||||
|
|
||||||
element = &Piano {
|
element = &Piano {
|
||||||
low: low,
|
low: low,
|
||||||
@ -58,16 +52,68 @@ func NewPiano (low, high music.Octave) (element *Piano) {
|
|||||||
element.theme.Case = tomo.C("tomo", "piano")
|
element.theme.Case = tomo.C("tomo", "piano")
|
||||||
element.flatTheme.Case = tomo.C("tomo", "piano", "flatKey")
|
element.flatTheme.Case = tomo.C("tomo", "piano", "flatKey")
|
||||||
element.sharpTheme.Case = tomo.C("tomo", "piano", "sharpKey")
|
element.sharpTheme.Case = tomo.C("tomo", "piano", "sharpKey")
|
||||||
element.Core, element.core = core.NewCore (element, func () {
|
element.entity = tomo.NewEntity(element).(tomo.FocusableEntity)
|
||||||
element.recalculate()
|
|
||||||
element.draw()
|
|
||||||
})
|
|
||||||
element.FocusableCore,
|
|
||||||
element.focusableControl = core.NewFocusableCore(element.core, element.redo)
|
|
||||||
element.updateMinimumSize()
|
element.updateMinimumSize()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Entity returns this element's entity.
|
||||||
|
func (element *Piano) Entity () tomo.Entity {
|
||||||
|
return element.entity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw causes the element to draw to the specified destination canvas.
|
||||||
|
func (element *Piano) Draw (destination canvas.Canvas) {
|
||||||
|
element.recalculate()
|
||||||
|
|
||||||
|
state := tomo.State {
|
||||||
|
Focused: element.entity.Focused(),
|
||||||
|
Disabled: !element.Enabled(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range element.flatKeys {
|
||||||
|
_, keynavPressed := element.keynavPressed[key.Note]
|
||||||
|
element.drawFlat (
|
||||||
|
destination,
|
||||||
|
key.Rectangle,
|
||||||
|
element.pressed != nil &&
|
||||||
|
(*element.pressed).Note == key.Note || keynavPressed,
|
||||||
|
state)
|
||||||
|
}
|
||||||
|
for _, key := range element.sharpKeys {
|
||||||
|
_, keynavPressed := element.keynavPressed[key.Note]
|
||||||
|
element.drawSharp (
|
||||||
|
destination,
|
||||||
|
key.Rectangle,
|
||||||
|
element.pressed != nil &&
|
||||||
|
(*element.pressed).Note == key.Note || keynavPressed,
|
||||||
|
state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern := element.theme.Pattern(tomo.PatternPinboard, state)
|
||||||
|
artist.DrawShatter (
|
||||||
|
destination, pattern, element.entity.Bounds(),
|
||||||
|
element.contentBounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus gives this element input focus.
|
||||||
|
func (element *Piano) Focus () {
|
||||||
|
element.entity.Focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled returns whether this piano can be played or not.
|
||||||
|
func (element *Piano) Enabled () bool {
|
||||||
|
return element.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEnabled sets whether this piano can be played or not.
|
||||||
|
func (element *Piano) SetEnabled (enabled bool) {
|
||||||
|
if element.enabled == enabled { return }
|
||||||
|
element.enabled = enabled
|
||||||
|
element.entity.Invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// OnPress sets a function to be called when a key is pressed.
|
// OnPress sets a function to be called when a key is pressed.
|
||||||
func (element *Piano) OnPress (callback func (note music.Note)) {
|
func (element *Piano) OnPress (callback func (note music.Note)) {
|
||||||
element.onPress = callback
|
element.onPress = callback
|
||||||
@ -90,7 +136,7 @@ func (element *Piano) HandleMouseUp (x, y int, button input.Button) {
|
|||||||
element.onRelease((*element.pressed).Note)
|
element.onRelease((*element.pressed).Note)
|
||||||
}
|
}
|
||||||
element.pressed = nil
|
element.pressed = nil
|
||||||
element.redo()
|
element.entity.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *Piano) HandleMotion (x, y int) {
|
func (element *Piano) HandleMotion (x, y int) {
|
||||||
@ -126,7 +172,7 @@ func (element *Piano) pressUnderMouseCursor (point image.Point) {
|
|||||||
if element.onPress != nil {
|
if element.onPress != nil {
|
||||||
element.onPress((*element.pressed).Note)
|
element.onPress((*element.pressed).Note)
|
||||||
}
|
}
|
||||||
element.redo()
|
element.entity.Invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +232,7 @@ func (element *Piano) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
|
|||||||
if element.onPress != nil {
|
if element.onPress != nil {
|
||||||
element.onPress(note)
|
element.onPress(note)
|
||||||
}
|
}
|
||||||
element.redo()
|
element.entity.Invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,7 +245,7 @@ func (element *Piano) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
|
|||||||
if element.onRelease != nil {
|
if element.onRelease != nil {
|
||||||
element.onRelease(note)
|
element.onRelease(note)
|
||||||
}
|
}
|
||||||
element.redo()
|
element.entity.Invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTheme sets the element's theme.
|
// SetTheme sets the element's theme.
|
||||||
@ -209,8 +255,7 @@ func (element *Piano) SetTheme (new tomo.Theme) {
|
|||||||
element.flatTheme.Theme = new
|
element.flatTheme.Theme = new
|
||||||
element.sharpTheme.Theme = new
|
element.sharpTheme.Theme = new
|
||||||
element.updateMinimumSize()
|
element.updateMinimumSize()
|
||||||
element.recalculate()
|
element.entity.Invalidate()
|
||||||
element.redo()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetConfig sets the element's configuration.
|
// SetConfig sets the element's configuration.
|
||||||
@ -218,13 +263,12 @@ func (element *Piano) SetConfig (new tomo.Config) {
|
|||||||
if new == element.config.Config { return }
|
if new == element.config.Config { return }
|
||||||
element.config.Config = new
|
element.config.Config = new
|
||||||
element.updateMinimumSize()
|
element.updateMinimumSize()
|
||||||
element.recalculate()
|
element.entity.Invalidate()
|
||||||
element.redo()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *Piano) updateMinimumSize () {
|
func (element *Piano) updateMinimumSize () {
|
||||||
padding := element.theme.Padding(tomo.PatternPinboard)
|
padding := element.theme.Padding(tomo.PatternPinboard)
|
||||||
element.core.SetMinimumSize (
|
element.entity.SetMinimumSize (
|
||||||
pianoKeyWidth * 7 * element.countOctaves() +
|
pianoKeyWidth * 7 * element.countOctaves() +
|
||||||
padding.Horizontal(),
|
padding.Horizontal(),
|
||||||
64 + padding.Vertical())
|
64 + padding.Vertical())
|
||||||
@ -242,19 +286,12 @@ func (element *Piano) countSharps () int {
|
|||||||
return element.countOctaves() * 5
|
return element.countOctaves() * 5
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *Piano) redo () {
|
|
||||||
if element.core.HasImage() {
|
|
||||||
element.draw()
|
|
||||||
element.core.DamageAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *Piano) recalculate () {
|
func (element *Piano) recalculate () {
|
||||||
element.flatKeys = make([]pianoKey, element.countFlats())
|
element.flatKeys = make([]pianoKey, element.countFlats())
|
||||||
element.sharpKeys = make([]pianoKey, element.countSharps())
|
element.sharpKeys = make([]pianoKey, element.countSharps())
|
||||||
|
|
||||||
padding := element.theme.Padding(tomo.PatternPinboard)
|
padding := element.theme.Padding(tomo.PatternPinboard)
|
||||||
bounds := padding.Apply(element.Bounds())
|
bounds := padding.Apply(element.entity.Bounds())
|
||||||
|
|
||||||
dot := bounds.Min
|
dot := bounds.Min
|
||||||
note := element.low.Note(0)
|
note := element.low.Note(0)
|
||||||
@ -285,50 +322,24 @@ func (element *Piano) recalculate () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *Piano) draw () {
|
|
||||||
state := tomo.State {
|
|
||||||
Focused: element.Focused(),
|
|
||||||
Disabled: !element.Enabled(),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, key := range element.flatKeys {
|
|
||||||
_, keynavPressed := element.keynavPressed[key.Note]
|
|
||||||
element.drawFlat (
|
|
||||||
key.Rectangle,
|
|
||||||
element.pressed != nil &&
|
|
||||||
(*element.pressed).Note == key.Note || keynavPressed,
|
|
||||||
state)
|
|
||||||
}
|
|
||||||
for _, key := range element.sharpKeys {
|
|
||||||
_, keynavPressed := element.keynavPressed[key.Note]
|
|
||||||
element.drawSharp (
|
|
||||||
key.Rectangle,
|
|
||||||
element.pressed != nil &&
|
|
||||||
(*element.pressed).Note == key.Note || keynavPressed,
|
|
||||||
state)
|
|
||||||
}
|
|
||||||
|
|
||||||
pattern := element.theme.Pattern(tomo.PatternPinboard, state)
|
|
||||||
artist.DrawShatter (
|
|
||||||
element.core, pattern, element.Bounds(), element.contentBounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (element *Piano) drawFlat (
|
func (element *Piano) drawFlat (
|
||||||
|
destination canvas.Canvas,
|
||||||
bounds image.Rectangle,
|
bounds image.Rectangle,
|
||||||
pressed bool,
|
pressed bool,
|
||||||
state tomo.State,
|
state tomo.State,
|
||||||
) {
|
) {
|
||||||
state.Pressed = pressed
|
state.Pressed = pressed
|
||||||
pattern := element.flatTheme.Pattern(tomo.PatternButton, state)
|
pattern := element.flatTheme.Pattern(tomo.PatternButton, state)
|
||||||
pattern.Draw(element.core, bounds)
|
pattern.Draw(destination, bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *Piano) drawSharp (
|
func (element *Piano) drawSharp (
|
||||||
|
destination canvas.Canvas,
|
||||||
bounds image.Rectangle,
|
bounds image.Rectangle,
|
||||||
pressed bool,
|
pressed bool,
|
||||||
state tomo.State,
|
state tomo.State,
|
||||||
) {
|
) {
|
||||||
state.Pressed = pressed
|
state.Pressed = pressed
|
||||||
pattern := element.sharpTheme.Pattern(tomo.PatternButton, state)
|
pattern := element.sharpTheme.Pattern(tomo.PatternButton, state)
|
||||||
pattern.Draw(element.core, bounds)
|
pattern.Draw(destination, bounds)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package main
|
|||||||
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
import "git.tebibyte.media/sashakoshka/tomo"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements"
|
import "git.tebibyte.media/sashakoshka/tomo/elements"
|
||||||
// import "git.tebibyte.media/sashakoshka/tomo/elements/testing"
|
import "git.tebibyte.media/sashakoshka/tomo/elements/testing"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/containers"
|
import "git.tebibyte.media/sashakoshka/tomo/elements/containers"
|
||||||
import _ "git.tebibyte.media/sashakoshka/tomo/backends/all"
|
import _ "git.tebibyte.media/sashakoshka/tomo/backends/all"
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ func run () {
|
|||||||
button.OnClick (func () {
|
button.OnClick (func () {
|
||||||
container.DisownAll()
|
container.DisownAll()
|
||||||
container.Adopt(elements.NewLabel("Draw here:", false), false)
|
container.Adopt(elements.NewLabel("Draw here:", false), false)
|
||||||
// container.Adopt(testing.NewMouse(), true)
|
container.Adopt(testing.NewMouse(), true)
|
||||||
container.Adopt(okButton, false)
|
container.Adopt(okButton, false)
|
||||||
okButton.Focus()
|
okButton.Focus()
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user