feat(tracker): undo entire modelData, not just Song

The modelData is moving towards clear meaning: it's the part of the
GUI state that is undone and also recovered from disk. This changes
the recovery data so that the undo and redo stacks are not undone,
but that is unlikely a good idea anyway, as it grows the recovery
data into unreasonable sizes.

This has also the nice benefit of undoing the cursor position, which
closes #64.
This commit is contained in:
5684185+vsariola@users.noreply.github.com 2023-10-20 17:30:54 +03:00
parent 486bab4185
commit 391b14493c

View File

@ -46,15 +46,16 @@ type (
InstrEnlarged bool InstrEnlarged bool
RecoveryFilePath string RecoveryFilePath string
ChangedSinceRecovery bool ChangedSinceRecovery bool
PrevUndoType string
UndoSkipCounter int
UndoStack []sointu.Song
RedoStack []sointu.Song
} }
Model struct { Model struct {
d modelData d modelData
prevUndoType string
undoSkipCounter int
undoStack []modelData
redoStack []modelData
PlayerMessages <-chan PlayerMessage PlayerMessages <-chan PlayerMessage
modelMessages chan<- interface{} modelMessages chan<- interface{}
} }
@ -223,13 +224,15 @@ func (m *Model) SetSong(song sointu.Song) {
m.setSongNoUndo(song) m.setSongNoUndo(song)
} }
// Returns the current octave for jamming and inputting nodes
func (m *Model) Octave() int {
return m.d.Octave
}
// Sets the current octave for jamming and inputting nodes and returns true if
// it changed. The value is clamped to 0..9
func (m *Model) SetOctave(value int) bool { func (m *Model) SetOctave(value int) bool {
if value < 0 { value = clamp(value, 0, 9)
value = 0
}
if value > 9 {
value = 9
}
if m.d.Octave == value { if m.d.Octave == value {
return false return false
} }
@ -1025,40 +1028,42 @@ func (m *Model) Undo() {
if !m.CanUndo() { if !m.CanUndo() {
return return
} }
m.d.RedoStack = append(m.d.RedoStack, m.d.Song.Copy()) m.redoStack = append(m.redoStack, m.d.Copy())
m.setSongNoUndo(m.d.UndoStack[len(m.d.UndoStack)-1]) m.d = m.undoStack[len(m.undoStack)-1]
m.d.UndoStack = m.d.UndoStack[:len(m.d.UndoStack)-1] m.undoStack = m.undoStack[:len(m.undoStack)-1]
m.limitUndoRedoLengths() m.limitUndoRedoLengths()
m.d.PrevUndoType = "" m.prevUndoType = ""
m.send(m.d.Song.Copy())
} }
func (m *Model) CanUndo() bool { func (m *Model) CanUndo() bool {
return len(m.d.UndoStack) > 0 return len(m.undoStack) > 0
} }
func (m *Model) ClearUndoHistory() { func (m *Model) ClearUndoHistory() {
if len(m.d.UndoStack) > 0 { if len(m.undoStack) > 0 {
m.d.UndoStack = m.d.UndoStack[:0] m.undoStack = m.undoStack[:0]
} }
if len(m.d.RedoStack) > 0 { if len(m.redoStack) > 0 {
m.d.RedoStack = m.d.RedoStack[:0] m.redoStack = m.redoStack[:0]
} }
m.d.PrevUndoType = "" m.prevUndoType = ""
} }
func (m *Model) Redo() { func (m *Model) Redo() {
if !m.CanRedo() { if !m.CanRedo() {
return return
} }
m.d.UndoStack = append(m.d.UndoStack, m.d.Song.Copy()) m.undoStack = append(m.undoStack, m.d.Copy())
m.setSongNoUndo(m.d.RedoStack[len(m.d.RedoStack)-1]) m.d = m.redoStack[len(m.redoStack)-1]
m.d.RedoStack = m.d.RedoStack[:len(m.d.RedoStack)-1] m.redoStack = m.redoStack[:len(m.redoStack)-1]
m.limitUndoRedoLengths() m.limitUndoRedoLengths()
m.d.PrevUndoType = "" m.prevUndoType = ""
m.send(m.d.Song.Copy())
} }
func (m *Model) CanRedo() bool { func (m *Model) CanRedo() bool {
return len(m.d.RedoStack) > 0 return len(m.redoStack) > 0
} }
func (m *Model) SetNoteTracking(value bool) { func (m *Model) SetNoteTracking(value bool) {
@ -1069,10 +1074,6 @@ func (m *Model) NoteTracking() bool {
return m.d.NoteTracking return m.d.NoteTracking
} }
func (m *Model) Octave() int {
return m.d.Octave
}
func (m *Model) Song() sointu.Song { func (m *Model) Song() sointu.Song {
return m.d.Song return m.d.Song
} }
@ -1128,11 +1129,11 @@ func (m *Model) ParamIndex() int {
} }
func (m *Model) limitUndoRedoLengths() { func (m *Model) limitUndoRedoLengths() {
if len(m.d.UndoStack) >= maxUndo { if len(m.undoStack) >= maxUndo {
m.d.UndoStack = m.d.UndoStack[len(m.d.UndoStack)-maxUndo:] m.undoStack = m.undoStack[len(m.undoStack)-maxUndo:]
} }
if len(m.d.RedoStack) >= maxUndo { if len(m.redoStack) >= maxUndo {
m.d.RedoStack = m.d.RedoStack[len(m.d.RedoStack)-maxUndo:] m.redoStack = m.redoStack[len(m.redoStack)-maxUndo:]
} }
} }
@ -1419,14 +1420,14 @@ func (m *Model) send(message interface{}) {
func (m *Model) saveUndo(undoType string, undoSkipping int) { func (m *Model) saveUndo(undoType string, undoSkipping int) {
m.d.ChangedSinceSave = true m.d.ChangedSinceSave = true
m.d.ChangedSinceRecovery = true m.d.ChangedSinceRecovery = true
if m.d.PrevUndoType == undoType && m.d.UndoSkipCounter < undoSkipping { if m.prevUndoType == undoType && m.undoSkipCounter < undoSkipping {
m.d.UndoSkipCounter++ m.undoSkipCounter++
return return
} }
m.d.PrevUndoType = undoType m.prevUndoType = undoType
m.d.UndoSkipCounter = 0 m.undoSkipCounter = 0
m.d.UndoStack = append(m.d.UndoStack, m.d.Song.Copy()) m.undoStack = append(m.undoStack, m.d.Copy())
m.d.RedoStack = m.d.RedoStack[:0] m.redoStack = m.redoStack[:0]
m.limitUndoRedoLengths() m.limitUndoRedoLengths()
} }
@ -1492,6 +1493,21 @@ func NoteIDTrack(track int, note byte) NoteID {
return NoteID{IsInstr: false, Track: track, Note: note} return NoteID{IsInstr: false, Track: track, Note: note}
} }
func (d *modelData) Copy() modelData {
ret := *d
ret.Song = d.Song.Copy()
ret.PatternUseCount = make([][]int, len(d.PatternUseCount))
for i := range ret.PatternUseCount {
ret.PatternUseCount[i] = make([]int, len(d.PatternUseCount[i]))
copy(ret.PatternUseCount[i], d.PatternUseCount[i])
}
ret.UsedIDs = make(map[int]bool)
for k, v := range d.UsedIDs {
ret.UsedIDs[k] = v
}
return ret
}
func clamp(a, min, max int) int { func clamp(a, min, max int) int {
if a < min { if a < min {
return min return min