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 | |
45 | namespace app { |
46 | |
47 | using namespace ui; |
48 | |
49 | class LayerPropertiesCommand : public Command { |
50 | public: |
51 | LayerPropertiesCommand(); |
52 | |
53 | protected: |
54 | bool onEnabled(Context* context) override; |
55 | void onExecute(Context* context) override; |
56 | }; |
57 | |
58 | class LayerPropertiesWindow; |
59 | static LayerPropertiesWindow* g_window = nullptr; |
60 | |
61 | class LayerPropertiesWindow : public app::gen::LayerProperties, |
62 | public ContextObserver, |
63 | public DocObserver { |
64 | public: |
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 | |
170 | private: |
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 | |
452 | LayerPropertiesCommand::LayerPropertiesCommand() |
453 | : Command(CommandId::LayerProperties(), CmdRecordableFlag) |
454 | { |
455 | } |
456 | |
457 | bool LayerPropertiesCommand::onEnabled(Context* context) |
458 | { |
459 | return context->checkFlags(ContextFlags::ActiveDocumentIsWritable | |
460 | ContextFlags::HasActiveLayer); |
461 | } |
462 | |
463 | void 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 | |
479 | Command* CommandFactory::createLayerPropertiesCommand() |
480 | { |
481 | return new LayerPropertiesCommand; |
482 | } |
483 | |
484 | } // namespace app |
485 | |