| 1 | // Aseprite | 
|---|
| 2 | // Copyright (C) 2019-2022  Igara Studio S.A. | 
|---|
| 3 | // Copyright (C) 2001-2018  David Capello | 
|---|
| 4 | // | 
|---|
| 5 | // This program is distributed under the terms of | 
|---|
| 6 | // the End-User License Agreement for Aseprite. | 
|---|
| 7 |  | 
|---|
| 8 | #ifdef HAVE_CONFIG_H | 
|---|
| 9 | #include "config.h" | 
|---|
| 10 | #endif | 
|---|
| 11 |  | 
|---|
| 12 | #include "app/app.h" | 
|---|
| 13 | #include "app/commands/cmd_export_sprite_sheet.h" | 
|---|
| 14 | #include "app/context.h" | 
|---|
| 15 | #include "app/context_access.h" | 
|---|
| 16 | #include "app/doc.h" | 
|---|
| 17 | #include "app/doc_exporter.h" | 
|---|
| 18 | #include "app/file/file.h" | 
|---|
| 19 | #include "app/file_selector.h" | 
|---|
| 20 | #include "app/filename_formatter.h" | 
|---|
| 21 | #include "app/i18n/strings.h" | 
|---|
| 22 | #include "app/job.h" | 
|---|
| 23 | #include "app/modules/editors.h" | 
|---|
| 24 | #include "app/modules/gui.h" | 
|---|
| 25 | #include "app/pref/preferences.h" | 
|---|
| 26 | #include "app/recent_files.h" | 
|---|
| 27 | #include "app/restore_visible_layers.h" | 
|---|
| 28 | #include "app/task.h" | 
|---|
| 29 | #include "app/ui/editor/editor.h" | 
|---|
| 30 | #include "app/ui/editor/navigate_state.h" | 
|---|
| 31 | #include "app/ui/layer_frame_comboboxes.h" | 
|---|
| 32 | #include "app/ui/optional_alert.h" | 
|---|
| 33 | #include "app/ui/status_bar.h" | 
|---|
| 34 | #include "app/ui/timeline/timeline.h" | 
|---|
| 35 | #include "base/convert_to.h" | 
|---|
| 36 | #include "base/fs.h" | 
|---|
| 37 | #include "base/string.h" | 
|---|
| 38 | #include "base/thread.h" | 
|---|
| 39 | #include "doc/layer.h" | 
|---|
| 40 | #include "doc/layer_tilemap.h" | 
|---|
| 41 | #include "doc/tag.h" | 
|---|
| 42 | #include "doc/tileset.h" | 
|---|
| 43 | #include "doc/tilesets.h" | 
|---|
| 44 | #include "fmt/format.h" | 
|---|
| 45 | #include "ui/message.h" | 
|---|
| 46 | #include "ui/system.h" | 
|---|
| 47 |  | 
|---|
| 48 | #include "export_sprite_sheet.xml.h" | 
|---|
| 49 |  | 
|---|
| 50 | #include <limits> | 
|---|
| 51 | #include <sstream> | 
|---|
| 52 |  | 
|---|
| 53 | namespace app { | 
|---|
| 54 |  | 
|---|
| 55 | using namespace ui; | 
|---|
| 56 |  | 
|---|
| 57 | namespace { | 
|---|
| 58 |  | 
|---|
| 59 | #ifdef ENABLE_UI | 
|---|
| 60 |  | 
|---|
| 61 | enum Section { | 
|---|
| 62 | kSectionLayout, | 
|---|
| 63 | kSectionSprite, | 
|---|
| 64 | kSectionBorders, | 
|---|
| 65 | kSectionOutput, | 
|---|
| 66 | }; | 
|---|
| 67 |  | 
|---|
| 68 | enum Source { | 
|---|
| 69 | kSource_Sprite, | 
|---|
| 70 | kSource_SpriteGrid, | 
|---|
| 71 | kSource_Tilesets, | 
|---|
| 72 | }; | 
|---|
| 73 |  | 
|---|
| 74 | enum ConstraintType { | 
|---|
| 75 | kConstraintType_None, | 
|---|
| 76 | kConstraintType_Cols, | 
|---|
| 77 | kConstraintType_Rows, | 
|---|
| 78 | kConstraintType_Width, | 
|---|
| 79 | kConstraintType_Height, | 
|---|
| 80 | kConstraintType_Size, | 
|---|
| 81 | }; | 
|---|
| 82 |  | 
|---|
| 83 | // Special key value used in default preferences to know if by default | 
|---|
| 84 | // the user wants to generate texture and/or files. | 
|---|
| 85 | static const char* kSpecifiedFilename = "**filename**"; | 
|---|
| 86 |  | 
|---|
| 87 | bool ask_overwrite(const bool askFilename, const std::string& filename, | 
|---|
| 88 | const bool askDataname, const std::string& dataname) | 
|---|
| 89 | { | 
|---|
| 90 | if ((askFilename && | 
|---|
| 91 | !filename.empty() && | 
|---|
| 92 | base::is_file(filename)) || | 
|---|
| 93 | (askDataname && | 
|---|
| 94 | !dataname.empty() && | 
|---|
| 95 | base::is_file(dataname))) { | 
|---|
| 96 | std::stringstream text; | 
|---|
| 97 |  | 
|---|
| 98 | if (base::is_file(filename)) | 
|---|
| 99 | text << "<<"<< base::get_file_name(filename).c_str(); | 
|---|
| 100 |  | 
|---|
| 101 | if (base::is_file(dataname)) | 
|---|
| 102 | text << "<<"<< base::get_file_name(dataname).c_str(); | 
|---|
| 103 |  | 
|---|
| 104 | const int ret = | 
|---|
| 105 | OptionalAlert::show( | 
|---|
| 106 | Preferences::instance().spriteSheet.showOverwriteFilesAlert, | 
|---|
| 107 | 1, // Yes is the default option when the alert dialog is disabled | 
|---|
| 108 | fmt::format(Strings::alerts_overwrite_files_on_export_sprite_sheet(), | 
|---|
| 109 | text.str())); | 
|---|
| 110 | if (ret != 1) | 
|---|
| 111 | return false; | 
|---|
| 112 | } | 
|---|
| 113 | return true; | 
|---|
| 114 | } | 
|---|
| 115 |  | 
|---|
| 116 | ConstraintType constraint_type_from_params(const ExportSpriteSheetParams& params) | 
|---|
| 117 | { | 
|---|
| 118 | switch (params.type()) { | 
|---|
| 119 | case app::SpriteSheetType::Rows: | 
|---|
| 120 | if (params.width() > 0) | 
|---|
| 121 | return kConstraintType_Width; | 
|---|
| 122 | else if (params.columns() > 0) | 
|---|
| 123 | return kConstraintType_Cols; | 
|---|
| 124 | break; | 
|---|
| 125 | case app::SpriteSheetType::Columns: | 
|---|
| 126 | if (params.height() > 0) | 
|---|
| 127 | return kConstraintType_Height; | 
|---|
| 128 | else if (params.rows() > 0) | 
|---|
| 129 | return kConstraintType_Rows; | 
|---|
| 130 | break; | 
|---|
| 131 | case app::SpriteSheetType::Packed: | 
|---|
| 132 | if (params.width() > 0 && params.height() > 0) | 
|---|
| 133 | return kConstraintType_Size; | 
|---|
| 134 | else if (params.width() > 0) | 
|---|
| 135 | return kConstraintType_Width; | 
|---|
| 136 | else if (params.height() > 0) | 
|---|
| 137 | return kConstraintType_Height; | 
|---|
| 138 | break; | 
|---|
| 139 | } | 
|---|
| 140 | return kConstraintType_None; | 
|---|
| 141 | } | 
|---|
| 142 |  | 
|---|
| 143 | #endif // ENABLE_UI | 
|---|
| 144 |  | 
|---|
| 145 | Doc* generate_sprite_sheet_from_params( | 
|---|
| 146 | DocExporter& exporter, | 
|---|
| 147 | Context* ctx, | 
|---|
| 148 | const Site& site, | 
|---|
| 149 | const ExportSpriteSheetParams& params, | 
|---|
| 150 | const bool saveData, | 
|---|
| 151 | base::task_token& token) | 
|---|
| 152 | { | 
|---|
| 153 | const app::SpriteSheetType type = params.type(); | 
|---|
| 154 | const int columns = params.columns(); | 
|---|
| 155 | const int rows = params.rows(); | 
|---|
| 156 | const int width = params.width(); | 
|---|
| 157 | const int height = params.height(); | 
|---|
| 158 | const std::string filename = params.textureFilename(); | 
|---|
| 159 | const std::string dataFilename = params.dataFilename(); | 
|---|
| 160 | const SpriteSheetDataFormat dataFormat = params.dataFormat(); | 
|---|
| 161 | const std::string filenameFormat = params.filenameFormat(); | 
|---|
| 162 | const std::string layerName = params.layer(); | 
|---|
| 163 | const int layerIndex = params.layerIndex(); | 
|---|
| 164 | const std::string tagName = params.tag(); | 
|---|
| 165 | const int borderPadding = std::clamp(params.borderPadding(), 0, 100); | 
|---|
| 166 | const int shapePadding = std::clamp(params.shapePadding(), 0, 100); | 
|---|
| 167 | const int innerPadding = std::clamp(params.innerPadding(), 0, 100); | 
|---|
| 168 | const bool trimSprite = params.trimSprite(); | 
|---|
| 169 | const bool trimCels = params.trim(); | 
|---|
| 170 | const bool trimByGrid = params.trimByGrid(); | 
|---|
| 171 | const bool extrude = params.extrude(); | 
|---|
| 172 | const bool ignoreEmpty = params.ignoreEmpty(); | 
|---|
| 173 | const bool mergeDuplicates = params.mergeDuplicates(); | 
|---|
| 174 | const bool splitLayers = params.splitLayers(); | 
|---|
| 175 | const bool splitTags = params.splitTags(); | 
|---|
| 176 | const bool splitGrid = params.splitGrid(); | 
|---|
| 177 | const bool listLayers = params.listLayers(); | 
|---|
| 178 | const bool listTags = params.listTags(); | 
|---|
| 179 | const bool listSlices = params.listSlices(); | 
|---|
| 180 | const bool fromTilesets = params.fromTilesets(); | 
|---|
| 181 |  | 
|---|
| 182 | SelectedFrames selFrames; | 
|---|
| 183 | Tag* tag = calculate_selected_frames(site, tagName, selFrames); | 
|---|
| 184 |  | 
|---|
| 185 | #ifdef _DEBUG | 
|---|
| 186 | frame_t nframes = selFrames.size(); | 
|---|
| 187 | ASSERT(nframes > 0); | 
|---|
| 188 | #endif | 
|---|
| 189 |  | 
|---|
| 190 | Doc* doc = const_cast<Doc*>(site.document()); | 
|---|
| 191 | const Sprite* sprite = site.sprite(); | 
|---|
| 192 |  | 
|---|
| 193 | // If the user choose to render selected layers only, we can | 
|---|
| 194 | // temporaly make them visible and hide the other ones. | 
|---|
| 195 | RestoreVisibleLayers layersVisibility; | 
|---|
| 196 | calculate_visible_layers(site, layerName, layerIndex, layersVisibility); | 
|---|
| 197 |  | 
|---|
| 198 | SelectedLayers selLayers; | 
|---|
| 199 | if (layerName != kSelectedLayers) { | 
|---|
| 200 | // TODO add a getLayerByName | 
|---|
| 201 | int i = sprite->allLayersCount(); | 
|---|
| 202 | for (const Layer* layer : sprite->allLayers()) { | 
|---|
| 203 | i--; | 
|---|
| 204 | if (layer->name() == layerName && (layerIndex == -1 || | 
|---|
| 205 | layerIndex == i)) { | 
|---|
| 206 | selLayers.insert(const_cast<Layer*>(layer)); | 
|---|
| 207 | break; | 
|---|
| 208 | } | 
|---|
| 209 | } | 
|---|
| 210 | } | 
|---|
| 211 |  | 
|---|
| 212 | exporter.reset(); | 
|---|
| 213 |  | 
|---|
| 214 | // Use each tileset from tilemap layers as a sprite | 
|---|
| 215 | if (fromTilesets) { | 
|---|
| 216 | exporter.addTilesetsSamples( | 
|---|
| 217 | doc, | 
|---|
| 218 | !selLayers.empty() ? &selLayers: nullptr); | 
|---|
| 219 | } | 
|---|
| 220 | // Use the whole canvas as a sprite | 
|---|
| 221 | else { | 
|---|
| 222 | exporter.addDocumentSamples( | 
|---|
| 223 | doc, tag, splitLayers, splitTags, splitGrid, | 
|---|
| 224 | !selLayers.empty() ? &selLayers: nullptr, | 
|---|
| 225 | !selFrames.empty() ? &selFrames: nullptr); | 
|---|
| 226 | } | 
|---|
| 227 |  | 
|---|
| 228 | if (saveData) { | 
|---|
| 229 | if (!filename.empty()) | 
|---|
| 230 | exporter.setTextureFilename(filename); | 
|---|
| 231 | if (!dataFilename.empty()) { | 
|---|
| 232 | exporter.setDataFilename(dataFilename); | 
|---|
| 233 | exporter.setDataFormat(dataFormat); | 
|---|
| 234 | } | 
|---|
| 235 | } | 
|---|
| 236 | if (!filenameFormat.empty()) | 
|---|
| 237 | exporter.setFilenameFormat(filenameFormat); | 
|---|
| 238 |  | 
|---|
| 239 | exporter.setTextureWidth(width); | 
|---|
| 240 | exporter.setTextureHeight(height); | 
|---|
| 241 | exporter.setTextureColumns(columns); | 
|---|
| 242 | exporter.setTextureRows(rows); | 
|---|
| 243 | exporter.setSpriteSheetType(type); | 
|---|
| 244 | exporter.setBorderPadding(borderPadding); | 
|---|
| 245 | exporter.setShapePadding(shapePadding); | 
|---|
| 246 | exporter.setInnerPadding(innerPadding); | 
|---|
| 247 | exporter.setTrimSprite(trimSprite); | 
|---|
| 248 | exporter.setTrimCels(trimCels); | 
|---|
| 249 | exporter.setTrimByGrid(trimByGrid); | 
|---|
| 250 | exporter.setExtrude(extrude); | 
|---|
| 251 | exporter.setSplitLayers(splitLayers); | 
|---|
| 252 | exporter.setSplitTags(splitTags); | 
|---|
| 253 | exporter.setIgnoreEmptyCels(ignoreEmpty); | 
|---|
| 254 | exporter.setMergeDuplicates(mergeDuplicates); | 
|---|
| 255 | if (listLayers) exporter.setListLayers(true); | 
|---|
| 256 | if (listTags) exporter.setListTags(true); | 
|---|
| 257 | if (listSlices) exporter.setListSlices(true); | 
|---|
| 258 |  | 
|---|
| 259 | // We have to call exportSheet() while RestoreVisibleLayers is still | 
|---|
| 260 | // alive. In this way we can export selected layers correctly if | 
|---|
| 261 | // that option (kSelectedLayers) is selected. | 
|---|
| 262 | return exporter.exportSheet(ctx, token); | 
|---|
| 263 | } | 
|---|
| 264 |  | 
|---|
| 265 | std::unique_ptr<Doc> generate_sprite_sheet( | 
|---|
| 266 | DocExporter& exporter, | 
|---|
| 267 | Context* ctx, | 
|---|
| 268 | const Site& site, | 
|---|
| 269 | const ExportSpriteSheetParams& params, | 
|---|
| 270 | bool saveData, | 
|---|
| 271 | base::task_token& token) | 
|---|
| 272 | { | 
|---|
| 273 | std::unique_ptr<Doc> newDocument( | 
|---|
| 274 | generate_sprite_sheet_from_params(exporter, ctx, site, params, saveData, token)); | 
|---|
| 275 | if (!newDocument) | 
|---|
| 276 | return nullptr; | 
|---|
| 277 |  | 
|---|
| 278 | // Setup a filename for the new document in case that user didn't | 
|---|
| 279 | // save the file/specified one output filename. | 
|---|
| 280 | if (params.textureFilename().empty()) { | 
|---|
| 281 | std::string fn = site.document()->filename(); | 
|---|
| 282 | std::string ext = base::get_file_extension(fn); | 
|---|
| 283 | if (!ext.empty()) | 
|---|
| 284 | ext.insert(0, 1, '.'); | 
|---|
| 285 |  | 
|---|
| 286 | newDocument->setFilename( | 
|---|
| 287 | base::join_path(base::get_file_path(fn), | 
|---|
| 288 | base::get_file_title(fn) + "-Sheet") + ext); | 
|---|
| 289 | } | 
|---|
| 290 | return newDocument; | 
|---|
| 291 | } | 
|---|
| 292 |  | 
|---|
| 293 | #if ENABLE_UI | 
|---|
| 294 |  | 
|---|
| 295 | class ExportSpriteSheetWindow : public app::gen::ExportSpriteSheet { | 
|---|
| 296 | public: | 
|---|
| 297 | ExportSpriteSheetWindow(DocExporter& exporter, | 
|---|
| 298 | Site& site, | 
|---|
| 299 | ExportSpriteSheetParams& params, | 
|---|
| 300 | Preferences& pref) | 
|---|
| 301 | : m_exporter(exporter) | 
|---|
| 302 | , m_frontBuffer(std::make_shared<doc::ImageBuffer>()) | 
|---|
| 303 | , m_backBuffer(std::make_shared<doc::ImageBuffer>()) | 
|---|
| 304 | , m_site(site) | 
|---|
| 305 | , m_sprite(site.sprite()) | 
|---|
| 306 | , m_filenameAskOverwrite(true) | 
|---|
| 307 | , m_dataFilenameAskOverwrite(true) | 
|---|
| 308 | , m_editor(nullptr) | 
|---|
| 309 | , m_genTimer(100, nullptr) | 
|---|
| 310 | , m_executionID(0) | 
|---|
| 311 | , m_filenameFormat(params.filenameFormat()) | 
|---|
| 312 | { | 
|---|
| 313 | sectionTabs()->ItemChange.connect([this]{ onChangeSection(); }); | 
|---|
| 314 | expandSections()->Click.connect([this]{ onExpandSections(); }); | 
|---|
| 315 | closeSpriteSection()->Click.connect([this]{ onCloseSection(kSectionSprite); }); | 
|---|
| 316 | closeBordersSection()->Click.connect([this]{ onCloseSection(kSectionBorders); }); | 
|---|
| 317 | closeOutputSection()->Click.connect([this]{ onCloseSection(kSectionOutput); }); | 
|---|
| 318 |  | 
|---|
| 319 | static_assert( | 
|---|
| 320 | (int)app::SpriteSheetType::None == 0 && | 
|---|
| 321 | (int)app::SpriteSheetType::Horizontal == 1 && | 
|---|
| 322 | (int)app::SpriteSheetType::Vertical == 2 && | 
|---|
| 323 | (int)app::SpriteSheetType::Rows == 3 && | 
|---|
| 324 | (int)app::SpriteSheetType::Columns == 4 && | 
|---|
| 325 | (int)app::SpriteSheetType::Packed == 5, | 
|---|
| 326 | "SpriteSheetType enum changed"); | 
|---|
| 327 |  | 
|---|
| 328 | sheetType()->addItem(Strings::export_sprite_sheet_type_horz()); | 
|---|
| 329 | sheetType()->addItem(Strings::export_sprite_sheet_type_vert()); | 
|---|
| 330 | sheetType()->addItem(Strings::export_sprite_sheet_type_rows()); | 
|---|
| 331 | sheetType()->addItem(Strings::export_sprite_sheet_type_cols()); | 
|---|
| 332 | sheetType()->addItem(Strings::export_sprite_sheet_type_pack()); | 
|---|
| 333 | { | 
|---|
| 334 | int i; | 
|---|
| 335 | if (params.type() != app::SpriteSheetType::None) | 
|---|
| 336 | i = (int)params.type()-1; | 
|---|
| 337 | else | 
|---|
| 338 | i = ((int)app::SpriteSheetType::Rows)-1; | 
|---|
| 339 | sheetType()->setSelectedItemIndex(i); | 
|---|
| 340 | } | 
|---|
| 341 |  | 
|---|
| 342 | constraintType()->addItem(Strings::export_sprite_sheet_constraint_fixed_none()); | 
|---|
| 343 | constraintType()->addItem(Strings::export_sprite_sheet_constraint_fixed_cols()); | 
|---|
| 344 | constraintType()->addItem(Strings::export_sprite_sheet_constraint_fixed_rows()); | 
|---|
| 345 | constraintType()->addItem(Strings::export_sprite_sheet_constraint_fixed_width()); | 
|---|
| 346 | constraintType()->addItem(Strings::export_sprite_sheet_constraint_fixed_height()); | 
|---|
| 347 | constraintType()->addItem(Strings::export_sprite_sheet_constraint_fixed_size()); | 
|---|
| 348 |  | 
|---|
| 349 | auto constraint = constraint_type_from_params(params); | 
|---|
| 350 | constraintType()->setSelectedItemIndex(constraint); | 
|---|
| 351 | switch (constraint) { | 
|---|
| 352 | case kConstraintType_Cols: | 
|---|
| 353 | widthConstraint()->setTextf( "%d", params.columns()); | 
|---|
| 354 | break; | 
|---|
| 355 | case kConstraintType_Rows: | 
|---|
| 356 | heightConstraint()->setTextf( "%d", params.rows()); | 
|---|
| 357 | break; | 
|---|
| 358 | case kConstraintType_Width: | 
|---|
| 359 | widthConstraint()->setTextf( "%d", params.width()); | 
|---|
| 360 | break; | 
|---|
| 361 | case kConstraintType_Height: | 
|---|
| 362 | heightConstraint()->setTextf( "%d", params.height()); | 
|---|
| 363 | break; | 
|---|
| 364 | case kConstraintType_Size: | 
|---|
| 365 | widthConstraint()->setTextf( "%d", params.width()); | 
|---|
| 366 | heightConstraint()->setTextf( "%d", params.height()); | 
|---|
| 367 | break; | 
|---|
| 368 | } | 
|---|
| 369 |  | 
|---|
| 370 | static_assert(kSource_Sprite == 0 && | 
|---|
| 371 | kSource_SpriteGrid == 1 && | 
|---|
| 372 | kSource_Tilesets == 2, | 
|---|
| 373 | "Source enum has changed"); | 
|---|
| 374 | source()->addItem(new ListItem( "Sprite")); | 
|---|
| 375 | source()->addItem(new ListItem( "Sprite Grid")); | 
|---|
| 376 | source()->addItem(new ListItem( "Tilesets")); | 
|---|
| 377 | if (params.splitGrid()) | 
|---|
| 378 | source()->setSelectedItemIndex(int(kSource_SpriteGrid)); | 
|---|
| 379 | else if (params.fromTilesets()) | 
|---|
| 380 | source()->setSelectedItemIndex(int(kSource_Tilesets)); | 
|---|
| 381 |  | 
|---|
| 382 | fill_layers_combobox( | 
|---|
| 383 | m_sprite, layers(), params.layer(), params.layerIndex()); | 
|---|
| 384 |  | 
|---|
| 385 | fill_frames_combobox( | 
|---|
| 386 | m_sprite, frames(), params.tag()); | 
|---|
| 387 |  | 
|---|
| 388 | openGenerated()->setSelected(params.openGenerated()); | 
|---|
| 389 | trimSpriteEnabled()->setSelected(params.trimSprite()); | 
|---|
| 390 | trimEnabled()->setSelected(params.trim()); | 
|---|
| 391 | trimContainer()->setVisible(trimSpriteEnabled()->isSelected() || | 
|---|
| 392 | trimEnabled()->isSelected()); | 
|---|
| 393 | gridTrimEnabled()->setSelected((trimSpriteEnabled()->isSelected() || | 
|---|
| 394 | trimEnabled()->isSelected()) && | 
|---|
| 395 | params.trimByGrid()); | 
|---|
| 396 | extrudeEnabled()->setSelected(params.extrude()); | 
|---|
| 397 | mergeDups()->setSelected(params.mergeDuplicates()); | 
|---|
| 398 | ignoreEmpty()->setSelected(params.ignoreEmpty()); | 
|---|
| 399 |  | 
|---|
| 400 | borderPadding()->setTextf( "%d", params.borderPadding()); | 
|---|
| 401 | shapePadding()->setTextf( "%d", params.shapePadding()); | 
|---|
| 402 | innerPadding()->setTextf( "%d", params.innerPadding()); | 
|---|
| 403 |  | 
|---|
| 404 | m_filename = params.textureFilename(); | 
|---|
| 405 | imageEnabled()->setSelected(!m_filename.empty()); | 
|---|
| 406 | imageFilename()->setVisible(imageEnabled()->isSelected()); | 
|---|
| 407 |  | 
|---|
| 408 | m_dataFilename = params.dataFilename(); | 
|---|
| 409 | dataEnabled()->setSelected(!m_dataFilename.empty()); | 
|---|
| 410 | dataFormat()->setSelectedItemIndex(int(params.dataFormat())); | 
|---|
| 411 | splitLayers()->setSelected(params.splitLayers()); | 
|---|
| 412 | splitTags()->setSelected(params.splitTags()); | 
|---|
| 413 | listLayers()->setSelected(params.listLayers()); | 
|---|
| 414 | listTags()->setSelected(params.listTags()); | 
|---|
| 415 | listSlices()->setSelected(params.listSlices()); | 
|---|
| 416 |  | 
|---|
| 417 | updateDefaultDataFilenameFormat(); | 
|---|
| 418 | updateDataFields(); | 
|---|
| 419 |  | 
|---|
| 420 | std::string base = site.document()->filename(); | 
|---|
| 421 | base = base::join_path(base::get_file_path(base), base::get_file_title(base)); | 
|---|
| 422 |  | 
|---|
| 423 | if (m_filename.empty() || | 
|---|
| 424 | m_filename == kSpecifiedFilename) { | 
|---|
| 425 | std::string defExt = pref.spriteSheet.defaultExtension(); | 
|---|
| 426 |  | 
|---|
| 427 | if (base::utf8_icmp(base::get_file_extension(site.document()->filename()), defExt) == 0) | 
|---|
| 428 | m_filename = base + "-sheet."+ defExt; | 
|---|
| 429 | else | 
|---|
| 430 | m_filename = base + "."+ defExt; | 
|---|
| 431 | } | 
|---|
| 432 |  | 
|---|
| 433 | if (m_dataFilename.empty() || | 
|---|
| 434 | m_dataFilename == kSpecifiedFilename) | 
|---|
| 435 | m_dataFilename = base + ".json"; | 
|---|
| 436 |  | 
|---|
| 437 | exportButton()->Click.connect([this]{ onExport(); }); | 
|---|
| 438 | sheetType()->Change.connect([this]{ onSheetTypeChange(); }); | 
|---|
| 439 | constraintType()->Change.connect([this]{ onConstraintTypeChange(); }); | 
|---|
| 440 | widthConstraint()->Change.connect([this]{ generatePreview(); }); | 
|---|
| 441 | heightConstraint()->Change.connect([this]{ generatePreview(); }); | 
|---|
| 442 | borderPadding()->Change.connect([this]{ generatePreview(); }); | 
|---|
| 443 | shapePadding()->Change.connect([this]{ generatePreview(); }); | 
|---|
| 444 | innerPadding()->Change.connect([this]{ generatePreview(); }); | 
|---|
| 445 | extrudeEnabled()->Click.connect([this]{ generatePreview(); }); | 
|---|
| 446 | mergeDups()->Click.connect([this]{ generatePreview(); }); | 
|---|
| 447 | ignoreEmpty()->Click.connect([this]{ generatePreview(); }); | 
|---|
| 448 | imageEnabled()->Click.connect([this]{ onImageEnabledChange(); }); | 
|---|
| 449 | imageFilename()->Click.connect([this]{ onImageFilename(); }); | 
|---|
| 450 | dataEnabled()->Click.connect([this]{ onDataEnabledChange(); }); | 
|---|
| 451 | dataFilename()->Click.connect([this]{ onDataFilename(); }); | 
|---|
| 452 | trimSpriteEnabled()->Click.connect([this]{ onTrimEnabledChange(); }); | 
|---|
| 453 | trimEnabled()->Click.connect([this]{ onTrimEnabledChange(); }); | 
|---|
| 454 | gridTrimEnabled()->Click.connect([this]{ generatePreview(); }); | 
|---|
| 455 | source()->Change.connect([this]{ generatePreview(); }); | 
|---|
| 456 | layers()->Change.connect([this]{ generatePreview(); }); | 
|---|
| 457 | splitLayers()->Click.connect([this]{ onSplitLayersOrFrames(); }); | 
|---|
| 458 | splitTags()->Click.connect([this]{ onSplitLayersOrFrames(); }); | 
|---|
| 459 | frames()->Change.connect([this]{ generatePreview(); }); | 
|---|
| 460 | dataFilenameFormat()->Change.connect([this]{ onDataFilenameFormatChange(); }); | 
|---|
| 461 | openGenerated()->Click.connect([this]{ onOpenGeneratedChange(); }); | 
|---|
| 462 | preview()->Click.connect([this]{ generatePreview(); }); | 
|---|
| 463 | m_genTimer.Tick.connect([this]{ onGenTimerTick(); }); | 
|---|
| 464 |  | 
|---|
| 465 | // Select tabs | 
|---|
| 466 | { | 
|---|
| 467 | const std::string s = pref.spriteSheet.sections(); | 
|---|
| 468 | const bool layout = (s.find( "layout") != std::string::npos); | 
|---|
| 469 | const bool sprite = (s.find( "sprite") != std::string::npos); | 
|---|
| 470 | const bool borders = (s.find( "borders") != std::string::npos); | 
|---|
| 471 | const bool output = (s.find( "output") != std::string::npos); | 
|---|
| 472 | sectionTabs()->getItem(kSectionLayout)->setSelected(layout || (!sprite & !borders && !output)); | 
|---|
| 473 | sectionTabs()->getItem(kSectionSprite)->setSelected(sprite); | 
|---|
| 474 | sectionTabs()->getItem(kSectionBorders)->setSelected(borders); | 
|---|
| 475 | sectionTabs()->getItem(kSectionOutput)->setSelected(output); | 
|---|
| 476 | } | 
|---|
| 477 |  | 
|---|
| 478 | onChangeSection(); | 
|---|
| 479 | onSheetTypeChange(); | 
|---|
| 480 | onFileNamesChange(); | 
|---|
| 481 | updateExportButton(); | 
|---|
| 482 |  | 
|---|
| 483 | preview()->setSelected(pref.spriteSheet.preview()); | 
|---|
| 484 | generatePreview(); | 
|---|
| 485 |  | 
|---|
| 486 | remapWindow(); | 
|---|
| 487 | centerWindow(); | 
|---|
| 488 | load_window_pos(this, "ExportSpriteSheet"); | 
|---|
| 489 | } | 
|---|
| 490 |  | 
|---|
| 491 | ~ExportSpriteSheetWindow() { | 
|---|
| 492 | cancelGenTask(); | 
|---|
| 493 | if (m_spriteSheet) { | 
|---|
| 494 | auto ctx = UIContext::instance(); | 
|---|
| 495 | ctx->setActiveDocument(m_site.document()); | 
|---|
| 496 |  | 
|---|
| 497 | DocDestroyer destroyer(ctx, m_spriteSheet.release(), 100); | 
|---|
| 498 | destroyer.destroyDocument(); | 
|---|
| 499 | } | 
|---|
| 500 | } | 
|---|
| 501 |  | 
|---|
| 502 | std::string selectedSectionsString() const { | 
|---|
| 503 | const bool layout = sectionTabs()->getItem(kSectionLayout)->isSelected(); | 
|---|
| 504 | const bool sprite = sectionTabs()->getItem(kSectionSprite)->isSelected(); | 
|---|
| 505 | const bool borders = sectionTabs()->getItem(kSectionBorders)->isSelected(); | 
|---|
| 506 | const bool output = sectionTabs()->getItem(kSectionOutput)->isSelected(); | 
|---|
| 507 | return | 
|---|
| 508 | fmt::format( "{} {} {} {}", | 
|---|
| 509 | (layout ? "layout": ""), | 
|---|
| 510 | (sprite ? "sprite": ""), | 
|---|
| 511 | (borders ? "borders": ""), | 
|---|
| 512 | (output ? "output": "")); | 
|---|
| 513 | } | 
|---|
| 514 |  | 
|---|
| 515 | bool ok() const { | 
|---|
| 516 | return closer() == exportButton(); | 
|---|
| 517 | } | 
|---|
| 518 |  | 
|---|
| 519 | void updateParams(ExportSpriteSheetParams& params) { | 
|---|
| 520 | params.type            (spriteSheetTypeValue()); | 
|---|
| 521 | params.columns         (columnsValue()); | 
|---|
| 522 | params.rows            (rowsValue()); | 
|---|
| 523 | params.width           (widthValue()); | 
|---|
| 524 | params.height          (heightValue()); | 
|---|
| 525 | params.textureFilename (filenameValue()); | 
|---|
| 526 | params.dataFilename    (dataFilenameValue()); | 
|---|
| 527 | params.dataFormat      (dataFormatValue()); | 
|---|
| 528 | params.filenameFormat  (filenameFormatValue()); | 
|---|
| 529 | params.borderPadding   (borderPaddingValue()); | 
|---|
| 530 | params.shapePadding    (shapePaddingValue()); | 
|---|
| 531 | params.innerPadding    (innerPaddingValue()); | 
|---|
| 532 | params.trimSprite      (trimSpriteValue()); | 
|---|
| 533 | params.trim            (trimValue()); | 
|---|
| 534 | params.trimByGrid      (trimByGridValue()); | 
|---|
| 535 | params.extrude         (extrudeValue()); | 
|---|
| 536 | params.mergeDuplicates (mergeDupsValue()); | 
|---|
| 537 | params.ignoreEmpty     (ignoreEmptyValue()); | 
|---|
| 538 | params.openGenerated   (openGeneratedValue()); | 
|---|
| 539 | params.layer           (layerValue()); | 
|---|
| 540 | params.layerIndex      (layerIndex()); | 
|---|
| 541 | params.tag             (tagValue()); | 
|---|
| 542 | params.splitLayers     (splitLayersValue()); | 
|---|
| 543 | params.splitTags       (splitTagsValue()); | 
|---|
| 544 | params.listLayers      (listLayersValue()); | 
|---|
| 545 | params.listTags        (listTagsValue()); | 
|---|
| 546 | params.listSlices      (listSlicesValue()); | 
|---|
| 547 | params.splitGrid       (source()->getSelectedItemIndex() == int(kSource_SpriteGrid)); | 
|---|
| 548 | params.fromTilesets    (source()->getSelectedItemIndex() == int(kSource_Tilesets)); | 
|---|
| 549 | } | 
|---|
| 550 |  | 
|---|
| 551 | private: | 
|---|
| 552 |  | 
|---|
| 553 | bool onProcessMessage(ui::Message* msg) override { | 
|---|
| 554 | switch (msg->type()) { | 
|---|
| 555 | case kCloseMessage: | 
|---|
| 556 | save_window_pos(this, "ExportSpriteSheet"); | 
|---|
| 557 | break; | 
|---|
| 558 | } | 
|---|
| 559 | return Window::onProcessMessage(msg); | 
|---|
| 560 | } | 
|---|
| 561 |  | 
|---|
| 562 | void onBroadcastMouseMessage(const gfx::Point& screenPos, | 
|---|
| 563 | WidgetsList& targets) override { | 
|---|
| 564 | Window::onBroadcastMouseMessage(screenPos, targets); | 
|---|
| 565 |  | 
|---|
| 566 | // Add the editor as receptor of mouse events too. | 
|---|
| 567 | if (m_editor) | 
|---|
| 568 | targets.push_back(View::getView(m_editor)); | 
|---|
| 569 | } | 
|---|
| 570 |  | 
|---|
| 571 | void onChangeSection() { | 
|---|
| 572 | panel()->showAllChildren(); | 
|---|
| 573 |  | 
|---|
| 574 | const bool layout = sectionTabs()->getItem(kSectionLayout)->isSelected(); | 
|---|
| 575 | const bool sprite = sectionTabs()->getItem(kSectionSprite)->isSelected(); | 
|---|
| 576 | const bool borders = sectionTabs()->getItem(kSectionBorders)->isSelected(); | 
|---|
| 577 | const bool output = sectionTabs()->getItem(kSectionOutput)->isSelected(); | 
|---|
| 578 |  | 
|---|
| 579 | sectionLayout()->setVisible(layout); | 
|---|
| 580 | sectionSpriteSeparator()->setVisible(sprite && layout); | 
|---|
| 581 | sectionSprite()->setVisible(sprite); | 
|---|
| 582 | sectionBordersSeparator()->setVisible(borders && (layout || sprite)); | 
|---|
| 583 | sectionBorders()->setVisible(borders); | 
|---|
| 584 | sectionOutputSeparator()->setVisible(output && (layout || sprite || borders)); | 
|---|
| 585 | sectionOutput()->setVisible(output); | 
|---|
| 586 |  | 
|---|
| 587 | resize(); | 
|---|
| 588 | } | 
|---|
| 589 |  | 
|---|
| 590 | void onExpandSections() { | 
|---|
| 591 | sectionTabs()->getItem(kSectionLayout)->setSelected(true); | 
|---|
| 592 | sectionTabs()->getItem(kSectionSprite)->setSelected(true); | 
|---|
| 593 | sectionTabs()->getItem(kSectionBorders)->setSelected(true); | 
|---|
| 594 | sectionTabs()->getItem(kSectionOutput)->setSelected(true); | 
|---|
| 595 | onChangeSection(); | 
|---|
| 596 | } | 
|---|
| 597 |  | 
|---|
| 598 | void onCloseSection(const Section section) { | 
|---|
| 599 | if (sectionTabs()->countSelectedItems() > 1) | 
|---|
| 600 | sectionTabs()->getItem(section)->setSelected(false); | 
|---|
| 601 | onChangeSection(); | 
|---|
| 602 | } | 
|---|
| 603 |  | 
|---|
| 604 | app::SpriteSheetType spriteSheetTypeValue() const { | 
|---|
| 605 | return (app::SpriteSheetType)(sheetType()->getSelectedItemIndex()+1); | 
|---|
| 606 | } | 
|---|
| 607 |  | 
|---|
| 608 | int columnsValue() const { | 
|---|
| 609 | if (spriteSheetTypeValue() == app::SpriteSheetType::Rows && | 
|---|
| 610 | constraintType()->getSelectedItemIndex() == (int)kConstraintType_Cols) { | 
|---|
| 611 | return widthConstraint()->textInt(); | 
|---|
| 612 | } | 
|---|
| 613 | else | 
|---|
| 614 | return 0; | 
|---|
| 615 | } | 
|---|
| 616 |  | 
|---|
| 617 | int rowsValue() const { | 
|---|
| 618 | if (spriteSheetTypeValue() == app::SpriteSheetType::Columns && | 
|---|
| 619 | constraintType()->getSelectedItemIndex() == (int)kConstraintType_Rows) { | 
|---|
| 620 | return heightConstraint()->textInt(); | 
|---|
| 621 | } | 
|---|
| 622 | else | 
|---|
| 623 | return 0; | 
|---|
| 624 | } | 
|---|
| 625 |  | 
|---|
| 626 | int widthValue() const { | 
|---|
| 627 | if ((spriteSheetTypeValue() == app::SpriteSheetType::Rows || | 
|---|
| 628 | spriteSheetTypeValue() == app::SpriteSheetType::Packed) && | 
|---|
| 629 | (constraintType()->getSelectedItemIndex() == (int)kConstraintType_Width || | 
|---|
| 630 | constraintType()->getSelectedItemIndex() == (int)kConstraintType_Size)) { | 
|---|
| 631 | return widthConstraint()->textInt(); | 
|---|
| 632 | } | 
|---|
| 633 | else | 
|---|
| 634 | return 0; | 
|---|
| 635 | } | 
|---|
| 636 |  | 
|---|
| 637 | int heightValue() const { | 
|---|
| 638 | if ((spriteSheetTypeValue() == app::SpriteSheetType::Columns || | 
|---|
| 639 | spriteSheetTypeValue() == app::SpriteSheetType::Packed) && | 
|---|
| 640 | (constraintType()->getSelectedItemIndex() == (int)kConstraintType_Height || | 
|---|
| 641 | constraintType()->getSelectedItemIndex() == (int)kConstraintType_Size)) { | 
|---|
| 642 | return heightConstraint()->textInt(); | 
|---|
| 643 | } | 
|---|
| 644 | else | 
|---|
| 645 | return 0; | 
|---|
| 646 | } | 
|---|
| 647 |  | 
|---|
| 648 | std::string filenameValue() const { | 
|---|
| 649 | if (imageEnabled()->isSelected()) | 
|---|
| 650 | return m_filename; | 
|---|
| 651 | else | 
|---|
| 652 | return std::string(); | 
|---|
| 653 | } | 
|---|
| 654 |  | 
|---|
| 655 | std::string dataFilenameValue() const { | 
|---|
| 656 | if (dataEnabled()->isSelected()) | 
|---|
| 657 | return m_dataFilename; | 
|---|
| 658 | else | 
|---|
| 659 | return std::string(); | 
|---|
| 660 | } | 
|---|
| 661 |  | 
|---|
| 662 | std::string filenameFormatValue() const { | 
|---|
| 663 | if (!m_filenameFormat.empty() && | 
|---|
| 664 | m_filenameFormat != m_filenameFormatDefault) | 
|---|
| 665 | return m_filenameFormat; | 
|---|
| 666 | else | 
|---|
| 667 | return std::string(); | 
|---|
| 668 | } | 
|---|
| 669 |  | 
|---|
| 670 | SpriteSheetDataFormat dataFormatValue() const { | 
|---|
| 671 | if (dataEnabled()->isSelected()) | 
|---|
| 672 | return SpriteSheetDataFormat(dataFormat()->getSelectedItemIndex()); | 
|---|
| 673 | else | 
|---|
| 674 | return SpriteSheetDataFormat::Default; | 
|---|
| 675 | } | 
|---|
| 676 |  | 
|---|
| 677 | int borderPaddingValue() const { | 
|---|
| 678 | int value = borderPadding()->textInt(); | 
|---|
| 679 | return std::clamp(value, 0, 100); | 
|---|
| 680 | } | 
|---|
| 681 |  | 
|---|
| 682 | int shapePaddingValue() const { | 
|---|
| 683 | int value = shapePadding()->textInt(); | 
|---|
| 684 | return std::clamp(value, 0, 100); | 
|---|
| 685 | } | 
|---|
| 686 |  | 
|---|
| 687 | int innerPaddingValue() const { | 
|---|
| 688 | int value = innerPadding()->textInt(); | 
|---|
| 689 | return std::clamp(value, 0, 100); | 
|---|
| 690 | } | 
|---|
| 691 |  | 
|---|
| 692 | bool trimSpriteValue() const { | 
|---|
| 693 | return trimSpriteEnabled()->isSelected(); | 
|---|
| 694 | } | 
|---|
| 695 |  | 
|---|
| 696 | bool trimValue() const { | 
|---|
| 697 | return trimEnabled()->isSelected(); | 
|---|
| 698 | } | 
|---|
| 699 |  | 
|---|
| 700 | bool trimByGridValue() const { | 
|---|
| 701 | return gridTrimEnabled()->isSelected(); | 
|---|
| 702 | } | 
|---|
| 703 |  | 
|---|
| 704 | bool extrudeValue() const { | 
|---|
| 705 | return extrudeEnabled()->isSelected(); | 
|---|
| 706 | } | 
|---|
| 707 |  | 
|---|
| 708 | bool extrudePadding() const { | 
|---|
| 709 | return (extrudeValue() ? 1: 0); | 
|---|
| 710 | } | 
|---|
| 711 |  | 
|---|
| 712 | bool mergeDupsValue() const { | 
|---|
| 713 | return mergeDups()->isSelected(); | 
|---|
| 714 | } | 
|---|
| 715 |  | 
|---|
| 716 | bool ignoreEmptyValue() const { | 
|---|
| 717 | return ignoreEmpty()->isSelected(); | 
|---|
| 718 | } | 
|---|
| 719 |  | 
|---|
| 720 | bool openGeneratedValue() const { | 
|---|
| 721 | return openGenerated()->isSelected(); | 
|---|
| 722 | } | 
|---|
| 723 |  | 
|---|
| 724 | std::string layerValue() const { | 
|---|
| 725 | return layers()->getValue(); | 
|---|
| 726 | } | 
|---|
| 727 |  | 
|---|
| 728 | int layerIndex() const { | 
|---|
| 729 | int i = layers()->getSelectedItemIndex() - kLayersComboboxExtraInitialItems; | 
|---|
| 730 | return i < 0 ? -1 : i; | 
|---|
| 731 | } | 
|---|
| 732 |  | 
|---|
| 733 | std::string tagValue() const { | 
|---|
| 734 | return frames()->getValue(); | 
|---|
| 735 | } | 
|---|
| 736 |  | 
|---|
| 737 | bool splitLayersValue() const { | 
|---|
| 738 | return splitLayers()->isSelected(); | 
|---|
| 739 | } | 
|---|
| 740 |  | 
|---|
| 741 | bool splitTagsValue() const { | 
|---|
| 742 | return splitTags()->isSelected(); | 
|---|
| 743 | } | 
|---|
| 744 |  | 
|---|
| 745 | bool splitGridValue() const { | 
|---|
| 746 | return (source()->getSelectedItemIndex() == int(kSource_SpriteGrid)); | 
|---|
| 747 | } | 
|---|
| 748 |  | 
|---|
| 749 | bool listLayersValue() const { | 
|---|
| 750 | return listLayers()->isSelected(); | 
|---|
| 751 | } | 
|---|
| 752 |  | 
|---|
| 753 | bool listTagsValue() const { | 
|---|
| 754 | return listTags()->isSelected(); | 
|---|
| 755 | } | 
|---|
| 756 |  | 
|---|
| 757 | bool listSlicesValue() const { | 
|---|
| 758 | return listSlices()->isSelected(); | 
|---|
| 759 | } | 
|---|
| 760 |  | 
|---|
| 761 | void onExport() { | 
|---|
| 762 | if (!ask_overwrite(m_filenameAskOverwrite, filenameValue(), | 
|---|
| 763 | m_dataFilenameAskOverwrite, dataFilenameValue())) | 
|---|
| 764 | return; | 
|---|
| 765 |  | 
|---|
| 766 | closeWindow(exportButton()); | 
|---|
| 767 | } | 
|---|
| 768 |  | 
|---|
| 769 | void onSheetTypeChange() { | 
|---|
| 770 | for (int i=1; i<constraintType()->getItemCount(); ++i) | 
|---|
| 771 | constraintType()->getItem(i)->setVisible(false); | 
|---|
| 772 |  | 
|---|
| 773 | mergeDups()->setEnabled(true); | 
|---|
| 774 |  | 
|---|
| 775 | const ConstraintType selectConstraint = | 
|---|
| 776 | (ConstraintType)constraintType()->getSelectedItemIndex(); | 
|---|
| 777 | switch (spriteSheetTypeValue()) { | 
|---|
| 778 | case app::SpriteSheetType::Horizontal: | 
|---|
| 779 | case app::SpriteSheetType::Vertical: | 
|---|
| 780 | constraintType()->setSelectedItemIndex(kConstraintType_None); | 
|---|
| 781 | break; | 
|---|
| 782 | case app::SpriteSheetType::Rows: | 
|---|
| 783 | constraintType()->getItem(kConstraintType_Cols)->setVisible(true); | 
|---|
| 784 | constraintType()->getItem(kConstraintType_Width)->setVisible(true); | 
|---|
| 785 | if (selectConstraint != kConstraintType_None && | 
|---|
| 786 | selectConstraint != kConstraintType_Cols && | 
|---|
| 787 | selectConstraint != kConstraintType_Width) | 
|---|
| 788 | constraintType()->setSelectedItemIndex(kConstraintType_None); | 
|---|
| 789 | break; | 
|---|
| 790 | case app::SpriteSheetType::Columns: | 
|---|
| 791 | constraintType()->getItem(kConstraintType_Rows)->setVisible(true); | 
|---|
| 792 | constraintType()->getItem(kConstraintType_Height)->setVisible(true); | 
|---|
| 793 | if (selectConstraint != kConstraintType_None && | 
|---|
| 794 | selectConstraint != kConstraintType_Rows && | 
|---|
| 795 | selectConstraint != kConstraintType_Height) | 
|---|
| 796 | constraintType()->setSelectedItemIndex(kConstraintType_None); | 
|---|
| 797 | break; | 
|---|
| 798 | case app::SpriteSheetType::Packed: | 
|---|
| 799 | constraintType()->getItem(kConstraintType_Width)->setVisible(true); | 
|---|
| 800 | constraintType()->getItem(kConstraintType_Height)->setVisible(true); | 
|---|
| 801 | constraintType()->getItem(kConstraintType_Size)->setVisible(true); | 
|---|
| 802 | if (selectConstraint != kConstraintType_None && | 
|---|
| 803 | selectConstraint != kConstraintType_Width && | 
|---|
| 804 | selectConstraint != kConstraintType_Height && | 
|---|
| 805 | selectConstraint != kConstraintType_Size) { | 
|---|
| 806 | constraintType()->setSelectedItemIndex(kConstraintType_None); | 
|---|
| 807 | } | 
|---|
| 808 | mergeDups()->setSelected(true); | 
|---|
| 809 | mergeDups()->setEnabled(false); | 
|---|
| 810 | break; | 
|---|
| 811 | } | 
|---|
| 812 | onConstraintTypeChange(); | 
|---|
| 813 | } | 
|---|
| 814 |  | 
|---|
| 815 | void onConstraintTypeChange() { | 
|---|
| 816 | bool withWidth = false; | 
|---|
| 817 | bool withHeight = false; | 
|---|
| 818 | switch ((ConstraintType)constraintType()->getSelectedItemIndex()) { | 
|---|
| 819 | case kConstraintType_Cols: | 
|---|
| 820 | withWidth = true; | 
|---|
| 821 | widthConstraint()->setSuffix( ""); | 
|---|
| 822 | break; | 
|---|
| 823 | case kConstraintType_Rows: | 
|---|
| 824 | withHeight = true; | 
|---|
| 825 | heightConstraint()->setSuffix( ""); | 
|---|
| 826 | break; | 
|---|
| 827 | case kConstraintType_Width: | 
|---|
| 828 | withWidth = true; | 
|---|
| 829 | widthConstraint()->setSuffix( "px"); | 
|---|
| 830 | break; | 
|---|
| 831 | case kConstraintType_Height: | 
|---|
| 832 | withHeight = true; | 
|---|
| 833 | heightConstraint()->setSuffix( "px"); | 
|---|
| 834 | break; | 
|---|
| 835 | case kConstraintType_Size: | 
|---|
| 836 | withWidth = true; | 
|---|
| 837 | withHeight = true; | 
|---|
| 838 | widthConstraint()->setSuffix( "px"); | 
|---|
| 839 | heightConstraint()->setSuffix( "px"); | 
|---|
| 840 | break; | 
|---|
| 841 | } | 
|---|
| 842 | widthConstraint()->setVisible(withWidth); | 
|---|
| 843 | heightConstraint()->setVisible(withHeight); | 
|---|
| 844 | resize(); | 
|---|
| 845 | generatePreview(); | 
|---|
| 846 | } | 
|---|
| 847 |  | 
|---|
| 848 | void onFileNamesChange() { | 
|---|
| 849 | imageFilename()->setText(base::get_file_name(m_filename)); | 
|---|
| 850 | dataFilename()->setText(base::get_file_name(m_dataFilename)); | 
|---|
| 851 | resize(); | 
|---|
| 852 | } | 
|---|
| 853 |  | 
|---|
| 854 | void onImageFilename() { | 
|---|
| 855 | base::paths newFilename; | 
|---|
| 856 | if (!app::show_file_selector( | 
|---|
| 857 | Strings::export_sprite_sheet_save_title(), m_filename, | 
|---|
| 858 | get_writable_extensions(), | 
|---|
| 859 | FileSelectorType::Save, newFilename)) | 
|---|
| 860 | return; | 
|---|
| 861 |  | 
|---|
| 862 | ASSERT(!newFilename.empty()); | 
|---|
| 863 |  | 
|---|
| 864 | m_filename = newFilename.front(); | 
|---|
| 865 | m_filenameAskOverwrite = false; // Already asked in file selector | 
|---|
| 866 | onFileNamesChange(); | 
|---|
| 867 | } | 
|---|
| 868 |  | 
|---|
| 869 | void onImageEnabledChange() { | 
|---|
| 870 | m_filenameAskOverwrite = true; | 
|---|
| 871 |  | 
|---|
| 872 | imageFilename()->setVisible(imageEnabled()->isSelected()); | 
|---|
| 873 | updateExportButton(); | 
|---|
| 874 | resize(); | 
|---|
| 875 | } | 
|---|
| 876 |  | 
|---|
| 877 | void onDataFilename() { | 
|---|
| 878 | // TODO hardcoded "json" extension | 
|---|
| 879 | base::paths exts = { "json"}; | 
|---|
| 880 | base::paths newFilename; | 
|---|
| 881 | if (!app::show_file_selector( | 
|---|
| 882 | Strings::export_sprite_sheet_save_json_title(), | 
|---|
| 883 | m_dataFilename, | 
|---|
| 884 | exts, | 
|---|
| 885 | FileSelectorType::Save, | 
|---|
| 886 | newFilename)) | 
|---|
| 887 | return; | 
|---|
| 888 |  | 
|---|
| 889 | ASSERT(!newFilename.empty()); | 
|---|
| 890 |  | 
|---|
| 891 | m_dataFilename = newFilename.front(); | 
|---|
| 892 | m_dataFilenameAskOverwrite = false; // Already asked in file selector | 
|---|
| 893 | onFileNamesChange(); | 
|---|
| 894 | } | 
|---|
| 895 |  | 
|---|
| 896 | void onDataEnabledChange() { | 
|---|
| 897 | m_dataFilenameAskOverwrite = true; | 
|---|
| 898 |  | 
|---|
| 899 | updateDataFields(); | 
|---|
| 900 | updateExportButton(); | 
|---|
| 901 | resize(); | 
|---|
| 902 | } | 
|---|
| 903 |  | 
|---|
| 904 | void onTrimEnabledChange() { | 
|---|
| 905 | trimContainer()->setVisible( | 
|---|
| 906 | trimSpriteEnabled()->isSelected() || | 
|---|
| 907 | trimEnabled()->isSelected()); | 
|---|
| 908 | resize(); | 
|---|
| 909 | generatePreview(); | 
|---|
| 910 | } | 
|---|
| 911 |  | 
|---|
| 912 | void onSplitLayersOrFrames() { | 
|---|
| 913 | updateDefaultDataFilenameFormat(); | 
|---|
| 914 | generatePreview(); | 
|---|
| 915 | } | 
|---|
| 916 |  | 
|---|
| 917 | void onDataFilenameFormatChange() { | 
|---|
| 918 | m_filenameFormat = dataFilenameFormat()->text(); | 
|---|
| 919 | if (m_filenameFormat.empty()) | 
|---|
| 920 | updateDefaultDataFilenameFormat(); | 
|---|
| 921 | } | 
|---|
| 922 |  | 
|---|
| 923 | void onOpenGeneratedChange() { | 
|---|
| 924 | updateExportButton(); | 
|---|
| 925 | } | 
|---|
| 926 |  | 
|---|
| 927 | void resize() { | 
|---|
| 928 | expandWindow(sizeHint()); | 
|---|
| 929 | } | 
|---|
| 930 |  | 
|---|
| 931 | void updateExportButton() { | 
|---|
| 932 | exportButton()->setEnabled( | 
|---|
| 933 | imageEnabled()->isSelected() || | 
|---|
| 934 | dataEnabled()->isSelected() || | 
|---|
| 935 | openGenerated()->isSelected()); | 
|---|
| 936 | } | 
|---|
| 937 |  | 
|---|
| 938 | void updateDefaultDataFilenameFormat() { | 
|---|
| 939 | m_filenameFormatDefault = | 
|---|
| 940 | get_default_filename_format_for_sheet( | 
|---|
| 941 | m_site.document()->filename(), | 
|---|
| 942 | m_site.document()->sprite()->totalFrames() > 0, | 
|---|
| 943 | splitLayersValue(), | 
|---|
| 944 | splitTagsValue()); | 
|---|
| 945 |  | 
|---|
| 946 | if (m_filenameFormat.empty()) { | 
|---|
| 947 | dataFilenameFormat()->setText(m_filenameFormatDefault); | 
|---|
| 948 | } | 
|---|
| 949 | else { | 
|---|
| 950 | dataFilenameFormat()->setText(m_filenameFormat); | 
|---|
| 951 | } | 
|---|
| 952 | } | 
|---|
| 953 |  | 
|---|
| 954 | void updateDataFields() { | 
|---|
| 955 | bool state = dataEnabled()->isSelected(); | 
|---|
| 956 | dataFilename()->setVisible(state); | 
|---|
| 957 | dataMeta()->setVisible(state); | 
|---|
| 958 | dataFilenameFormatPlaceholder()->setVisible(state); | 
|---|
| 959 | } | 
|---|
| 960 |  | 
|---|
| 961 | void onGenTimerTick() { | 
|---|
| 962 | if (!m_genTask) { | 
|---|
| 963 | m_genTimer.stop(); | 
|---|
| 964 | setText(Strings::export_sprite_sheet_title()); | 
|---|
| 965 | return; | 
|---|
| 966 | } | 
|---|
| 967 | setText( | 
|---|
| 968 | fmt::format( | 
|---|
| 969 | "{} ({} {}%)", | 
|---|
| 970 | Strings::export_sprite_sheet_title(), | 
|---|
| 971 | Strings::export_sprite_sheet_preview(), | 
|---|
| 972 | int(100.0f * m_genTask->progress()))); | 
|---|
| 973 | } | 
|---|
| 974 |  | 
|---|
| 975 | void generatePreview() { | 
|---|
| 976 | cancelGenTask(); | 
|---|
| 977 |  | 
|---|
| 978 | if (!preview()->isSelected()) { | 
|---|
| 979 | if (m_spriteSheet) { | 
|---|
| 980 | auto ctx = UIContext::instance(); | 
|---|
| 981 | ctx->setActiveDocument(m_site.document()); | 
|---|
| 982 |  | 
|---|
| 983 | DocDestroyer destroyer(ctx, m_spriteSheet.release(), 100); | 
|---|
| 984 | destroyer.destroyDocument(); | 
|---|
| 985 | m_editor = nullptr; | 
|---|
| 986 | } | 
|---|
| 987 | return; | 
|---|
| 988 | } | 
|---|
| 989 |  | 
|---|
| 990 | ASSERT(m_genTask == nullptr); | 
|---|
| 991 |  | 
|---|
| 992 | ExportSpriteSheetParams params; | 
|---|
| 993 | updateParams(params); | 
|---|
| 994 |  | 
|---|
| 995 | std::unique_ptr<Task> task(new Task); | 
|---|
| 996 | task->run( | 
|---|
| 997 | [this, params](base::task_token& token){ | 
|---|
| 998 | generateSpriteSheetOnBackground(params, token); | 
|---|
| 999 | }); | 
|---|
| 1000 | m_genTask = std::move(task); | 
|---|
| 1001 | m_genTimer.start(); | 
|---|
| 1002 | onGenTimerTick(); | 
|---|
| 1003 | } | 
|---|
| 1004 |  | 
|---|
| 1005 | void generateSpriteSheetOnBackground(const ExportSpriteSheetParams& params, | 
|---|
| 1006 | base::task_token& token) { | 
|---|
| 1007 | // Sometimes (more often on Linux) the back buffer is still being | 
|---|
| 1008 | // used by the new document after | 
|---|
| 1009 | // generateSpriteSheetOnBackground() and before | 
|---|
| 1010 | // openGeneratedSpriteSheet(). In this case the use counter is 3 | 
|---|
| 1011 | // which means that 2 or more openGeneratedSpriteSheet() are | 
|---|
| 1012 | // queued in the laf-os events queue. In this case we just create | 
|---|
| 1013 | // a new back buffer and the old one will be discarded by | 
|---|
| 1014 | // openGeneratedSpriteSheet() when m_executionID != executionID. | 
|---|
| 1015 | if (m_backBuffer.use_count() > 2) { | 
|---|
| 1016 | auto ptr = std::make_shared<doc::ImageBuffer>(); | 
|---|
| 1017 | m_backBuffer.swap(ptr); | 
|---|
| 1018 | } | 
|---|
| 1019 | m_exporter.setDocImageBuffer(m_backBuffer); | 
|---|
| 1020 |  | 
|---|
| 1021 | ASSERT(m_backBuffer.use_count() == 2); | 
|---|
| 1022 |  | 
|---|
| 1023 | // Create a non-UI context to avoid showing UI dialogs for | 
|---|
| 1024 | // GifOptions or JpegOptions from the background thread. | 
|---|
| 1025 | Context tmpCtx; | 
|---|
| 1026 |  | 
|---|
| 1027 | Doc* newDocument = | 
|---|
| 1028 | generate_sprite_sheet( | 
|---|
| 1029 | m_exporter, &tmpCtx, m_site, params, false, token) | 
|---|
| 1030 | .release(); | 
|---|
| 1031 | if (!newDocument) | 
|---|
| 1032 | return; | 
|---|
| 1033 |  | 
|---|
| 1034 | if (token.canceled()) { | 
|---|
| 1035 | DocDestroyer destroyer(&tmpCtx, newDocument, 100); | 
|---|
| 1036 | destroyer.destroyDocument(); | 
|---|
| 1037 | return; | 
|---|
| 1038 | } | 
|---|
| 1039 |  | 
|---|
| 1040 | ++m_executionID; | 
|---|
| 1041 | int executionID = m_executionID; | 
|---|
| 1042 |  | 
|---|
| 1043 | tmpCtx.documents().remove(newDocument); | 
|---|
| 1044 |  | 
|---|
| 1045 | ui::execute_from_ui_thread( | 
|---|
| 1046 | [this, newDocument, executionID]{ | 
|---|
| 1047 | openGeneratedSpriteSheet(newDocument, executionID); | 
|---|
| 1048 | }); | 
|---|
| 1049 | } | 
|---|
| 1050 |  | 
|---|
| 1051 | void openGeneratedSpriteSheet(Doc* newDocument, int executionID) { | 
|---|
| 1052 | auto context = UIContext::instance(); | 
|---|
| 1053 |  | 
|---|
| 1054 | if (!isVisible() || | 
|---|
| 1055 | // Other openGeneratedSpriteSheet() is queued and we are the | 
|---|
| 1056 | // old one. IN this case the newDocument contains a back | 
|---|
| 1057 | // buffer (ImageBufferPtr) that will be discarded. | 
|---|
| 1058 | m_executionID != executionID) { | 
|---|
| 1059 | DocDestroyer destroyer(context, newDocument, 100); | 
|---|
| 1060 | destroyer.destroyDocument(); | 
|---|
| 1061 | return; | 
|---|
| 1062 | } | 
|---|
| 1063 |  | 
|---|
| 1064 | // Was the preview unselected when we were generating the preview? | 
|---|
| 1065 | if (!preview()->isSelected()) | 
|---|
| 1066 | return; | 
|---|
| 1067 |  | 
|---|
| 1068 | // Now the "m_frontBuffer" is the current "m_backBuffer" which was | 
|---|
| 1069 | // used by the generator to create the "newDocument", in the next | 
|---|
| 1070 | // iteration we'll use the "m_backBuffer" to re-generate the | 
|---|
| 1071 | // sprite sheet (while the document being displayed in the Editor | 
|---|
| 1072 | // will use the m_frontBuffer). | 
|---|
| 1073 | m_frontBuffer.swap(m_backBuffer); | 
|---|
| 1074 |  | 
|---|
| 1075 | if (!m_spriteSheet) { | 
|---|
| 1076 | m_spriteSheet.reset(newDocument); | 
|---|
| 1077 | m_spriteSheet->setInhibitBackup(true); | 
|---|
| 1078 | m_spriteSheet->setContext(context); | 
|---|
| 1079 |  | 
|---|
| 1080 | m_editor = context->getEditorFor(m_spriteSheet.get()); | 
|---|
| 1081 | if (m_editor) { | 
|---|
| 1082 | m_editor->setState(EditorStatePtr(new NavigateState)); | 
|---|
| 1083 | m_editor->setDefaultScroll(); | 
|---|
| 1084 | } | 
|---|
| 1085 | } | 
|---|
| 1086 | else { | 
|---|
| 1087 | // Replace old cel with the new one | 
|---|
| 1088 | auto spriteSheetLay = static_cast<LayerImage*>(m_spriteSheet->sprite()->root()->firstLayer()); | 
|---|
| 1089 | auto newDocLay = static_cast<LayerImage*>(newDocument->sprite()->root()->firstLayer()); | 
|---|
| 1090 | Cel* oldCel = m_spriteSheet->sprite()->firstLayer()->cel(0); | 
|---|
| 1091 | Cel* newCel = newDocument->sprite()->firstLayer()->cel(0); | 
|---|
| 1092 |  | 
|---|
| 1093 | spriteSheetLay->removeCel(oldCel); | 
|---|
| 1094 | delete oldCel; | 
|---|
| 1095 |  | 
|---|
| 1096 | newDocLay->removeCel(newCel); | 
|---|
| 1097 | spriteSheetLay->addCel(newCel); | 
|---|
| 1098 |  | 
|---|
| 1099 | // Update sprite sheet size | 
|---|
| 1100 | m_spriteSheet->sprite()->setSize( | 
|---|
| 1101 | newDocument->sprite()->width(), | 
|---|
| 1102 | newDocument->sprite()->height()); | 
|---|
| 1103 |  | 
|---|
| 1104 | m_spriteSheet->notifyGeneralUpdate(); | 
|---|
| 1105 |  | 
|---|
| 1106 | DocDestroyer destroyer(context, newDocument, 100); | 
|---|
| 1107 | destroyer.destroyDocument(); | 
|---|
| 1108 | } | 
|---|
| 1109 |  | 
|---|
| 1110 | waitGenTaskAndDelete(); | 
|---|
| 1111 | } | 
|---|
| 1112 |  | 
|---|
| 1113 | void cancelGenTask() { | 
|---|
| 1114 | if (m_genTask) { | 
|---|
| 1115 | m_genTask->cancel(); | 
|---|
| 1116 | waitGenTaskAndDelete(); | 
|---|
| 1117 | } | 
|---|
| 1118 | } | 
|---|
| 1119 |  | 
|---|
| 1120 | void waitGenTaskAndDelete() { | 
|---|
| 1121 | if (m_genTask) { | 
|---|
| 1122 | if (!m_genTask->completed()) { | 
|---|
| 1123 | while (!m_genTask->completed()) | 
|---|
| 1124 | base::this_thread::sleep_for(0.01); | 
|---|
| 1125 | } | 
|---|
| 1126 | m_genTask.reset(); | 
|---|
| 1127 | } | 
|---|
| 1128 | } | 
|---|
| 1129 |  | 
|---|
| 1130 | DocExporter& m_exporter; | 
|---|
| 1131 | doc::ImageBufferPtr m_frontBuffer; // ImageBuffer in the preview ImageBuffer | 
|---|
| 1132 | doc::ImageBufferPtr m_backBuffer; // ImageBuffer in the generator | 
|---|
| 1133 | Site& m_site; | 
|---|
| 1134 | Sprite* m_sprite; | 
|---|
| 1135 | std::string m_filename; | 
|---|
| 1136 | std::string m_dataFilename; | 
|---|
| 1137 | bool m_filenameAskOverwrite; | 
|---|
| 1138 | bool m_dataFilenameAskOverwrite; | 
|---|
| 1139 | std::unique_ptr<Doc> m_spriteSheet; | 
|---|
| 1140 | Editor* m_editor; | 
|---|
| 1141 | std::unique_ptr<Task> m_genTask; | 
|---|
| 1142 | ui::Timer m_genTimer; | 
|---|
| 1143 | int m_executionID; | 
|---|
| 1144 | std::string m_filenameFormat; | 
|---|
| 1145 | std::string m_filenameFormatDefault; | 
|---|
| 1146 | }; | 
|---|
| 1147 |  | 
|---|
| 1148 | class ExportSpriteSheetJob : public Job { | 
|---|
| 1149 | public: | 
|---|
| 1150 | ExportSpriteSheetJob( | 
|---|
| 1151 | DocExporter& exporter, | 
|---|
| 1152 | const Site& site, | 
|---|
| 1153 | const ExportSpriteSheetParams& params) | 
|---|
| 1154 | : Job(Strings::export_sprite_sheet_generating().c_str()) | 
|---|
| 1155 | , m_exporter(exporter) | 
|---|
| 1156 | , m_site(site) | 
|---|
| 1157 | , m_params(params) { } | 
|---|
| 1158 |  | 
|---|
| 1159 | std::unique_ptr<Doc> releaseDoc() { return std::move(m_doc); } | 
|---|
| 1160 |  | 
|---|
| 1161 | private: | 
|---|
| 1162 | void onJob() override { | 
|---|
| 1163 | // Create a non-UI context to avoid showing UI dialogs for | 
|---|
| 1164 | // GifOptions or JpegOptions from the background thread. | 
|---|
| 1165 | Context tmpCtx; | 
|---|
| 1166 |  | 
|---|
| 1167 | m_doc = generate_sprite_sheet( | 
|---|
| 1168 | m_exporter, &tmpCtx, m_site, m_params, true, m_token); | 
|---|
| 1169 |  | 
|---|
| 1170 | if (m_doc) | 
|---|
| 1171 | tmpCtx.documents().remove(m_doc.get()); | 
|---|
| 1172 | } | 
|---|
| 1173 |  | 
|---|
| 1174 | void onMonitoringTick() override { | 
|---|
| 1175 | Job::onMonitoringTick(); | 
|---|
| 1176 | if (isCanceled()) | 
|---|
| 1177 | m_token.cancel(); | 
|---|
| 1178 | else { | 
|---|
| 1179 | jobProgress(m_token.progress()); | 
|---|
| 1180 | } | 
|---|
| 1181 | } | 
|---|
| 1182 |  | 
|---|
| 1183 | DocExporter& m_exporter; | 
|---|
| 1184 | base::task_token m_token; | 
|---|
| 1185 | const Site& m_site; | 
|---|
| 1186 | const ExportSpriteSheetParams& m_params; | 
|---|
| 1187 | std::unique_ptr<Doc> m_doc; | 
|---|
| 1188 | }; | 
|---|
| 1189 |  | 
|---|
| 1190 | #endif // ENABLE_UI | 
|---|
| 1191 |  | 
|---|
| 1192 | } // anonymous namespace | 
|---|
| 1193 |  | 
|---|
| 1194 | ExportSpriteSheetCommand::ExportSpriteSheetCommand(const char* id) | 
|---|
| 1195 | : CommandWithNewParams(id, CmdRecordableFlag) | 
|---|
| 1196 | { | 
|---|
| 1197 | } | 
|---|
| 1198 |  | 
|---|
| 1199 | bool ExportSpriteSheetCommand::onEnabled(Context* context) | 
|---|
| 1200 | { | 
|---|
| 1201 | return context->checkFlags(ContextFlags::ActiveDocumentIsWritable); | 
|---|
| 1202 | } | 
|---|
| 1203 |  | 
|---|
| 1204 | void ExportSpriteSheetCommand::onExecute(Context* context) | 
|---|
| 1205 | { | 
|---|
| 1206 | Site site = context->activeSite(); | 
|---|
| 1207 | auto& params = this->params(); | 
|---|
| 1208 | DocExporter exporter; | 
|---|
| 1209 |  | 
|---|
| 1210 | #ifdef ENABLE_UI | 
|---|
| 1211 | // TODO if we use this line when !ENABLE_UI, | 
|---|
| 1212 | // Preferences::~Preferences() crashes on Linux when it wants to | 
|---|
| 1213 | // save the document preferences. It looks like | 
|---|
| 1214 | // Preferences::onRemoveDocument() is not called for some documents | 
|---|
| 1215 | // and when the Preferences::m_docs collection is iterated to save | 
|---|
| 1216 | // all DocumentPreferences, it accesses an invalid Doc* pointer (an | 
|---|
| 1217 | // already removed/deleted document). | 
|---|
| 1218 | Doc* document = site.document(); | 
|---|
| 1219 | DocumentPreferences& docPref(Preferences::instance().document(document)); | 
|---|
| 1220 |  | 
|---|
| 1221 | // Show UI if the user specified it explicitly (params.ui=true) or | 
|---|
| 1222 | // the sprite sheet type wasn't specified. | 
|---|
| 1223 | const bool showUI = (context->isUIAvailable() && params.ui() && | 
|---|
| 1224 | (params.ui.isSet() || !params.type.isSet())); | 
|---|
| 1225 |  | 
|---|
| 1226 | // Copy document preferences to undefined params | 
|---|
| 1227 | { | 
|---|
| 1228 | auto& defPref = (docPref.spriteSheet.defined() ? docPref: Preferences::instance().document(nullptr)); | 
|---|
| 1229 | if (!params.type.isSet()) { | 
|---|
| 1230 | params.type(defPref.spriteSheet.type()); | 
|---|
| 1231 | if (!params.columns.isSet())          params.columns(         defPref.spriteSheet.columns()); | 
|---|
| 1232 | if (!params.rows.isSet())             params.rows(            defPref.spriteSheet.rows()); | 
|---|
| 1233 | if (!params.width.isSet())            params.width(           defPref.spriteSheet.width()); | 
|---|
| 1234 | if (!params.height.isSet())           params.height(          defPref.spriteSheet.height()); | 
|---|
| 1235 | if (!params.textureFilename.isSet())  params.textureFilename( defPref.spriteSheet.textureFilename()); | 
|---|
| 1236 | if (!params.dataFilename.isSet())     params.dataFilename(    defPref.spriteSheet.dataFilename()); | 
|---|
| 1237 | if (!params.dataFormat.isSet())       params.dataFormat(      defPref.spriteSheet.dataFormat()); | 
|---|
| 1238 | if (!params.filenameFormat.isSet())   params.filenameFormat(  defPref.spriteSheet.filenameFormat()); | 
|---|
| 1239 | if (!params.borderPadding.isSet())    params.borderPadding(   defPref.spriteSheet.borderPadding()); | 
|---|
| 1240 | if (!params.shapePadding.isSet())     params.shapePadding(    defPref.spriteSheet.shapePadding()); | 
|---|
| 1241 | if (!params.innerPadding.isSet())     params.innerPadding(    defPref.spriteSheet.innerPadding()); | 
|---|
| 1242 | if (!params.trimSprite.isSet())       params.trimSprite(      defPref.spriteSheet.trimSprite()); | 
|---|
| 1243 | if (!params.trim.isSet())             params.trim(            defPref.spriteSheet.trim()); | 
|---|
| 1244 | if (!params.trimByGrid.isSet())       params.trimByGrid(      defPref.spriteSheet.trimByGrid()); | 
|---|
| 1245 | if (!params.extrude.isSet())          params.extrude(         defPref.spriteSheet.extrude()); | 
|---|
| 1246 | if (!params.mergeDuplicates.isSet())  params.mergeDuplicates( defPref.spriteSheet.mergeDuplicates()); | 
|---|
| 1247 | if (!params.ignoreEmpty.isSet())      params.ignoreEmpty(     defPref.spriteSheet.ignoreEmpty()); | 
|---|
| 1248 | if (!params.openGenerated.isSet())    params.openGenerated(   defPref.spriteSheet.openGenerated()); | 
|---|
| 1249 | if (!params.layer.isSet())            params.layer(           defPref.spriteSheet.layer()); | 
|---|
| 1250 | if (!params.layerIndex.isSet())       params.layerIndex(      defPref.spriteSheet.layerIndex()); | 
|---|
| 1251 | if (!params.tag.isSet())              params.tag(             defPref.spriteSheet.frameTag()); | 
|---|
| 1252 | if (!params.splitLayers.isSet())      params.splitLayers(     defPref.spriteSheet.splitLayers()); | 
|---|
| 1253 | if (!params.splitTags.isSet())        params.splitTags(       defPref.spriteSheet.splitTags()); | 
|---|
| 1254 | if (!params.splitGrid.isSet())        params.splitGrid(       defPref.spriteSheet.splitGrid()); | 
|---|
| 1255 | if (!params.listLayers.isSet())       params.listLayers(      defPref.spriteSheet.listLayers()); | 
|---|
| 1256 | if (!params.listTags.isSet())         params.listTags(        defPref.spriteSheet.listFrameTags()); | 
|---|
| 1257 | if (!params.listSlices.isSet())       params.listSlices(      defPref.spriteSheet.listSlices()); | 
|---|
| 1258 | } | 
|---|
| 1259 | } | 
|---|
| 1260 |  | 
|---|
| 1261 | bool askOverwrite = params.askOverwrite(); | 
|---|
| 1262 | if (showUI) { | 
|---|
| 1263 | auto& pref = Preferences::instance(); | 
|---|
| 1264 |  | 
|---|
| 1265 | ExportSpriteSheetWindow window(exporter, site, params, pref); | 
|---|
| 1266 | window.openWindowInForeground(); | 
|---|
| 1267 |  | 
|---|
| 1268 | // Save global sprite sheet generation settings anyway (even if | 
|---|
| 1269 | // the user cancel the dialog, the global settings are stored). | 
|---|
| 1270 | pref.spriteSheet.preview(window.preview()->isSelected()); | 
|---|
| 1271 | pref.spriteSheet.sections(window.selectedSectionsString()); | 
|---|
| 1272 |  | 
|---|
| 1273 | if (!window.ok()) | 
|---|
| 1274 | return; | 
|---|
| 1275 |  | 
|---|
| 1276 | window.updateParams(params); | 
|---|
| 1277 | docPref.spriteSheet.defined(true); | 
|---|
| 1278 | docPref.spriteSheet.type            (params.type()); | 
|---|
| 1279 | docPref.spriteSheet.columns         (params.columns()); | 
|---|
| 1280 | docPref.spriteSheet.rows            (params.rows()); | 
|---|
| 1281 | docPref.spriteSheet.width           (params.width()); | 
|---|
| 1282 | docPref.spriteSheet.height          (params.height()); | 
|---|
| 1283 | docPref.spriteSheet.textureFilename (params.textureFilename()); | 
|---|
| 1284 | docPref.spriteSheet.dataFilename    (params.dataFilename()); | 
|---|
| 1285 | docPref.spriteSheet.dataFormat      (params.dataFormat()); | 
|---|
| 1286 | docPref.spriteSheet.filenameFormat  (params.filenameFormat()); | 
|---|
| 1287 | docPref.spriteSheet.borderPadding   (params.borderPadding()); | 
|---|
| 1288 | docPref.spriteSheet.shapePadding    (params.shapePadding()); | 
|---|
| 1289 | docPref.spriteSheet.innerPadding    (params.innerPadding()); | 
|---|
| 1290 | docPref.spriteSheet.trimSprite      (params.trimSprite()); | 
|---|
| 1291 | docPref.spriteSheet.trim            (params.trim()); | 
|---|
| 1292 | docPref.spriteSheet.trimByGrid      (params.trimByGrid()); | 
|---|
| 1293 | docPref.spriteSheet.extrude         (params.extrude()); | 
|---|
| 1294 | docPref.spriteSheet.mergeDuplicates (params.mergeDuplicates()); | 
|---|
| 1295 | docPref.spriteSheet.ignoreEmpty     (params.ignoreEmpty()); | 
|---|
| 1296 | docPref.spriteSheet.openGenerated   (params.openGenerated()); | 
|---|
| 1297 | docPref.spriteSheet.layer           (params.layer()); | 
|---|
| 1298 | docPref.spriteSheet.layerIndex      (params.layerIndex()); | 
|---|
| 1299 | docPref.spriteSheet.frameTag        (params.tag()); | 
|---|
| 1300 | docPref.spriteSheet.splitLayers     (params.splitLayers()); | 
|---|
| 1301 | docPref.spriteSheet.splitTags       (params.splitTags()); | 
|---|
| 1302 | docPref.spriteSheet.splitGrid       (params.splitGrid()); | 
|---|
| 1303 | docPref.spriteSheet.listLayers      (params.listLayers()); | 
|---|
| 1304 | docPref.spriteSheet.listFrameTags   (params.listTags()); | 
|---|
| 1305 | docPref.spriteSheet.listSlices      (params.listSlices()); | 
|---|
| 1306 |  | 
|---|
| 1307 | // Default preferences for future sprites | 
|---|
| 1308 | DocumentPreferences& defPref(Preferences::instance().document(nullptr)); | 
|---|
| 1309 | defPref.spriteSheet = docPref.spriteSheet; | 
|---|
| 1310 | defPref.spriteSheet.defined(false); | 
|---|
| 1311 | if (!defPref.spriteSheet.textureFilename().empty()) | 
|---|
| 1312 | defPref.spriteSheet.textureFilename.setValueAndDefault(kSpecifiedFilename); | 
|---|
| 1313 | if (!defPref.spriteSheet.dataFilename().empty()) | 
|---|
| 1314 | defPref.spriteSheet.dataFilename.setValueAndDefault(kSpecifiedFilename); | 
|---|
| 1315 | defPref.save(); | 
|---|
| 1316 |  | 
|---|
| 1317 | askOverwrite = false; // Already asked in the ExportSpriteSheetWindow | 
|---|
| 1318 | } | 
|---|
| 1319 |  | 
|---|
| 1320 | if (context->isUIAvailable() && askOverwrite) { | 
|---|
| 1321 | if (!ask_overwrite(true, params.textureFilename(), | 
|---|
| 1322 | true, params.dataFilename())) | 
|---|
| 1323 | return;                   // Do not overwrite | 
|---|
| 1324 | } | 
|---|
| 1325 | #endif | 
|---|
| 1326 |  | 
|---|
| 1327 | exporter.setDocImageBuffer(std::make_shared<doc::ImageBuffer>()); | 
|---|
| 1328 | std::unique_ptr<Doc> newDocument; | 
|---|
| 1329 | #ifdef ENABLE_UI | 
|---|
| 1330 | if (context->isUIAvailable()) { | 
|---|
| 1331 | ExportSpriteSheetJob job(exporter, site, params); | 
|---|
| 1332 | job.startJob(); | 
|---|
| 1333 | job.waitJob(); | 
|---|
| 1334 |  | 
|---|
| 1335 | newDocument = job.releaseDoc(); | 
|---|
| 1336 | if (!newDocument) | 
|---|
| 1337 | return; | 
|---|
| 1338 |  | 
|---|
| 1339 | StatusBar* statusbar = StatusBar::instance(); | 
|---|
| 1340 | if (statusbar) | 
|---|
| 1341 | statusbar->showTip(1000, Strings::export_sprite_sheet_generated()); | 
|---|
| 1342 |  | 
|---|
| 1343 | // Save the exported sprite sheet as a recent file | 
|---|
| 1344 | if (newDocument->isAssociatedToFile()) | 
|---|
| 1345 | App::instance()->recentFiles()->addRecentFile(newDocument->filename()); | 
|---|
| 1346 |  | 
|---|
| 1347 | // Copy background and grid preferences | 
|---|
| 1348 | DocumentPreferences& newDocPref( | 
|---|
| 1349 | Preferences::instance().document(newDocument.get())); | 
|---|
| 1350 | newDocPref.bg = docPref.bg; | 
|---|
| 1351 | newDocPref.grid = docPref.grid; | 
|---|
| 1352 | newDocPref.pixelGrid = docPref.pixelGrid; | 
|---|
| 1353 | Preferences::instance().removeDocument(newDocument.get()); | 
|---|
| 1354 | } | 
|---|
| 1355 | else | 
|---|
| 1356 | #endif | 
|---|
| 1357 | { | 
|---|
| 1358 | base::task_token token; | 
|---|
| 1359 | newDocument = generate_sprite_sheet( | 
|---|
| 1360 | exporter, context, site, params, true, token); | 
|---|
| 1361 | if (!newDocument) | 
|---|
| 1362 | return; | 
|---|
| 1363 | } | 
|---|
| 1364 |  | 
|---|
| 1365 | ASSERT(newDocument); | 
|---|
| 1366 |  | 
|---|
| 1367 | if (params.openGenerated()) { | 
|---|
| 1368 | newDocument->setContext(context); | 
|---|
| 1369 | newDocument.release(); | 
|---|
| 1370 | } | 
|---|
| 1371 | else { | 
|---|
| 1372 | DocDestroyer destroyer(context, newDocument.release(), 100); | 
|---|
| 1373 | destroyer.destroyDocument(); | 
|---|
| 1374 | } | 
|---|
| 1375 | } | 
|---|
| 1376 |  | 
|---|
| 1377 | Command* CommandFactory::createExportSpriteSheetCommand() | 
|---|
| 1378 | { | 
|---|
| 1379 | return new ExportSpriteSheetCommand; | 
|---|
| 1380 | } | 
|---|
| 1381 |  | 
|---|
| 1382 | } // namespace app | 
|---|
| 1383 |  | 
|---|