mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-25 18:00:37 -04:00
317 lines
7.8 KiB
Go
317 lines
7.8 KiB
Go
package gioui
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gioui.org/io/clipboard"
|
|
"gioui.org/io/key"
|
|
"gopkg.in/yaml.v2"
|
|
)
|
|
|
|
type (
|
|
KeyAction string
|
|
|
|
KeyBinding struct {
|
|
Key string
|
|
Shortcut, Ctrl, Command, Shift, Alt, Super bool
|
|
Action string
|
|
}
|
|
)
|
|
|
|
var keyBindingMap = map[key.Event]string{}
|
|
var keyActionMap = map[KeyAction]string{} // holds an informative string of the first key bound to an action
|
|
|
|
//go:embed keybindings.yml
|
|
var defaultKeyBindings []byte
|
|
|
|
func init() {
|
|
var keyBindings, userKeybindings []KeyBinding
|
|
if err := yaml.UnmarshalStrict(defaultKeyBindings, &keyBindings); err != nil {
|
|
panic(fmt.Errorf("failed to unmarshal default keybindings: %w", err))
|
|
}
|
|
if err := ReadCustomConfig("keybindings.yml", &userKeybindings); err == nil {
|
|
keyBindings = append(keyBindings, userKeybindings...)
|
|
}
|
|
|
|
for _, kb := range keyBindings {
|
|
var mods key.Modifiers
|
|
if kb.Shortcut {
|
|
mods |= key.ModShortcut
|
|
}
|
|
if kb.Ctrl {
|
|
mods |= key.ModCtrl
|
|
}
|
|
if kb.Command {
|
|
mods |= key.ModCommand
|
|
}
|
|
if kb.Shift {
|
|
mods |= key.ModShift
|
|
}
|
|
if kb.Alt {
|
|
mods |= key.ModAlt
|
|
}
|
|
if kb.Super {
|
|
mods |= key.ModSuper
|
|
}
|
|
|
|
keyEvent := key.Event{Name: key.Name(kb.Key), Modifiers: mods, State: key.Press}
|
|
action, ok := keyBindingMap[keyEvent] // if this key has been previously bound, remove it from the hint map
|
|
if ok {
|
|
delete(keyActionMap, KeyAction(action))
|
|
}
|
|
if kb.Action == "" { // unbind
|
|
delete(keyBindingMap, keyEvent)
|
|
} else { // bind
|
|
keyBindingMap[keyEvent] = kb.Action
|
|
// last binding of the some action wins for displaying the hint
|
|
modString := strings.Replace(mods.String(), "-", "+", -1)
|
|
text := kb.Key
|
|
if modString != "" {
|
|
text = modString + "+" + text
|
|
}
|
|
keyActionMap[KeyAction(kb.Action)] = text
|
|
}
|
|
}
|
|
}
|
|
|
|
func makeHint(hint, format, action string) string {
|
|
if keyActionMap[KeyAction(action)] != "" {
|
|
return hint + fmt.Sprintf(format, keyActionMap[KeyAction(action)])
|
|
}
|
|
return hint
|
|
}
|
|
|
|
// KeyEvent handles incoming key events and returns true if repaint is needed.
|
|
func (t *Tracker) KeyEvent(e key.Event, gtx C) {
|
|
if e.State == key.Release {
|
|
t.JammingReleased(e)
|
|
return
|
|
}
|
|
action, ok := keyBindingMap[e]
|
|
if !ok {
|
|
return
|
|
}
|
|
switch action {
|
|
// Actions
|
|
case "AddTrack":
|
|
t.AddTrack().Do()
|
|
case "DeleteTrack":
|
|
t.DeleteTrack().Do()
|
|
case "AddInstrument":
|
|
t.AddInstrument().Do()
|
|
case "DeleteInstrument":
|
|
t.DeleteInstrument().Do()
|
|
case "AddUnitAfter":
|
|
t.AddUnit(false).Do()
|
|
case "AddUnitBefore":
|
|
t.AddUnit(true).Do()
|
|
case "DeleteUnit":
|
|
t.DeleteUnit().Do()
|
|
case "ClearUnit":
|
|
t.ClearUnit().Do()
|
|
case "Undo":
|
|
t.Undo().Do()
|
|
case "Redo":
|
|
t.Redo().Do()
|
|
case "AddSemitone":
|
|
t.AddSemitone().Do()
|
|
case "SubtractSemitone":
|
|
t.SubtractSemitone().Do()
|
|
case "AddOctave":
|
|
t.AddOctave().Do()
|
|
case "SubtractOctave":
|
|
t.SubtractOctave().Do()
|
|
case "EditNoteOff":
|
|
t.EditNoteOff().Do()
|
|
case "RemoveUnused":
|
|
t.RemoveUnused().Do()
|
|
case "PlayCurrentPosFollow":
|
|
t.Follow().Bool().Set(true)
|
|
t.PlayCurrentPos().Do()
|
|
case "PlayCurrentPosUnfollow":
|
|
t.Follow().Bool().Set(false)
|
|
t.PlayCurrentPos().Do()
|
|
case "PlaySongStartFollow":
|
|
t.Follow().Bool().Set(true)
|
|
t.PlaySongStart().Do()
|
|
case "PlaySongStartUnfollow":
|
|
t.Follow().Bool().Set(false)
|
|
t.PlaySongStart().Do()
|
|
case "PlaySelectedFollow":
|
|
t.Follow().Bool().Set(true)
|
|
t.PlaySelected().Do()
|
|
case "PlaySelectedUnfollow":
|
|
t.Follow().Bool().Set(false)
|
|
t.PlaySelected().Do()
|
|
case "PlayLoopFollow":
|
|
t.Follow().Bool().Set(true)
|
|
t.PlayFromLoopStart().Do()
|
|
case "PlayLoopUnfollow":
|
|
t.Follow().Bool().Set(false)
|
|
t.PlayFromLoopStart().Do()
|
|
case "StopPlaying":
|
|
t.StopPlaying().Do()
|
|
case "AddOrderRowBefore":
|
|
t.AddOrderRow(true).Do()
|
|
case "AddOrderRowAfter":
|
|
t.AddOrderRow(false).Do()
|
|
case "DeleteOrderRowBackwards":
|
|
t.DeleteOrderRow(true).Do()
|
|
case "DeleteOrderRowForwards":
|
|
t.DeleteOrderRow(false).Do()
|
|
case "NewSong":
|
|
t.NewSong().Do()
|
|
case "OpenSong":
|
|
t.OpenSong().Do()
|
|
case "Quit":
|
|
if canQuit {
|
|
t.RequestQuit().Do()
|
|
}
|
|
case "SaveSong":
|
|
t.SaveSong().Do()
|
|
case "SaveSongAs":
|
|
t.SaveSongAs().Do()
|
|
case "ExportWav":
|
|
t.Export().Do()
|
|
case "ExportFloat":
|
|
t.ExportFloat().Do()
|
|
case "ExportInt16":
|
|
t.ExportInt16().Do()
|
|
case "SplitTrack":
|
|
t.SplitTrack().Do()
|
|
case "SplitInstrument":
|
|
t.SplitInstrument().Do()
|
|
// Booleans
|
|
case "PanicToggle":
|
|
t.Panic().Bool().Toggle()
|
|
case "RecordingToggle":
|
|
t.IsRecording().Bool().Toggle()
|
|
case "PlayingToggleFollow":
|
|
t.Follow().Bool().Set(true)
|
|
t.Playing().Bool().Toggle()
|
|
case "PlayingToggleUnfollow":
|
|
t.Follow().Bool().Set(false)
|
|
t.Playing().Bool().Toggle()
|
|
case "InstrEnlargedToggle":
|
|
t.InstrEnlarged().Bool().Toggle()
|
|
case "LinkInstrTrackToggle":
|
|
t.LinkInstrTrack().Bool().Toggle()
|
|
case "CommentExpandedToggle":
|
|
t.CommentExpanded().Bool().Toggle()
|
|
case "FollowToggle":
|
|
t.Follow().Bool().Toggle()
|
|
case "UnitDisabledToggle":
|
|
t.UnitDisabled().Bool().Toggle()
|
|
case "LoopToggle":
|
|
t.LoopToggle().Bool().Toggle()
|
|
case "UniquePatternsToggle":
|
|
t.UniquePatterns().Bool().Toggle()
|
|
case "MuteToggle":
|
|
t.Mute().Bool().Toggle()
|
|
case "SoloToggle":
|
|
t.Solo().Bool().Toggle()
|
|
// Integers
|
|
case "InstrumentVoicesAdd":
|
|
t.Model.InstrumentVoices().Int().Add(1)
|
|
case "InstrumentVoicesSubtract":
|
|
t.Model.InstrumentVoices().Int().Add(-1)
|
|
case "TrackVoicesAdd":
|
|
t.TrackVoices().Int().Add(1)
|
|
case "TrackVoicesSubtract":
|
|
t.TrackVoices().Int().Add(-1)
|
|
case "SongLengthAdd":
|
|
t.SongLength().Int().Add(1)
|
|
case "SongLengthSubtract":
|
|
t.SongLength().Int().Add(-1)
|
|
case "BPMAdd":
|
|
t.BPM().Int().Add(1)
|
|
case "BPMSubtract":
|
|
t.BPM().Int().Add(-1)
|
|
case "RowsPerPatternAdd":
|
|
t.RowsPerPattern().Int().Add(1)
|
|
case "RowsPerPatternSubtract":
|
|
t.RowsPerPattern().Int().Add(-1)
|
|
case "RowsPerBeatAdd":
|
|
t.RowsPerBeat().Int().Add(1)
|
|
case "RowsPerBeatSubtract":
|
|
t.RowsPerBeat().Int().Add(-1)
|
|
case "StepAdd":
|
|
t.Step().Int().Add(1)
|
|
case "StepSubtract":
|
|
t.Step().Int().Add(-1)
|
|
case "OctaveAdd":
|
|
t.Octave().Int().Add(1)
|
|
case "OctaveSubtract":
|
|
t.Octave().Int().Add(-1)
|
|
// Other miscellaneous
|
|
case "Paste":
|
|
gtx.Execute(clipboard.ReadCmd{Tag: t})
|
|
case "OrderEditorFocus":
|
|
t.OrderEditor.scrollTable.Focus()
|
|
case "TrackEditorFocus":
|
|
t.TrackEditor.scrollTable.Focus()
|
|
case "InstrumentEditorFocus":
|
|
t.InstrumentEditor.Focus()
|
|
case "FocusPrev":
|
|
switch {
|
|
case t.OrderEditor.scrollTable.Focused():
|
|
t.InstrumentEditor.unitEditor.sliderList.Focus()
|
|
case t.TrackEditor.scrollTable.Focused():
|
|
t.OrderEditor.scrollTable.Focus()
|
|
case t.InstrumentEditor.Focused():
|
|
if t.InstrumentEditor.enlargeBtn.Bool.Value() {
|
|
t.InstrumentEditor.unitEditor.sliderList.Focus()
|
|
} else {
|
|
t.TrackEditor.scrollTable.Focus()
|
|
}
|
|
default:
|
|
t.InstrumentEditor.Focus()
|
|
}
|
|
case "FocusNext":
|
|
switch {
|
|
case t.OrderEditor.scrollTable.Focused():
|
|
t.TrackEditor.scrollTable.Focus()
|
|
case t.TrackEditor.scrollTable.Focused():
|
|
t.InstrumentEditor.Focus()
|
|
case t.InstrumentEditor.Focused():
|
|
t.InstrumentEditor.unitEditor.sliderList.Focus()
|
|
default:
|
|
if t.InstrumentEditor.enlargeBtn.Bool.Value() {
|
|
t.InstrumentEditor.Focus()
|
|
} else {
|
|
t.OrderEditor.scrollTable.Focus()
|
|
}
|
|
}
|
|
default:
|
|
if action[:4] == "Note" {
|
|
val, err := strconv.Atoi(string(action[4:]))
|
|
if err != nil {
|
|
break
|
|
}
|
|
t.JammingPressed(e, val-12)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *Tracker) JammingPressed(e key.Event, val int) byte {
|
|
if _, ok := t.KeyPlaying[e.Name]; !ok {
|
|
n := noteAsValue(t.OctaveNumberInput.Int.Value(), val)
|
|
instr := t.InstrumentEditor.instrumentDragList.TrackerList.Selected()
|
|
t.KeyPlaying[e.Name] = t.InstrNoteOn(instr, n)
|
|
return n
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (t *Tracker) JammingReleased(e key.Event) bool {
|
|
if noteID, ok := t.KeyPlaying[e.Name]; ok {
|
|
noteID.NoteOff()
|
|
delete(t.KeyPlaying, e.Name)
|
|
return true
|
|
}
|
|
return false
|
|
}
|