1// Aseprite
2// Copyright (C) 2020-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/cmd/set_cel_opacity.h"
14#include "app/cmd/set_user_data.h"
15#include "app/commands/command.h"
16#include "app/console.h"
17#include "app/context_access.h"
18#include "app/doc_event.h"
19#include "app/doc_range.h"
20#include "app/modules/gui.h"
21#include "app/tx.h"
22#include "app/ui/timeline/timeline.h"
23#include "app/ui/user_data_view.h"
24#include "app/ui_context.h"
25#include "base/mem_utils.h"
26#include "base/scoped_value.h"
27#include "doc/cel.h"
28#include "doc/cels_range.h"
29#include "doc/image.h"
30#include "doc/layer.h"
31#include "doc/sprite.h"
32#include "ui/ui.h"
33
34#include "cel_properties.xml.h"
35
36namespace app {
37
38using namespace ui;
39
40class CelPropertiesWindow;
41static CelPropertiesWindow* g_window = nullptr;
42
43class CelPropertiesWindow : public app::gen::CelProperties,
44 public ContextObserver,
45 public DocObserver {
46public:
47 CelPropertiesWindow()
48 : m_timer(250, this)
49 , m_userDataView(Preferences::instance().cels.userDataVisibility)
50 {
51 opacity()->Change.connect([this]{ onStartTimer(); });
52 userData()->Click.connect([this]{ onToggleUserData(); });
53 m_timer.Tick.connect([this]{ onCommitChange(); });
54
55 m_userDataView.UserDataChange.connect([this]{ onStartTimer(); });
56
57 remapWindow();
58 centerWindow();
59 load_window_pos(this, "CelProperties");
60
61 UIContext::instance()->add_observer(this);
62 }
63
64 ~CelPropertiesWindow() {
65 UIContext::instance()->remove_observer(this);
66 }
67
68 void setCel(Doc* doc, Cel* cel) {
69 if (m_document) {
70 m_document->remove_observer(this);
71 m_document = nullptr;
72 m_cel = nullptr;
73 }
74
75 m_timer.stop();
76 m_document = doc;
77 m_cel = cel;
78 m_range = App::instance()->timeline()->range();
79
80 if (m_document)
81 m_document->add_observer(this);
82
83 if (countCels() > 0) {
84 m_userDataView.configureAndSet(
85 (m_cel ? m_cel->data()->userData(): UserData()),
86 g_window->propertiesGrid());
87 }
88 else if (!m_cel)
89 m_userDataView.setVisible(false, false);
90
91 g_window->expandWindow(gfx::Size(g_window->bounds().w,
92 g_window->sizeHint().h));
93 updateFromCel();
94 }
95
96private:
97
98 int opacityValue() const {
99 return opacity()->getValue();
100 }
101
102 int countCels(int* backgroundCount = nullptr) const {
103 if (backgroundCount)
104 *backgroundCount = 0;
105
106 if (!m_document)
107 return 0;
108 else if (m_cel &&
109 (!m_range.enabled() ||
110 (m_range.frames() == 1 &&
111 m_range.layers() == 1))) {
112 if (backgroundCount && m_cel->layer()->isBackground())
113 *backgroundCount = 1;
114 return 1;
115 }
116 else if (m_range.enabled()) {
117 Sprite* sprite = m_document->sprite();
118 int count = 0;
119 for (Cel* cel : sprite->uniqueCels(m_range.selectedFrames())) {
120 if (m_range.contains(cel->layer())) {
121 if (backgroundCount && cel->layer()->isBackground())
122 ++(*backgroundCount);
123 ++count;
124 }
125 }
126 return count;
127 }
128 else
129 return 0;
130 }
131
132 bool onProcessMessage(ui::Message* msg) override {
133 switch (msg->type()) {
134
135 case kKeyDownMessage:
136 if (opacity()->hasFocus()) {
137 KeyScancode scancode = static_cast<KeyMessage*>(msg)->scancode();
138 if (scancode == kKeyEnter ||
139 scancode == kKeyEsc) {
140 onCommitChange();
141 closeWindow(this);
142 return true;
143 }
144 }
145 break;
146
147 case kCloseMessage:
148 // Save changes before we close the window
149 if (m_cel)
150 save_window_pos(this, "CelProperties");
151 setCel(nullptr, nullptr);
152 deferDelete();
153 g_window = nullptr;
154 break;
155
156 }
157 return Window::onProcessMessage(msg);
158 }
159
160 void onStartTimer() {
161 if (m_selfUpdate)
162 return;
163
164 m_timer.start();
165 m_pendingChanges = true;
166 }
167
168 void onCommitChange() {
169 // Nothing to change
170 if (!m_pendingChanges)
171 return;
172
173 base::ScopedValue<bool> switchSelf(m_selfUpdate, true, false);
174
175 m_timer.stop();
176
177 const int newOpacity = opacityValue();
178 const UserData newUserData= m_userDataView.userData();
179 const int count = countCels();
180
181 if ((count > 1) ||
182 (count == 1 && m_cel && (newOpacity != m_cel->opacity() ||
183 newUserData != m_cel->data()->userData()))) {
184 try {
185 ContextWriter writer(UIContext::instance());
186 Tx tx(writer.context(), "Set Cel Properties");
187
188 DocRange range;
189 if (m_range.enabled())
190 range = m_range;
191 else {
192 range.startRange(m_cel->layer(), m_cel->frame(), DocRange::kCels);
193 range.endRange(m_cel->layer(), m_cel->frame());
194 }
195
196 Sprite* sprite = m_document->sprite();
197 for (Cel* cel : sprite->uniqueCels(range.selectedFrames())) {
198 if (range.contains(cel->layer())) {
199 if (!cel->layer()->isBackground() && newOpacity != cel->opacity()) {
200 tx(new cmd::SetCelOpacity(cel, newOpacity));
201 }
202
203 if (newUserData != cel->data()->userData()) {
204 tx(new cmd::SetUserData(cel->data(), newUserData, m_document));
205
206 // Redraw timeline because the cel's user data/color
207 // might have changed.
208 if (newUserData.color() != cel->data()->userData().color()) {
209 App::instance()->timeline()->invalidate();
210 }
211 }
212 }
213 }
214
215 tx.commit();
216 }
217 catch (const std::exception& e) {
218 Console::showException(e);
219 }
220
221 update_screen_for_document(m_document);
222 }
223
224 m_pendingChanges = false;
225 }
226
227 void onToggleUserData() {
228 if (countCels() > 0) {
229 m_userDataView.toggleVisibility();
230 g_window->expandWindow(gfx::Size(g_window->bounds().w,
231 g_window->sizeHint().h));
232 }
233 }
234
235 // ContextObserver impl
236 void onActiveSiteChange(const Site& site) override {
237 onCommitChange();
238 if (isVisible())
239 setCel(const_cast<Doc*>(site.document()),
240 const_cast<Cel*>(site.cel()));
241 else if (m_document)
242 setCel(nullptr, nullptr);
243 }
244
245 // DocObserver impl
246 void onBeforeRemoveCel(DocEvent& ev) override {
247 if (m_cel == ev.cel())
248 setCel(m_document, nullptr);
249 }
250
251 void onCelOpacityChange(DocEvent& ev) override {
252 if (m_cel == ev.cel())
253 updateFromCel();
254 }
255
256 void onUserDataChange(DocEvent& ev) override {
257 if (m_cel && m_cel->data() == ev.withUserData())
258 updateFromCel();
259 }
260
261 void updateFromCel() {
262 if (m_selfUpdate)
263 return;
264
265 m_timer.stop(); // Cancel current editions (just in case)
266
267 base::ScopedValue<bool> switchSelf(m_selfUpdate, true, false);
268
269 int bgCount = 0;
270 int count = countCels(&bgCount);
271
272 if (count > 0) {
273 if (m_cel) {
274 opacity()->setValue(m_cel->opacity());
275 color_t c = m_cel->data()->userData().color();
276 m_userDataView.color()->setColor(Color::fromRgb(rgba_getr(c), rgba_getg(c), rgba_getb(c), rgba_geta(c)));
277 m_userDataView.entry()->setText(m_cel->data()->userData().text());
278 }
279 opacity()->setEnabled(bgCount < count);
280 }
281 else {
282 opacity()->setEnabled(false);
283 m_userDataView.setVisible(false, false);
284 }
285 }
286
287 Timer m_timer;
288 bool m_pendingChanges = false;
289 Doc* m_document = nullptr;
290 Cel* m_cel = nullptr;
291 DocRange m_range;
292 bool m_selfUpdate = false;
293 UserDataView m_userDataView;
294};
295
296class CelPropertiesCommand : public Command {
297public:
298 CelPropertiesCommand();
299
300protected:
301 bool onEnabled(Context* context) override;
302 void onExecute(Context* context) override;
303};
304
305CelPropertiesCommand::CelPropertiesCommand()
306 : Command(CommandId::CelProperties(), CmdUIOnlyFlag)
307{
308}
309
310bool CelPropertiesCommand::onEnabled(Context* context)
311{
312 return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
313 ContextFlags::ActiveLayerIsImage);
314}
315
316void CelPropertiesCommand::onExecute(Context* context)
317{
318 ContextReader reader(context);
319
320 if (!g_window)
321 g_window = new CelPropertiesWindow;
322
323 g_window->setCel(reader.document(), reader.cel());
324 g_window->openWindow();
325
326 // Focus layer name
327 g_window->opacity()->requestFocus();
328}
329
330Command* CommandFactory::createCelPropertiesCommand()
331{
332 return new CelPropertiesCommand;
333}
334
335} // namespace app
336