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 | #define COLOR_BAR_TRACE(...) // TRACE(__VA_ARGS__) |
9 | |
10 | #ifdef HAVE_CONFIG_H |
11 | #include "config.h" |
12 | #endif |
13 | |
14 | #include "app/ui/color_bar.h" |
15 | |
16 | #include "app/app.h" |
17 | #include "app/app_menus.h" |
18 | #include "app/cmd/remap_colors.h" |
19 | #include "app/cmd/remap_tilemaps.h" |
20 | #include "app/cmd/remap_tileset.h" |
21 | #include "app/cmd/remove_tile.h" |
22 | #include "app/cmd/replace_image.h" |
23 | #include "app/cmd/set_palette.h" |
24 | #include "app/cmd/set_transparent_color.h" |
25 | #include "app/cmd_sequence.h" |
26 | #include "app/color.h" |
27 | #include "app/commands/command.h" |
28 | #include "app/commands/commands.h" |
29 | #include "app/commands/params.h" |
30 | #include "app/commands/quick_command.h" |
31 | #include "app/console.h" |
32 | #include "app/context_access.h" |
33 | #include "app/doc_api.h" |
34 | #include "app/doc_undo.h" |
35 | #include "app/i18n/strings.h" |
36 | #include "app/ini_file.h" |
37 | #include "app/inline_command_execution.h" |
38 | #include "app/modules/editors.h" |
39 | #include "app/modules/gui.h" |
40 | #include "app/modules/palettes.h" |
41 | #include "app/pref/preferences.h" |
42 | #include "app/tx.h" |
43 | #include "app/ui/color_spectrum.h" |
44 | #include "app/ui/color_tint_shade_tone.h" |
45 | #include "app/ui/color_wheel.h" |
46 | #include "app/ui/editor/editor.h" |
47 | #include "app/ui/hex_color_entry.h" |
48 | #include "app/ui/input_chain.h" |
49 | #include "app/ui/keyboard_shortcuts.h" |
50 | #include "app/ui/palette_popup.h" |
51 | #include "app/ui/skin/skin_theme.h" |
52 | #include "app/ui/status_bar.h" |
53 | #include "app/ui/timeline/timeline.h" |
54 | #include "app/ui_context.h" |
55 | #include "app/ui_context.h" |
56 | #include "app/util/cel_ops.h" |
57 | #include "app/util/clipboard.h" |
58 | #include "base/scoped_value.h" |
59 | #include "doc/cel.h" |
60 | #include "doc/cels_range.h" |
61 | #include "doc/image.h" |
62 | #include "doc/image_impl.h" |
63 | #include "doc/layer_tilemap.h" |
64 | #include "doc/palette.h" |
65 | #include "doc/palette_gradient_type.h" |
66 | #include "doc/primitives.h" |
67 | #include "doc/remap.h" |
68 | #include "doc/rgbmap.h" |
69 | #include "doc/sort_palette.h" |
70 | #include "doc/sprite.h" |
71 | #include "doc/tileset.h" |
72 | #include "os/surface.h" |
73 | #include "ui/alert.h" |
74 | #include "ui/graphics.h" |
75 | #include "ui/menu.h" |
76 | #include "ui/message.h" |
77 | #include "ui/paint_event.h" |
78 | #include "ui/separator.h" |
79 | #include "ui/splitter.h" |
80 | #include "ui/system.h" |
81 | #include "ui/tooltips.h" |
82 | |
83 | #include <cstring> |
84 | #include <limits> |
85 | #include <memory> |
86 | |
87 | namespace app { |
88 | |
89 | enum class PalButton { |
90 | SORT, |
91 | PRESETS, |
92 | OPTIONS, |
93 | MAX |
94 | }; |
95 | |
96 | using namespace app::skin; |
97 | using namespace ui; |
98 | |
99 | class ColorBar::WarningIcon : public ui::Button { |
100 | public: |
101 | WarningIcon() : ui::Button(std::string()) { |
102 | initTheme(); |
103 | } |
104 | protected: |
105 | void onInitTheme(ui::InitThemeEvent& ev) { |
106 | ui::Button::onInitTheme(ev); |
107 | |
108 | auto theme = skin::SkinTheme::get(this); |
109 | setStyle(theme->styles.warningBox()); |
110 | } |
111 | }; |
112 | |
113 | ////////////////////////////////////////////////////////////////////// |
114 | // ColorBar::ScrollableView class |
115 | |
116 | ColorBar::ScrollableView::ScrollableView() |
117 | { |
118 | initTheme(); |
119 | } |
120 | |
121 | void ColorBar::ScrollableView::onInitTheme(InitThemeEvent& ev) |
122 | { |
123 | auto hbar = horizontalBar(); |
124 | auto vbar = verticalBar(); |
125 | setup_mini_look(hbar); |
126 | setup_mini_look(vbar); |
127 | |
128 | View::onInitTheme(ev); |
129 | |
130 | auto theme = SkinTheme::get(this); |
131 | |
132 | setStyle(theme->styles.colorbarView()); |
133 | |
134 | hbar->setStyle(theme->styles.miniScrollbar()); |
135 | vbar->setStyle(theme->styles.miniScrollbar()); |
136 | hbar->setThumbStyle(theme->styles.miniScrollbarThumb()); |
137 | vbar->setThumbStyle(theme->styles.miniScrollbarThumb()); |
138 | |
139 | const int scrollBarWidth = theme->dimensions.miniScrollbarSize(); |
140 | hbar->setBarWidth(scrollBarWidth); |
141 | vbar->setBarWidth(scrollBarWidth); |
142 | } |
143 | |
144 | ////////////////////////////////////////////////////////////////////// |
145 | // ColorBar class |
146 | |
147 | ColorBar* ColorBar::m_instance = NULL; |
148 | |
149 | ColorBar::ColorBar(int align, TooltipManager* tooltipManager) |
150 | : Box(align) |
151 | , m_editPal(1) |
152 | , m_buttons(int(PalButton::MAX)) |
153 | , m_tilesButton(1) |
154 | , m_tilesetModeButtons(3) |
155 | , m_splitter(Splitter::ByPercentage, VERTICAL) |
156 | , m_splitterPalTil(Splitter::ByPercentage, VERTICAL) |
157 | , m_paletteView(true, PaletteView::FgBgColors, this, 16) |
158 | , m_tilesView(true, PaletteView::FgBgTiles, this, 16) |
159 | , m_remapPalButton(Strings::color_bar_remap_palette()) |
160 | , m_remapTilesButton(Strings::color_bar_remap_tiles()) |
161 | , m_selector(ColorSelector::NONE) |
162 | , m_tintShadeTone(nullptr) |
163 | , m_spectrum(nullptr) |
164 | , m_wheel(nullptr) |
165 | , m_fgColor(app::Color::fromRgb(255, 255, 255), IMAGE_RGB, ColorBarButtonsOptions()) |
166 | , m_bgColor(app::Color::fromRgb(0, 0, 0), IMAGE_RGB, ColorBarButtonsOptions()) |
167 | , m_fgWarningIcon(new WarningIcon) |
168 | , m_bgWarningIcon(new WarningIcon) |
169 | , m_fromPalView(false) |
170 | , m_fromPref(false) |
171 | , m_fromFgButton(false) |
172 | , m_fromBgButton(false) |
173 | , m_lastDocument(nullptr) |
174 | , m_lastTilesetId(doc::NullId) |
175 | , m_ascending(true) |
176 | , m_lastButton(kButtonLeft) |
177 | , m_editMode(false) |
178 | , m_tilemapMode(TilemapMode::Pixels) |
179 | , m_tilesetMode(TilesetMode::Auto) |
180 | , m_redrawTimer(250, this) |
181 | , m_redrawAll(false) |
182 | , m_implantChange(false) |
183 | , m_selfPalChange(false) |
184 | , m_splitterPalTilPos(50.0) |
185 | { |
186 | m_instance = this; |
187 | |
188 | auto theme = SkinTheme::get(this); |
189 | |
190 | m_editPal.addItem(theme->parts.timelineOpenPadlockActive()); |
191 | m_buttons.addItem(theme->parts.palSort()); |
192 | m_buttons.addItem(theme->parts.palPresets()); |
193 | m_buttons.addItem(theme->parts.palOptions()); |
194 | m_tilesButton.addItem(theme->parts.tiles()); |
195 | |
196 | static_assert(0 == int(TilesetMode::Manual) && |
197 | 1 == int(TilesetMode::Auto) && |
198 | 2 == int(TilesetMode::Stack), "Tileset mode buttons doesn't match TilesetMode enum values" ); |
199 | |
200 | m_tilesetModeButtons.addItem(theme->parts.tilesManual()); |
201 | m_tilesetModeButtons.addItem(theme->parts.tilesAuto()); |
202 | m_tilesetModeButtons.addItem(theme->parts.tilesStack()); |
203 | setTilesetMode(m_tilesetMode); |
204 | |
205 | m_paletteView.setColumns(8); |
206 | m_tilesView.setColumns(8); |
207 | |
208 | m_scrollablePalView.attachToView(&m_paletteView); |
209 | m_scrollableTilesView.attachToView(&m_tilesView); |
210 | m_scrollablePalView.setExpansive(true); |
211 | m_scrollableTilesView.setExpansive(true); |
212 | m_splitterPalTil.setExpansive(true); |
213 | |
214 | m_scrollableTilesView.setVisible(false); |
215 | m_tilesHelpers.setVisible(false); |
216 | m_remapPalButton.setVisible(false); |
217 | m_remapTilesButton.setVisible(false); |
218 | |
219 | m_splitterPalTil.addChild(&m_scrollablePalView); |
220 | m_splitterPalTil.addChild(&m_scrollableTilesView); |
221 | m_palettePlaceholder.addChild(&m_splitterPalTil); |
222 | m_palettePlaceholder.addChild(&m_remapPalButton); |
223 | m_palettePlaceholder.addChild(&m_remapTilesButton); |
224 | m_splitter.setId("palette_spectrum_splitter" ); |
225 | m_splitter.setPosition(80); |
226 | m_splitter.setExpansive(true); |
227 | m_splitter.addChild(&m_palettePlaceholder); |
228 | m_splitter.addChild(&m_selectorPlaceholder); |
229 | |
230 | setColorSelector( |
231 | Preferences::instance().colorBar.selector()); |
232 | |
233 | m_tilesHBox.addChild(&m_tilesButton); |
234 | m_tilesHBox.addChild(&m_tilesetModeButtons); |
235 | |
236 | m_palHBox.addChild(&m_editPal); |
237 | m_palHBox.addChild(&m_buttons); |
238 | |
239 | m_buttons.setExpansive(true); |
240 | m_tilesetModeButtons.setExpansive(true); |
241 | |
242 | // Hide the tiles controls by default. Without this, when the first |
243 | // onActiveSiteChange() event is received, and the we ask for the |
244 | // m_tilesHBox visibility, it might say that it's hidden because the |
245 | // color bar is hidden (because it's not yet in the screen, it's the |
246 | // first time it will be displayed). So we have to add this to make |
247 | // the tiles controls invisible in the first appearance of the color |
248 | // bar. |
249 | m_tilesHBox.setVisible(false); |
250 | |
251 | addChild(&m_palHBox); |
252 | addChild(&m_tilesHBox); |
253 | addChild(&m_splitter); |
254 | |
255 | addChild(&m_colorHelpers); |
256 | addChild(&m_tilesHelpers); |
257 | |
258 | HBox* fgBox = new HBox; |
259 | HBox* bgBox = new HBox; |
260 | fgBox->addChild(&m_fgColor); |
261 | fgBox->addChild(m_fgWarningIcon); |
262 | bgBox->addChild(&m_bgColor); |
263 | bgBox->addChild(m_bgWarningIcon); |
264 | m_colorHelpers.addChild(fgBox); |
265 | m_colorHelpers.addChild(bgBox); |
266 | |
267 | m_tilesHelpers.addChild(new BoxFiller); |
268 | m_tilesHelpers.addChild(&m_fgTile); |
269 | m_tilesHelpers.addChild(&m_bgTile); |
270 | m_tilesHelpers.addChild(new BoxFiller); |
271 | |
272 | m_fgColor.setId("fg_color" ); |
273 | m_bgColor.setId("bg_color" ); |
274 | m_fgColor.setExpansive(true); |
275 | m_bgColor.setExpansive(true); |
276 | |
277 | m_remapPalButton.Click.connect([this]{ onRemapPalButtonClick(); }); |
278 | m_remapTilesButton.Click.connect([this]{ onRemapTilesButtonClick(); }); |
279 | m_fgColor.Change.connect(&ColorBar::onFgColorButtonChange, this); |
280 | m_fgColor.BeforeChange.connect(&ColorBar::onFgColorButtonBeforeChange, this); |
281 | m_bgColor.Change.connect(&ColorBar::onBgColorButtonChange, this); |
282 | m_fgWarningIcon->Click.connect([this]{ onFixWarningClick(&m_fgColor, m_fgWarningIcon); }); |
283 | m_bgWarningIcon->Click.connect([this]{ onFixWarningClick(&m_bgColor, m_bgWarningIcon); }); |
284 | m_redrawTimer.Tick.connect([this]{ onTimerTick(); }); |
285 | m_editPal.ItemChange.connect([this]{ onSwitchPalEditMode(); }); |
286 | m_buttons.ItemChange.connect([this]{ onPaletteButtonClick(); }); |
287 | m_tilesButton.ItemChange.connect([this]{ onTilesButtonClick(); }); |
288 | m_tilesButton.RightClick.connect([this]{ onTilesButtonRightClick(); }); |
289 | m_fgTile.Change.connect(&ColorBar::onFgTileButtonChange, this); |
290 | m_bgTile.Change.connect(&ColorBar::onBgTileButtonChange, this); |
291 | m_tilesetModeButtons.ItemChange.connect([this]{ onTilesetModeButtonClick(); }); |
292 | |
293 | InitTheme.connect( |
294 | [this, fgBox, bgBox]{ |
295 | auto theme = SkinTheme::get(this); |
296 | |
297 | setBorder(gfx::Border(2*guiscale(), 0, 0, 0)); |
298 | setChildSpacing(2*guiscale()); |
299 | |
300 | m_fgColor.resetSizeHint(); |
301 | m_bgColor.resetSizeHint(); |
302 | m_fgColor.setSizeHint(0, m_fgColor.sizeHint().h); |
303 | m_bgColor.setSizeHint(0, m_bgColor.sizeHint().h); |
304 | |
305 | for (auto w : { &m_editPal, &m_buttons, |
306 | &m_tilesButton, &m_tilesetModeButtons }) { |
307 | w->setMinSize(gfx::Size(0, theme->dimensions.colorBarButtonsHeight())); |
308 | w->setMaxSize(gfx::Size(std::numeric_limits<int>::max(), |
309 | theme->dimensions.colorBarButtonsHeight())); // TODO add resetMaxSize |
310 | } |
311 | |
312 | m_buttons.setMaxSize( |
313 | gfx::Size(m_buttons.sizeHint().w, |
314 | theme->dimensions.colorBarButtonsHeight())); |
315 | m_tilesetModeButtons.setMaxSize( |
316 | gfx::Size(m_tilesetModeButtons.sizeHint().w, |
317 | theme->dimensions.colorBarButtonsHeight())); |
318 | |
319 | // Change color-bar background color (not ColorBar::setBgColor) |
320 | this->Widget::setBgColor(theme->colors.tabActiveFace()); |
321 | m_paletteView.setBgColor(theme->colors.tabActiveFace()); |
322 | m_paletteView.setBoxSize(Preferences::instance().colorBar.boxSize()); |
323 | m_paletteView.initTheme(); |
324 | |
325 | m_tilesView.setBgColor(theme->colors.tabActiveFace()); |
326 | m_tilesView.setBoxSize(Preferences::instance().colorBar.tilesBoxSize()); |
327 | m_tilesView.initTheme(); |
328 | |
329 | // Styles |
330 | m_splitter.setStyle(theme->styles.workspaceSplitter()); |
331 | m_splitterPalTil.setStyle(theme->styles.workspaceSplitter()); |
332 | |
333 | fgBox->noBorderNoChildSpacing(); |
334 | bgBox->noBorderNoChildSpacing(); |
335 | |
336 | if (m_palettePopup) |
337 | m_palettePopup->initTheme(); |
338 | }); |
339 | initTheme(); |
340 | |
341 | // Set background color reading its value from the configuration. |
342 | setBgColor(Preferences::instance().colorBar.bgColor()); |
343 | |
344 | // Clear the selection of the BG color in the palette. |
345 | m_paletteView.deselect(); |
346 | |
347 | // Set foreground color reading its value from the configuration. |
348 | setFgColor(Preferences::instance().colorBar.fgColor()); |
349 | |
350 | // Tooltips |
351 | setupTooltips(tooltipManager); |
352 | |
353 | onColorButtonChange(getFgColor()); |
354 | |
355 | UIContext::instance()->add_observer(this); |
356 | m_beforeCmdConn = UIContext::instance()->BeforeCommandExecution.connect(&ColorBar::onBeforeExecuteCommand, this); |
357 | m_afterCmdConn = UIContext::instance()->AfterCommandExecution.connect(&ColorBar::onAfterExecuteCommand, this); |
358 | m_fgConn = Preferences::instance().colorBar.fgColor.AfterChange.connect([this]{ onFgColorChangeFromPreferences(); }); |
359 | m_bgConn = Preferences::instance().colorBar.bgColor.AfterChange.connect([this]{ onBgColorChangeFromPreferences(); }); |
360 | m_fgTileConn = Preferences::instance().colorBar.fgTile.AfterChange.connect([this]{ onFgTileChangeFromPreferences(); }); |
361 | m_bgTileConn = Preferences::instance().colorBar.bgTile.AfterChange.connect([this]{ onBgTileChangeFromPreferences(); }); |
362 | m_sepConn = Preferences::instance().colorBar.entriesSeparator.AfterChange.connect([this]{ invalidate(); }); |
363 | m_paletteView.FocusOrClick.connect(&ColorBar::onFocusPaletteView, this); |
364 | m_tilesView.FocusOrClick.connect(&ColorBar::onFocusTilesView, this); |
365 | m_appPalChangeConn = App::instance()->PaletteChange.connect(&ColorBar::onAppPaletteChange, this); |
366 | KeyboardShortcuts::instance()->UserChange.connect( |
367 | [this, tooltipManager]{ setupTooltips(tooltipManager); }); |
368 | |
369 | setEditMode(false); |
370 | registerCommands(); |
371 | } |
372 | |
373 | ColorBar::~ColorBar() |
374 | { |
375 | UIContext::instance()->remove_observer(this); |
376 | } |
377 | |
378 | void ColorBar::setPixelFormat(PixelFormat pixelFormat) |
379 | { |
380 | m_fgColor.setPixelFormat(pixelFormat); |
381 | m_bgColor.setPixelFormat(pixelFormat); |
382 | } |
383 | |
384 | app::Color ColorBar::getFgColor() const |
385 | { |
386 | return m_fgColor.getColor(); |
387 | } |
388 | |
389 | app::Color ColorBar::getBgColor() const |
390 | { |
391 | return m_bgColor.getColor(); |
392 | } |
393 | |
394 | void ColorBar::setFgColor(const app::Color& color) |
395 | { |
396 | if (m_fromFgButton) |
397 | return; |
398 | |
399 | m_fgColor.setColor(color); |
400 | if (!m_fromPalView) |
401 | onColorButtonChange(color); |
402 | } |
403 | |
404 | void ColorBar::setBgColor(const app::Color& color) |
405 | { |
406 | if (m_fromBgButton) |
407 | return; |
408 | |
409 | m_bgColor.setColor(color); |
410 | if (!m_fromPalView) |
411 | onColorButtonChange(color); |
412 | } |
413 | |
414 | void ColorBar::setFgTile(doc::tile_t tile) |
415 | { |
416 | m_fgTile.setTile(tile); |
417 | m_tilesView.selectColor(tile); |
418 | if (!m_fromPalView) |
419 | onFgTileButtonChange(tile); |
420 | } |
421 | |
422 | void ColorBar::setBgTile(doc::tile_t tile) |
423 | { |
424 | m_bgTile.setTile(tile); |
425 | m_tilesView.selectColor(tile); |
426 | if (!m_fromPalView) |
427 | onBgTileButtonChange(tile); |
428 | } |
429 | |
430 | doc::tile_index ColorBar::getFgTile() const |
431 | { |
432 | return m_fgTile.getTile(); |
433 | } |
434 | |
435 | doc::tile_index ColorBar::getBgTile() const |
436 | { |
437 | return m_bgTile.getTile(); |
438 | } |
439 | |
440 | ColorBar::ColorSelector ColorBar::getColorSelector() const |
441 | { |
442 | return m_selector; |
443 | } |
444 | |
445 | void ColorBar::setColorSelector(ColorSelector selector) |
446 | { |
447 | if (m_selector == selector) |
448 | return; |
449 | |
450 | if (m_tintShadeTone) m_tintShadeTone->setVisible(false); |
451 | if (m_spectrum) m_spectrum->setVisible(false); |
452 | if (m_wheel) m_wheel->setVisible(false); |
453 | |
454 | m_selector = selector; |
455 | Preferences::instance().colorBar.selector(m_selector); |
456 | |
457 | switch (m_selector) { |
458 | |
459 | case ColorSelector::TINT_SHADE_TONE: |
460 | if (!m_tintShadeTone) { |
461 | m_tintShadeTone = new ColorTintShadeTone; |
462 | m_tintShadeTone->setExpansive(true); |
463 | m_tintShadeTone->selectColor(m_fgColor.getColor()); |
464 | m_tintShadeTone->ColorChange.connect(&ColorBar::onPickSpectrum, this); |
465 | m_selectorPlaceholder.addChild(m_tintShadeTone); |
466 | } |
467 | m_tintShadeTone->setVisible(true); |
468 | break; |
469 | |
470 | case ColorSelector::SPECTRUM: |
471 | if (!m_spectrum) { |
472 | m_spectrum = new ColorSpectrum; |
473 | m_spectrum->setExpansive(true); |
474 | m_spectrum->selectColor(m_fgColor.getColor()); |
475 | m_spectrum->ColorChange.connect(&ColorBar::onPickSpectrum, this); |
476 | m_selectorPlaceholder.addChild(m_spectrum); |
477 | } |
478 | m_spectrum->setVisible(true); |
479 | break; |
480 | |
481 | case ColorSelector::RGB_WHEEL: |
482 | case ColorSelector::RYB_WHEEL: |
483 | case ColorSelector::NORMAL_MAP_WHEEL: |
484 | if (!m_wheel) { |
485 | m_wheel = new ColorWheel; |
486 | m_wheel->setExpansive(true); |
487 | m_wheel->selectColor(m_fgColor.getColor()); |
488 | m_wheel->ColorChange.connect(&ColorBar::onPickSpectrum, this); |
489 | m_selectorPlaceholder.addChild(m_wheel); |
490 | } |
491 | if (m_selector == ColorSelector::RGB_WHEEL) { |
492 | m_wheel->setColorModel(ColorWheel::ColorModel::RGB); |
493 | } |
494 | else if (m_selector == ColorSelector::RYB_WHEEL) { |
495 | m_wheel->setColorModel(ColorWheel::ColorModel::RYB); |
496 | } |
497 | else if (m_selector == ColorSelector::NORMAL_MAP_WHEEL) { |
498 | m_wheel->setColorModel(ColorWheel::ColorModel::NORMAL_MAP); |
499 | } |
500 | m_wheel->setVisible(true); |
501 | break; |
502 | |
503 | } |
504 | |
505 | m_selectorPlaceholder.layout(); |
506 | } |
507 | |
508 | bool ColorBar::inEditMode() const |
509 | { |
510 | return |
511 | (m_editMode && |
512 | m_lastDocument && |
513 | m_lastDocument->sprite() && |
514 | m_lastDocument->sprite()->pixelFormat() != IMAGE_GRAYSCALE); |
515 | } |
516 | |
517 | void ColorBar::setEditMode(bool state) |
518 | { |
519 | auto theme = SkinTheme::get(this); |
520 | ButtonSet::Item* item = m_editPal.getItem(0); |
521 | |
522 | m_editMode = state; |
523 | item->setIcon(state ? theme->parts.timelineOpenPadlockActive(): |
524 | theme->parts.timelineClosedPadlockNormal()); |
525 | item->setHotColor(state ? theme->colors.editPalFace(): gfx::ColorNone); |
526 | |
527 | // Deselect color entries when we cancel editing |
528 | if (!state) |
529 | m_paletteView.deselect(); |
530 | } |
531 | |
532 | TilemapMode ColorBar::tilemapMode() const |
533 | { |
534 | return |
535 | (m_lastDocument && |
536 | m_lastDocument->sprite()) ? m_tilemapMode: |
537 | TilemapMode::Pixels; |
538 | } |
539 | |
540 | void ColorBar::setTilemapMode(TilemapMode mode) |
541 | { |
542 | if (m_tilemapMode != mode) { |
543 | m_tilemapMode = mode; |
544 | updateFromTilemapMode(); |
545 | } |
546 | } |
547 | |
548 | void ColorBar::updateFromTilemapMode() |
549 | { |
550 | SkinTheme* theme = static_cast<SkinTheme*>(this->theme()); |
551 | ButtonSet::Item* item = m_tilesButton.getItem(0); |
552 | |
553 | const bool canEditTiles = this->canEditTiles(); |
554 | const bool editTiles = (canEditTiles && |
555 | m_tilemapMode == TilemapMode::Tiles); |
556 | |
557 | item->setHotColor(editTiles ? theme->colors.editPalFace(): |
558 | gfx::ColorNone); |
559 | item->setMono(true); |
560 | |
561 | if (Preferences::instance().colorBar.showColorAndTiles()) { |
562 | m_scrollablePalView.setVisible(true); |
563 | m_selectorPlaceholder.setVisible(true); |
564 | if (canEditTiles) { |
565 | m_scrollableTilesView.setVisible(true); |
566 | } |
567 | else { |
568 | manager()->freeWidget(&m_tilesView); |
569 | m_scrollableTilesView.setVisible(false); |
570 | } |
571 | |
572 | if (editTiles) { |
573 | m_colorHelpers.setVisible(false); |
574 | m_tilesHelpers.setVisible(true); |
575 | } |
576 | else { |
577 | m_colorHelpers.setVisible(true); |
578 | m_tilesHelpers.setVisible(false); |
579 | } |
580 | } |
581 | else { |
582 | if (editTiles) { |
583 | manager()->freeWidget(&m_paletteView); |
584 | m_scrollablePalView.setVisible(false); |
585 | m_scrollableTilesView.setVisible(true); |
586 | m_selectorPlaceholder.setVisible(false); |
587 | m_colorHelpers.setVisible(false); |
588 | m_tilesHelpers.setVisible(true); |
589 | } |
590 | else { |
591 | manager()->freeWidget(&m_tilesView); |
592 | m_scrollablePalView.setVisible(true); |
593 | m_scrollableTilesView.setVisible(false); |
594 | m_selectorPlaceholder.setVisible(true); |
595 | m_colorHelpers.setVisible(true); |
596 | m_tilesHelpers.setVisible(false); |
597 | } |
598 | } |
599 | |
600 | layout(); |
601 | } |
602 | |
603 | TilesetMode ColorBar::tilesetMode() const |
604 | { |
605 | if (m_lastDocument && |
606 | m_lastDocument->sprite()) { |
607 | return m_tilesetMode; |
608 | } |
609 | else |
610 | return TilesetMode::Manual; |
611 | } |
612 | |
613 | void ColorBar::setTilesetMode(const TilesetMode mode) |
614 | { |
615 | m_tilesetMode = mode; |
616 | |
617 | for (int i=0; i<3; ++i) { |
618 | ButtonSet::Item* item = m_tilesetModeButtons.getItem(i); |
619 | item->setSelected(int(mode) == i); |
620 | } |
621 | |
622 | // Change to pixels mode automatically |
623 | if (m_tilemapMode == TilemapMode::Tiles) |
624 | setTilemapMode(TilemapMode::Pixels); |
625 | } |
626 | |
627 | void ColorBar::onSizeHint(ui::SizeHintEvent& ev) |
628 | { |
629 | m_colorHelpers.resetSizeHint(); |
630 | m_tilesHelpers.resetSizeHint(); |
631 | gfx::Size sz = m_colorHelpers.sizeHint(); |
632 | sz |= m_tilesHelpers.sizeHint(); |
633 | m_colorHelpers.setSizeHint(sz); |
634 | m_tilesHelpers.setSizeHint(sz); |
635 | |
636 | Box::onSizeHint(ev); |
637 | } |
638 | |
639 | void ColorBar::onActiveSiteChange(const Site& site) |
640 | { |
641 | if (m_lastDocument != site.document()) { |
642 | if (m_lastDocument) |
643 | m_lastDocument->remove_observer(this); |
644 | |
645 | m_lastDocument = const_cast<Doc*>(site.document()); |
646 | |
647 | if (m_lastDocument) |
648 | m_lastDocument->add_observer(this); |
649 | |
650 | hideRemapPal(); |
651 | hideRemapTiles(); |
652 | } |
653 | |
654 | bool isTilemap = false; |
655 | if (site.layer()) |
656 | isTilemap = site.layer()->isTilemap(); |
657 | |
658 | if (m_tilesHBox.isVisible() != isTilemap) { |
659 | m_tilesHBox.setVisible(isTilemap); |
660 | updateFromTilemapMode(); |
661 | } |
662 | |
663 | if (isTilemap) { |
664 | doc::ObjectId newTilesetId = |
665 | static_cast<const doc::LayerTilemap*>(site.layer())->tileset()->id(); |
666 | if (m_lastTilesetId != newTilesetId) { |
667 | m_lastTilesetId = newTilesetId; |
668 | m_scrollableTilesView.updateView(); |
669 | } |
670 | } |
671 | else { |
672 | m_lastTilesetId = doc::NullId; |
673 | } |
674 | } |
675 | |
676 | void ColorBar::onGeneralUpdate(DocEvent& ev) |
677 | { |
678 | // TODO Observe palette changes only |
679 | invalidate(); |
680 | } |
681 | |
682 | void ColorBar::onTilesetChanged(DocEvent& ev) |
683 | { |
684 | // This can happen when a filter is applied to each tile in a |
685 | // background thread. |
686 | if (!ui::is_ui_thread()) |
687 | return; |
688 | |
689 | if (m_scrollableTilesView.isVisible()) |
690 | m_scrollableTilesView.updateView(); |
691 | |
692 | m_tilesView.deselect(); |
693 | } |
694 | |
695 | void ColorBar::onAppPaletteChange() |
696 | { |
697 | COLOR_BAR_TRACE("ColorBar::onAppPaletteChange()\n" ); |
698 | |
699 | fixColorIndex(m_fgColor); |
700 | fixColorIndex(m_bgColor); |
701 | |
702 | updateWarningIcon(m_fgColor.getColor(), m_fgWarningIcon); |
703 | updateWarningIcon(m_bgColor.getColor(), m_bgWarningIcon); |
704 | } |
705 | |
706 | void ColorBar::onFocusPaletteView(ui::Message* msg) |
707 | { |
708 | if (tilemapMode() != TilemapMode::Pixels) |
709 | setTilemapMode(TilemapMode::Pixels); |
710 | App::instance()->inputChain().prioritize(this, msg); |
711 | } |
712 | |
713 | void ColorBar::onFocusTilesView(ui::Message* msg) |
714 | { |
715 | if (tilemapMode() != TilemapMode::Tiles) |
716 | setTilemapMode(TilemapMode::Tiles); |
717 | App::instance()->inputChain().prioritize(this, msg); |
718 | } |
719 | |
720 | void ColorBar::onBeforeExecuteCommand(CommandExecutionEvent& ev) |
721 | { |
722 | if (ev.command()->id() == CommandId::SetPalette() || |
723 | ev.command()->id() == CommandId::LoadPalette() || |
724 | ev.command()->id() == CommandId::ColorQuantization()) { |
725 | showRemapPal(); |
726 | } |
727 | } |
728 | |
729 | void ColorBar::onAfterExecuteCommand(CommandExecutionEvent& ev) |
730 | { |
731 | if (ev.command()->id() == CommandId::Undo() || |
732 | ev.command()->id() == CommandId::Redo()) |
733 | invalidate(); |
734 | |
735 | // If the sprite isn't Indexed anymore (e.g. because we've just |
736 | // undone a "RGB -> Indexed" conversion), we hide the "Remap |
737 | // Palette" button. |
738 | Site site = UIContext::instance()->activeSite(); |
739 | if (site.sprite() && |
740 | site.sprite()->pixelFormat() != IMAGE_INDEXED) { |
741 | hideRemapPal(); |
742 | } |
743 | |
744 | // If the layer isn't a tilemap anymore, we hide the "Remap Tiles" |
745 | // button. |
746 | if (site.layer() && |
747 | !site.layer()->isTilemap()) { |
748 | hideRemapTiles(); |
749 | } |
750 | } |
751 | |
752 | void ColorBar::onSwitchPalEditMode() |
753 | { |
754 | m_editPal.deselectItems(); |
755 | setEditMode(!inEditMode()); |
756 | } |
757 | |
758 | // Switches the palette-editor |
759 | void ColorBar::onPaletteButtonClick() |
760 | { |
761 | int item = m_buttons.selectedItem(); |
762 | m_buttons.deselectItems(); |
763 | |
764 | switch (static_cast<PalButton>(item)) { |
765 | |
766 | case PalButton::SORT: |
767 | showPaletteSortOptions(); |
768 | break; |
769 | |
770 | case PalButton::PRESETS: |
771 | showPalettePresets(); |
772 | break; |
773 | |
774 | case PalButton::OPTIONS: |
775 | showPaletteOptions(); |
776 | break; |
777 | |
778 | } |
779 | } |
780 | |
781 | void ColorBar::onTilesButtonClick() |
782 | { |
783 | m_tilesButton.deselectItems(); |
784 | setTilemapMode( |
785 | (tilemapMode() == TilemapMode::Pixels ? TilemapMode::Tiles: |
786 | TilemapMode::Pixels)); |
787 | } |
788 | |
789 | void ColorBar::onTilesButtonRightClick() |
790 | { |
791 | auto& pref = Preferences::instance(); |
792 | pref.colorBar.showColorAndTiles(!pref.colorBar.showColorAndTiles()); |
793 | updateFromTilemapMode(); |
794 | } |
795 | |
796 | void ColorBar::onTilesetModeButtonClick() |
797 | { |
798 | int item = m_tilesetModeButtons.selectedItem(); |
799 | m_tilesetModeButtons.deselectItems(); |
800 | setTilesetMode(static_cast<TilesetMode>(item)); |
801 | } |
802 | |
803 | void ColorBar::onRemapPalButtonClick() |
804 | { |
805 | ASSERT(m_oldPalette); |
806 | |
807 | // Create remap from m_oldPalette to the current palette |
808 | Remap remap(1); |
809 | try { |
810 | InlineCommandExecution inlineCmd(UIContext::instance()); |
811 | ContextWriter writer(UIContext::instance()); |
812 | Sprite* sprite = writer.sprite(); |
813 | ASSERT(sprite); |
814 | if (!sprite) |
815 | return; |
816 | |
817 | remap = create_remap_to_change_palette( |
818 | m_oldPalette.get(), get_current_palette(), |
819 | sprite->transparentColor(), true); |
820 | } |
821 | catch (base::Exception& e) { |
822 | Console::showException(e); |
823 | } |
824 | |
825 | // Check the remap |
826 | if (!remap.isFor8bit() && |
827 | Alert::show(Strings::alerts_auto_remap()) != 1) { |
828 | return; |
829 | } |
830 | |
831 | try { |
832 | InlineCommandExecution inlineCmd(UIContext::instance()); |
833 | ContextWriter writer(UIContext::instance()); |
834 | Sprite* sprite = writer.sprite(); |
835 | if (sprite) { |
836 | ASSERT(sprite->pixelFormat() == IMAGE_INDEXED); |
837 | |
838 | Tx tx(writer.context(), "Remap Colors" , ModifyDocument); |
839 | bool remapPixels = true; |
840 | |
841 | std::vector<ImageRef> images; |
842 | sprite->getImages(images); |
843 | |
844 | if (remap.isFor8bit()) { |
845 | PalettePicks usedEntries(256); |
846 | |
847 | for (ImageRef& image : images) { |
848 | for (const auto& i : LockImageBits<IndexedTraits>(image.get())) |
849 | usedEntries[i] = true; |
850 | } |
851 | |
852 | if (remap.isInvertible(usedEntries)) { |
853 | for (int i=0; i<remap.size(); ++i) { |
854 | if (i >= usedEntries.size() || !usedEntries[i]) { |
855 | remap.unused(i); |
856 | } |
857 | } |
858 | |
859 | tx(new cmd::RemapColors(sprite, remap)); |
860 | remapPixels = false; |
861 | } |
862 | } |
863 | |
864 | // Special remap saving original images in undo history |
865 | if (remapPixels) { |
866 | for (ImageRef& image : images) { |
867 | ImageRef newImage(Image::createCopy(image.get())); |
868 | doc::remap_image(newImage.get(), remap); |
869 | |
870 | tx(new cmd::ReplaceImage(sprite, image, newImage)); |
871 | } |
872 | } |
873 | |
874 | color_t oldTransparent = sprite->transparentColor(); |
875 | color_t newTransparent = (remap[oldTransparent] >= 0) ? remap[oldTransparent] : oldTransparent; |
876 | if (newTransparent >= get_current_palette()->size()) |
877 | newTransparent = get_current_palette()->size() - 1; |
878 | if (oldTransparent != newTransparent) |
879 | tx(new cmd::SetTransparentColor(sprite, newTransparent)); |
880 | |
881 | tx.commit(); |
882 | } |
883 | update_screen_for_document(writer.document()); |
884 | hideRemapPal(); |
885 | } |
886 | catch (base::Exception& e) { |
887 | Console::showException(e); |
888 | } |
889 | } |
890 | |
891 | void ColorBar::onRemapTilesButtonClick() |
892 | { |
893 | COLOR_BAR_TRACE("remapTiles\n" ); |
894 | try { |
895 | InlineCommandExecution inlineCmd(UIContext::instance()); |
896 | ContextWriter writer(UIContext::instance(), 500); |
897 | Sprite* sprite = writer.sprite(); |
898 | if (!sprite) |
899 | return; |
900 | |
901 | doc::Tileset* tileset = m_tilesView.tileset(); |
902 | std::vector<ImageRef> tilemaps; |
903 | sprite->getTilemapsByTileset(tileset, tilemaps); |
904 | |
905 | const int n = std::max(m_oldTileset->size(), |
906 | tileset->size()); |
907 | PalettePicks usedTiles(n); |
908 | |
909 | if (n > 0) { |
910 | for (const ImageRef& tilemap : tilemaps) { |
911 | for (const doc::tile_t t : LockImageBits<TilemapTraits>(tilemap.get())) |
912 | if (t != doc::notile) |
913 | usedTiles[doc::tile_geti(t)] = true; |
914 | } |
915 | } |
916 | |
917 | // Remap all tiles in the same order as in newTileset |
918 | Remap remap(n); |
919 | bool existMapToEmpty = false; |
920 | |
921 | for (tile_index ti=0; ti<n; ++ti) { |
922 | auto img = m_oldTileset->get(ti); |
923 | tile_index destTi; |
924 | if (img) { |
925 | tileset->findTileIndex(img, destTi); |
926 | |
927 | COLOR_BAR_TRACE(" - Remap tile %d -> %d\n" , ti, destTi); |
928 | remap.map(ti, destTi); |
929 | } |
930 | else { |
931 | remap.map(ti, destTi = doc::notile); |
932 | } |
933 | |
934 | if (destTi == doc::notile && |
935 | ti < usedTiles.size() && usedTiles[ti]) { |
936 | COLOR_BAR_TRACE(" - Remap tile %d to empty (used=%d)\n" , ti, usedTiles[ti]); |
937 | existMapToEmpty = true; |
938 | } |
939 | } |
940 | |
941 | // Nothing to remap |
942 | if (remap.isIdentity()) { |
943 | COLOR_BAR_TRACE(" - Nothing to remap\n" ); |
944 | return; |
945 | } |
946 | |
947 | Tx tx(writer.context(), Strings::color_bar_remap_tiles(), ModifyDocument); |
948 | if (!existMapToEmpty && |
949 | remap.isInvertible(usedTiles)) { |
950 | tx(new cmd::RemapTilemaps(tileset, remap)); |
951 | } |
952 | else { |
953 | for (const ImageRef& tilemap : tilemaps) { |
954 | ImageRef newTilemap(Image::createCopy(tilemap.get())); |
955 | doc::remap_image(newTilemap.get(), remap); |
956 | |
957 | // TODO improve this with a cmd::CopyRegion() |
958 | tx(new cmd::ReplaceImage(sprite, tilemap, newTilemap)); |
959 | } |
960 | } |
961 | tx.commit(); |
962 | |
963 | hideRemapTiles(); |
964 | // TODO this should be automatic in last ~Tx() destruction |
965 | manager()->invalidate(); |
966 | } |
967 | catch (base::Exception& e) { |
968 | Console::showException(e); |
969 | } |
970 | } |
971 | |
972 | bool ColorBar::onIsPaletteViewActive(PaletteView* paletteView) const |
973 | { |
974 | if (paletteView == &m_paletteView) { |
975 | return |
976 | (m_tilemapMode == TilemapMode::Pixels) || |
977 | // As the m_tilemapMode value is kept to restore it if we go |
978 | // back to a tilemap layer, there is a possibility where we are |
979 | // in a regular layer with the tiles mode enabled (because we |
980 | // were in a tilemap layer just right before). We have to check |
981 | // this special case where we are in "tiles mode" but we are |
982 | // actually in a regular layer (canEditTiles() is false). |
983 | (m_tilemapMode == TilemapMode::Tiles && !canEditTiles()); |
984 | } |
985 | else if (paletteView == &m_tilesView) { |
986 | return (m_tilemapMode == TilemapMode::Tiles); |
987 | } |
988 | else { |
989 | return false; |
990 | } |
991 | } |
992 | |
993 | void ColorBar::onPaletteViewIndexChange(int index, ui::MouseButton button) |
994 | { |
995 | COLOR_BAR_TRACE("ColorBar::onPaletteViewIndexChange(%d)\n" , index); |
996 | |
997 | base::ScopedValue<bool> lock(m_fromPalView, true, m_fromPalView); |
998 | |
999 | app::Color color = app::Color::fromIndex(index); |
1000 | |
1001 | if (button == kButtonRight) |
1002 | setBgColor(color); |
1003 | else if (button == kButtonLeft) |
1004 | setFgColor(color); |
1005 | else if (button == kButtonMiddle) |
1006 | setTransparentIndex(index); |
1007 | |
1008 | ChangeSelection(); |
1009 | } |
1010 | |
1011 | void ColorBar::onPaletteViewModification(const Palette* newPalette, |
1012 | PaletteViewModification mod) |
1013 | { |
1014 | const char* text = "Palette Change" ; |
1015 | switch (mod) { |
1016 | case PaletteViewModification::CLEAR: text = "Clear Colors" ; break; |
1017 | case PaletteViewModification::DRAGANDDROP: text = "Drag-and-Drop Colors" ; break; |
1018 | case PaletteViewModification::RESIZE: text = "Resize Palette" ; break; |
1019 | } |
1020 | setPalette(newPalette, text); |
1021 | } |
1022 | |
1023 | void ColorBar::setPalette(const doc::Palette* newPalette, const std::string& actionText) |
1024 | { |
1025 | showRemapPal(); |
1026 | |
1027 | try { |
1028 | InlineCommandExecution inlineCmd(UIContext::instance()); |
1029 | ContextWriter writer(UIContext::instance(), 500); |
1030 | Sprite* sprite = writer.sprite(); |
1031 | frame_t frame = writer.frame(); |
1032 | if (sprite && |
1033 | newPalette->countDiff(sprite->palette(frame), nullptr, nullptr)) { |
1034 | Tx tx(writer.context(), actionText, ModifyDocument); |
1035 | tx(new cmd::SetPalette(sprite, frame, newPalette)); |
1036 | tx.commit(); |
1037 | } |
1038 | } |
1039 | catch (base::Exception& e) { |
1040 | Console::showException(e); |
1041 | } |
1042 | } |
1043 | |
1044 | void ColorBar::setTransparentIndex(int index) |
1045 | { |
1046 | try { |
1047 | InlineCommandExecution inlineCmd(UIContext::instance()); |
1048 | ContextWriter writer(UIContext::instance()); |
1049 | Sprite* sprite = writer.sprite(); |
1050 | if (sprite && |
1051 | sprite->pixelFormat() == IMAGE_INDEXED && |
1052 | int(sprite->transparentColor()) != index) { |
1053 | // TODO merge this code with SpritePropertiesCommand |
1054 | Tx tx(writer.context(), "Set Transparent Color" ); |
1055 | DocApi api = writer.document()->getApi(tx); |
1056 | api.setSpriteTransparentColor(sprite, index); |
1057 | tx.commit(); |
1058 | |
1059 | update_screen_for_document(writer.document()); |
1060 | } |
1061 | } |
1062 | catch (base::Exception& e) { |
1063 | Console::showException(e); |
1064 | } |
1065 | } |
1066 | |
1067 | void ColorBar::onPaletteViewChangeSize(PaletteView* paletteView, int boxsize) |
1068 | { |
1069 | if (paletteView == &m_tilesView) |
1070 | Preferences::instance().colorBar.tilesBoxSize(boxsize); |
1071 | else |
1072 | Preferences::instance().colorBar.boxSize(boxsize); |
1073 | } |
1074 | |
1075 | void ColorBar::onPaletteViewPasteColors( |
1076 | const Palette* fromPal, const doc::PalettePicks& from, const doc::PalettePicks& _to) |
1077 | { |
1078 | if (!from.picks() || !_to.picks()) // Nothing to do |
1079 | return; |
1080 | |
1081 | doc::PalettePicks to = _to; |
1082 | int to_first = to.firstPick(); |
1083 | int to_last = to.lastPick(); |
1084 | |
1085 | // Add extra picks in to range if it's needed to paste more colors. |
1086 | int from_picks = from.picks(); |
1087 | int to_picks = to.picks(); |
1088 | if (to_picks < from_picks) { |
1089 | for (int j=to_last+1; j<to.size() && to_picks<from_picks; ++j) { |
1090 | to[j] = true; |
1091 | ++to_picks; |
1092 | } |
1093 | } |
1094 | |
1095 | Palette newPalette(*get_current_palette()); |
1096 | |
1097 | int i = 0; |
1098 | int j = to_first; |
1099 | |
1100 | for (auto state : from) { |
1101 | if (state) { |
1102 | if (j < newPalette.size()) |
1103 | newPalette.setEntry(j, fromPal->getEntry(i)); |
1104 | else |
1105 | newPalette.addEntry(fromPal->getEntry(i)); |
1106 | |
1107 | for (++j; j<to.size(); ++j) |
1108 | if (to[j]) |
1109 | break; |
1110 | } |
1111 | ++i; |
1112 | } |
1113 | |
1114 | setPalette(&newPalette, "Paste Colors" ); |
1115 | } |
1116 | |
1117 | app::Color ColorBar::onPaletteViewGetForegroundIndex() |
1118 | { |
1119 | return getFgColor(); |
1120 | } |
1121 | |
1122 | app::Color ColorBar::onPaletteViewGetBackgroundIndex() |
1123 | { |
1124 | return getBgColor(); |
1125 | } |
1126 | |
1127 | doc::tile_index ColorBar::onPaletteViewGetForegroundTile() |
1128 | { |
1129 | return doc::tile_geti(getFgTile()); |
1130 | } |
1131 | |
1132 | doc::tile_index ColorBar::onPaletteViewGetBackgroundTile() |
1133 | { |
1134 | return doc::tile_geti(getBgTile()); |
1135 | } |
1136 | |
1137 | void ColorBar::onTilesViewClearTiles(const doc::PalettePicks& _picks) |
1138 | { |
1139 | // Copy the collection of selected tiles because in case that the |
1140 | // user want to delete a range of tiles (several tiles at the same |
1141 | // time), after the first cmd::RemoveTile() is executed this |
1142 | // collection (_picks) will be modified and we'll lost the other |
1143 | // selected tiles to remove. |
1144 | doc::PalettePicks picks = _picks; |
1145 | try { |
1146 | InlineCommandExecution inlineCmd(UIContext::instance()); |
1147 | ContextWriter writer(UIContext::instance(), 500); |
1148 | Sprite* sprite = writer.sprite(); |
1149 | ASSERT(writer.layer()->isTilemap()); |
1150 | if (sprite) { |
1151 | auto tileset = m_tilesView.tileset(); |
1152 | |
1153 | Tx tx(writer.context(), "Clear Tiles" , ModifyDocument); |
1154 | for (int ti=int(picks.size())-1; ti>=0; --ti) { |
1155 | if (picks[ti]) |
1156 | tx(new cmd::RemoveTile(tileset, ti)); |
1157 | } |
1158 | tx.commit(); |
1159 | |
1160 | update_screen_for_document(writer.document()); |
1161 | } |
1162 | } |
1163 | catch (base::Exception& e) { |
1164 | Console::showException(e); |
1165 | } |
1166 | } |
1167 | |
1168 | void ColorBar::onTilesViewResize(const int newSize) |
1169 | { |
1170 | auto tileset = m_tilesView.tileset(); |
1171 | if (!tileset || tileset->size() == newSize) |
1172 | return; |
1173 | |
1174 | showRemapTiles(); |
1175 | |
1176 | try { |
1177 | InlineCommandExecution inlineCmd(UIContext::instance()); |
1178 | ContextWriter writer(UIContext::instance(), 500); |
1179 | Sprite* sprite = writer.sprite(); |
1180 | ASSERT(writer.layer()->isTilemap()); |
1181 | if (sprite) { |
1182 | auto tileset = m_tilesView.tileset(); |
1183 | |
1184 | Tx tx(writer.context(), Strings::color_bar_resize_tiles(), ModifyDocument); |
1185 | if (tileset->size() < newSize) { |
1186 | for (doc::tile_index ti=tileset->size(); ti<newSize; ++ti) { |
1187 | ImageRef img = tileset->makeEmptyTile(); |
1188 | tx(new cmd::AddTile(tileset, img)); |
1189 | } |
1190 | } |
1191 | else { |
1192 | for (doc::tile_index ti=tileset->size()-1; |
1193 | ti!=(doc::tile_index)newSize-1; --ti) { |
1194 | tx(new cmd::RemoveTile(tileset, ti)); |
1195 | } |
1196 | } |
1197 | |
1198 | tx.commit(); |
1199 | |
1200 | // TODO this should be automatic (when tileset is changed after a transaction) |
1201 | m_scrollableTilesView.updateView(); |
1202 | update_screen_for_document(writer.document()); |
1203 | } |
1204 | } |
1205 | catch (base::Exception& e) { |
1206 | Console::showException(e); |
1207 | } |
1208 | } |
1209 | |
1210 | void ColorBar::onTilesViewDragAndDrop(doc::Tileset* tileset, |
1211 | doc::PalettePicks& picks, |
1212 | int& currentEntry, |
1213 | const int beforeIndex, |
1214 | const bool isCopy) |
1215 | { |
1216 | COLOR_BAR_TRACE("ColorBar::onTilesViewDragAndDrop() -> beforeIndex=%d\n" , |
1217 | beforeIndex); |
1218 | |
1219 | showRemapTiles(); |
1220 | |
1221 | try { |
1222 | Context* ctx = UIContext::instance(); |
1223 | InlineCommandExecution inlineCmd(ctx); |
1224 | ContextWriter writer(ctx, 500); |
1225 | Tx tx(writer.context(), Strings::color_bar_drag_and_drop_tiles(), ModifyDocument); |
1226 | if (isCopy) |
1227 | copy_tiles_in_tileset(tx, tileset, picks, currentEntry, beforeIndex); |
1228 | else |
1229 | move_tiles_in_tileset(tx, tileset, picks, currentEntry, beforeIndex); |
1230 | tx.commit(); |
1231 | |
1232 | m_scrollableTilesView.updateView(); |
1233 | update_screen_for_document(writer.document()); |
1234 | |
1235 | ctx->setSelectedTiles(picks); |
1236 | } |
1237 | catch (base::Exception& e) { |
1238 | Console::showException(e); |
1239 | } |
1240 | } |
1241 | |
1242 | void ColorBar::onTilesViewIndexChange(int index, ui::MouseButton button) |
1243 | { |
1244 | auto& pref = Preferences::instance(); |
1245 | if (button == kButtonRight) |
1246 | pref.colorBar.bgTile(doc::tile(index, 0)); |
1247 | else if (button == kButtonLeft) |
1248 | pref.colorBar.fgTile(doc::tile(index, 0)); |
1249 | else if (button == kButtonMiddle) { |
1250 | // TODO ? |
1251 | } |
1252 | } |
1253 | |
1254 | void ColorBar::onFgColorChangeFromPreferences() |
1255 | { |
1256 | COLOR_BAR_TRACE("ColorBar::onFgColorChangeFromPreferences() -> %s\n" , |
1257 | Preferences::instance().colorBar.fgColor().toString().c_str()); |
1258 | |
1259 | if (m_fromPref) |
1260 | return; |
1261 | |
1262 | base::ScopedValue<bool> sync(m_fromPref, true, false); |
1263 | setFgColor(Preferences::instance().colorBar.fgColor()); |
1264 | } |
1265 | |
1266 | void ColorBar::onBgColorChangeFromPreferences() |
1267 | { |
1268 | COLOR_BAR_TRACE("ColorBar::onBgColorChangeFromPreferences() -> %s\n" , |
1269 | Preferences::instance().colorBar.bgColor().toString().c_str()); |
1270 | |
1271 | if (m_fromPref) |
1272 | return; |
1273 | |
1274 | if (inEditMode()) { |
1275 | // In edit mode, clicking with right-click will copy the color |
1276 | // selected with eyedropper to the active color entry. |
1277 | setFgColor(Preferences::instance().colorBar.bgColor()); |
1278 | } |
1279 | else { |
1280 | base::ScopedValue<bool> sync(m_fromPref, true, false); |
1281 | setBgColor(Preferences::instance().colorBar.bgColor()); |
1282 | } |
1283 | } |
1284 | |
1285 | void ColorBar::onFgTileChangeFromPreferences() |
1286 | { |
1287 | if (m_fromPref) |
1288 | return; |
1289 | |
1290 | base::ScopedValue<bool> sync(m_fromPref, true, false); |
1291 | auto tile = Preferences::instance().colorBar.fgTile(); |
1292 | m_fgTile.setTile(tile); |
1293 | m_tilesView.selectColor(tile); |
1294 | } |
1295 | |
1296 | void ColorBar::onBgTileChangeFromPreferences() |
1297 | { |
1298 | if (m_fromPref) |
1299 | return; |
1300 | |
1301 | base::ScopedValue<bool> sync(m_fromPref, true, false); |
1302 | auto tile = Preferences::instance().colorBar.bgTile(); |
1303 | m_bgTile.setTile(tile); |
1304 | m_tilesView.selectColor(tile); |
1305 | } |
1306 | |
1307 | void ColorBar::onFgColorButtonBeforeChange(app::Color& color) |
1308 | { |
1309 | COLOR_BAR_TRACE("ColorBar::onFgColorButtonBeforeChange(%s)\n" , color.toString().c_str()); |
1310 | |
1311 | if (m_fromPalView) |
1312 | return; |
1313 | |
1314 | if (!inEditMode() || color.getType() == app::Color::IndexType) { |
1315 | m_paletteView.deselect(); |
1316 | return; |
1317 | } |
1318 | |
1319 | // Here we change the selected colors in the |
1320 | // palette. "m_fromPref" must be false to edit the color. (It |
1321 | // means, if the eyedropper was used with the left-click, we don't |
1322 | // edit the color, we just select the color to as the normal |
1323 | // non-edit mode.) |
1324 | if (!m_fromPref) { |
1325 | int i = setPaletteEntry(color); |
1326 | if (i >= 0) { |
1327 | updateCurrentSpritePalette("Color Change" ); |
1328 | color = app::Color::fromIndex(i); |
1329 | } |
1330 | } |
1331 | } |
1332 | |
1333 | void ColorBar::onFgColorButtonChange(const app::Color& color) |
1334 | { |
1335 | COLOR_BAR_TRACE("ColorBar::onFgColorButtonChange(%s)\n" , color.toString().c_str()); |
1336 | |
1337 | if (m_fromFgButton) |
1338 | return; |
1339 | |
1340 | base::ScopedValue<bool> lock(m_fromFgButton, true, false); |
1341 | |
1342 | if (!m_fromPref) { |
1343 | base::ScopedValue<bool> sync(m_fromPref, true, false); |
1344 | Preferences::instance().colorBar.fgColor(color); |
1345 | } |
1346 | |
1347 | updateWarningIcon(color, m_fgWarningIcon); |
1348 | onColorButtonChange(color); |
1349 | } |
1350 | |
1351 | void ColorBar::onBgColorButtonChange(const app::Color& color) |
1352 | { |
1353 | COLOR_BAR_TRACE("ColorBar::onBgColorButtonChange(%s)\n" , color.toString().c_str()); |
1354 | |
1355 | if (m_fromBgButton) |
1356 | return; |
1357 | |
1358 | base::ScopedValue<bool> lock(m_fromBgButton, true, false); |
1359 | |
1360 | if (!m_fromPalView && !inEditMode()) |
1361 | m_paletteView.deselect(); |
1362 | |
1363 | if (!m_fromPref) { |
1364 | base::ScopedValue<bool> sync(m_fromPref, true, false); |
1365 | Preferences::instance().colorBar.bgColor(color); |
1366 | } |
1367 | |
1368 | updateWarningIcon(color, m_bgWarningIcon); |
1369 | onColorButtonChange(color); |
1370 | } |
1371 | |
1372 | void ColorBar::onColorButtonChange(const app::Color& color) |
1373 | { |
1374 | COLOR_BAR_TRACE("ColorBar::onColorButtonChange(%s)\n" , color.toString().c_str()); |
1375 | |
1376 | if (!inEditMode() || color.getType() == app::Color::IndexType || m_fromPref) { |
1377 | if (color.getType() == app::Color::IndexType) |
1378 | m_paletteView.selectColor(color.getIndex()); |
1379 | else { |
1380 | m_paletteView.selectExactMatchColor(color); |
1381 | |
1382 | // As foreground or background color changed, we've to redraw the |
1383 | // palette view fg/bg indicators. |
1384 | m_paletteView.invalidate(); |
1385 | } |
1386 | } |
1387 | |
1388 | if (m_tintShadeTone && m_tintShadeTone->isVisible()) |
1389 | m_tintShadeTone->selectColor(color); |
1390 | |
1391 | if (m_spectrum && m_spectrum->isVisible()) |
1392 | m_spectrum->selectColor(color); |
1393 | |
1394 | if (m_wheel && m_wheel->isVisible()) |
1395 | m_wheel->selectColor(color); |
1396 | } |
1397 | |
1398 | void ColorBar::onFgTileButtonChange(doc::tile_t tile) |
1399 | { |
1400 | if (!m_fromPref) { |
1401 | Preferences::instance().colorBar.fgTile(tile); |
1402 | m_tilesView.deselect(); |
1403 | } |
1404 | } |
1405 | |
1406 | void ColorBar::onBgTileButtonChange(doc::tile_t tile) |
1407 | { |
1408 | if (!m_fromPref) { |
1409 | Preferences::instance().colorBar.bgTile(tile); |
1410 | m_tilesView.deselect(); |
1411 | } |
1412 | } |
1413 | |
1414 | void ColorBar::onPickSpectrum(const app::Color& color, ui::MouseButton button) |
1415 | { |
1416 | // Change to pixels mode automatically |
1417 | if (m_tilemapMode == TilemapMode::Tiles) |
1418 | setTilemapMode(TilemapMode::Pixels); |
1419 | |
1420 | if (button == kButtonNone) |
1421 | button = m_lastButton; |
1422 | |
1423 | if (button == kButtonRight) |
1424 | setBgColor(color); |
1425 | else if (button == kButtonLeft) |
1426 | setFgColor(color); |
1427 | |
1428 | m_lastButton = button; |
1429 | } |
1430 | |
1431 | void ColorBar::onReverseColors() |
1432 | { |
1433 | doc::PalettePicks entries; |
1434 | m_paletteView.getSelectedEntries(entries); |
1435 | |
1436 | entries.pickAllIfNeeded(); |
1437 | int n = entries.picks(); |
1438 | |
1439 | std::vector<int> mapToOriginal(n); // Maps index from selectedPalette -> palette |
1440 | int i = 0, j = 0; |
1441 | for (bool state : entries) { |
1442 | if (state) |
1443 | mapToOriginal[j++] = i; |
1444 | ++i; |
1445 | } |
1446 | |
1447 | Remap remap(get_current_palette()->size()); |
1448 | i = 0; |
1449 | j = n; |
1450 | for (bool state : entries) { |
1451 | if (state) |
1452 | remap.map(i, mapToOriginal[--j]); |
1453 | else |
1454 | remap.map(i, i); |
1455 | ++i; |
1456 | } |
1457 | |
1458 | Palette newPalette(*get_current_palette(), remap); |
1459 | setPalette(&newPalette, Strings::color_bar_reverse_colors()); |
1460 | } |
1461 | |
1462 | void ColorBar::onSortBy(SortPaletteBy channel) |
1463 | { |
1464 | PalettePicks entries; |
1465 | m_paletteView.getSelectedEntries(entries); |
1466 | |
1467 | entries.pickAllIfNeeded(); |
1468 | int n = entries.picks(); |
1469 | |
1470 | // Create a "subpalette" with selected entries only. |
1471 | Palette palette(*get_current_palette()); |
1472 | Palette selectedPalette(0, n); |
1473 | std::vector<int> mapToOriginal(n); // Maps index from selectedPalette -> palette |
1474 | int i = 0, j = 0; |
1475 | for (bool state : entries) { |
1476 | if (state) { |
1477 | selectedPalette.setEntry(j, palette.getEntry(i)); |
1478 | mapToOriginal[j] = i; |
1479 | ++j; |
1480 | } |
1481 | ++i; |
1482 | } |
1483 | |
1484 | // Create a remap to sort the selected entries with the given color |
1485 | // component/channel. |
1486 | Remap remap = doc::sort_palette(&selectedPalette, channel, m_ascending); |
1487 | |
1488 | // Create a bigger new remap for the original palette (with all |
1489 | // entries, selected and deselected). |
1490 | Remap remapOrig(palette.size()); |
1491 | i = j = 0; |
1492 | for (bool state : entries) { |
1493 | if (state) |
1494 | remapOrig.map(i, mapToOriginal[remap[j++]]); |
1495 | else |
1496 | remapOrig.map(i, i); |
1497 | ++i; |
1498 | } |
1499 | |
1500 | // Create a new palette and apply the remap. This is the final new |
1501 | // palette for the sprite. |
1502 | Palette newPalette(palette, remapOrig); |
1503 | setPalette(&newPalette, Strings::color_bar_sort_colors()); |
1504 | } |
1505 | |
1506 | void ColorBar::onGradient(GradientType gradientType) |
1507 | { |
1508 | int index1, index2; |
1509 | if (!m_paletteView.getSelectedRange(index1, index2)) |
1510 | return; |
1511 | |
1512 | Palette newPalette(*get_current_palette()); |
1513 | if (gradientType == GradientType::LINEAR) { |
1514 | newPalette.makeGradient(index1, index2); |
1515 | setPalette(&newPalette, Strings::color_bar_gradient()); |
1516 | } |
1517 | else { |
1518 | newPalette.makeHueGradient(index1, index2); |
1519 | setPalette(&newPalette, Strings::color_bar_gradient_by_hue()); |
1520 | } |
1521 | } |
1522 | |
1523 | void ColorBar::setAscending(bool ascending) |
1524 | { |
1525 | m_ascending = ascending; |
1526 | } |
1527 | |
1528 | void ColorBar::showRemapPal() |
1529 | { |
1530 | Site site = UIContext::instance()->activeSite(); |
1531 | if (site.sprite() && |
1532 | site.sprite()->pixelFormat() == IMAGE_INDEXED) { |
1533 | if (!m_oldPalette) { |
1534 | m_oldPalette.reset(new Palette(*get_current_palette())); |
1535 | m_remapPalButton.setVisible(true); |
1536 | layout(); |
1537 | } |
1538 | } |
1539 | } |
1540 | |
1541 | void ColorBar::showRemapTiles() |
1542 | { |
1543 | Site site = UIContext::instance()->activeSite(); |
1544 | if (site.layer() && |
1545 | site.layer()->isTilemap()) { |
1546 | if (!m_oldTileset) { |
1547 | m_oldTileset.reset( |
1548 | Tileset::MakeCopyCopyingImages( |
1549 | static_cast<LayerTilemap*>(site.layer())->tileset())); |
1550 | m_remapTilesButton.setVisible(true); |
1551 | layout(); |
1552 | } |
1553 | } |
1554 | } |
1555 | |
1556 | void ColorBar::hideRemapPal() |
1557 | { |
1558 | if (!m_oldPalette) |
1559 | return; |
1560 | |
1561 | m_oldPalette.reset(); |
1562 | m_remapPalButton.setVisible(false); |
1563 | layout(); |
1564 | } |
1565 | |
1566 | void ColorBar::hideRemapTiles() |
1567 | { |
1568 | if (!m_oldTileset) |
1569 | return; |
1570 | |
1571 | m_oldTileset.reset(); |
1572 | m_remapTilesButton.setVisible(false); |
1573 | layout(); |
1574 | } |
1575 | |
1576 | void ColorBar::onNewInputPriority(InputChainElement* element, |
1577 | const ui::Message* msg) |
1578 | { |
1579 | if (dynamic_cast<Timeline*>(element) && |
1580 | msg && (msg->ctrlPressed() || msg->shiftPressed())) |
1581 | return; |
1582 | |
1583 | if (element != this) { |
1584 | m_paletteView.deselect(); |
1585 | m_tilesView.deselect(); |
1586 | } |
1587 | } |
1588 | |
1589 | bool ColorBar::onCanCut(Context* ctx) |
1590 | { |
1591 | if (m_tilemapMode == TilemapMode::Tiles) |
1592 | return (m_tilesView.getSelectedEntriesCount() > 0); |
1593 | else |
1594 | return (m_paletteView.getSelectedEntriesCount() > 0); |
1595 | } |
1596 | |
1597 | bool ColorBar::onCanCopy(Context* ctx) |
1598 | { |
1599 | return onCanCut(ctx); |
1600 | } |
1601 | |
1602 | bool ColorBar::onCanPaste(Context* ctx) |
1603 | { |
1604 | auto format = ctx->clipboard()->format(); |
1605 | if (m_tilemapMode == TilemapMode::Tiles) |
1606 | return (format == ClipboardFormat::Tileset); |
1607 | else |
1608 | return (format == ClipboardFormat::PaletteEntries); |
1609 | } |
1610 | |
1611 | bool ColorBar::onCanClear(Context* ctx) |
1612 | { |
1613 | return onCanCut(ctx); |
1614 | } |
1615 | |
1616 | bool ColorBar::onCut(Context* ctx) |
1617 | { |
1618 | if (m_tilemapMode == TilemapMode::Tiles) { |
1619 | showRemapTiles(); |
1620 | m_tilesView.cutToClipboard(); |
1621 | } |
1622 | else |
1623 | m_paletteView.cutToClipboard(); |
1624 | return true; |
1625 | } |
1626 | |
1627 | bool ColorBar::onCopy(Context* ctx) |
1628 | { |
1629 | if (m_tilemapMode == TilemapMode::Tiles) |
1630 | m_tilesView.copyToClipboard(); |
1631 | else |
1632 | m_paletteView.copyToClipboard(); |
1633 | return true; |
1634 | } |
1635 | |
1636 | bool ColorBar::onPaste(Context* ctx) |
1637 | { |
1638 | if (m_tilemapMode == TilemapMode::Tiles) { |
1639 | showRemapTiles(); |
1640 | m_tilesView.pasteFromClipboard(); |
1641 | } |
1642 | else |
1643 | m_paletteView.pasteFromClipboard(); |
1644 | return true; |
1645 | } |
1646 | |
1647 | bool ColorBar::onClear(Context* ctx) |
1648 | { |
1649 | if (m_tilemapMode == TilemapMode::Tiles) { |
1650 | showRemapTiles(); |
1651 | m_tilesView.clearSelection(); |
1652 | } |
1653 | else |
1654 | m_paletteView.clearSelection(); |
1655 | return true; |
1656 | } |
1657 | |
1658 | void ColorBar::onCancel(Context* ctx) |
1659 | { |
1660 | m_tilesView.deselect(); |
1661 | m_tilesView.discardClipboardSelection(); |
1662 | m_paletteView.deselect(); |
1663 | m_paletteView.discardClipboardSelection(); |
1664 | invalidate(); |
1665 | } |
1666 | |
1667 | void ColorBar::onFixWarningClick(ColorButton* colorButton, ui::Button* warningIcon) |
1668 | { |
1669 | COLOR_BAR_TRACE("ColorBar::onFixWarningClick(%s)\n" , colorButton->getColor().toString().c_str()); |
1670 | |
1671 | Palette* palette = get_current_palette(); |
1672 | const int oldEntries = palette->size(); |
1673 | |
1674 | Command* command = Commands::instance()->byId(CommandId::AddColor()); |
1675 | Params params; |
1676 | params.set("source" , "color" ); |
1677 | params.set("color" , colorButton->getColor().toString().c_str()); |
1678 | UIContext::instance()->executeCommand(command, params); |
1679 | |
1680 | // Select the new FG/BG color as an indexed color |
1681 | if (inEditMode()) { |
1682 | const int newEntries = palette->size(); |
1683 | if (oldEntries != newEntries) { |
1684 | base::ScopedValue<bool> sync(m_fromPref, true, m_fromPref); |
1685 | app::Color newIndex = app::Color::fromIndex(newEntries-1); |
1686 | if (colorButton == &m_bgColor) |
1687 | setBgColor(newIndex); |
1688 | setFgColor(newIndex); |
1689 | } |
1690 | } |
1691 | } |
1692 | |
1693 | void ColorBar::onTimerTick() |
1694 | { |
1695 | // Redraw all editors |
1696 | if (m_redrawAll) { |
1697 | m_redrawAll = false; |
1698 | m_implantChange = false; |
1699 | m_redrawTimer.stop(); |
1700 | |
1701 | // Call all observers of PaletteChange event. |
1702 | m_selfPalChange = true; |
1703 | App::instance()->PaletteChange(); |
1704 | m_selfPalChange = false; |
1705 | |
1706 | // Redraw all editors |
1707 | try { |
1708 | InlineCommandExecution inlineCmd(UIContext::instance()); |
1709 | ContextWriter writer(UIContext::instance()); |
1710 | Doc* document(writer.document()); |
1711 | if (document != NULL) |
1712 | document->notifyGeneralUpdate(); |
1713 | } |
1714 | catch (...) { |
1715 | // Do nothing |
1716 | } |
1717 | } |
1718 | // Redraw just the current editor |
1719 | else { |
1720 | m_redrawAll = true; |
1721 | if (current_editor) |
1722 | current_editor->updateEditor(true); |
1723 | } |
1724 | } |
1725 | |
1726 | void ColorBar::updateWarningIcon(const app::Color& color, ui::Button* warningIcon) |
1727 | { |
1728 | int index = -1; |
1729 | |
1730 | if (color.getType() == app::Color::MaskType) { |
1731 | if (current_editor && |
1732 | current_editor->sprite()) { |
1733 | index = current_editor->sprite()->transparentColor(); |
1734 | } |
1735 | else |
1736 | index = 0; |
1737 | } |
1738 | else { |
1739 | index = get_current_palette()->findExactMatch( |
1740 | color.getRed(), |
1741 | color.getGreen(), |
1742 | color.getBlue(), |
1743 | color.getAlpha(), -1); |
1744 | } |
1745 | |
1746 | warningIcon->setVisible(index < 0); |
1747 | warningIcon->parent()->layout(); |
1748 | } |
1749 | |
1750 | // Changes the selected color palettes with the given |
1751 | // app::Color. Returns the first modified index in the palette. |
1752 | int ColorBar::setPaletteEntry(const app::Color& color) |
1753 | { |
1754 | int selIdx = m_paletteView.getSelectedEntry(); |
1755 | if (selIdx < 0) { |
1756 | if (getFgColor().getType() == app::Color::IndexType) { |
1757 | selIdx = getFgColor().getIndex(); |
1758 | } |
1759 | } |
1760 | |
1761 | PalettePicks entries; |
1762 | m_paletteView.getSelectedEntries(entries); |
1763 | if (entries.picks() == 0) { |
1764 | if (selIdx >= 0 && selIdx < entries.size()) { |
1765 | entries[selIdx] = true; |
1766 | } |
1767 | } |
1768 | |
1769 | doc::color_t c = |
1770 | doc::rgba(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); |
1771 | |
1772 | Palette* palette = get_current_palette(); |
1773 | for (int i=0; i<palette->size(); ++i) { |
1774 | if (entries[i]) |
1775 | palette->setEntry(i, c); |
1776 | } |
1777 | |
1778 | if (selIdx < 0 || |
1779 | selIdx >= entries.size() || |
1780 | !entries[selIdx]) |
1781 | selIdx = entries.firstPick(); |
1782 | |
1783 | return selIdx; |
1784 | } |
1785 | |
1786 | void ColorBar::updateCurrentSpritePalette(const char* operationName) |
1787 | { |
1788 | if (UIContext::instance()->activeDocument() && |
1789 | UIContext::instance()->activeDocument()->sprite()) { |
1790 | try { |
1791 | InlineCommandExecution inlineCmd(UIContext::instance()); |
1792 | ContextWriter writer(UIContext::instance()); |
1793 | Doc* document(writer.document()); |
1794 | Sprite* sprite(writer.sprite()); |
1795 | Palette* newPalette = get_current_palette(); // System current pal |
1796 | frame_t frame = writer.frame(); |
1797 | Palette* currentSpritePalette = sprite->palette(frame); // Sprite current pal |
1798 | int from, to; |
1799 | |
1800 | // Check differences between current sprite palette and current system palette |
1801 | from = to = -1; |
1802 | currentSpritePalette->countDiff(newPalette, &from, &to); |
1803 | |
1804 | if (from >= 0 && to >= from) { |
1805 | DocUndo* undo = document->undoHistory(); |
1806 | std::unique_ptr<cmd::SetPalette> cmd( |
1807 | new cmd::SetPalette(sprite, frame, newPalette)); |
1808 | |
1809 | // Add undo information to save the range of pal entries that will be modified. |
1810 | if (m_implantChange && |
1811 | undo->lastExecutedCmd() && |
1812 | undo->lastExecutedCmd()->label() == operationName) { |
1813 | // Implant the cmd in the last CmdSequence if it's |
1814 | // related about color palette modifications |
1815 | ASSERT(dynamic_cast<CmdSequence*>(undo->lastExecutedCmd())); |
1816 | static_cast<CmdSequence*>(undo->lastExecutedCmd())->add(cmd.get()); |
1817 | // Release the unique pointer because it's already in the |
1818 | // last executed command CmdSequence::m_cmds container, and |
1819 | // execute it. |
1820 | cmd.release()->execute(UIContext::instance()); |
1821 | } |
1822 | else { |
1823 | Tx tx(writer.context(), operationName, ModifyDocument); |
1824 | // If tx() fails it will delete the cmd anyway, so we can |
1825 | // release the unique pointer here. |
1826 | tx(cmd.release()); |
1827 | tx.commit(); |
1828 | } |
1829 | } |
1830 | } |
1831 | catch (base::Exception& e) { |
1832 | Console::showException(e); |
1833 | } |
1834 | } |
1835 | |
1836 | m_paletteView.invalidate(); |
1837 | |
1838 | if (!m_redrawTimer.isRunning()) |
1839 | m_redrawTimer.start(); |
1840 | |
1841 | m_redrawAll = false; |
1842 | m_implantChange = true; |
1843 | } |
1844 | |
1845 | void ColorBar::setupTooltips(TooltipManager* tooltipManager) |
1846 | { |
1847 | tooltipManager->addTooltipFor(&m_fgColor, Strings::color_bar_fg(), LEFT); |
1848 | tooltipManager->addTooltipFor(&m_bgColor, Strings::color_bar_bg(), LEFT); |
1849 | tooltipManager->addTooltipFor(m_fgWarningIcon, Strings::color_bar_fg_warning(), LEFT); |
1850 | tooltipManager->addTooltipFor(m_bgWarningIcon, Strings::color_bar_bg_warning(), LEFT); |
1851 | |
1852 | tooltipManager->addTooltipFor( |
1853 | m_editPal.getItem(0), |
1854 | key_tooltip(Strings::color_bar_edit_color().c_str(), CommandId::PaletteEditor()), |
1855 | BOTTOM); |
1856 | |
1857 | tooltipManager->addTooltipFor(m_buttons.getItem((int)PalButton::SORT), Strings::color_bar_sort_and_gradients(), BOTTOM); |
1858 | tooltipManager->addTooltipFor(m_buttons.getItem((int)PalButton::PRESETS), Strings::color_bar_presets(), BOTTOM); |
1859 | tooltipManager->addTooltipFor(m_buttons.getItem((int)PalButton::OPTIONS), Strings::color_bar_options(), BOTTOM); |
1860 | tooltipManager->addTooltipFor(&m_remapPalButton, Strings::color_bar_remap_palette_tooltip(), BOTTOM); |
1861 | tooltipManager->addTooltipFor(&m_remapTilesButton, Strings::color_bar_remap_tiles_tooltip(), BOTTOM); |
1862 | |
1863 | tooltipManager->addTooltipFor( |
1864 | m_tilesButton.getItem(0), |
1865 | key_tooltip(Strings::color_bar_switch_tileset().c_str(), CommandId::ToggleTilesMode()), |
1866 | BOTTOM); |
1867 | Command* cmd = Commands::instance()->byId(CommandId::TilesetMode()); |
1868 | Params params; |
1869 | params.set("mode" , "manual" ); |
1870 | tooltipManager->addTooltipFor( |
1871 | m_tilesetModeButtons.getItem((int)TilesetMode::Manual), |
1872 | key_tooltip(Strings::color_bar_tileset_mode_manual().c_str(), cmd->id().c_str(), params), BOTTOM); |
1873 | params.set("mode" , "auto" ); |
1874 | tooltipManager->addTooltipFor( |
1875 | m_tilesetModeButtons.getItem((int)TilesetMode::Auto), |
1876 | key_tooltip(Strings::color_bar_tileset_mode_auto().c_str(), cmd->id().c_str(), params), BOTTOM); |
1877 | params.set("mode" , "stack" ); |
1878 | tooltipManager->addTooltipFor( |
1879 | m_tilesetModeButtons.getItem((int)TilesetMode::Stack), |
1880 | key_tooltip(Strings::color_bar_tileset_mode_stack().c_str(), cmd->id().c_str(), params), BOTTOM); |
1881 | } |
1882 | |
1883 | // static |
1884 | void ColorBar::fixColorIndex(ColorButton& colorButton) |
1885 | { |
1886 | app::Color color = colorButton.getColor(); |
1887 | |
1888 | if (color.getType() == Color::IndexType) { |
1889 | int oldIndex = color.getIndex(); |
1890 | int newIndex = std::clamp(oldIndex, 0, get_current_palette()->size()-1); |
1891 | if (oldIndex != newIndex) { |
1892 | color = Color::fromIndex(newIndex); |
1893 | colorButton.setColor(color); |
1894 | } |
1895 | } |
1896 | } |
1897 | |
1898 | void ColorBar::registerCommands() |
1899 | { |
1900 | Commands::instance() |
1901 | ->add( |
1902 | new QuickCommand( |
1903 | CommandId::ShowPaletteSortOptions(), |
1904 | [this]{ this->showPaletteSortOptions(); })) |
1905 | ->add( |
1906 | new QuickCommand( |
1907 | CommandId::ShowPalettePresets(), |
1908 | [this]{ this->showPalettePresets(); })) |
1909 | ->add( |
1910 | new QuickCommand( |
1911 | CommandId::ShowPaletteOptions(), |
1912 | [this]{ this->showPaletteOptions(); })); |
1913 | } |
1914 | |
1915 | void ColorBar::showPaletteSortOptions() |
1916 | { |
1917 | gfx::Rect bounds = m_buttons.getItem( |
1918 | static_cast<int>(PalButton::SORT))->bounds(); |
1919 | |
1920 | Menu ; |
1921 | MenuItem |
1922 | rev(Strings::color_bar_reverse_colors()), |
1923 | grd(Strings::color_bar_gradient()), |
1924 | grh(Strings::color_bar_gradient_by_hue()), |
1925 | hue(Strings::color_bar_sort_by_hue()), |
1926 | sat(Strings::color_bar_sort_by_saturation()), |
1927 | bri(Strings::color_bar_sort_by_brightness()), |
1928 | lum(Strings::color_bar_sort_by_luminance()), |
1929 | red(Strings::color_bar_sort_by_red()), |
1930 | grn(Strings::color_bar_sort_by_green()), |
1931 | blu(Strings::color_bar_sort_by_blue()), |
1932 | alp(Strings::color_bar_sort_by_alpha()), |
1933 | asc(Strings::color_bar_ascending()), |
1934 | des(Strings::color_bar_descending()); |
1935 | menu.addChild(&rev); |
1936 | menu.addChild(&grd); |
1937 | menu.addChild(&grh); |
1938 | menu.addChild(new ui::MenuSeparator); |
1939 | menu.addChild(&hue); |
1940 | menu.addChild(&sat); |
1941 | menu.addChild(&bri); |
1942 | menu.addChild(&lum); |
1943 | menu.addChild(new ui::MenuSeparator); |
1944 | menu.addChild(&red); |
1945 | menu.addChild(&grn); |
1946 | menu.addChild(&blu); |
1947 | menu.addChild(&alp); |
1948 | menu.addChild(new ui::MenuSeparator); |
1949 | menu.addChild(&asc); |
1950 | menu.addChild(&des); |
1951 | |
1952 | if (m_ascending) asc.setSelected(true); |
1953 | else des.setSelected(true); |
1954 | |
1955 | rev.Click.connect([this]{ onReverseColors(); }); |
1956 | grd.Click.connect([this]{ onGradient(GradientType::LINEAR); }); |
1957 | grh.Click.connect([this]{ onGradient(GradientType::HUE); }); |
1958 | hue.Click.connect([this]{ onSortBy(SortPaletteBy::HUE); }); |
1959 | sat.Click.connect([this]{ onSortBy(SortPaletteBy::SATURATION); }); |
1960 | bri.Click.connect([this]{ onSortBy(SortPaletteBy::VALUE); }); |
1961 | lum.Click.connect([this]{ onSortBy(SortPaletteBy::LUMA); }); |
1962 | red.Click.connect([this]{ onSortBy(SortPaletteBy::RED); }); |
1963 | grn.Click.connect([this]{ onSortBy(SortPaletteBy::GREEN); }); |
1964 | blu.Click.connect([this]{ onSortBy(SortPaletteBy::BLUE); }); |
1965 | alp.Click.connect([this]{ onSortBy(SortPaletteBy::ALPHA); }); |
1966 | asc.Click.connect([this]{ setAscending(true); }); |
1967 | des.Click.connect([this]{ setAscending(false); }); |
1968 | |
1969 | menu.showPopup(gfx::Point(bounds.x, bounds.y2()), display()); |
1970 | } |
1971 | |
1972 | void ColorBar::showPalettePresets() |
1973 | { |
1974 | if (!m_palettePopup) { |
1975 | try { |
1976 | m_palettePopup.reset(new PalettePopup()); |
1977 | } |
1978 | catch (const std::exception& ex) { |
1979 | Console::showException(ex); |
1980 | return; |
1981 | } |
1982 | } |
1983 | |
1984 | if (!m_palettePopup->isVisible()) { |
1985 | gfx::Rect bounds = m_buttons.getItem( |
1986 | static_cast<int>(PalButton::PRESETS))->bounds(); |
1987 | |
1988 | m_palettePopup->showPopup(display(), bounds); |
1989 | } |
1990 | else { |
1991 | m_palettePopup->closeWindow(nullptr); |
1992 | } |
1993 | } |
1994 | |
1995 | void ColorBar::showPaletteOptions() |
1996 | { |
1997 | Menu* = AppMenus::instance()->getPalettePopupMenu(); |
1998 | if (menu) { |
1999 | gfx::Rect bounds = m_buttons.getItem( |
2000 | static_cast<int>(PalButton::OPTIONS))->bounds(); |
2001 | |
2002 | menu->showPopup(gfx::Point(bounds.x, bounds.y2()), display()); |
2003 | } |
2004 | } |
2005 | |
2006 | bool ColorBar::canEditTiles() const |
2007 | { |
2008 | const Site site = UIContext::instance()->activeSite(); |
2009 | return (site.layer() && |
2010 | site.layer()->isTilemap()); |
2011 | } |
2012 | |
2013 | } // namespace app |
2014 | |