1// Aseprite
2// Copyright (C) 2018-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/app.h"
13#include "app/color.h"
14#include "app/commands/command.h"
15#include "app/commands/params.h"
16#include "app/console.h"
17#include "app/context_access.h"
18#include "app/doc_api.h"
19#include "app/i18n/strings.h"
20#include "app/modules/gui.h"
21#include "app/tx.h"
22#include "app/ui/doc_view.h"
23#include "app/ui/editor/editor.h"
24#include "app/ui/main_window.h"
25#include "app/ui/status_bar.h"
26#include "app/ui/timeline/timeline.h"
27#include "app/ui_context.h"
28#include "doc/cel.h"
29#include "doc/image.h"
30#include "doc/layer.h"
31#include "doc/sprite.h"
32#include "fmt/format.h"
33#include "ui/ui.h"
34
35#include <stdexcept>
36
37namespace app {
38
39class NewFrameCommand : public Command {
40public:
41 enum class Content {
42 DUPLICATE_FRAME,
43 NEW_EMPTY_FRAME,
44 DUPLICATE_CELS,
45 DUPLICATE_CELS_COPIES,
46 DUPLICATE_CELS_LINKED,
47 };
48
49 NewFrameCommand();
50
51protected:
52 void onLoadParams(const Params& params) override;
53 bool onEnabled(Context* context) override;
54 void onExecute(Context* context) override;
55 std::string onGetFriendlyName() const override;
56
57private:
58 Content m_content;
59};
60
61NewFrameCommand::NewFrameCommand()
62 : Command(CommandId::NewFrame(), CmdRecordableFlag)
63{
64}
65
66void NewFrameCommand::onLoadParams(const Params& params)
67{
68 m_content = Content::DUPLICATE_FRAME;
69
70 std::string content = params.get("content");
71 if (content == "current" ||
72 content == "frame")
73 m_content = Content::DUPLICATE_FRAME;
74 else if (content == "empty")
75 m_content = Content::NEW_EMPTY_FRAME;
76 else if (content == "cel")
77 m_content = Content::DUPLICATE_CELS;
78 else if (content == "celblock" ||
79 content == "celcopies")
80 m_content = Content::DUPLICATE_CELS_COPIES;
81 else if (content == "cellinked")
82 m_content = Content::DUPLICATE_CELS_LINKED;
83}
84
85bool NewFrameCommand::onEnabled(Context* context)
86{
87 return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
88 ContextFlags::HasActiveSprite);
89}
90
91void NewFrameCommand::onExecute(Context* context)
92{
93 ContextWriter writer(context);
94 Doc* document(writer.document());
95 Sprite* sprite(writer.sprite());
96
97#if ENABLE_UI
98 // Show the tooltip feedback only if we are not inside a transaction
99 // (e.g. we can be already in a transaction if we are running in a
100 // Lua script app.transaction()).
101 const bool showTooltip = (document->transaction() == nullptr);
102#endif
103
104 {
105 Tx tx(writer.context(), friendlyName());
106 DocApi api = document->getApi(tx);
107
108 switch (m_content) {
109
110 case Content::DUPLICATE_FRAME:
111 api.addFrame(sprite, writer.frame()+1);
112 break;
113
114 case Content::NEW_EMPTY_FRAME:
115 api.addEmptyFrame(sprite, writer.frame()+1);
116 break;
117
118 case Content::DUPLICATE_CELS:
119 case Content::DUPLICATE_CELS_LINKED:
120 case Content::DUPLICATE_CELS_COPIES: {
121 std::unique_ptr<bool> continuous = nullptr;
122 switch (m_content) {
123 case Content::DUPLICATE_CELS_COPIES: continuous.reset(new bool(false)); break;
124 case Content::DUPLICATE_CELS_LINKED: continuous.reset(new bool(true)); break;
125 }
126
127 const Site* site = writer.site();
128 if (site->inTimeline() &&
129 !site->selectedLayers().empty() &&
130 !site->selectedFrames().empty()) {
131#if ENABLE_UI
132 auto timeline = App::instance()->timeline();
133 timeline->prepareToMoveRange();
134 DocRange range = timeline->range();
135#endif
136
137 SelectedLayers selLayers;
138 if (site->inFrames())
139 selLayers.selectAllLayers(writer.sprite()->root());
140 else {
141 selLayers = site->selectedLayers();
142 selLayers.expandCollapsedGroups();
143 }
144
145 frame_t frameRange =
146 (site->selectedFrames().lastFrame() -
147 site->selectedFrames().firstFrame() + 1);
148
149 for (Layer* layer : selLayers) {
150 if (layer->isImage()) {
151 for (frame_t srcFrame : site->selectedFrames().reversed()) {
152 frame_t dstFrame = srcFrame+frameRange;
153 api.copyCel(
154 static_cast<LayerImage*>(layer), srcFrame,
155 static_cast<LayerImage*>(layer), dstFrame, continuous.get());
156 }
157 }
158 }
159
160#ifdef ENABLE_UI // TODO the range should be part of the Site
161 range.displace(0, frameRange);
162 timeline->moveRange(range);
163#endif
164 }
165 else if (auto layer = static_cast<LayerImage*>(writer.layer())) {
166 api.copyCel(
167 layer, writer.frame(),
168 layer, writer.frame()+1, continuous.get());
169
170 context->setActiveFrame(writer.frame()+1);
171 }
172 break;
173 }
174 }
175
176 tx.commit();
177 }
178
179#ifdef ENABLE_UI
180 if (context->isUIAvailable() && showTooltip) {
181 update_screen_for_document(document);
182
183 StatusBar::instance()->showTip(
184 1000, fmt::format("New frame {}/{}",
185 (int)context->activeSite().frame()+1,
186 (int)sprite->totalFrames()));
187
188 App::instance()->mainWindow()->popTimeline();
189 }
190#endif
191}
192
193std::string NewFrameCommand::onGetFriendlyName() const
194{
195 std::string text;
196
197 switch (m_content) {
198 case Content::DUPLICATE_FRAME:
199 text = Strings::commands_NewFrame();
200 break;
201 case Content::NEW_EMPTY_FRAME:
202 text = Strings::commands_NewFrame_NewEmptyFrame();
203 break;
204 case Content::DUPLICATE_CELS:
205 text = Strings::commands_NewFrame_DuplicateCels();
206 break;
207 case Content::DUPLICATE_CELS_COPIES:
208 text = Strings::commands_NewFrame_DuplicateCelsCopies();
209 break;
210 case Content::DUPLICATE_CELS_LINKED:
211 text = Strings::commands_NewFrame_DuplicateCelsLinked();
212 break;
213 }
214
215 return text;
216}
217
218Command* CommandFactory::createNewFrameCommand()
219{
220 return new NewFrameCommand;
221}
222
223} // namespace app
224