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
28namespace app {
29
30using namespace ui;
31using namespace doc;
32
33class GotoCommand : public Command {
34protected:
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
51class GotoFirstFrameCommand : public GotoCommand {
52public:
53 GotoFirstFrameCommand()
54 : GotoCommand(CommandId::GotoFirstFrame()) { }
55
56protected:
57 frame_t onGetFrame(Editor* editor) override {
58 return 0;
59 }
60};
61
62class GotoFirstFrameInTagCommand : public GotoCommand {
63public:
64 GotoFirstFrameInTagCommand()
65 : GotoCommand(CommandId::GotoFirstFrameInTag()) { }
66
67protected:
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
78class GotoPreviousFrameCommand : public GotoCommand {
79public:
80 GotoPreviousFrameCommand()
81 : GotoCommand(CommandId::GotoPreviousFrame()) { }
82
83protected:
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
92class GotoNextFrameCommand : public GotoCommand {
93public:
94 GotoNextFrameCommand() : GotoCommand(CommandId::GotoNextFrame()) { }
95
96protected:
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
105class GotoNextFrameWithSameTagCommand : public GotoCommand {
106public:
107 GotoNextFrameWithSameTagCommand() : GotoCommand(CommandId::GotoNextFrameWithSameTag()) { }
108
109protected:
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
123class GotoPreviousFrameWithSameTagCommand : public GotoCommand {
124public:
125 GotoPreviousFrameWithSameTagCommand() : GotoCommand(CommandId::GotoPreviousFrameWithSameTag()) { }
126
127protected:
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
141class GotoLastFrameCommand : public GotoCommand {
142public:
143 GotoLastFrameCommand() : GotoCommand(CommandId::GotoLastFrame()) { }
144
145protected:
146 frame_t onGetFrame(Editor* editor) override {
147 return editor->sprite()->lastFrame();
148 }
149};
150
151class GotoLastFrameInTagCommand : public GotoCommand {
152public:
153 GotoLastFrameInTagCommand()
154 : GotoCommand(CommandId::GotoLastFrameInTag()) { }
155
156protected:
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
167class GotoFrameCommand : public GotoCommand {
168public:
169 GotoFrameCommand() : GotoCommand(CommandId::GotoFrame())
170 , m_showUI(true) { }
171
172private:
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 frameNum = 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
264private:
265 bool m_showUI;
266 int m_frame;
267};
268
269Command* CommandFactory::createGotoFirstFrameCommand()
270{
271 return new GotoFirstFrameCommand;
272}
273
274Command* CommandFactory::createGotoFirstFrameInTagCommand()
275{
276 return new GotoFirstFrameInTagCommand;
277}
278
279Command* CommandFactory::createGotoPreviousFrameCommand()
280{
281 return new GotoPreviousFrameCommand;
282}
283
284Command* CommandFactory::createGotoNextFrameCommand()
285{
286 return new GotoNextFrameCommand;
287}
288
289Command* CommandFactory::createGotoLastFrameCommand()
290{
291 return new GotoLastFrameCommand;
292}
293
294Command* CommandFactory::createGotoLastFrameInTagCommand()
295{
296 return new GotoLastFrameInTagCommand;
297}
298
299Command* CommandFactory::createGotoNextFrameWithSameTagCommand()
300{
301 return new GotoNextFrameWithSameTagCommand;
302}
303
304Command* CommandFactory::createGotoPreviousFrameWithSameTagCommand()
305{
306 return new GotoPreviousFrameWithSameTagCommand;
307}
308
309Command* CommandFactory::createGotoFrameCommand()
310{
311 return new GotoFrameCommand;
312}
313
314} // namespace app
315