Migrated fun elements

This commit is contained in:
Sasha Koshka 2023-04-15 18:24:16 -04:00
parent 986315d5db
commit 9d78a599aa
3 changed files with 109 additions and 108 deletions

View File

@ -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)

View File

@ -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)
} }

View File

@ -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()
}) })