From 30f55d196b08f4d8553b55fa702bfe824be1aa39 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 12 Mar 2024 15:13:35 -0300 Subject: [PATCH 01/11] Show "Apply pixel ratio" option in File > Export only when needed (#4308) If the sprite doesn't have a custom pixel aspect ratio configured there is no need to show this option. --- src/app/ui/export_file_window.cpp | 14 ++++++++++++-- src/doc/sprite.cpp | 7 ++++++- src/doc/sprite.h | 3 ++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/app/ui/export_file_window.cpp b/src/app/ui/export_file_window.cpp index 2fba8baae..07801b1c8 100644 --- a/src/app/ui/export_file_window.cpp +++ b/src/app/ui/export_file_window.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2022 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2018 David Capello // // This program is distributed under the terms of @@ -56,7 +56,17 @@ ExportFileWindow::ExportFileWindow(const Doc* doc) fill_layers_combobox(m_doc->sprite(), layers(), m_docPref.saveCopy.layer(), m_docPref.saveCopy.layerIndex()); fill_frames_combobox(m_doc->sprite(), frames(), m_docPref.saveCopy.frameTag()); fill_anidir_combobox(anidir(), m_docPref.saveCopy.aniDir()); - pixelRatio()->setSelected(m_docPref.saveCopy.applyPixelRatio()); + + if (doc->sprite()->hasPixelRatio()) { + pixelRatio()->setSelected(m_docPref.saveCopy.applyPixelRatio()); + } + else { + // Hide "Apply pixel ratio" checkbox when there is no pixel aspect + // ratio to apply. + pixelRatio()->setSelected(false); + pixelRatio()->setVisible(false); + } + forTwitter()->setSelected(m_docPref.saveCopy.forTwitter()); adjustResize()->setVisible(false); playSubtags()->setSelected(m_docPref.saveCopy.playSubtags()); diff --git a/src/doc/sprite.cpp b/src/doc/sprite.cpp index b26cd0e77..c9cfe2856 100644 --- a/src/doc/sprite.cpp +++ b/src/doc/sprite.cpp @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (C) 2018-2023 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This file is released under the terms of the MIT license. @@ -198,6 +198,11 @@ Sprite* Sprite::MakeStdTilemapSpriteWithTileset(const ImageSpec& spec, ////////////////////////////////////////////////////////////////////// // Main properties +bool Sprite::hasPixelRatio() const +{ + return m_pixelRatio != PixelRatio(1, 1); +} + void Sprite::setPixelFormat(PixelFormat format) { m_spec.setColorMode((ColorMode)format); diff --git a/src/doc/sprite.h b/src/doc/sprite.h index 2d5ffffae..b2831ea58 100644 --- a/src/doc/sprite.h +++ b/src/doc/sprite.h @@ -1,5 +1,5 @@ // Aseprite Document Library -// Copyright (C) 2018-2023 Igara Studio S.A. +// Copyright (C) 2018-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This file is released under the terms of the MIT license. @@ -93,6 +93,7 @@ namespace doc { PixelFormat pixelFormat() const { return (PixelFormat)m_spec.colorMode(); } ColorMode colorMode() const { return m_spec.colorMode(); } const PixelRatio& pixelRatio() const { return m_pixelRatio; } + bool hasPixelRatio() const; gfx::Size size() const { return m_spec.size(); } gfx::Rect bounds() const { return m_spec.bounds(); } int width() const { return m_spec.width(); } From e129fefb2e50e3abe8b9f4b0be177476966ce2be Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 12 Mar 2024 15:54:05 -0300 Subject: [PATCH 02/11] Hide "Play subtags" option in File > Export if there are no tags (fix #4308) --- src/app/ui/export_file_window.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/ui/export_file_window.cpp b/src/app/ui/export_file_window.cpp index 07801b1c8..b342d71e5 100644 --- a/src/app/ui/export_file_window.cpp +++ b/src/app/ui/export_file_window.cpp @@ -228,7 +228,9 @@ void ExportFileWindow::updateAniDir() void ExportFileWindow::updatePlaySubtags() { std::string framesValue = this->framesValue(); - playSubtags()->setVisible(framesValue != kSelectedFrames); + playSubtags()->setVisible(framesValue != kSelectedFrames && + // We hide the option if there is no tag + !m_doc->sprite()->tags().empty()); layout(); } From 8709219d68ec1b97cb8b950909de71bd18b7de40 Mon Sep 17 00:00:00 2001 From: David Capello Date: Fri, 15 Mar 2024 11:25:59 -0300 Subject: [PATCH 03/11] Update laf (fix #4368) --- laf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laf b/laf index 6d19cc2e8..c0c88e397 160000 --- a/laf +++ b/laf @@ -1 +1 @@ -Subproject commit 6d19cc2e890576b07399e3d157da83d04acbc99b +Subproject commit c0c88e397a6c7dcdc78f0f67ee8bdb8e0d0760c5 From 571a3965e960a58e7a6a906b0f13fded3eef4e50 Mon Sep 17 00:00:00 2001 From: Charles Bradley Date: Thu, 14 Mar 2024 15:23:50 -0600 Subject: [PATCH 04/11] Fix copy+move cursor icon bug (fix #3887) Add logic to show the correct curson icon when holding the copy key (alt) while hovering over a selected layer. --- src/app/ui/timeline/timeline.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/ui/timeline/timeline.cpp b/src/app/ui/timeline/timeline.cpp index b81f8f6da..ea1ce1842 100644 --- a/src/app/ui/timeline/timeline.cpp +++ b/src/app/ui/timeline/timeline.cpp @@ -2093,7 +2093,10 @@ void Timeline::setCursor(ui::Message* msg, const Hit& hit) ui::set_mouse_cursor(kSizeECursor); } else if (hit.part == PART_RANGE_OUTLINE) { - ui::set_mouse_cursor(kMoveCursor); + if (is_copy_key_pressed(msg)) + ui::set_mouse_cursor(kArrowPlusCursor); + else + ui::set_mouse_cursor(kMoveCursor); } else if (hit.part == PART_SEPARATOR) { ui::set_mouse_cursor(kSizeWECursor); From 427ee6f5b5d83daa61d0ab2c46969cea549b2413 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 19 Mar 2024 16:03:46 -0300 Subject: [PATCH 05/11] Use target_sources() instead of variables to list app-lib sources target_sources() was added on cmake 3.1, long time ago, although we started with the 2.6 versions, so now we can modernize the cmake file a little. --- src/app/CMakeLists.txt | 64 ++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 4fdaceeb8..e6d7b71f6 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -101,23 +101,20 @@ add_definitions(-DLIBARCHIVE_STATIC) ###################################################################### # app-lib target +add_library(app-lib ${generated_files}) + # These specific-platform files should be in an external library # (e.g. "base" or "os"). -set(app_platform_files) if(WIN32) - set(app_platform_files - font_path_win.cpp) + target_sources(app-lib PRIVATE font_path_win.cpp) elseif(APPLE) - set(app_platform_files - font_path_osx.mm) + target_sources(app-lib PRIVATE font_path_osx.mm) else() - set(app_platform_files - font_path_unix.cpp) + target_sources(app-lib PRIVATE font_path_unix.cpp) endif() -set(data_recovery_files) if(ENABLE_DATA_RECOVERY) - set(data_recovery_files + target_sources(app-lib PRIVATE crash/backup_observer.cpp crash/data_recovery.cpp crash/read_document.cpp @@ -126,7 +123,7 @@ if(ENABLE_DATA_RECOVERY) ui/data_recovery_view.cpp) endif() -set(file_formats +target_sources(app-lib PRIVATE file/ase_format.cpp file/bmp_format.cpp file/css_format.cpp @@ -140,27 +137,27 @@ set(file_formats file/svg_format.cpp file/tga_format.cpp) if(ENABLE_WEBP) - list(APPEND file_formats file/webp_format.cpp) + target_sources(app-lib PRIVATE + file/webp_format.cpp) endif() if(ENABLE_PSD) - list(APPEND file_formats file/psd_format.cpp) + target_sources(app-lib PRIVATE + file/psd_format.cpp) endif() -set(scripting_files) if(ENABLE_SCRIPTING) - set(scripting_files_ui) if(ENABLE_UI) - set(scripting_files_ui + target_sources(app-lib PRIVATE commands/cmd_developer_console.cpp commands/cmd_open_script_folder.cpp commands/debugger.cpp ui/devconsole_view.cpp) endif() if(ENABLE_WEBSOCKET) - set(scripting_files_ws + target_sources(app-lib PRIVATE script/websocket_class.cpp) endif() - set(scripting_files + target_sources(app-lib PRIVATE commands/cmd_run_script.cpp script/app_command_object.cpp script/app_fs_object.cpp @@ -219,14 +216,11 @@ if(ENABLE_SCRIPTING) script/values.cpp script/version_class.cpp script/window_class.cpp - shell.cpp - ${scripting_files_ws} - ${scripting_files_ui}) + shell.cpp) endif() -set(ui_app_files) if(ENABLE_UI) - set(ui_app_files + target_sources(app-lib PRIVATE app_brushes.cpp app_menus.cpp closed_docs.cpp @@ -436,34 +430,30 @@ if(ENABLE_UI) ui_context.cpp widget_loader.cpp) if(ENABLE_NEWS) - set(ui_app_files + target_sources(app-lib PRIVATE res/http_loader.cpp - ui/news_listbox.cpp - ${ui_app_files}) + ui/news_listbox.cpp) endif() if(ENABLE_DRM) - set(ui_app_files + target_sources(app-lib PRIVATE ui/enter_license.cpp - ui/aseprite_update.cpp - ${ui_app_files}) + ui/aseprite_update.cpp) endif() endif() -set(send_crash_files) if(ENABLE_SENTRY) - set(send_crash_files sentry_wrapper.cpp) + target_sources(app-lib PRIVATE sentry_wrapper.cpp) else() - set(send_crash_files send_crash.cpp) + target_sources(app-lib PRIVATE send_crash.cpp) endif() -add_library(app-lib +target_sources(app-lib PRIVATE active_site_handler.cpp app.cpp check_update.cpp cli/app_options.cpp cli/cli_open_file.cpp cli/cli_processor.cpp - ${file_formats} cli/default_cli_delegate.cpp cli/preview_cli_delegate.cpp cmd.cpp @@ -704,13 +694,7 @@ add_library(app-lib util/tileset_utils.cpp util/wrap_point.cpp xml_document.cpp - xml_exception.cpp - ${send_crash_files} - ${ui_app_files} - ${app_platform_files} - ${data_recovery_files} - ${scripting_files} - ${generated_files}) + xml_exception.cpp) if(TARGET generated_version) add_dependencies(app-lib generated_version) From ec4e82bdc01e8bcfb56fcf64cbd0ec1ad7e6394a Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 25 Mar 2024 15:37:11 -0300 Subject: [PATCH 06/11] Fix crashes with SpriteJob(s) that weren't locking the doc correctly (fix #4315) This was mainly found in SpriteSizeJob crash reports. In these reports deleted image buffers were still used to paint the Editor canvas because the doc was write-locked in the main thread (same thread where the canvas is painted). This produced a re-entrant lock in the Editor::onPaint() as we can still read-lock from the same thread where we write-locked the doc. With this change we write-lock the doc from the SpriteJob background thread (not the main thread) only if it's necessary (i.e. when the doc is not already locked in the main thread, e.g. when running a script). This makes that the main thread (Editor::onPaint) cannot read the doc until we finish the whole SpriteJob transaction/Tx. --- src/app/commands/cmd_change_pixel_format.cpp | 15 ++++--- src/app/commands/cmd_color_quantization.cpp | 47 ++++++++++---------- src/app/commands/cmd_rotate.cpp | 21 +++++---- src/app/commands/cmd_sprite_size.cpp | 30 +++++++------ src/app/job.cpp | 4 +- src/app/job.h | 5 ++- src/app/sprite_job.cpp | 40 +++++++++++++---- src/app/sprite_job.h | 36 ++++++++++++--- 8 files changed, 125 insertions(+), 73 deletions(-) diff --git a/src/app/commands/cmd_change_pixel_format.cpp b/src/app/commands/cmd_change_pixel_format.cpp index 73fb002a8..6f6f4fc1b 100644 --- a/src/app/commands/cmd_change_pixel_format.cpp +++ b/src/app/commands/cmd_change_pixel_format.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2023 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -646,12 +646,12 @@ void ChangePixelFormatCommand::onExecute(Context* context) #endif // ENABLE_UI // No conversion needed - if (context->activeDocument()->sprite()->pixelFormat() == m_format) + Doc* doc = context->activeDocument(); + if (doc->sprite()->pixelFormat() == m_format) return; { - const ContextReader reader(context); - SpriteJob job(reader, Strings::color_mode_title().c_str()); + SpriteJob job(context, doc, Strings::color_mode_title()); Sprite* sprite(job.sprite()); // TODO this was moved in the main UI thread because @@ -662,16 +662,17 @@ void ChangePixelFormatCommand::onExecute(Context* context) // https://github.com/aseprite/aseprite/issues/509 // https://github.com/aseprite/aseprite/issues/378 if (flatten) { + Tx tx(Tx::LockDoc, context, doc); const bool newBlend = Preferences::instance().experimental.newBlend(); SelectedLayers selLayers; for (auto layer : sprite->root()->layers()) selLayers.insert(layer); - job.tx()(new cmd::FlattenLayers(sprite, selLayers, newBlend)); + tx(new cmd::FlattenLayers(sprite, selLayers, newBlend)); } job.startJobWithCallback( - [this, &job, sprite] { - job.tx()( + [this, &job, sprite](Tx& tx) { + tx( new cmd::SetPixelFormat( sprite, m_format, m_dithering, diff --git a/src/app/commands/cmd_color_quantization.cpp b/src/app/commands/cmd_color_quantization.cpp index 1fcf01d5d..dcf339412 100644 --- a/src/app/commands/cmd_color_quantization.cpp +++ b/src/app/commands/cmd_color_quantization.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2023 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -177,45 +177,46 @@ void ColorQuantizationCommand::onExecute(Context* ctx) return; try { - ContextReader reader(ctx); + Doc* doc = site.document(); Sprite* sprite = site.sprite(); frame_t frame = site.frame(); const Palette* curPalette = site.sprite()->palette(frame); Palette tmpPalette(frame, entries.picks()); - SpriteJob job(reader, "Color Quantization"); + SpriteJob job(ctx, doc, "Color Quantization"); const bool newBlend = pref.experimental.newBlend(); job.startJobWithCallback( - [sprite, withAlpha, &tmpPalette, &job, newBlend, algorithm]{ + [sprite, withAlpha, curPalette, &tmpPalette, &job, &entries, + newBlend, algorithm, createPal, site, frame](Tx& tx) { render::create_palette_from_sprite( sprite, 0, sprite->lastFrame(), withAlpha, &tmpPalette, &job, // SpriteJob is a render::TaskDelegate newBlend, algorithm); + + std::unique_ptr newPalette( + new Palette(createPal ? tmpPalette: + *site.palette())); + + if (createPal) { + entries = PalettePicks(newPalette->size()); + entries.all(); + } + + int i = 0, j = 0; + for (bool state : entries) { + if (state) + newPalette->setEntry(i, tmpPalette.getEntry(j++)); + ++i; + } + + if (*curPalette != *newPalette) + tx(new cmd::SetPalette(sprite, frame, newPalette.get())); }); job.waitJob(); if (job.isCanceled()) return; - - std::unique_ptr newPalette( - new Palette(createPal ? tmpPalette: - *site.palette())); - - if (createPal) { - entries = PalettePicks(newPalette->size()); - entries.all(); - } - - int i = 0, j = 0; - for (bool state : entries) { - if (state) - newPalette->setEntry(i, tmpPalette.getEntry(j++)); - ++i; - } - - if (*curPalette != *newPalette) - job.tx()(new cmd::SetPalette(sprite, frame, newPalette.get())); } catch (const base::Exception& e) { Console::showException(e); diff --git a/src/app/commands/cmd_rotate.cpp b/src/app/commands/cmd_rotate.cpp index 3419a672f..49ef0826f 100644 --- a/src/app/commands/cmd_rotate.cpp +++ b/src/app/commands/cmd_rotate.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2022 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -13,7 +13,6 @@ #include "app/cmd/set_cel_bounds.h" #include "app/commands/cmd_rotate.h" #include "app/commands/params.h" -#include "app/context_access.h" #include "app/doc_api.h" #include "app/doc_range.h" #include "app/i18n/strings.h" @@ -45,10 +44,10 @@ class RotateJob : public SpriteJob { public: - RotateJob(const ContextReader& reader, + RotateJob(Context* ctx, Doc* doc, const std::string& jobName, int angle, const CelList& cels, bool rotateSprite) - : SpriteJob(reader, jobName.c_str()) + : SpriteJob(ctx, doc, jobName) , m_cels(cels) , m_rotateSprite(rotateSprite) { m_angle = angle; @@ -80,8 +79,8 @@ protected: } // [working thread] - void onJob() override { - DocApi api = document()->getApi(tx()); + void onSpriteJob(Tx& tx) override { + DocApi api = document()->getApi(tx); // 1) Rotate cel positions for (Cel* cel : m_cels) { @@ -93,7 +92,7 @@ protected: gfx::RectF bounds = cel->boundsF(); rotate_rect(bounds); if (cel->boundsF() != bounds) - tx()(new cmd::SetCelBoundsF(cel, bounds)); + tx(new cmd::SetCelBoundsF(cel, bounds)); } else { gfx::Rect bounds = cel->bounds(); @@ -192,6 +191,7 @@ void RotateCommand::onExecute(Context* context) { { Site site = context->activeSite(); + Doc* doc = site.document(); CelList cels; bool rotateSprite = false; @@ -203,7 +203,7 @@ void RotateCommand::onExecute(Context* context) // If we want to rotate the visible mask, we can go to // MovingPixelsState (even when the range is enabled, because // now PixelsMovement support ranges). - if (site.document()->isMaskVisible()) { + if (doc->isMaskVisible()) { // Select marquee tool if (tools::Tool* tool = App::instance()->toolBox() ->getToolById(tools::WellKnownTools::RectangularMarquee)) { @@ -237,13 +237,12 @@ void RotateCommand::onExecute(Context* context) rotateSprite = true; } - ContextReader reader(context); { - RotateJob job(reader, friendlyName(), m_angle, cels, rotateSprite); + RotateJob job(context, doc, friendlyName(), m_angle, cels, rotateSprite); job.startJob(); job.waitJob(); } - update_screen_for_document(reader.document()); + update_screen_for_document(doc); } } diff --git a/src/app/commands/cmd_sprite_size.cpp b/src/app/commands/cmd_sprite_size.cpp index 3327fc30f..98383def9 100644 --- a/src/app/commands/cmd_sprite_size.cpp +++ b/src/app/commands/cmd_sprite_size.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2023 Igara Studio S.A. +// Copyright (C) 2019-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -80,8 +80,11 @@ class SpriteSizeJob : public SpriteJob { public: - SpriteSizeJob(const ContextReader& reader, int new_width, int new_height, ResizeMethod resize_method) - : SpriteJob(reader, Strings::sprite_size_title().c_str()) { + SpriteSizeJob(Context* ctx, Doc* doc, + const int new_width, + const int new_height, + const ResizeMethod resize_method) + : SpriteJob(ctx, doc, Strings::sprite_size_title()) { m_new_width = new_width; m_new_height = new_height; m_resize_method = resize_method; @@ -90,8 +93,8 @@ public: protected: // [working thread] - void onJob() override { - DocApi api = writer().document()->getApi(tx()); + void onSpriteJob(Tx& tx) override { + DocApi api = document()->getApi(tx); Tilesets* tilesets = sprite()->tilesets(); int img_count = 0; @@ -147,7 +150,7 @@ protected: ++progress; ++idx; } - tx()(new cmd::ReplaceTileset(sprite(), tsi, newTileset)); + tx(new cmd::ReplaceTileset(sprite(), tsi, newTileset)); // Cancel all the operation? if (isCanceled()) @@ -170,11 +173,11 @@ protected: cel->y()*scale.h, canvasSize.w, canvasSize.h); - tx()(new cmd::SetCelBoundsF(cel, newBounds)); + tx(new cmd::SetCelBoundsF(cel, newBounds)); } else { resize_cel_image( - tx(), cel, scale, + tx, cel, scale, m_resize_method, cel->layer()->isReference() ? -cel->boundsF().origin(): @@ -239,7 +242,7 @@ protected: newKey.setPivot(gfx::Point(scale_x(newKey.pivot().x), scale_y(newKey.pivot().y))); - tx()(new cmd::SetSliceKey(slice, k.frame(), newKey)); + tx(new cmd::SetSliceKey(slice, k.frame(), newKey)); } } @@ -373,8 +376,9 @@ void SpriteSizeCommand::onExecute(Context* context) #ifdef ENABLE_UI const bool ui = (params().ui() && context->isUIAvailable()); #endif - const ContextReader reader(context); - const Sprite* sprite(reader.sprite()); + const Site site = context->activeSite(); + Doc* doc = site.document(); + Sprite* sprite = site.sprite(); auto& params = this->params(); double ratio = sprite->width() / double(sprite->height()); @@ -461,13 +465,13 @@ void SpriteSizeCommand::onExecute(Context* context) new_height = std::clamp(new_height, 1, DOC_SPRITE_MAX_HEIGHT); { - SpriteSizeJob job(reader, new_width, new_height, resize_method); + SpriteSizeJob job(context, doc, new_width, new_height, resize_method); job.startJob(); job.waitJob(); } #ifdef ENABLE_UI - update_screen_for_document(reader.document()); + update_screen_for_document(doc); #endif } diff --git a/src/app/job.cpp b/src/app/job.cpp index 601709c32..592bbd7ea 100644 --- a/src/app/job.cpp +++ b/src/app/job.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2021 Igara Studio S.A. +// Copyright (C) 2021-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -33,7 +33,7 @@ int Job::runningJobs() return g_runningJobs; } -Job::Job(const char* jobName) +Job::Job(const std::string& jobName) { m_last_progress = 0.0; m_done_flag = false; diff --git a/src/app/job.h b/src/app/job.h index 0adc9a169..6c21bdd22 100644 --- a/src/app/job.h +++ b/src/app/job.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2021 Igara Studio S.A. +// Copyright (C) 2021-2024 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -15,6 +15,7 @@ #include #include #include +#include #include namespace app { @@ -23,7 +24,7 @@ namespace app { public: static int runningJobs(); - Job(const char* jobName); + Job(const std::string& jobName); virtual ~Job(); // Starts the job calling onJob() event in another thread and diff --git a/src/app/sprite_job.cpp b/src/app/sprite_job.cpp index 8d8792ccd..dbbac0615 100644 --- a/src/app/sprite_job.cpp +++ b/src/app/sprite_job.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2024 Igara Studio S.A. // Copyright (C) 2017 David Capello // // This program is distributed under the terms of @@ -10,26 +11,49 @@ #include "app/sprite_job.h" +#include "base/log.h" + namespace app { -SpriteJob::SpriteJob(const ContextReader& reader, const char* jobName) +SpriteJob::SpriteJob(Context* ctx, Doc* doc, + const std::string& jobName) : Job(jobName) - , m_writer(reader, 500) - , m_document(m_writer.document()) - , m_sprite(m_writer.sprite()) - , m_tx(m_writer, jobName, ModifyDocument) + , m_doc(doc) + , m_sprite(doc->sprite()) + , m_tx(Tx::DontLockDoc, ctx, doc, jobName, ModifyDocument) + , m_lockAction(Tx::LockDoc) { + // Try to write-lock the document to see if we have to lock the + // document in the background thread. + auto lockResult = m_doc->writeLock(500); + if (lockResult != Doc::LockResult::Fail) { + if (lockResult == Doc::LockResult::Reentrant) + m_lockAction = Tx::DontLockDoc; + m_doc->unlock(lockResult); + } } SpriteJob::~SpriteJob() { - if (!isCanceled()) - m_tx.commit(); + try { + if (!isCanceled()) + m_tx.commit(); + } + catch (const std::exception& ex) { + LOG(ERROR, "Error committing changes: %s\n", ex.what()); + } +} + +void SpriteJob::onSpriteJob(Tx& tx) +{ + if (m_callback) + m_callback(tx); } void SpriteJob::onJob() { - m_callback(); + Tx subtx(m_lockAction, m_ctx, m_doc); + onSpriteJob(subtx); } bool SpriteJob::continueTask() diff --git a/src/app/sprite_job.h b/src/app/sprite_job.h index d6cbf61b4..d0fe180df 100644 --- a/src/app/sprite_job.h +++ b/src/app/sprite_job.h @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2024 Igara Studio S.A. // Copyright (C) 2017-2018 David Capello // // This program is distributed under the terms of @@ -15,19 +16,32 @@ #include "render/task_delegate.h" #include +#include +#include namespace app { +class Context; + +// Creates a Job to run a task in a background thread. At the same +// time it creates a new Tx in the main thread (to group all sub-Txs) +// without locking the sprite. You have to lock the sprite with a sub +// Tx (the onSpriteJob(Tx) already has the sprite locked for write +// access). +// +// This class takes care to lock the sprite in the background thread +// for write access, or to avoid re-locking the sprite in case it's +// already locked from the main thread (where SpriteJob was created, +// generally true when we're running a script). class SpriteJob : public Job, public render::TaskDelegate { public: - SpriteJob(const ContextReader& reader, const char* jobName); + SpriteJob(Context* ctx, Doc* doc, + const std::string& jobName); ~SpriteJob(); - ContextWriter& writer() { return m_writer; } - Doc* document() const { return m_document; } + Doc* document() const { return m_doc; } Sprite* sprite() const { return m_sprite; } - Tx& tx() { return m_tx; } template void startJobWithCallback(T&& callback) { @@ -36,6 +50,8 @@ public: } private: + virtual void onSpriteJob(Tx& tx); + // Job impl void onJob() override; @@ -44,15 +60,21 @@ private: bool continueTask() override; void notifyTaskProgress(double progress) override; - ContextWriter m_writer; - Doc* m_document; + Context* m_ctx; + Doc* m_doc; Sprite* m_sprite; Tx m_tx; + // What action to do with the sub-Tx inside the background thread. + // This is required to check if the sprite is already locked for + // write access in the main thread, in that case we don't need to + // lock it again from the background thread. + Tx::LockAction m_lockAction; + // Default implementation calls the given function in // startJob(). Anyway you can just extended the SpriteJob and // override onJob(). - std::function m_callback; + std::function m_callback; }; } // namespace app From e21859c4afe6a60c494248c880d481434dbf8b78 Mon Sep 17 00:00:00 2001 From: David Capello Date: Tue, 26 Mar 2024 13:31:22 -0300 Subject: [PATCH 07/11] Migrate clip module from aseprite to laf --- .gitmodules | 3 --- laf | 2 +- src/CMakeLists.txt | 6 ------ src/clip | 1 - 4 files changed, 1 insertion(+), 11 deletions(-) delete mode 160000 src/clip diff --git a/.gitmodules b/.gitmodules index dec6600f3..012ecb9d1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -24,9 +24,6 @@ [submodule "third_party/libpng"] path = third_party/libpng url = https://github.com/aseprite/libpng.git -[submodule "src/clip"] - path = src/clip - url = https://github.com/aseprite/clip.git [submodule "src/observable"] path = src/observable url = https://github.com/aseprite/observable.git diff --git a/laf b/laf index c0c88e397..7397d460f 160000 --- a/laf +++ b/laf @@ -1 +1 @@ -Subproject commit c0c88e397a6c7dcdc78f0f67ee8bdb8e0d0760c5 +Subproject commit 7397d460f67540f5feb3dbf72d95fc207cd9425d diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b44d245bf..0f676af1e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -95,12 +95,6 @@ set(OBSERVABLE_TESTS OFF CACHE BOOL "Compile observable tests") add_subdirectory(observable) include_directories(observable) -# Disable clip examples and tests -set(CLIP_EXAMPLES OFF CACHE BOOL "Compile clip examples") -set(CLIP_TESTS OFF CACHE BOOL "Compile clip tests") -set(CLIP_X11_PNG_LIBRARY "${PNG_LIBRARY}") -add_subdirectory(clip) - # Disable undo tests set(UNDO_TESTS OFF CACHE BOOL "Compile undo tests") add_subdirectory(undo) diff --git a/src/clip b/src/clip deleted file mode 160000 index 835cd0f7e..000000000 --- a/src/clip +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 835cd0f7e7a964bb969482117856bc56a0ac12bf From 86ae7024d2ba6763c64f94d7178e0b33cc477681 Mon Sep 17 00:00:00 2001 From: David Capello Date: Fri, 5 Apr 2024 22:48:02 -0300 Subject: [PATCH 08/11] Update laf --- laf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laf b/laf index 7397d460f..590a70e26 160000 --- a/laf +++ b/laf @@ -1 +1 @@ -Subproject commit 7397d460f67540f5feb3dbf72d95fc207cd9425d +Subproject commit 590a70e2652e63b111dd083ac2b1a5bb8c3985ea From fd2a98c0e68ecd92b233bfde4fcf433421009439 Mon Sep 17 00:00:00 2001 From: David Capello Date: Sun, 7 Apr 2024 11:39:16 -0300 Subject: [PATCH 09/11] [win] Fix title bar flickering selecting menus/dialogs --- laf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/laf b/laf index 590a70e26..0af1f8b4c 160000 --- a/laf +++ b/laf @@ -1 +1 @@ -Subproject commit 590a70e2652e63b111dd083ac2b1a5bb8c3985ea +Subproject commit 0af1f8b4c41d4ad32c36fd4c29943399a03366a5 From 4d5bf53be85d3f993b797a42e2aa205077b375a7 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 8 Apr 2024 10:44:44 -0300 Subject: [PATCH 10/11] Fix crash accessing empty list of sprites updating ContextBar fields (fix #4407) Not sure if this will be the final solution for this crash, as a Doc shouldn't have an empty list of sprites. --- src/app/ui/context_bar.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/ui/context_bar.cpp b/src/app/ui/context_bar.cpp index 500375919..d9f0a296a 100644 --- a/src/app/ui/context_bar.cpp +++ b/src/app/ui/context_bar.cpp @@ -1770,7 +1770,9 @@ private: } void updateLayout() { - const bool visible = (m_doc && !m_doc->sprite()->slices().empty()); + const bool visible = (m_doc && + m_doc->sprite() && + !m_doc->sprite()->slices().empty()); const bool relayout = (visible != m_combobox.isVisible() || visible != m_action.isVisible()); From 6a12c7014de0d2eb5803f369e9025d43fa71527e Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 8 Apr 2024 13:23:38 -0300 Subject: [PATCH 11/11] Select "English" if the current language is not found in Preferences If the user preferences file (aseprite.ini) contained a non-existent language, the first option of the languages combo box was selected, which might lead to a confusing situation where just opening the preferences dialog will change from English to other language (non-English, the first language in the combobox). --- src/app/commands/cmd_options.cpp | 13 ++++++++++++- src/app/i18n/strings.cpp | 3 ++- src/app/i18n/strings.h | 4 +++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp index 63f9d89fa..c9ad1fcc6 100644 --- a/src/app/commands/cmd_options.cpp +++ b/src/app/commands/cmd_options.cpp @@ -1317,9 +1317,20 @@ private: if (language()->getItemCount() > 0) return; - // Select current language by lang ID + // Check if the current language exists, in other case select English. Strings* strings = Strings::instance(); std::string curLang = strings->currentLanguage(); + bool found = false; + for (const LangInfo& lang : strings->availableLanguages()) { + if (lang.id == curLang) { + found = true; + break; + } + } + if (!found) + curLang = Strings::kDefLanguage; + + // Select current language by lang ID for (const LangInfo& lang : strings->availableLanguages()) { int i = language()->addItem(new LangItem(lang)); if (lang.id == curLang) diff --git a/src/app/i18n/strings.cpp b/src/app/i18n/strings.cpp index 23916637f..1f065c066 100644 --- a/src/app/i18n/strings.cpp +++ b/src/app/i18n/strings.cpp @@ -25,7 +25,8 @@ namespace app { static Strings* singleton = nullptr; -static const char* kDefLanguage = "en"; + +const char* Strings::kDefLanguage = "en"; // static void Strings::createInstance(Preferences& pref, diff --git a/src/app/i18n/strings.h b/src/app/i18n/strings.h index 4ff627192..e115f16d5 100644 --- a/src/app/i18n/strings.h +++ b/src/app/i18n/strings.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2023 Igara Studio S.A. +// Copyright (C) 2023-2024 Igara Studio S.A. // Copyright (C) 2016-2018 David Capello // // This program is distributed under the terms of @@ -25,6 +25,8 @@ namespace app { // Singleton class to load and access "strings/en.ini" file. class Strings : public app::gen::Strings { public: + static const char* kDefLanguage; + static void createInstance(Preferences& pref, Extensions& exts); static Strings* instance();