add cursor undo/redo actions
This commit is contained in:
parent
8d81ef87b8
commit
572eb10e91
|
@ -69,6 +69,9 @@ void FurnaceGUI::startSelection(int xCoarse, int xFine, int y, bool fullRow) {
|
||||||
selStart.y=y;
|
selStart.y=y;
|
||||||
selEnd.y=y;
|
selEnd.y=y;
|
||||||
} else {
|
} else {
|
||||||
|
if (xCoarse!=cursor.xCoarse || y!=cursor.y) {
|
||||||
|
makeCursorUndo();
|
||||||
|
}
|
||||||
cursor.xCoarse=xCoarse;
|
cursor.xCoarse=xCoarse;
|
||||||
cursor.xFine=xFine;
|
cursor.xFine=xFine;
|
||||||
cursor.y=y;
|
cursor.y=y;
|
||||||
|
@ -208,6 +211,9 @@ void FurnaceGUI::finishSelection() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::moveCursor(int x, int y, bool select) {
|
void FurnaceGUI::moveCursor(int x, int y, bool select) {
|
||||||
|
if (y>=editStepCoarse || y<=-editStepCoarse || x<=-5 || x>=5 ) {
|
||||||
|
makeCursorUndo();
|
||||||
|
}
|
||||||
if (!select) {
|
if (!select) {
|
||||||
finishSelection();
|
finishSelection();
|
||||||
}
|
}
|
||||||
|
@ -326,6 +332,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::moveCursorPrevChannel(bool overflow) {
|
void FurnaceGUI::moveCursorPrevChannel(bool overflow) {
|
||||||
|
makeCursorUndo();
|
||||||
finishSelection();
|
finishSelection();
|
||||||
curNibble=false;
|
curNibble=false;
|
||||||
|
|
||||||
|
@ -354,6 +361,7 @@ void FurnaceGUI::moveCursorPrevChannel(bool overflow) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::moveCursorNextChannel(bool overflow) {
|
void FurnaceGUI::moveCursorNextChannel(bool overflow) {
|
||||||
|
makeCursorUndo();
|
||||||
finishSelection();
|
finishSelection();
|
||||||
curNibble=false;
|
curNibble=false;
|
||||||
|
|
||||||
|
@ -382,6 +390,7 @@ void FurnaceGUI::moveCursorNextChannel(bool overflow) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::moveCursorTop(bool select) {
|
void FurnaceGUI::moveCursorTop(bool select) {
|
||||||
|
makeCursorUndo();
|
||||||
if (!select) {
|
if (!select) {
|
||||||
finishSelection();
|
finishSelection();
|
||||||
}
|
}
|
||||||
|
@ -403,6 +412,7 @@ void FurnaceGUI::moveCursorTop(bool select) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::moveCursorBottom(bool select) {
|
void FurnaceGUI::moveCursorBottom(bool select) {
|
||||||
|
makeCursorUndo();
|
||||||
if (!select) {
|
if (!select) {
|
||||||
finishSelection();
|
finishSelection();
|
||||||
}
|
}
|
||||||
|
|
|
@ -731,6 +731,25 @@ void FurnaceGUI::drawDebug() {
|
||||||
ImGui::Text("result: %.0f%%",realVol*100.0f);
|
ImGui::Text("result: %.0f%%",realVol*100.0f);
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
|
if (ImGui::TreeNode("Cursor Undo Debug")) {
|
||||||
|
auto DrawSpot=[&](const CursorJumpPoint& spot) {
|
||||||
|
ImGui::Text("[%d:%d] <%d:%d, %d>", spot.subSong, spot.order, spot.point.xCoarse, spot.point.xFine, spot.point.y);
|
||||||
|
};
|
||||||
|
if (ImGui::BeginChild("##CursorUndoDebugChild", ImVec2(0, 300), true)) {
|
||||||
|
if (ImGui::BeginTable("##CursorUndoDebug", 2, ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) {
|
||||||
|
for (size_t row=0; row<MAX(cursorUndoHist.size(),cursorRedoHist.size()); ++row) {
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (row<cursorUndoHist.size()) DrawSpot(cursorUndoHist[cursorUndoHist.size()-row-1]);
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
if (row<cursorRedoHist.size()) DrawSpot(cursorRedoHist[cursorRedoHist.size()-row-1]);
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
if (ImGui::TreeNode("User Interface")) {
|
if (ImGui::TreeNode("User Interface")) {
|
||||||
if (ImGui::Button("Inspect")) {
|
if (ImGui::Button("Inspect")) {
|
||||||
inspectorOpen=!inspectorOpen;
|
inspectorOpen=!inspectorOpen;
|
||||||
|
|
|
@ -680,10 +680,15 @@ void FurnaceGUI::doAction(int what) {
|
||||||
latchTarget=0;
|
latchTarget=0;
|
||||||
latchNibble=false;
|
latchNibble=false;
|
||||||
break;
|
break;
|
||||||
case GUI_ACTION_PAT_ABSORB_INSTRUMENT: {
|
case GUI_ACTION_PAT_ABSORB_INSTRUMENT:
|
||||||
doAbsorbInstrument();
|
doAbsorbInstrument();
|
||||||
break;
|
break;
|
||||||
}
|
case GUI_ACTION_PAT_CURSOR_UNDO:
|
||||||
|
doCursorUndo();
|
||||||
|
break;
|
||||||
|
case GUI_ACTION_PAT_CURSOR_REDO:
|
||||||
|
doCursorRedo();
|
||||||
|
break;
|
||||||
|
|
||||||
case GUI_ACTION_INS_LIST_ADD:
|
case GUI_ACTION_INS_LIST_ADD:
|
||||||
if (settings.insTypeMenu) {
|
if (settings.insTypeMenu) {
|
||||||
|
|
|
@ -678,6 +678,7 @@ void FurnaceGUI::doPasteFurnace(PasteMode mode, int arg, bool readClipboard, Str
|
||||||
|
|
||||||
if (readClipboard) {
|
if (readClipboard) {
|
||||||
if (settings.cursorPastePos) {
|
if (settings.cursorPastePos) {
|
||||||
|
makeCursorUndo();
|
||||||
cursor.y=j;
|
cursor.y=j;
|
||||||
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
|
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
|
||||||
selStart=cursor;
|
selStart=cursor;
|
||||||
|
@ -1220,6 +1221,7 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
|
||||||
|
|
||||||
if (readClipboard) {
|
if (readClipboard) {
|
||||||
if (settings.cursorPastePos) {
|
if (settings.cursorPastePos) {
|
||||||
|
makeCursorUndo();
|
||||||
cursor.y=j;
|
cursor.y=j;
|
||||||
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
|
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
|
||||||
selStart=cursor;
|
selStart=cursor;
|
||||||
|
@ -2061,3 +2063,52 @@ void FurnaceGUI::doRedo() {
|
||||||
|
|
||||||
redoHist.pop_back();
|
redoHist.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CursorJumpPoint FurnaceGUI::getCurrentCursorJumpPoint() {
|
||||||
|
return CursorJumpPoint(cursor, curOrder, e->getCurrentSubSong());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FurnaceGUI::applyCursorJumpPoint(const CursorJumpPoint& spot) {
|
||||||
|
cursor=spot.point;
|
||||||
|
curOrder=MIN(e->curSubSong->ordersLen-1, spot.order);
|
||||||
|
e->setOrder(curOrder);
|
||||||
|
e->changeSongP(spot.subSong);
|
||||||
|
if (!settings.cursorMoveNoScroll) {
|
||||||
|
updateScroll(cursor.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FurnaceGUI::makeCursorUndo() {
|
||||||
|
CursorJumpPoint spot = getCurrentCursorJumpPoint();
|
||||||
|
if (!cursorUndoHist.empty() && spot == cursorUndoHist.back()) return;
|
||||||
|
|
||||||
|
if (cursorUndoHist.size()>=settings.maxUndoSteps) cursorUndoHist.pop_front();
|
||||||
|
cursorUndoHist.push_back(spot);
|
||||||
|
|
||||||
|
// redo history no longer relevant, we've changed timeline
|
||||||
|
cursorRedoHist.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FurnaceGUI::doCursorUndo() {
|
||||||
|
if (cursorUndoHist.empty()) return;
|
||||||
|
|
||||||
|
// allow returning to current spot
|
||||||
|
if (cursorRedoHist.size()>=settings.maxUndoSteps) cursorRedoHist.pop_front();
|
||||||
|
cursorRedoHist.push_back(getCurrentCursorJumpPoint());
|
||||||
|
|
||||||
|
// apply spot
|
||||||
|
applyCursorJumpPoint(cursorUndoHist.back());
|
||||||
|
cursorUndoHist.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FurnaceGUI::doCursorRedo() {
|
||||||
|
if (cursorRedoHist.empty()) return;
|
||||||
|
|
||||||
|
// allow returning to current spot
|
||||||
|
if (cursorUndoHist.size()>=settings.maxUndoSteps) cursorUndoHist.pop_front();
|
||||||
|
cursorUndoHist.push_back(getCurrentCursorJumpPoint());
|
||||||
|
|
||||||
|
// apply spot
|
||||||
|
applyCursorJumpPoint(cursorRedoHist.back());
|
||||||
|
cursorRedoHist.pop_back();
|
||||||
|
}
|
|
@ -560,6 +560,7 @@ void FurnaceGUI::drawFindReplace() {
|
||||||
if (ImGui::TableNextColumn()) {
|
if (ImGui::TableNextColumn()) {
|
||||||
snprintf(tempID,1024,ICON_FA_CHEVRON_RIGHT "##_FR%d",index);
|
snprintf(tempID,1024,ICON_FA_CHEVRON_RIGHT "##_FR%d",index);
|
||||||
if (ImGui::Selectable(tempID)) {
|
if (ImGui::Selectable(tempID)) {
|
||||||
|
makeCursorUndo();
|
||||||
e->changeSongP(i.subsong);
|
e->changeSongP(i.subsong);
|
||||||
if (e->isPlaying()) {
|
if (e->isPlaying()) {
|
||||||
followPattern=false;
|
followPattern=false;
|
||||||
|
|
|
@ -1211,6 +1211,7 @@ void FurnaceGUI::play(int row) {
|
||||||
memset(chanOscBright,0,DIV_MAX_CHANS*sizeof(float));
|
memset(chanOscBright,0,DIV_MAX_CHANS*sizeof(float));
|
||||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||||
memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS);
|
memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS);
|
||||||
|
if (followPattern) makeCursorUndo();
|
||||||
if (!followPattern) e->setOrder(curOrder);
|
if (!followPattern) e->setOrder(curOrder);
|
||||||
if (row>0) {
|
if (row>0) {
|
||||||
if (!e->playToRow(row)) {
|
if (!e->playToRow(row)) {
|
||||||
|
|
|
@ -818,6 +818,8 @@ enum FurnaceGUIActions {
|
||||||
GUI_ACTION_PAT_SCROLL_MODE,
|
GUI_ACTION_PAT_SCROLL_MODE,
|
||||||
GUI_ACTION_PAT_CLEAR_LATCH,
|
GUI_ACTION_PAT_CLEAR_LATCH,
|
||||||
GUI_ACTION_PAT_ABSORB_INSTRUMENT,
|
GUI_ACTION_PAT_ABSORB_INSTRUMENT,
|
||||||
|
GUI_ACTION_PAT_CURSOR_UNDO,
|
||||||
|
GUI_ACTION_PAT_CURSOR_REDO,
|
||||||
GUI_ACTION_PAT_MAX,
|
GUI_ACTION_PAT_MAX,
|
||||||
|
|
||||||
GUI_ACTION_INS_LIST_MIN,
|
GUI_ACTION_INS_LIST_MIN,
|
||||||
|
@ -1103,6 +1105,22 @@ struct UndoStep {
|
||||||
newPatLen(0) {}
|
newPatLen(0) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CursorJumpPoint {
|
||||||
|
SelectionPoint point;
|
||||||
|
int order;
|
||||||
|
int subSong;
|
||||||
|
CursorJumpPoint(const SelectionPoint& p, int o, int ss):
|
||||||
|
point(p), order(o), subSong(ss) {}
|
||||||
|
CursorJumpPoint():
|
||||||
|
point(), order(0), subSong(0) {}
|
||||||
|
bool operator== (const CursorJumpPoint& spot) {
|
||||||
|
return point.xCoarse==spot.point.xCoarse && point.xFine==spot.point.xFine && point.y==spot.point.y && order==spot.order && subSong==spot.subSong;
|
||||||
|
}
|
||||||
|
bool operator!= (const CursorJumpPoint& spot) {
|
||||||
|
return !(*this == spot);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// -1 = any
|
// -1 = any
|
||||||
struct MIDIBind {
|
struct MIDIBind {
|
||||||
int type, channel, data1, data2;
|
int type, channel, data1, data2;
|
||||||
|
@ -2500,6 +2518,8 @@ class FurnaceGUI {
|
||||||
std::map<unsigned short,DivPattern*> oldPatMap;
|
std::map<unsigned short,DivPattern*> oldPatMap;
|
||||||
FixedQueue<UndoStep,256> undoHist;
|
FixedQueue<UndoStep,256> undoHist;
|
||||||
FixedQueue<UndoStep,256> redoHist;
|
FixedQueue<UndoStep,256> redoHist;
|
||||||
|
FixedQueue<CursorJumpPoint,256> cursorUndoHist;
|
||||||
|
FixedQueue<CursorJumpPoint,256> cursorRedoHist;
|
||||||
|
|
||||||
// sample editor specific
|
// sample editor specific
|
||||||
double sampleZoom;
|
double sampleZoom;
|
||||||
|
@ -2936,6 +2956,12 @@ class FurnaceGUI {
|
||||||
|
|
||||||
void doGenerateWave();
|
void doGenerateWave();
|
||||||
|
|
||||||
|
CursorJumpPoint getCurrentCursorJumpPoint();
|
||||||
|
void applyCursorJumpPoint(const CursorJumpPoint& spot);
|
||||||
|
void makeCursorUndo();
|
||||||
|
void doCursorUndo();
|
||||||
|
void doCursorRedo();
|
||||||
|
|
||||||
void doUndoSample();
|
void doUndoSample();
|
||||||
void doRedoSample();
|
void doRedoSample();
|
||||||
|
|
||||||
|
|
|
@ -688,6 +688,8 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
|
||||||
D("PAT_SCROLL_MODE", _N("Change mobile scroll mode"), 0),
|
D("PAT_SCROLL_MODE", _N("Change mobile scroll mode"), 0),
|
||||||
D("PAT_CLEAR_LATCH", _N("Clear note input latch"), 0),
|
D("PAT_CLEAR_LATCH", _N("Clear note input latch"), 0),
|
||||||
D("PAT_ABSORB_INSTRUMENT", _N("Absorb instrument/octave from status at cursor"), 0),
|
D("PAT_ABSORB_INSTRUMENT", _N("Absorb instrument/octave from status at cursor"), 0),
|
||||||
|
D("PAT_CURSOR_UNDO", _N("Return cursor to previous jump point"), 0),
|
||||||
|
D("PAT_CURSOR_REDO", _N("Reverse recent cursor undo"), 0),
|
||||||
D("PAT_MAX", "", NOT_AN_ACTION),
|
D("PAT_MAX", "", NOT_AN_ACTION),
|
||||||
|
|
||||||
D("INS_LIST_MIN", _N("---Instrument list"), NOT_AN_ACTION),
|
D("INS_LIST_MIN", _N("---Instrument list"), NOT_AN_ACTION),
|
||||||
|
|
|
@ -2428,6 +2428,8 @@ void FurnaceGUI::drawSettings() {
|
||||||
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_LATCH);
|
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_LATCH);
|
||||||
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CLEAR_LATCH);
|
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CLEAR_LATCH);
|
||||||
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_ABSORB_INSTRUMENT);
|
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_ABSORB_INSTRUMENT);
|
||||||
|
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_UNDO);
|
||||||
|
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_REDO);
|
||||||
|
|
||||||
KEYBIND_CONFIG_END;
|
KEYBIND_CONFIG_END;
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
|
|
|
@ -36,6 +36,7 @@ void FurnaceGUI::drawSubSongs(bool asChild) {
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
if (ImGui::Selectable(id,i==e->getCurrentSubSong())) {
|
if (ImGui::Selectable(id,i==e->getCurrentSubSong())) {
|
||||||
|
makeCursorUndo();
|
||||||
e->changeSongP(i);
|
e->changeSongP(i);
|
||||||
updateScroll(0);
|
updateScroll(0);
|
||||||
oldRow=0;
|
oldRow=0;
|
||||||
|
@ -72,6 +73,7 @@ void FurnaceGUI::drawSubSongs(bool asChild) {
|
||||||
if (!e->addSubSong()) {
|
if (!e->addSubSong()) {
|
||||||
showError(_("too many subsongs!"));
|
showError(_("too many subsongs!"));
|
||||||
} else {
|
} else {
|
||||||
|
makeCursorUndo();
|
||||||
e->changeSongP(e->song.subsong.size()-1);
|
e->changeSongP(e->song.subsong.size()-1);
|
||||||
updateScroll(0);
|
updateScroll(0);
|
||||||
oldRow=0;
|
oldRow=0;
|
||||||
|
@ -92,6 +94,7 @@ void FurnaceGUI::drawSubSongs(bool asChild) {
|
||||||
if (!e->duplicateSubSong(e->getCurrentSubSong())) {
|
if (!e->duplicateSubSong(e->getCurrentSubSong())) {
|
||||||
showError(_("too many subsongs!"));
|
showError(_("too many subsongs!"));
|
||||||
} else {
|
} else {
|
||||||
|
makeCursorUndo();
|
||||||
e->changeSongP(e->song.subsong.size()-1);
|
e->changeSongP(e->song.subsong.size()-1);
|
||||||
updateScroll(0);
|
updateScroll(0);
|
||||||
oldRow=0;
|
oldRow=0;
|
||||||
|
|
Loading…
Reference in a new issue