1 | // Aseprite |
2 | // Copyright (C) 2019-2022 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/commands/command.h" |
13 | #include "app/commands/params.h" |
14 | #include "app/loop_tag.h" |
15 | #include "app/match_words.h" |
16 | #include "app/modules/editors.h" |
17 | #include "app/modules/gui.h" |
18 | #include "app/ui/editor/editor.h" |
19 | #include "app/ui/editor/editor_customization_delegate.h" |
20 | #include "app/ui/search_entry.h" |
21 | #include "doc/sprite.h" |
22 | #include "doc/tag.h" |
23 | #include "ui/combobox.h" |
24 | #include "ui/window.h" |
25 | |
26 | #include "goto_frame.xml.h" |
27 | |
28 | namespace app { |
29 | |
30 | using namespace ui; |
31 | using namespace doc; |
32 | |
33 | class GotoCommand : public Command { |
34 | protected: |
35 | GotoCommand(const char* id) |
36 | : Command(id, CmdRecordableFlag) { } |
37 | |
38 | bool onEnabled(Context* context) override { |
39 | return (current_editor != NULL); |
40 | } |
41 | |
42 | void onExecute(Context* context) override { |
43 | ASSERT(current_editor != NULL); |
44 | |
45 | current_editor->setFrame(onGetFrame(current_editor)); |
46 | } |
47 | |
48 | virtual frame_t onGetFrame(Editor* editor) = 0; |
49 | }; |
50 | |
51 | class GotoFirstFrameCommand : public GotoCommand { |
52 | public: |
53 | GotoFirstFrameCommand() |
54 | : GotoCommand(CommandId::GotoFirstFrame()) { } |
55 | |
56 | protected: |
57 | frame_t onGetFrame(Editor* editor) override { |
58 | return 0; |
59 | } |
60 | }; |
61 | |
62 | class GotoFirstFrameInTagCommand : public GotoCommand { |
63 | public: |
64 | GotoFirstFrameInTagCommand() |
65 | : GotoCommand(CommandId::GotoFirstFrameInTag()) { } |
66 | |
67 | protected: |
68 | frame_t onGetFrame(Editor* editor) override { |
69 | frame_t frame = editor->frame(); |
70 | Tag* tag = editor |
71 | ->getCustomizationDelegate() |
72 | ->getTagProvider() |
73 | ->getTagByFrame(frame, false); |
74 | return (tag ? tag->fromFrame(): 0); |
75 | } |
76 | }; |
77 | |
78 | class GotoPreviousFrameCommand : public GotoCommand { |
79 | public: |
80 | GotoPreviousFrameCommand() |
81 | : GotoCommand(CommandId::GotoPreviousFrame()) { } |
82 | |
83 | protected: |
84 | frame_t onGetFrame(Editor* editor) override { |
85 | frame_t frame = editor->frame(); |
86 | frame_t last = editor->sprite()->lastFrame(); |
87 | |
88 | return (frame > 0 ? frame-1: last); |
89 | } |
90 | }; |
91 | |
92 | class GotoNextFrameCommand : public GotoCommand { |
93 | public: |
94 | GotoNextFrameCommand() : GotoCommand(CommandId::GotoNextFrame()) { } |
95 | |
96 | protected: |
97 | frame_t onGetFrame(Editor* editor) override { |
98 | frame_t frame = editor->frame(); |
99 | frame_t last = editor->sprite()->lastFrame(); |
100 | |
101 | return (frame < last ? frame+1: 0); |
102 | } |
103 | }; |
104 | |
105 | class GotoNextFrameWithSameTagCommand : public GotoCommand { |
106 | public: |
107 | GotoNextFrameWithSameTagCommand() : GotoCommand(CommandId::GotoNextFrameWithSameTag()) { } |
108 | |
109 | protected: |
110 | frame_t onGetFrame(Editor* editor) override { |
111 | frame_t frame = editor->frame(); |
112 | Tag* tag = editor |
113 | ->getCustomizationDelegate() |
114 | ->getTagProvider() |
115 | ->getTagByFrame(frame, false); |
116 | frame_t first = (tag ? tag->fromFrame(): 0); |
117 | frame_t last = (tag ? tag->toFrame(): editor->sprite()->lastFrame()); |
118 | |
119 | return (frame < last ? frame+1: first); |
120 | } |
121 | }; |
122 | |
123 | class GotoPreviousFrameWithSameTagCommand : public GotoCommand { |
124 | public: |
125 | GotoPreviousFrameWithSameTagCommand() : GotoCommand(CommandId::GotoPreviousFrameWithSameTag()) { } |
126 | |
127 | protected: |
128 | frame_t onGetFrame(Editor* editor) override { |
129 | frame_t frame = editor->frame(); |
130 | Tag* tag = editor |
131 | ->getCustomizationDelegate() |
132 | ->getTagProvider() |
133 | ->getTagByFrame(frame, false); |
134 | frame_t first = (tag ? tag->fromFrame(): 0); |
135 | frame_t last = (tag ? tag->toFrame(): editor->sprite()->lastFrame()); |
136 | |
137 | return (frame > first ? frame-1: last); |
138 | } |
139 | }; |
140 | |
141 | class GotoLastFrameCommand : public GotoCommand { |
142 | public: |
143 | GotoLastFrameCommand() : GotoCommand(CommandId::GotoLastFrame()) { } |
144 | |
145 | protected: |
146 | frame_t onGetFrame(Editor* editor) override { |
147 | return editor->sprite()->lastFrame(); |
148 | } |
149 | }; |
150 | |
151 | class GotoLastFrameInTagCommand : public GotoCommand { |
152 | public: |
153 | GotoLastFrameInTagCommand() |
154 | : GotoCommand(CommandId::GotoLastFrameInTag()) { } |
155 | |
156 | protected: |
157 | frame_t onGetFrame(Editor* editor) override { |
158 | frame_t frame = editor->frame(); |
159 | Tag* tag = editor |
160 | ->getCustomizationDelegate() |
161 | ->getTagProvider() |
162 | ->getTagByFrame(frame, false); |
163 | return (tag ? tag->toFrame(): editor->sprite()->lastFrame()); |
164 | } |
165 | }; |
166 | |
167 | class GotoFrameCommand : public GotoCommand { |
168 | public: |
169 | GotoFrameCommand() : GotoCommand(CommandId::GotoFrame()) |
170 | , m_showUI(true) { } |
171 | |
172 | private: |
173 | |
174 | // TODO this combobox is similar to FileSelector::CustomFileNameEntry |
175 | class TagsEntry : public ComboBox { |
176 | public: |
177 | TagsEntry(Tags& tags) |
178 | : m_tags(tags) { |
179 | setEditable(true); |
180 | getEntryWidget()->Change.connect(&TagsEntry::onEntryChange, this); |
181 | fill(true); |
182 | } |
183 | |
184 | private: |
185 | void fill(bool all) { |
186 | deleteAllItems(); |
187 | |
188 | MatchWords match(getEntryWidget()->text()); |
189 | |
190 | bool matchAny = false; |
191 | for (const auto& tag : m_tags) { |
192 | if (match(tag->name())) { |
193 | matchAny = true; |
194 | break; |
195 | } |
196 | } |
197 | for (const auto& tag : m_tags) { |
198 | if (all || !matchAny || match(tag->name())) |
199 | addItem(tag->name()); |
200 | } |
201 | } |
202 | |
203 | void onEntryChange() { |
204 | closeListBox(); |
205 | fill(false); |
206 | if (getItemCount() > 0) |
207 | openListBox(); |
208 | } |
209 | |
210 | Tags& m_tags; |
211 | }; |
212 | |
213 | void onLoadParams(const Params& params) override { |
214 | std::string frame = params.get("frame" ); |
215 | if (!frame.empty()) { |
216 | m_frame = strtol(frame.c_str(), nullptr, 10); |
217 | m_showUI = false; |
218 | } |
219 | else |
220 | m_showUI = true; |
221 | } |
222 | |
223 | frame_t onGetFrame(Editor* editor) override { |
224 | auto& docPref = editor->docPref(); |
225 | |
226 | if (m_showUI) { |
227 | app::gen::GotoFrame window; |
228 | TagsEntry combobox(editor->sprite()->tags()); |
229 | |
230 | window.framePlaceholder()->addChild(&combobox); |
231 | |
232 | combobox.setFocusMagnet(true); |
233 | combobox.getEntryWidget()->setTextf( |
234 | "%d" , editor->frame()+docPref.timeline.firstFrame()); |
235 | |
236 | window.openWindowInForeground(); |
237 | if (window.closer() != window.ok()) |
238 | return editor->frame(); |
239 | |
240 | std::string text = combobox.getEntryWidget()->text(); |
241 | frame_t = base::convert_to<int>(text); |
242 | std::string textFromInt = base::convert_to<std::string>(frameNum); |
243 | if (text == textFromInt) { |
244 | m_frame = frameNum; |
245 | } |
246 | // Search a tag name |
247 | else { |
248 | MatchWords match(text); |
249 | for (const auto& tag : editor->sprite()->tags()) { |
250 | if (match(tag->name())) { |
251 | m_frame = |
252 | tag->fromFrame()+docPref.timeline.firstFrame(); |
253 | break; |
254 | } |
255 | } |
256 | } |
257 | } |
258 | |
259 | return std::clamp( |
260 | m_frame-docPref.timeline.firstFrame(), |
261 | 0, editor->sprite()->lastFrame()); |
262 | } |
263 | |
264 | private: |
265 | bool m_showUI; |
266 | int m_frame; |
267 | }; |
268 | |
269 | Command* CommandFactory::createGotoFirstFrameCommand() |
270 | { |
271 | return new GotoFirstFrameCommand; |
272 | } |
273 | |
274 | Command* CommandFactory::createGotoFirstFrameInTagCommand() |
275 | { |
276 | return new GotoFirstFrameInTagCommand; |
277 | } |
278 | |
279 | Command* CommandFactory::createGotoPreviousFrameCommand() |
280 | { |
281 | return new GotoPreviousFrameCommand; |
282 | } |
283 | |
284 | Command* CommandFactory::createGotoNextFrameCommand() |
285 | { |
286 | return new GotoNextFrameCommand; |
287 | } |
288 | |
289 | Command* CommandFactory::createGotoLastFrameCommand() |
290 | { |
291 | return new GotoLastFrameCommand; |
292 | } |
293 | |
294 | Command* CommandFactory::createGotoLastFrameInTagCommand() |
295 | { |
296 | return new GotoLastFrameInTagCommand; |
297 | } |
298 | |
299 | Command* CommandFactory::createGotoNextFrameWithSameTagCommand() |
300 | { |
301 | return new GotoNextFrameWithSameTagCommand; |
302 | } |
303 | |
304 | Command* CommandFactory::createGotoPreviousFrameWithSameTagCommand() |
305 | { |
306 | return new GotoPreviousFrameWithSameTagCommand; |
307 | } |
308 | |
309 | Command* CommandFactory::createGotoFrameCommand() |
310 | { |
311 | return new GotoFrameCommand; |
312 | } |
313 | |
314 | } // namespace app |
315 | |