1 | // Aseprite |
2 | // Copyright (C) 2020-2021 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "app/app.h" |
13 | #include "app/commands/command.h" |
14 | #include "app/context_access.h" |
15 | #include "app/doc_undo.h" |
16 | #include "app/ini_file.h" |
17 | #include "app/modules/editors.h" |
18 | #include "app/modules/gui.h" |
19 | #include "app/modules/palettes.h" |
20 | #include "app/pref/preferences.h" |
21 | #include "app/ui/editor/editor.h" |
22 | #include "app/ui/status_bar.h" |
23 | #include "base/thread.h" |
24 | #include "doc/sprite.h" |
25 | #include "ui/manager.h" |
26 | #include "ui/system.h" |
27 | |
28 | #ifdef ENABLE_UI |
29 | #include "app/ui/timeline/timeline.h" |
30 | #endif |
31 | |
32 | namespace app { |
33 | |
34 | class UndoCommand : public Command { |
35 | public: |
36 | enum Type { Undo, Redo }; |
37 | |
38 | UndoCommand(Type type); |
39 | |
40 | protected: |
41 | bool onEnabled(Context* context) override; |
42 | void onExecute(Context* context) override; |
43 | |
44 | private: |
45 | Type m_type; |
46 | }; |
47 | |
48 | UndoCommand::UndoCommand(Type type) |
49 | : Command((type == Undo ? CommandId::Undo(): |
50 | CommandId::Redo()), CmdUIOnlyFlag) |
51 | , m_type(type) |
52 | { |
53 | } |
54 | |
55 | bool UndoCommand::onEnabled(Context* context) |
56 | { |
57 | const ContextReader reader(context); |
58 | const Doc* doc(reader.document()); |
59 | return |
60 | doc && |
61 | ((m_type == Undo ? doc->undoHistory()->canUndo(): |
62 | doc->undoHistory()->canRedo())); |
63 | } |
64 | |
65 | void UndoCommand::onExecute(Context* context) |
66 | { |
67 | ContextWriter writer(context); |
68 | Doc* document(writer.document()); |
69 | DocUndo* undo = document->undoHistory(); |
70 | |
71 | #ifdef ENABLE_UI |
72 | Sprite* sprite = document->sprite(); |
73 | SpritePosition spritePosition; |
74 | const bool gotoModified = |
75 | (Preferences::instance().undo.gotoModified() && |
76 | context->isUIAvailable() && current_editor); |
77 | if (gotoModified) { |
78 | SpritePosition currentPosition(writer.site()->layer(), |
79 | writer.site()->frame()); |
80 | |
81 | if (m_type == Undo) |
82 | spritePosition = undo->nextUndoSpritePosition(); |
83 | else |
84 | spritePosition = undo->nextRedoSpritePosition(); |
85 | |
86 | if (spritePosition != currentPosition) { |
87 | Layer* selectLayer = spritePosition.layer(); |
88 | if (selectLayer) |
89 | current_editor->setLayer(selectLayer); |
90 | current_editor->setFrame(spritePosition.frame()); |
91 | |
92 | // Draw the current layer/frame (which is not undone yet) so the |
93 | // user can see the doUndo/doRedo effect. |
94 | current_editor->drawSpriteClipped( |
95 | gfx::Region(gfx::Rect(0, 0, sprite->width(), sprite->height()))); |
96 | |
97 | current_editor->display()->flipDisplay(); |
98 | base::this_thread::sleep_for(0.01); |
99 | } |
100 | } |
101 | |
102 | // Get the stream to deserialize the document range after executing |
103 | // the undo/redo action. We cannot yet deserialize the document |
104 | // range because there could be inexistent layers. |
105 | std::istream* docRangeStream; |
106 | if (m_type == Undo) |
107 | docRangeStream = undo->nextUndoDocRange(); |
108 | else |
109 | docRangeStream = undo->nextRedoDocRange(); |
110 | |
111 | StatusBar* statusbar = StatusBar::instance(); |
112 | if (statusbar) { |
113 | std::string msg; |
114 | if (m_type == Undo) |
115 | msg = "Undid " + undo->nextUndoLabel(); |
116 | else |
117 | msg = "Redid " + undo->nextRedoLabel(); |
118 | if (Preferences::instance().undo.showTooltip()) |
119 | statusbar->showTip(1000, msg); |
120 | else |
121 | statusbar->setStatusText(0, msg); |
122 | } |
123 | #endif // ENABLE_UI |
124 | |
125 | // Effectively undo/redo. |
126 | if (m_type == Undo) |
127 | undo->undo(); |
128 | else |
129 | undo->redo(); |
130 | |
131 | #ifdef ENABLE_UI |
132 | // After redo/undo, we retry to change the current SpritePosition |
133 | // (because new frames/layers could be added, positions that we |
134 | // weren't able to reach before the undo). |
135 | if (gotoModified) { |
136 | Site newSite = context->activeSite(); |
137 | SpritePosition currentPosition( |
138 | newSite.layer(), |
139 | newSite.frame()); |
140 | |
141 | if (spritePosition != currentPosition) { |
142 | Layer* selectLayer = spritePosition.layer(); |
143 | if (selectLayer) |
144 | current_editor->setLayer(selectLayer); |
145 | current_editor->setFrame(spritePosition.frame()); |
146 | } |
147 | } |
148 | |
149 | // Update timeline range. We've to deserialize the DocRange at |
150 | // this point when objects (possible layers) are re-created after |
151 | // the undo and we can deserialize them. |
152 | if (docRangeStream) { |
153 | Timeline* timeline = App::instance()->timeline(); |
154 | if (timeline) { |
155 | DocRange docRange; |
156 | if (docRange.read(*docRangeStream)) |
157 | timeline->setRange(docRange); |
158 | } |
159 | } |
160 | #endif // ENABLE_UI |
161 | |
162 | document->generateMaskBoundaries(); |
163 | document->setExtraCel(ExtraCelRef(nullptr)); |
164 | |
165 | #ifdef ENABLE_UI |
166 | update_screen_for_document(document); |
167 | #endif |
168 | set_current_palette(writer.palette(), false); |
169 | } |
170 | |
171 | Command* CommandFactory::createUndoCommand() |
172 | { |
173 | return new UndoCommand(UndoCommand::Undo); |
174 | } |
175 | |
176 | Command* CommandFactory::createRedoCommand() |
177 | { |
178 | return new UndoCommand(UndoCommand::Redo); |
179 | } |
180 | |
181 | } // namespace app |
182 | |