GUI: undo, part 1
This commit is contained in:
parent
8d587f72f2
commit
ca62347917
203
src/gui/gui.cpp
203
src/gui/gui.cpp
|
@ -406,6 +406,7 @@ void FurnaceGUI::drawOrders() {
|
|||
snprintf(selID,16,"%.2x##O_%.2x_%.2x",e->song.orders.ord[j][i],j,i);
|
||||
if (ImGui::Selectable(selID)) {
|
||||
if (e->getOrder()==i) {
|
||||
prepareUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
if (changeAllOrders) {
|
||||
for (int k=0; k<e->getChannelCount(e->song.system); k++) {
|
||||
if (e->song.orders.ord[k][i]<0x7f) e->song.orders.ord[k][i]++;
|
||||
|
@ -413,12 +414,14 @@ void FurnaceGUI::drawOrders() {
|
|||
} else {
|
||||
if (e->song.orders.ord[j][i]<0x7f) e->song.orders.ord[j][i]++;
|
||||
}
|
||||
makeUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
} else {
|
||||
e->setOrder(i);
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||
if (e->getOrder()==i) {
|
||||
prepareUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
if (changeAllOrders) {
|
||||
for (int k=0; k<e->getChannelCount(e->song.system); k++) {
|
||||
if (e->song.orders.ord[k][i]>0) e->song.orders.ord[k][i]--;
|
||||
|
@ -426,6 +429,7 @@ void FurnaceGUI::drawOrders() {
|
|||
} else {
|
||||
if (e->song.orders.ord[j][i]>0) e->song.orders.ord[j][i]--;
|
||||
}
|
||||
makeUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
} else {
|
||||
e->setOrder(i);
|
||||
}
|
||||
|
@ -439,27 +443,39 @@ void FurnaceGUI::drawOrders() {
|
|||
ImGui::NextColumn();
|
||||
if (ImGui::Button(ICON_FA_PLUS)) {
|
||||
// add order row (new)
|
||||
prepareUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
e->addOrder(false,false);
|
||||
makeUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
}
|
||||
if (ImGui::Button(ICON_FA_MINUS)) {
|
||||
// remove this order row
|
||||
prepareUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
e->deleteOrder();
|
||||
makeUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
}
|
||||
if (ImGui::Button(ICON_FA_FILES_O)) {
|
||||
// duplicate order row
|
||||
prepareUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
e->addOrder(true,false);
|
||||
makeUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
}
|
||||
if (ImGui::Button(ICON_FA_ANGLE_UP)) {
|
||||
// move order row up
|
||||
prepareUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
e->moveOrderUp();
|
||||
makeUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
}
|
||||
if (ImGui::Button(ICON_FA_ANGLE_DOWN)) {
|
||||
// move order row down
|
||||
prepareUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
e->moveOrderDown();
|
||||
makeUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
}
|
||||
if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) {
|
||||
// duplicate order row at end
|
||||
prepareUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
e->addOrder(true,true);
|
||||
makeUndo(GUI_ACTION_CHANGE_ORDER);
|
||||
}
|
||||
if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) {
|
||||
// whether to change one or all orders in a row
|
||||
|
@ -1611,8 +1627,95 @@ void FurnaceGUI::editAdvance() {
|
|||
updateScroll(cursor.y);
|
||||
}
|
||||
|
||||
void FurnaceGUI::prepareUndo(ActionType action) {
|
||||
int order=e->getOrder();
|
||||
switch (action) {
|
||||
case GUI_ACTION_CHANGE_SYSTEM:
|
||||
oldSystem=e->song.system;
|
||||
break;
|
||||
case GUI_ACTION_CHANGE_ORDER:
|
||||
oldOrders=e->song.orders;
|
||||
oldOrdersLen=e->song.ordersLen;
|
||||
break;
|
||||
case GUI_ACTION_PATTERN_EDIT:
|
||||
case GUI_ACTION_PATTERN_DELETE:
|
||||
case GUI_ACTION_PATTERN_PULL:
|
||||
case GUI_ACTION_PATTERN_PUSH:
|
||||
case GUI_ACTION_PATTERN_CUT:
|
||||
case GUI_ACTION_PATTERN_PASTE:
|
||||
for (int i=0; i<17; i++) {
|
||||
memcpy(oldPat[i],e->song.pat[i].getPattern(e->song.orders.ord[i][order],false),sizeof(DivPattern));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FurnaceGUI::makeUndo(ActionType action) {
|
||||
bool doPush=false;
|
||||
UndoStep s;
|
||||
s.type=action;
|
||||
s.cursor=cursor;
|
||||
s.selStart=selStart;
|
||||
s.selEnd=selEnd;
|
||||
int order=e->getOrder();
|
||||
s.order=order;
|
||||
s.nibble=curNibble;
|
||||
switch (action) {
|
||||
case GUI_ACTION_CHANGE_SYSTEM:
|
||||
if (oldSystem!=e->song.system) {
|
||||
s.oldSystem=oldSystem;
|
||||
s.newSystem=e->song.system;
|
||||
doPush=true;
|
||||
}
|
||||
break;
|
||||
case GUI_ACTION_CHANGE_ORDER:
|
||||
for (int i=0; i<32; i++) {
|
||||
for (int j=0; j<128; j++) {
|
||||
if (oldOrders.ord[i][j]!=e->song.orders.ord[i][j]) {
|
||||
s.ord.push_back(UndoOrderData(i,j,oldOrders.ord[i][j],e->song.orders.ord[i][j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
s.oldOrdersLen=oldOrdersLen;
|
||||
s.newOrdersLen=e->song.ordersLen;
|
||||
if (oldOrdersLen!=e->song.ordersLen) {
|
||||
doPush=true;
|
||||
}
|
||||
if (!s.ord.empty()) {
|
||||
doPush=true;
|
||||
}
|
||||
break;
|
||||
case GUI_ACTION_PATTERN_EDIT:
|
||||
case GUI_ACTION_PATTERN_DELETE:
|
||||
case GUI_ACTION_PATTERN_PULL:
|
||||
case GUI_ACTION_PATTERN_PUSH:
|
||||
case GUI_ACTION_PATTERN_CUT:
|
||||
case GUI_ACTION_PATTERN_PASTE:
|
||||
for (int i=0; i<17; i++) {
|
||||
DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][order],false);
|
||||
for (int j=0; j<e->song.patLen; j++) {
|
||||
for (int k=0; k<16; k++) {
|
||||
if (p->data[j][k]!=oldPat[i]->data[j][k]) {
|
||||
s.pat.push_back(UndoPatternData(i,e->song.orders.ord[i][order],j,k,p->data[j][k],oldPat[i]->data[j][k]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!s.pat.empty()) {
|
||||
doPush=true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (doPush) {
|
||||
undoHist.push_back(s);
|
||||
redoHist.clear();
|
||||
if (undoHist.size()>maxUndoSteps) undoHist.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void FurnaceGUI::doDelete() {
|
||||
finishSelection();
|
||||
prepareUndo(GUI_ACTION_PATTERN_DELETE);
|
||||
curNibble=false;
|
||||
|
||||
int iCoarse=selStart.xCoarse;
|
||||
|
@ -1630,10 +1733,13 @@ void FurnaceGUI::doDelete() {
|
|||
}
|
||||
iFine=0;
|
||||
}
|
||||
|
||||
makeUndo(GUI_ACTION_PATTERN_DELETE);
|
||||
}
|
||||
|
||||
void FurnaceGUI::doPullDelete() {
|
||||
finishSelection();
|
||||
prepareUndo(GUI_ACTION_PATTERN_PULL);
|
||||
curNibble=false;
|
||||
|
||||
int iCoarse=selStart.xCoarse;
|
||||
|
@ -1658,10 +1764,13 @@ void FurnaceGUI::doPullDelete() {
|
|||
}
|
||||
iFine=0;
|
||||
}
|
||||
|
||||
makeUndo(GUI_ACTION_PATTERN_PULL);
|
||||
}
|
||||
|
||||
void FurnaceGUI::doInsert() {
|
||||
finishSelection();
|
||||
prepareUndo(GUI_ACTION_PATTERN_PUSH);
|
||||
curNibble=false;
|
||||
|
||||
int iCoarse=selStart.xCoarse;
|
||||
|
@ -1686,11 +1795,16 @@ void FurnaceGUI::doInsert() {
|
|||
}
|
||||
iFine=0;
|
||||
}
|
||||
|
||||
makeUndo(GUI_ACTION_PATTERN_PUSH);
|
||||
}
|
||||
|
||||
void FurnaceGUI::doCopy(bool cut) {
|
||||
finishSelection();
|
||||
if (cut) curNibble=false;
|
||||
if (cut) {
|
||||
curNibble=false;
|
||||
prepareUndo(GUI_ACTION_PATTERN_CUT);
|
||||
}
|
||||
clipboard=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)\n%d",DIV_ENGINE_VERSION,selStart.xFine);
|
||||
|
||||
for (int j=selStart.y; j<=selEnd.y; j++) {
|
||||
|
@ -1723,10 +1837,15 @@ void FurnaceGUI::doCopy(bool cut) {
|
|||
}
|
||||
}
|
||||
SDL_SetClipboardText(clipboard.c_str());
|
||||
|
||||
if (cut) {
|
||||
makeUndo(GUI_ACTION_PATTERN_CUT);
|
||||
}
|
||||
}
|
||||
|
||||
void FurnaceGUI::doPaste() {
|
||||
finishSelection();
|
||||
prepareUndo(GUI_ACTION_PATTERN_PASTE);
|
||||
char* clipText=SDL_GetClipboardText();
|
||||
if (clipText!=NULL) {
|
||||
if (clipText[0]) {
|
||||
|
@ -1828,6 +1947,53 @@ void FurnaceGUI::doPaste() {
|
|||
}
|
||||
j++;
|
||||
}
|
||||
|
||||
makeUndo(GUI_ACTION_PATTERN_PASTE);
|
||||
}
|
||||
|
||||
void FurnaceGUI::doUndo() {
|
||||
if (undoHist.empty()) return;
|
||||
UndoStep& us=undoHist.back();
|
||||
redoHist.push_back(us);
|
||||
|
||||
switch (us.type) {
|
||||
case GUI_ACTION_CHANGE_SYSTEM:
|
||||
e->changeSystem(us.oldSystem);
|
||||
updateWindowTitle();
|
||||
break;
|
||||
case GUI_ACTION_CHANGE_ORDER:
|
||||
e->song.ordersLen=us.oldOrdersLen;
|
||||
for (UndoOrderData& i: us.ord) {
|
||||
e->song.orders.ord[i.chan][i.ord]=i.oldVal;
|
||||
}
|
||||
break;
|
||||
case GUI_ACTION_PATTERN_EDIT:
|
||||
case GUI_ACTION_PATTERN_DELETE:
|
||||
case GUI_ACTION_PATTERN_PULL:
|
||||
case GUI_ACTION_PATTERN_PUSH:
|
||||
case GUI_ACTION_PATTERN_CUT:
|
||||
case GUI_ACTION_PATTERN_PASTE:
|
||||
for (UndoPatternData& i: us.pat) {
|
||||
DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true);
|
||||
p->data[i.row][i.col]=i.oldVal;
|
||||
}
|
||||
if (!e->isPlaying()) {
|
||||
cursor=us.cursor;
|
||||
selStart=us.selStart;
|
||||
selEnd=us.selEnd;
|
||||
curNibble=us.nibble;
|
||||
updateScroll(cursor.y);
|
||||
e->setOrder(us.order);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
undoHist.pop_back();
|
||||
}
|
||||
|
||||
void FurnaceGUI::doRedo() {
|
||||
|
||||
}
|
||||
|
||||
void FurnaceGUI::keyDown(SDL_Event& ev) {
|
||||
|
@ -1858,6 +2024,13 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
|
|||
case GUI_WINDOW_PATTERN: {
|
||||
if (ev.key.keysym.mod&KMOD_CTRL) {
|
||||
switch (ev.key.keysym.sym) {
|
||||
case SDLK_z:
|
||||
if (ev.key.keysym.mod&KMOD_SHIFT) {
|
||||
doRedo();
|
||||
} else {
|
||||
doUndo();
|
||||
}
|
||||
break;
|
||||
case SDLK_x:
|
||||
doCopy(true);
|
||||
break;
|
||||
|
@ -1904,6 +2077,8 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
|
|||
int key=noteKeys.at(ev.key.keysym.sym);
|
||||
int num=12*curOctave+key;
|
||||
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true);
|
||||
|
||||
prepareUndo(GUI_ACTION_PATTERN_EDIT);
|
||||
|
||||
if (key==100) { // note off
|
||||
pat->data[cursor.y][0]=100;
|
||||
|
@ -1919,12 +2094,14 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
|
|||
}
|
||||
editAdvance();
|
||||
curNibble=false;
|
||||
makeUndo(GUI_ACTION_PATTERN_EDIT);
|
||||
} catch (std::out_of_range& e) {
|
||||
}
|
||||
} else { // value
|
||||
try {
|
||||
int num=valueKeys.at(ev.key.keysym.sym);
|
||||
DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true);
|
||||
prepareUndo(GUI_ACTION_PATTERN_EDIT);
|
||||
if (pat->data[cursor.y][cursor.xFine+1]==-1) pat->data[cursor.y][cursor.xFine+1]=0;
|
||||
pat->data[cursor.y][cursor.xFine+1]=((pat->data[cursor.y][cursor.xFine+1]<<4)|num)&0xff;
|
||||
if (cursor.xFine==1) { // instrument
|
||||
|
@ -1951,6 +2128,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
|
|||
curNibble=!curNibble;
|
||||
if (!curNibble) editAdvance();
|
||||
}
|
||||
makeUndo(GUI_ACTION_PATTERN_EDIT);
|
||||
} catch (std::out_of_range& e) {
|
||||
}
|
||||
}
|
||||
|
@ -2123,6 +2301,8 @@ int FurnaceGUI::load(String path) {
|
|||
}
|
||||
}
|
||||
lastError="everything OK";
|
||||
undoHist.clear();
|
||||
redoHist.clear();
|
||||
updateWindowTitle();
|
||||
return 0;
|
||||
}
|
||||
|
@ -2167,8 +2347,10 @@ void FurnaceGUI::processDrags(int dragX, int dragY) {
|
|||
|
||||
#define sysChangeOption(x) \
|
||||
if (ImGui::MenuItem(e->getSystemName(x),NULL,e->song.system==x)) { \
|
||||
prepareUndo(GUI_ACTION_CHANGE_SYSTEM); \
|
||||
e->changeSystem(x); \
|
||||
updateWindowTitle(); \
|
||||
makeUndo(GUI_ACTION_CHANGE_SYSTEM); \
|
||||
}
|
||||
|
||||
bool FurnaceGUI::loop() {
|
||||
|
@ -2239,6 +2421,8 @@ bool FurnaceGUI::loop() {
|
|||
if (ImGui::BeginMenu("file")) {
|
||||
if (ImGui::MenuItem("new")) {
|
||||
e->createNew();
|
||||
undoHist.clear();
|
||||
redoHist.clear();
|
||||
}
|
||||
if (ImGui::MenuItem("open...")) {
|
||||
openFileDialog(GUI_FILE_OPEN);
|
||||
|
@ -2270,8 +2454,8 @@ bool FurnaceGUI::loop() {
|
|||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("edit")) {
|
||||
ImGui::MenuItem("undo");
|
||||
ImGui::MenuItem("redo");
|
||||
if (ImGui::MenuItem("undo")) doUndo();
|
||||
if (ImGui::MenuItem("redo")) doRedo();
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("cut")) doCopy(true);
|
||||
if (ImGui::MenuItem("copy")) doCopy(false);
|
||||
|
@ -2489,6 +2673,10 @@ bool FurnaceGUI::init() {
|
|||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".wav",ImVec4(1.0f,1.0f,0.5f,1.0f),ICON_FA_FILE_AUDIO_O);
|
||||
|
||||
updateWindowTitle();
|
||||
|
||||
for (int i=0; i<17; i++) {
|
||||
oldPat[i]=new DivPattern;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2499,6 +2687,10 @@ bool FurnaceGUI::finish() {
|
|||
ImGui::DestroyContext();
|
||||
SDL_DestroyRenderer(sdlRend);
|
||||
SDL_DestroyWindow(sdlWin);
|
||||
|
||||
for (int i=0; i<17; i++) {
|
||||
delete oldPat[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -2515,6 +2707,7 @@ FurnaceGUI::FurnaceGUI():
|
|||
aboutHue(0.0f),
|
||||
mainFontSize(18),
|
||||
patFontSize(18),
|
||||
maxUndoSteps(100),
|
||||
curIns(0),
|
||||
curWave(0),
|
||||
curSample(0),
|
||||
|
@ -2551,7 +2744,9 @@ FurnaceGUI::FurnaceGUI():
|
|||
macroDragMax(0),
|
||||
macroDragActive(false),
|
||||
nextScroll(-1.0f),
|
||||
nextAddScroll(0.0f) {
|
||||
nextAddScroll(0.0f),
|
||||
oldSystem(DIV_SYSTEM_NULL),
|
||||
oldOrdersLen(0) {
|
||||
uiColors[GUI_COLOR_BACKGROUND]=ImVec4(0.1f,0.1f,0.1f,1.0f);
|
||||
uiColors[GUI_COLOR_FRAME_BACKGROUND]=ImVec4(0.0f,0.0f,0.0f,0.85f);
|
||||
uiColors[GUI_COLOR_CHANNEL_FM]=ImVec4(0.2f,0.8f,1.0f,1.0f);
|
||||
|
|
|
@ -79,6 +79,7 @@ struct SelectionPoint {
|
|||
|
||||
enum ActionType {
|
||||
GUI_ACTION_CHANGE_SYSTEM,
|
||||
GUI_ACTION_CHANGE_ORDER,
|
||||
GUI_ACTION_PATTERN_EDIT,
|
||||
GUI_ACTION_PATTERN_DELETE,
|
||||
GUI_ACTION_PATTERN_PULL,
|
||||
|
@ -87,13 +88,38 @@ enum ActionType {
|
|||
GUI_ACTION_PATTERN_PASTE
|
||||
};
|
||||
|
||||
struct UndoData {
|
||||
int data;
|
||||
struct UndoPatternData {
|
||||
int chan, pat, row, col;
|
||||
short oldVal, newVal;
|
||||
UndoPatternData(int c, int p, int r, int co, short v1, short v2):
|
||||
chan(c),
|
||||
pat(p),
|
||||
row(r),
|
||||
col(c),
|
||||
oldVal(v1),
|
||||
newVal(v2) {}
|
||||
};
|
||||
|
||||
struct UndoOrderData {
|
||||
int chan, ord;
|
||||
unsigned char oldVal, newVal;
|
||||
UndoOrderData(int c, int o, unsigned char v1, unsigned char v2):
|
||||
chan(c),
|
||||
ord(o),
|
||||
oldVal(v1),
|
||||
newVal(v2) {}
|
||||
};
|
||||
|
||||
struct UndoStep {
|
||||
ActionType type;
|
||||
std::vector<UndoData> data;
|
||||
SelectionPoint cursor, selStart, selEnd;
|
||||
int order;
|
||||
bool nibble;
|
||||
DivSystem oldSystem, newSystem;
|
||||
int oldOrdersLen, newOrdersLen;
|
||||
int oldPatLen, newPatLen;
|
||||
std::vector<UndoOrderData> ord;
|
||||
std::vector<UndoPatternData> pat;
|
||||
};
|
||||
|
||||
class FurnaceGUI {
|
||||
|
@ -123,6 +149,7 @@ class FurnaceGUI {
|
|||
ImVec4 volColors[128];
|
||||
|
||||
int mainFontSize, patFontSize;
|
||||
size_t maxUndoSteps;
|
||||
|
||||
char finalLayoutPath[4096];
|
||||
|
||||
|
@ -162,6 +189,10 @@ class FurnaceGUI {
|
|||
|
||||
ImVec2 patWindowPos, patWindowSize;
|
||||
|
||||
DivSystem oldSystem;
|
||||
int oldOrdersLen;
|
||||
DivOrders oldOrders;
|
||||
DivPattern* oldPat[17];
|
||||
std::deque<UndoStep> undoHist;
|
||||
std::deque<UndoStep> redoHist;
|
||||
|
||||
|
@ -190,11 +221,15 @@ class FurnaceGUI {
|
|||
|
||||
void moveCursor(int x, int y);
|
||||
void editAdvance();
|
||||
void prepareUndo(ActionType action);
|
||||
void makeUndo(ActionType action);
|
||||
void doDelete();
|
||||
void doPullDelete();
|
||||
void doInsert();
|
||||
void doCopy(bool cut);
|
||||
void doPaste();
|
||||
void doUndo();
|
||||
void doRedo();
|
||||
|
||||
void keyDown(SDL_Event& ev);
|
||||
void keyUp(SDL_Event& ev);
|
||||
|
|
Loading…
Reference in New Issue