diff --git a/elements/fun/clock.go b/elements/fun/clock.go index 23397e2..a23e828 100644 --- a/elements/fun/clock.go +++ b/elements/fun/clock.go @@ -5,18 +5,15 @@ import "math" import "image" import "image/color" 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/elements/core" import "git.tebibyte.media/sashakoshka/tomo/default/theme" import "git.tebibyte.media/sashakoshka/tomo/default/config" // AnalogClock can display the time of day in an analog format. type AnalogClock struct { - *core.Core - core core.CoreControl - time time.Time - - config config.Wrapped + entity tomo.Entity + time time.Time theme theme.Wrapped } @@ -24,40 +21,18 @@ type AnalogClock struct { func NewAnalogClock (newTime time.Time) (element *AnalogClock) { element = &AnalogClock { } 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) return } -// SetTime changes the time that the clock displays. -func (element *AnalogClock) SetTime (newTime time.Time) { - if newTime == element.time { return } - element.time = newTime - element.redo() +// Entity returns this element's entity. +func (element *AnalogClock) Entity () tomo.Entity { + return element.entity } -// SetTheme sets the element's theme. -func (element *AnalogClock) SetTheme (new tomo.Theme) { - 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 () { +// Draw causes the element to draw to the specified destination canvas. +func (element *AnalogClock) Draw (destination canvas.Canvas) { bounds := element.Bounds() state := tomo.State { } @@ -80,24 +55,39 @@ func (element *AnalogClock) draw () { minute := float64(element.time.Minute()) + second / 60 hour := float64(element.time.Hour()) + minute / 60 - element.radialLine(foreground, 0, 0.5, (hour - 3) / 6 * math.Pi) - element.radialLine(foreground, 0, 0.7, (minute - 15) / 30 * math.Pi) - element.radialLine(accent, 0, 0.7, (second - 15) / 30 * math.Pi) + element.radialLine(destination, foreground, 0, 0.5, (hour - 3) / 6 * math.Pi) + element.radialLine(destination, foreground, 0, 0.7, (minute - 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 ( + destination canvas.Canvas, source color.RGBA, inner float64, outer float64, radian float64, ) { - bounds := element.Bounds() + bounds := element.entity.Bounds() width := float64(bounds.Dx()) / 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.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.Sin(radian) * outer * height + height))) shapes.ColorLine(element.core, source, 1, min, max) diff --git a/elements/fun/piano.go b/elements/fun/piano.go index 9097fc7..5776ed1 100644 --- a/elements/fun/piano.go +++ b/elements/fun/piano.go @@ -3,8 +3,8 @@ package fun import "image" import "git.tebibyte.media/sashakoshka/tomo" 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/elements/core" import "git.tebibyte.media/sashakoshka/tomo/default/theme" import "git.tebibyte.media/sashakoshka/tomo/default/config" 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. type Piano struct { - *core.Core - *core.FocusableCore - core core.CoreControl - focusableControl core.FocusableCoreControl - low, high music.Octave - + entity tomo.FocusableEntity + config config.Wrapped theme theme.Wrapped flatTheme theme.Wrapped sharpTheme theme.Wrapped + low, high music.Octave flatKeys []pianoKey sharpKeys []pianoKey contentBounds image.Rectangle + enabled bool pressed *pianoKey keynavPressed map[music.Note] bool @@ -43,11 +41,7 @@ type Piano struct { // NewPiano returns a new piano element with a lowest and highest octave, // inclusive. If low is greater than high, they will be swapped. func NewPiano (low, high music.Octave) (element *Piano) { - if low > high { - temp := low - low = high - high = temp - } + if low > high { low, high = high, low } element = &Piano { low: low, @@ -58,16 +52,68 @@ func NewPiano (low, high music.Octave) (element *Piano) { element.theme.Case = tomo.C("tomo", "piano") element.flatTheme.Case = tomo.C("tomo", "piano", "flatKey") element.sharpTheme.Case = tomo.C("tomo", "piano", "sharpKey") - element.Core, element.core = core.NewCore (element, func () { - element.recalculate() - element.draw() - }) - element.FocusableCore, - element.focusableControl = core.NewFocusableCore(element.core, element.redo) + element.entity = tomo.NewEntity(element).(tomo.FocusableEntity) element.updateMinimumSize() 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. func (element *Piano) OnPress (callback func (note music.Note)) { element.onPress = callback @@ -90,7 +136,7 @@ func (element *Piano) HandleMouseUp (x, y int, button input.Button) { element.onRelease((*element.pressed).Note) } element.pressed = nil - element.redo() + element.entity.Invalidate() } func (element *Piano) HandleMotion (x, y int) { @@ -126,7 +172,7 @@ func (element *Piano) pressUnderMouseCursor (point image.Point) { if element.onPress != nil { 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 { 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 { element.onRelease(note) } - element.redo() + element.entity.Invalidate() } // SetTheme sets the element's theme. @@ -209,8 +255,7 @@ func (element *Piano) SetTheme (new tomo.Theme) { element.flatTheme.Theme = new element.sharpTheme.Theme = new element.updateMinimumSize() - element.recalculate() - element.redo() + element.entity.Invalidate() } // SetConfig sets the element's configuration. @@ -218,13 +263,12 @@ func (element *Piano) SetConfig (new tomo.Config) { if new == element.config.Config { return } element.config.Config = new element.updateMinimumSize() - element.recalculate() - element.redo() + element.entity.Invalidate() } func (element *Piano) updateMinimumSize () { padding := element.theme.Padding(tomo.PatternPinboard) - element.core.SetMinimumSize ( + element.entity.SetMinimumSize ( pianoKeyWidth * 7 * element.countOctaves() + padding.Horizontal(), 64 + padding.Vertical()) @@ -242,19 +286,12 @@ func (element *Piano) countSharps () int { return element.countOctaves() * 5 } -func (element *Piano) redo () { - if element.core.HasImage() { - element.draw() - element.core.DamageAll() - } -} - func (element *Piano) recalculate () { element.flatKeys = make([]pianoKey, element.countFlats()) element.sharpKeys = make([]pianoKey, element.countSharps()) - padding := element.theme.Padding(tomo.PatternPinboard) - bounds := padding.Apply(element.Bounds()) + padding := element.theme.Padding(tomo.PatternPinboard) + bounds := padding.Apply(element.entity.Bounds()) dot := bounds.Min 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 ( + destination canvas.Canvas, bounds image.Rectangle, pressed bool, state tomo.State, ) { state.Pressed = pressed pattern := element.flatTheme.Pattern(tomo.PatternButton, state) - pattern.Draw(element.core, bounds) + pattern.Draw(destination, bounds) } func (element *Piano) drawSharp ( + destination canvas.Canvas, bounds image.Rectangle, pressed bool, state tomo.State, ) { state.Pressed = pressed pattern := element.sharpTheme.Pattern(tomo.PatternButton, state) - pattern.Draw(element.core, bounds) + pattern.Draw(destination, bounds) } diff --git a/examples/verticalLayout/main.go b/examples/verticalLayout/main.go index 7860ec5..7b9c7b7 100644 --- a/examples/verticalLayout/main.go +++ b/examples/verticalLayout/main.go @@ -2,7 +2,7 @@ package main import "git.tebibyte.media/sashakoshka/tomo" 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/backends/all" @@ -22,7 +22,7 @@ func run () { button.OnClick (func () { container.DisownAll() container.Adopt(elements.NewLabel("Draw here:", false), false) - // container.Adopt(testing.NewMouse(), true) + container.Adopt(testing.NewMouse(), true) container.Adopt(okButton, false) okButton.Focus() })