This repository has been archived on 2023-08-08. You can view files and clone it, but cannot push or open issues or pull requests.
tomo-old/elements/fun/piano.go

233 lines
5.8 KiB
Go

package fun
import "image"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/config"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
import "git.tebibyte.media/sashakoshka/tomo/elements/fun/music"
const pianoKeyWidth = 18
type pianoKey struct {
image.Rectangle
music.Note
}
// Piano is an element that can be used to input midi notes.
type Piano struct {
*core.Core
core core.CoreControl
low, high music.Octave
config config.Wrapped
theme theme.Wrapped
flatKeys []pianoKey
sharpKeys []pianoKey
pressed *pianoKey
onPress func (music.Note)
onRelease func (music.Note)
}
// 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
}
element = &Piano {
low: low,
high: high,
}
element.theme.Case = theme.C("fun", "piano")
element.Core, element.core = core.NewCore (func () {
element.recalculate()
element.draw()
})
element.updateMinimumSize()
return
}
// OnPress sets a function to be called when a key is pressed.
func (element *Piano) OnPress (callback func (note music.Note)) {
element.onPress = callback
}
// OnRelease sets a function to be called when a key is released.
func (element *Piano) OnRelease (callback func (note music.Note)) {
element.onRelease = callback
}
func (element *Piano) HandleMouseDown (x, y int, button input.Button) {
if button != input.ButtonLeft { return }
element.pressUnderMouseCursor(image.Pt(x, y))
}
func (element *Piano) HandleMouseUp (x, y int, button input.Button) {
if button != input.ButtonLeft { return }
if element.onRelease != nil {
element.onRelease((*element.pressed).Note)
}
element.pressed = nil
element.redo()
}
func (element *Piano) HandleMouseMove (x, y int) {
if element.pressed == nil { return }
element.pressUnderMouseCursor(image.Pt(x, y))
}
func (element *Piano) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
func (element *Piano) pressUnderMouseCursor (point image.Point) {
// find out which note is being pressed
newKey := (*pianoKey)(nil)
for index, key := range element.flatKeys {
if point.In(key.Rectangle) {
newKey = &element.flatKeys[index]
break
}
}
for index, key := range element.sharpKeys {
if point.In(key.Rectangle) {
newKey = &element.sharpKeys[index]
break
}
}
if newKey == nil { return }
if newKey != element.pressed {
// release previous note
if element.pressed != nil && element.onRelease != nil {
element.onRelease((*element.pressed).Note)
}
// press new note
element.pressed = newKey
if element.onPress != nil {
element.onPress((*element.pressed).Note)
}
element.redo()
}
}
// SetTheme sets the element's theme.
func (element *Piano) SetTheme (new theme.Theme) {
if new == element.theme.Theme { return }
element.theme.Theme = new
element.updateMinimumSize()
element.recalculate()
element.redo()
}
// SetConfig sets the element's configuration.
func (element *Piano) SetConfig (new config.Config) {
if new == element.config.Config { return }
element.config.Config = new
element.updateMinimumSize()
element.recalculate()
element.redo()
}
func (element *Piano) updateMinimumSize () {
inset := element.theme.Inset(theme.PatternSunken)
element.core.SetMinimumSize (
pianoKeyWidth * 7 * element.countOctaves() + inset[1] + inset[3],
64 + inset[0] + inset[2])
}
func (element *Piano) countOctaves () int {
return int(element.high - element.low + 1)
}
func (element *Piano) countFlats () int {
return element.countOctaves() * 8
}
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())
inset := element.theme.Inset(theme.PatternSunken)
bounds := inset.Apply(element.Bounds())
dot := bounds.Min
note := element.low.Note(0)
limit := element.high.Note(12)
flatIndex := 0
sharpIndex := 0
for note < limit {
if note.IsSharp() {
element.sharpKeys[sharpIndex].Rectangle = image.Rect (
-(pianoKeyWidth * 3) / 7, 0,
(pianoKeyWidth * 3) / 7,
(bounds.Dy() * 5) / 8).Add(dot)
element.sharpKeys[sharpIndex].Note = note
sharpIndex ++
} else {
element.flatKeys[flatIndex].Rectangle = image.Rect (
0, 0, pianoKeyWidth, bounds.Dy()).Add(dot)
dot.X += pianoKeyWidth
element.flatKeys[flatIndex].Note = note
flatIndex ++
}
note ++
}
}
func (element *Piano) draw () {
state := theme.PatternState { }
pattern := element.theme.Pattern(theme.PatternSunken, state)
// inset := element.theme.Inset(theme.PatternSunken)
artist.FillRectangle(element, pattern, element.Bounds())
for _, key := range element.flatKeys {
element.drawFlat (
key.Rectangle,
element.pressed != nil &&
(*element.pressed).Note == key.Note)
}
for _, key := range element.sharpKeys {
element.drawSharp (
key.Rectangle,
element.pressed != nil &&
(*element.pressed).Note == key.Note)
}
}
func (element *Piano) drawFlat (bounds image.Rectangle, pressed bool) {
state := theme.PatternState {
Pressed: pressed,
}
pattern := element.theme.Theme.Pattern (
theme.PatternButton, theme.C("fun", "flatKey"), state)
artist.FillRectangle(element, pattern, bounds)
}
func (element *Piano) drawSharp (bounds image.Rectangle, pressed bool) {
state := theme.PatternState {
Pressed: pressed,
}
pattern := element.theme.Theme.Pattern (
theme.PatternButton, theme.C("fun", "sharpKey"), state)
artist.FillRectangle(element, pattern, bounds)
}