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_layer_blend_mode.h"
14#include "app/cmd/set_layer_name.h"
15#include "app/cmd/set_layer_opacity.h"
16#include "app/cmd/set_tileset_base_index.h"
17#include "app/cmd/set_tileset_name.h"
18#include "app/cmd/set_user_data.h"
19#include "app/commands/command.h"
20#include "app/console.h"
21#include "app/context_access.h"
22#include "app/doc.h"
23#include "app/doc_event.h"
24#include "app/i18n/strings.h"
25#include "app/modules/gui.h"
26#include "app/tx.h"
27#include "app/ui/main_window.h"
28#include "app/ui/separator_in_view.h"
29#include "app/ui/tileset_selector.h"
30#include "app/ui/timeline/timeline.h"
31#include "app/ui/user_data_view.h"
32#include "app/ui_context.h"
33#include "base/scoped_value.h"
34#include "doc/image.h"
35#include "doc/layer.h"
36#include "doc/layer_tilemap.h"
37#include "doc/sprite.h"
38#include "doc/tileset.h"
39#include "doc/user_data.h"
40#include "ui/ui.h"
41
42#include "layer_properties.xml.h"
43#include "tileset_selector_window.xml.h"
44
45namespace app {
46
47using namespace ui;
48
49class LayerPropertiesCommand : public Command {
50public:
51 LayerPropertiesCommand();
52
53protected:
54 bool onEnabled(Context* context) override;
55 void onExecute(Context* context) override;
56};
57
58class LayerPropertiesWindow;
59static LayerPropertiesWindow* g_window = nullptr;
60
61class LayerPropertiesWindow : public app::gen::LayerProperties,
62 public ContextObserver,
63 public DocObserver {
64public:
65 class BlendModeItem : public ListItem {
66 public:
67 BlendModeItem(const std::string& name,
68 const doc::BlendMode mode)
69 : ListItem(name)
70 , m_mode(mode) {
71 }
72 doc::BlendMode mode() const { return m_mode; }
73 private:
74 doc::BlendMode m_mode;
75 };
76
77 LayerPropertiesWindow()
78 : m_timer(250, this)
79 , m_userDataView(Preferences::instance().layers.userDataVisibility) {
80 name()->setMinSize(gfx::Size(128, 0));
81 name()->setExpansive(true);
82
83 mode()->addItem(new BlendModeItem(Strings::layer_properties_normal(),
84 doc::BlendMode::NORMAL));
85 mode()->addItem(new SeparatorInView);
86 mode()->addItem(new BlendModeItem(Strings::layer_properties_darken(),
87 doc::BlendMode::DARKEN));
88 mode()->addItem(new BlendModeItem(Strings::layer_properties_multiply(),
89 doc::BlendMode::MULTIPLY));
90 mode()->addItem(new BlendModeItem(Strings::layer_properties_color_burn(),
91 doc::BlendMode::COLOR_BURN));
92 mode()->addItem(new SeparatorInView);
93 mode()->addItem(new BlendModeItem(Strings::layer_properties_lighten(),
94 doc::BlendMode::LIGHTEN));
95 mode()->addItem(new BlendModeItem(Strings::layer_properties_screen(),
96 doc::BlendMode::SCREEN));
97 mode()->addItem(new BlendModeItem(Strings::layer_properties_color_dodge(),
98 doc::BlendMode::COLOR_DODGE));
99 mode()->addItem(new BlendModeItem(Strings::layer_properties_addition(),
100 doc::BlendMode::ADDITION));
101 mode()->addItem(new SeparatorInView);
102 mode()->addItem(new BlendModeItem(Strings::layer_properties_overlay(),
103 doc::BlendMode::OVERLAY));
104 mode()->addItem(new BlendModeItem(Strings::layer_properties_soft_light(),
105 doc::BlendMode::SOFT_LIGHT));
106 mode()->addItem(new BlendModeItem(Strings::layer_properties_hard_light(),
107 doc::BlendMode::HARD_LIGHT));
108 mode()->addItem(new SeparatorInView);
109 mode()->addItem(new BlendModeItem(Strings::layer_properties_difference(),
110 doc::BlendMode::DIFFERENCE));
111 mode()->addItem(new BlendModeItem(Strings::layer_properties_exclusion(),
112 doc::BlendMode::EXCLUSION));
113 mode()->addItem(new BlendModeItem(Strings::layer_properties_subtract(),
114 doc::BlendMode::SUBTRACT));
115 mode()->addItem(new BlendModeItem(Strings::layer_properties_divide(),
116 doc::BlendMode::DIVIDE));
117 mode()->addItem(new SeparatorInView);
118 mode()->addItem(new BlendModeItem(Strings::layer_properties_hue(),
119 doc::BlendMode::HSL_HUE));
120 mode()->addItem(new BlendModeItem(Strings::layer_properties_saturation(),
121 doc::BlendMode::HSL_SATURATION));
122 mode()->addItem(new BlendModeItem(Strings::layer_properties_color(),
123 doc::BlendMode::HSL_COLOR));
124 mode()->addItem(new BlendModeItem(Strings::layer_properties_luminosity(),
125 doc::BlendMode::HSL_LUMINOSITY));
126
127 name()->Change.connect([this]{ onStartTimer(); });
128 mode()->Change.connect([this]{ onStartTimer(); });
129 opacity()->Change.connect([this]{ onStartTimer(); });
130 m_timer.Tick.connect([this]{ onCommitChange(); });
131 userData()->Click.connect([this]{ onToggleUserData(); });
132 tileset()->Click.connect([this]{ onTileset(); });
133 tileset()->setVisible(false);
134
135 m_userDataView.UserDataChange.connect([this]{ onStartTimer(); });
136
137 remapWindow();
138 centerWindow();
139 load_window_pos(this, "LayerProperties");
140
141 UIContext::instance()->add_observer(this);
142 }
143
144 ~LayerPropertiesWindow() {
145 UIContext::instance()->remove_observer(this);
146 }
147
148 void setLayer(Doc* doc, Layer* layer) {
149 if (m_layer) {
150 m_document->remove_observer(this);
151 m_layer = nullptr;
152 }
153
154 m_timer.stop();
155 m_document = doc;
156 m_layer = layer;
157 m_range = App::instance()->timeline()->range();
158
159 if (m_document)
160 m_document->add_observer(this);
161
162 if (countLayers() > 0) {
163 m_userDataView.configureAndSet(m_layer->userData(),
164 g_window->propertiesGrid());
165 }
166
167 updateFromLayer();
168 }
169
170private:
171
172 std::string nameValue() const {
173 return name()->text();
174 }
175
176 doc::BlendMode blendModeValue() const {
177 BlendModeItem* item = dynamic_cast<BlendModeItem*>(mode()->getSelectedItem());
178 if (item)
179 return item->mode();
180 else
181 return doc::BlendMode::NORMAL;
182 }
183
184 int opacityValue() const {
185 return opacity()->getValue();
186 }
187
188 int countLayers() const {
189 if (!m_document)
190 return 0;
191 else if (m_layer && !m_range.enabled())
192 return 1;
193 else if (m_range.enabled())
194 return m_range.layers();
195 else
196 return 0;
197 }
198
199 bool onProcessMessage(ui::Message* msg) override {
200 switch (msg->type()) {
201
202 case kKeyDownMessage:
203 if (name()->hasFocus() ||
204 opacity()->hasFocus() ||
205 mode()->hasFocus()) {
206 KeyScancode scancode = static_cast<KeyMessage*>(msg)->scancode();
207 if (scancode == kKeyEnter ||
208 scancode == kKeyEsc) {
209 onCommitChange();
210 closeWindow(this);
211 return true;
212 }
213 }
214 break;
215
216 case kCloseMessage:
217 // Save changes before we close the window
218 setLayer(nullptr, nullptr);
219 save_window_pos(this, "LayerProperties");
220
221 deferDelete();
222 g_window = nullptr;
223 break;
224
225 }
226 return Window::onProcessMessage(msg);
227 }
228
229 void onStartTimer() {
230 if (m_selfUpdate)
231 return;
232
233 m_timer.start();
234 m_pendingChanges = true;
235 }
236
237 void onCommitChange() {
238 // Nothing to change
239 if (!m_pendingChanges)
240 return;
241
242 // Nothing to do here, as there is no layer selected.
243 if (!m_layer)
244 return;
245
246 base::ScopedValue<bool> switchSelf(m_selfUpdate, true, false);
247
248 m_timer.stop();
249
250 const int count = countLayers();
251
252 std::string newName = nameValue();
253 int newOpacity = opacityValue();
254 const doc::UserData newUserData = m_userDataView.userData();
255 doc::BlendMode newBlendMode = blendModeValue();
256
257 if ((count > 1) ||
258 (count == 1 && m_layer && (newName != m_layer->name() ||
259 newUserData != m_layer->userData() ||
260 (m_layer->isImage() &&
261 (newOpacity != static_cast<LayerImage*>(m_layer)->opacity() ||
262 newBlendMode != static_cast<LayerImage*>(m_layer)->blendMode()))))) {
263 try {
264 ContextWriter writer(UIContext::instance());
265 Tx tx(writer.context(), "Set Layer Properties");
266
267 DocRange range;
268 if (m_range.enabled())
269 range = m_range;
270 else {
271 range.startRange(m_layer, -1, DocRange::kLayers);
272 range.endRange(m_layer, -1);
273 }
274
275 const bool nameChanged = (newName != m_layer->name());
276 const bool userDataChanged = (newUserData != m_layer->userData());
277 const bool opacityChanged = (m_layer->isImage() && newOpacity != static_cast<LayerImage*>(m_layer)->opacity());
278 const bool blendModeChanged = (m_layer->isImage() && newBlendMode != static_cast<LayerImage*>(m_layer)->blendMode());
279
280 for (Layer* layer : range.selectedLayers()) {
281 if (nameChanged && newName != layer->name())
282 tx(new cmd::SetLayerName(layer, newName));
283
284 if (userDataChanged && newUserData != layer->userData())
285 tx(new cmd::SetUserData(layer, newUserData, m_document));
286
287 if (layer->isImage()) {
288 if (opacityChanged && newOpacity != static_cast<LayerImage*>(layer)->opacity())
289 tx(new cmd::SetLayerOpacity(static_cast<LayerImage*>(layer), newOpacity));
290
291 if (blendModeChanged && newBlendMode != static_cast<LayerImage*>(layer)->blendMode())
292 tx(new cmd::SetLayerBlendMode(static_cast<LayerImage*>(layer), newBlendMode));
293 }
294 }
295
296 // Redraw timeline because the layer's name/user data/color
297 // might have changed.
298 App::instance()->timeline()->invalidate();
299
300 tx.commit();
301 }
302 catch (const std::exception& e) {
303 Console::showException(e);
304 }
305
306 update_screen_for_document(m_document);
307 }
308 }
309
310 // ContextObserver impl
311 void onActiveSiteChange(const Site& site) override {
312 onCommitChange();
313 if (isVisible())
314 setLayer(const_cast<Doc*>(site.document()),
315 const_cast<Layer*>(site.layer()));
316 else if (m_layer)
317 setLayer(nullptr, nullptr);
318 }
319
320 // DocObserver impl
321 void onLayerNameChange(DocEvent& ev) override {
322 if (m_layer == ev.layer())
323 updateFromLayer();
324 }
325
326 void onLayerOpacityChange(DocEvent& ev) override {
327 if (m_layer == ev.layer())
328 updateFromLayer();
329 }
330
331 void onLayerBlendModeChange(DocEvent& ev) override {
332 if (m_layer == ev.layer())
333 updateFromLayer();
334 }
335
336 void onUserDataChange(DocEvent& ev) override {
337 if (m_layer == ev.withUserData())
338 updateFromLayer();
339 }
340
341 void onToggleUserData() {
342 if (m_layer) {
343 m_userDataView.toggleVisibility();
344 g_window->remapWindow();
345 manager()->invalidate();
346 }
347 }
348
349 void onTileset() {
350 if (!m_layer || !m_layer->isTilemap())
351 return;
352
353 auto tilemap = static_cast<LayerTilemap*>(m_layer);
354 auto tileset = tilemap->tileset();
355
356 // Information about the tileset to be used for new tilemaps
357 TilesetSelector::Info tilesetInfo;
358 tilesetInfo.enabled = false;
359 tilesetInfo.newTileset = false;
360 tilesetInfo.grid = tileset->grid();
361 tilesetInfo.name = tileset->name();
362 tilesetInfo.baseIndex = tileset->baseIndex();
363 tilesetInfo.tsi = tilemap->tilesetIndex();
364
365 try {
366 gen::TilesetSelectorWindow window;
367 TilesetSelector tilesetSel(tilemap->sprite(), tilesetInfo);
368 window.tilesetOptions()->addChild(&tilesetSel);
369 window.openWindowInForeground();
370 if (window.closer() != window.ok())
371 return;
372
373 tilesetInfo = tilesetSel.getInfo();
374
375 if (tileset->name() != tilesetInfo.name ||
376 tileset->baseIndex() != tilesetInfo.baseIndex) {
377 ContextWriter writer(UIContext::instance());
378 Tx tx(writer.context(), "Set Tileset Properties");
379 if (tileset->name() != tilesetInfo.name)
380 tx(new cmd::SetTilesetName(tileset, tilesetInfo.name));
381 if (tileset->baseIndex() != tilesetInfo.baseIndex)
382 tx(new cmd::SetTilesetBaseIndex(tileset, tilesetInfo.baseIndex));
383 // TODO catch the tileset base index modification from the editor
384 App::instance()->mainWindow()->invalidate();
385 tx.commit();
386 }
387 }
388 catch (const std::exception& e) {
389 Console::showException(e);
390 }
391 }
392
393 void updateFromLayer() {
394 if (m_selfUpdate)
395 return;
396
397 m_timer.stop(); // Cancel current editions (just in case)
398
399 base::ScopedValue<bool> switchSelf(m_selfUpdate, true, false);
400
401 const bool tilemapVisibility = (m_layer && m_layer->isTilemap());
402 if (m_layer) {
403 name()->setText(m_layer->name().c_str());
404 name()->setEnabled(true);
405
406 if (m_layer->isImage()) {
407 mode()->setSelectedItem(nullptr);
408 for (auto item : *mode()) {
409 if (auto blendModeItem = dynamic_cast<BlendModeItem*>(item)) {
410 if (blendModeItem->mode() == static_cast<LayerImage*>(m_layer)->blendMode()) {
411 mode()->setSelectedItem(item);
412 break;
413 }
414 }
415 }
416 mode()->setEnabled(!m_layer->isBackground());
417 opacity()->setValue(static_cast<LayerImage*>(m_layer)->opacity());
418 opacity()->setEnabled(!m_layer->isBackground());
419 }
420 else {
421 mode()->setEnabled(false);
422 opacity()->setEnabled(false);
423 }
424
425 color_t c = m_layer->userData().color();
426 m_userDataView.color()->setColor(Color::fromRgb(rgba_getr(c), rgba_getg(c), rgba_getb(c), rgba_geta(c)));
427 m_userDataView.entry()->setText(m_layer->userData().text());
428 }
429 else {
430 name()->setText(Strings::layer_properties_no_layer());
431 name()->setEnabled(false);
432 mode()->setEnabled(false);
433 opacity()->setEnabled(false);
434 m_userDataView.setVisible(false, false);
435 }
436
437 if (tileset()->isVisible() != tilemapVisibility) {
438 tileset()->setVisible(tilemapVisibility);
439 tileset()->parent()->layout();
440 }
441 }
442
443 Timer m_timer;
444 bool m_pendingChanges = false;
445 Doc* m_document = nullptr;
446 Layer* m_layer = nullptr;
447 DocRange m_range;
448 bool m_selfUpdate = false;
449 UserDataView m_userDataView;
450};
451
452LayerPropertiesCommand::LayerPropertiesCommand()
453 : Command(CommandId::LayerProperties(), CmdRecordableFlag)
454{
455}
456
457bool LayerPropertiesCommand::onEnabled(Context* context)
458{
459 return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
460 ContextFlags::HasActiveLayer);
461}
462
463void LayerPropertiesCommand::onExecute(Context* context)
464{
465 ContextReader reader(context);
466 Doc* doc = static_cast<Doc*>(reader.document());
467 LayerImage* layer = static_cast<LayerImage*>(reader.layer());
468
469 if (!g_window)
470 g_window = new LayerPropertiesWindow;
471
472 g_window->setLayer(doc, layer);
473 g_window->openWindow();
474
475 // Focus layer name
476 g_window->name()->requestFocus();
477}
478
479Command* CommandFactory::createLayerPropertiesCommand()
480{
481 return new LayerPropertiesCommand;
482}
483
484} // namespace app
485