| 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 | |