Merge branch 'main' into beta

This commit is contained in:
David Capello 2024-03-05 19:22:23 -03:00
commit 4cfb3cfa3f
69 changed files with 569 additions and 247 deletions

View File

@ -1,4 +1,65 @@
#
# Disabled or configured checks:
#
# bugprone-easily-swappable-parameters: We have a lot of functions
# with (int, int) or (string, string) so it does't make sense to enable
# this option.
#
# readability-braces-around-statements: We use a lot of:
# if (cond)
# stmt;
# else
# stmt;
# and there is no way to allow this with this check.
#
# readability-function-cognitive-complexity: We have this disabled
# temporarily, but it'd be nice to enable this with a high threshold
# in the future.
#
# readability-identifier-length: We use a lot of short names like x,
# y, w, h so we prefer to remove this.
#
# readability-magic-numbers: We use a lot of magic numbers like 8, 16,
# 24 for masks like 0xFF00, etc.
#
# readability-isolate-declaration: We use multiple declarations
# several times (e.g. int x, y, etc.)
#
# readability-uppercase-literal-suffix: We use a lot of 0.0f, but in a
# future we might enable this.
#
# readability-named-parameter: We prefer misc-unused-parameters to
# remove a parameter name that is not used.
#
# misc-use-anonymous-namespace: We use anonymous namespaces or static
# functions indifferently.
#
# misc-non-private-member-variables-in-classes: We use structs with
# all public members in some cases.
#
--- ---
Checks: 'clang-analyzer-*' Checks: >
-*,
bugprone-*,
clang-analyzer-*,
concurrency-*,
misc-*,
performance-*,
portability-*,
readability-*,
-bugprone-easily-swappable-parameters,
-misc-use-anonymous-namespace,
-readability-braces-around-statements,
-readability-function-cognitive-complexity,
-readability-identifier-length,
-readability-isolate-declaration,
-readability-magic-numbers,
-readability-named-parameter,
-readability-uppercase-literal-suffix
WarningsAsErrors: '' WarningsAsErrors: ''
CheckOptions:
- key: readability-implicit-bool-conversion.AllowPointerConditions
value: true
- key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic
value: true
... ...

32
.github/workflows/clang_tidy.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: Clang Tidy Diff
on:
pull_request:
paths:
- '**.cpp'
- '**.h'
- '.github/workflows/clang_tidy.yml'
permissions:
contents: read
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: 'recursive'
- uses: ZedThree/clang-tidy-review@v0.14.0
id: review
with:
build_dir: build
config_file: .clang-tidy
split_workflow: true
apt_packages: |
libc++-dev, libc++abi-dev, libpixman-1-dev,
libfreetype6-dev, libharfbuzz-dev, zlib1g-dev, libx11-dev,
libxcursor-dev, libxi-dev, libgl1-mesa-dev
cmake_command: |
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DLAF_BACKEND=none -DCMAKE_EXPORT_COMPILE_COMMANDS=on
- uses: ZedThree/clang-tidy-review/upload@v0.14.0
id: upload-review

18
.github/workflows/clang_tidy_post.yml vendored Normal file
View File

@ -0,0 +1,18 @@
name: Post Clang Tidy Comments
on:
workflow_run:
workflows: ["Clang Tidy Diff"]
types:
- completed
permissions:
checks: write
pull-requests: write
jobs:
post-comments:
runs-on: ubuntu-latest
steps:
- uses: ZedThree/clang-tidy-review/post@v0.14.0
with:
token: ${{ secrets.CLANG_TIDY_TOKEN }}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 57 KiB

BIN
data/icons/ase20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

BIN
data/icons/ase28.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

View File

@ -126,9 +126,12 @@ at least.
### Old palette chunk (0x0004) ### Old palette chunk (0x0004)
Ignore this chunk if you find the new palette chunk (0x2019) Aseprite Ignore this chunk if you find the new palette chunk (0x2019). Aseprite
v1.1 saves both chunks 0x0004 and 0x2019 just for backward v1.1 saves both chunks (0x0004 and 0x2019) just for backward
compatibility. compatibility. Aseprite v1.3.5 writes this chunk if the palette
doesn't have alpha channel and contains 256 colors or less (because
this chunk is smaller), in other case the new palette chunk (0x2019)
will be used (and the old one is not saved anymore).
WORD Number of packets WORD Number of packets
+ For each packet + For each packet

View File

@ -776,7 +776,10 @@ if(ENABLE_UPDATER)
endif() endif()
if(ENABLE_STEAM) if(ENABLE_STEAM)
add_definitions(-DENABLE_STEAM) # We need the ENABLE_STEAM flag in main module too so AppOptions are
# equal in both modules, app-lib and main (that's why this flag is
# marked as PUBLIC).
target_compile_definitions(app-lib PUBLIC -DENABLE_STEAM)
target_link_libraries(app-lib steam-lib) target_link_libraries(app-lib steam-lib)
endif() endif()

View File

@ -328,6 +328,11 @@ int App::initialize(const AppOptions& options)
app_configure_drm(); app_configure_drm();
#endif #endif
#ifdef ENABLE_STEAM
if (options.noInApp())
m_inAppSteam = false;
#endif
// Load modules // Load modules
m_modules = std::make_unique<Modules>(createLogInDesktop, preferences()); m_modules = std::make_unique<Modules>(createLogInDesktop, preferences());
m_legacy = std::make_unique<LegacyModules>(isGui() ? REQUIRE_INTERFACE: 0); m_legacy = std::make_unique<LegacyModules>(isGui() ? REQUIRE_INTERFACE: 0);
@ -511,9 +516,18 @@ void App::run()
// Initialize Steam API // Initialize Steam API
#ifdef ENABLE_STEAM #ifdef ENABLE_STEAM
steam::SteamAPI steam; std::unique_ptr<steam::SteamAPI> steam;
if (steam.initialized()) if (m_inAppSteam) {
os::System::instance()->activateApp(); steam = std::make_unique<steam::SteamAPI>();
if (steam->isInitialized())
os::System::instance()->activateApp();
}
else {
// We tried to load the Steam SDK without calling
// SteamAPI_InitSafe() to check if we could run the program
// without "in game" indication but still capturing screenshots
// on Steam, and that wasn't the case.
}
#endif #endif
#if defined(_DEBUG) || defined(ENABLE_DEVMODE) #if defined(_DEBUG) || defined(ENABLE_DEVMODE)
@ -707,24 +721,21 @@ Workspace* App::workspace() const
{ {
if (m_mainWindow) if (m_mainWindow)
return m_mainWindow->getWorkspace(); return m_mainWindow->getWorkspace();
else return nullptr;
return nullptr;
} }
ContextBar* App::contextBar() const ContextBar* App::contextBar() const
{ {
if (m_mainWindow) if (m_mainWindow)
return m_mainWindow->getContextBar(); return m_mainWindow->getContextBar();
else return nullptr;
return nullptr;
} }
Timeline* App::timeline() const Timeline* App::timeline() const
{ {
if (m_mainWindow) if (m_mainWindow)
return m_mainWindow->getTimeline(); return m_mainWindow->getTimeline();
else return nullptr;
return nullptr;
} }
Preferences& App::preferences() const Preferences& App::preferences() const
@ -853,8 +864,7 @@ PixelFormat app_get_current_pixel_format()
Doc* doc = ctx->activeDocument(); Doc* doc = ctx->activeDocument();
if (doc) if (doc)
return doc->sprite()->pixelFormat(); return doc->sprite()->pixelFormat();
else return IMAGE_RGB;
return IMAGE_RGB;
} }
int app_get_color_to_clear_layer(Layer* layer) int app_get_color_to_clear_layer(Layer* layer)

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -142,6 +142,9 @@ namespace app {
std::unique_ptr<LegacyModules> m_legacy; std::unique_ptr<LegacyModules> m_legacy;
bool m_isGui; bool m_isGui;
bool m_isShell; bool m_isShell;
#ifdef ENABLE_STEAM
bool m_inAppSteam = true;
#endif
std::unique_ptr<MainWindow> m_mainWindow; std::unique_ptr<MainWindow> m_mainWindow;
base::paths m_files; base::paths m_files;
#ifdef ENABLE_UI #ifdef ENABLE_UI

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -79,6 +79,9 @@ AppOptions::AppOptions(int argc, const char* argv[])
, m_exportTileset(m_po.add("export-tileset").description("Export only tilesets from visible tilemap layers")) , m_exportTileset(m_po.add("export-tileset").description("Export only tilesets from visible tilemap layers"))
, m_verbose(m_po.add("verbose").mnemonic('v').description("Explain what is being done")) , m_verbose(m_po.add("verbose").mnemonic('v').description("Explain what is being done"))
, m_debug(m_po.add("debug").description("Extreme verbose mode and\ncopy log to desktop")) , m_debug(m_po.add("debug").description("Extreme verbose mode and\ncopy log to desktop"))
#ifdef ENABLE_STEAM
, m_noInApp(m_po.add("noinapp").description("Disable \"in game\" visibility on Steam\nDoesn't count playtime"))
#endif
#ifdef _WIN32 #ifdef _WIN32
, m_disableWintab(m_po.add("disable-wintab").description("Don't load wintab32.dll library")) , m_disableWintab(m_po.add("disable-wintab").description("Don't load wintab32.dll library"))
#endif #endif
@ -121,6 +124,13 @@ bool AppOptions::hasExporterParams() const
m_po.enabled(m_sheet); m_po.enabled(m_sheet);
} }
#ifdef ENABLE_STEAM
bool AppOptions::noInApp() const
{
return m_po.enabled(m_noInApp);
}
#endif
#ifdef _WIN32 #ifdef _WIN32
bool AppOptions::disableWintab() const bool AppOptions::disableWintab() const
{ {

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -95,6 +95,9 @@ public:
const Option& exportTileset() const { return m_exportTileset; } const Option& exportTileset() const { return m_exportTileset; }
bool hasExporterParams() const; bool hasExporterParams() const;
#ifdef ENABLE_STEAM
bool noInApp() const;
#endif
#ifdef _WIN32 #ifdef _WIN32
bool disableWintab() const; bool disableWintab() const;
#endif #endif
@ -166,6 +169,9 @@ private:
Option& m_verbose; Option& m_verbose;
Option& m_debug; Option& m_debug;
#ifdef ENABLE_STEAM
Option& m_noInApp;
#endif
#ifdef _WIN32 #ifdef _WIN32
Option& m_disableWintab; Option& m_disableWintab;
#endif #endif

View File

@ -1,4 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -8,7 +9,6 @@
#include "config.h" #include "config.h"
#endif #endif
#include "app/app.h"
#include "app/commands/command.h" #include "app/commands/command.h"
#include "app/context_access.h" #include "app/context_access.h"
#include "app/modules/gui.h" #include "app/modules/gui.h"
@ -49,7 +49,7 @@ bool LayerVisibilityCommand::onChecked(Context* context)
return false; return false;
SelectedLayers selLayers; SelectedLayers selLayers;
auto range = App::instance()->timeline()->range(); DocRange range = context->activeSite().range();
if (range.enabled()) { if (range.enabled()) {
selLayers = range.selectedLayers(); selLayers = range.selectedLayers();
} }
@ -67,24 +67,25 @@ bool LayerVisibilityCommand::onChecked(Context* context)
void LayerVisibilityCommand::onExecute(Context* context) void LayerVisibilityCommand::onExecute(Context* context)
{ {
ContextWriter writer(context); ContextWriter writer(context);
Doc* doc = writer.document();
SelectedLayers selLayers; SelectedLayers selLayers;
auto range = App::instance()->timeline()->range(); DocRange range = context->activeSite().range();
if (range.enabled()) { if (range.enabled()) {
selLayers = range.selectedLayers(); selLayers = range.selectedLayers();
} }
else { else {
selLayers.insert(writer.layer()); selLayers.insert(writer.layer());
} }
bool anyVisible = false; bool anyVisible = false;
for (auto layer : selLayers) { for (auto layer : selLayers) {
if (layer->isVisible()) if (layer->isVisible())
anyVisible = true; anyVisible = true;
} }
for (auto layer : selLayers) {
layer->setVisible(!anyVisible);
}
update_screen_for_document(writer.document()); const bool newState = !anyVisible;
for (auto layer : selLayers)
doc->setLayerVisibilityWithNotifications(layer, newState);
} }
Command* CommandFactory::createLayerVisibilityCommand() Command* CommandFactory::createLayerVisibilityCommand()

View File

@ -151,7 +151,7 @@ void FilterWorker::run()
} }
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
if (m_done && m_filterMgr->isTransaction()) if (m_done && m_filterMgr->isTransaction())
m_filterMgr->commitTransaction(); m_filterMgr->commitTransaction();
else else
@ -180,7 +180,7 @@ void FilterWorker::run()
// //
void FilterWorker::reportProgress(float progress) void FilterWorker::reportProgress(float progress)
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
m_pos = progress; m_pos = progress;
} }
@ -192,7 +192,7 @@ bool FilterWorker::isCancelled()
{ {
bool cancelled; bool cancelled;
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
cancelled = (m_cancelled || m_abort); cancelled = (m_cancelled || m_abort);
return cancelled; return cancelled;
@ -209,7 +209,7 @@ void FilterWorker::applyFilterInBackground()
m_filterMgr->applyToTarget(); m_filterMgr->applyToTarget();
// Mark the work as 'done'. // Mark the work as 'done'.
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
m_done = true; m_done = true;
} }
catch (std::exception& e) { catch (std::exception& e) {
@ -224,7 +224,7 @@ void FilterWorker::applyFilterInBackground()
// every 100 milliseconds). // every 100 milliseconds).
void FilterWorker::onMonitoringTick() void FilterWorker::onMonitoringTick()
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
if (m_alert) { if (m_alert) {
m_alert->setProgress(m_pos); m_alert->setProgress(m_pos);

View File

@ -48,8 +48,7 @@ namespace app {
auto it = m_params.find(name); auto it = m_params.find(name);
if (it != m_params.end()) if (it != m_params.end())
return it->second; return it->second;
else return std::string();
return std::string();
} }
void operator|=(const Params& params) { void operator|=(const Params& params) {

View File

@ -193,6 +193,16 @@ color_t Doc::bgColor(Layer* layer) const
return layer->sprite()->transparentColor(); return layer->sprite()->transparentColor();
} }
//////////////////////////////////////////////////////////////////////
// Modifications with notifications
void Doc::setLayerVisibilityWithNotifications(Layer* layer, const bool visible)
{
notifyBeforeLayerVisibilityChange(layer, visible);
layer->setVisible(visible);
notifyAfterLayerVisibilityChange(layer);
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Notifications // Notifications
@ -244,6 +254,20 @@ void Doc::notifyLayerMergedDown(Layer* srcLayer, Layer* targetLayer)
notify_observers<DocEvent&>(&DocObserver::onLayerMergedDown, ev); notify_observers<DocEvent&>(&DocObserver::onLayerMergedDown, ev);
} }
void Doc::notifyBeforeLayerVisibilityChange(Layer* layer, bool newState)
{
DocEvent ev(this);
ev.layer(layer);
notify_observers<DocEvent&, bool>(&DocObserver::onBeforeLayerVisibilityChange, ev, newState);
}
void Doc::notifyAfterLayerVisibilityChange(Layer* layer)
{
DocEvent ev(this);
ev.layer(layer);
notify_observers<DocEvent&>(&DocObserver::onAfterLayerVisibilityChange, ev);
}
void Doc::notifyCelMoved(Layer* fromLayer, frame_t fromFrame, Layer* toLayer, frame_t toFrame) void Doc::notifyCelMoved(Layer* fromLayer, frame_t fromFrame, Layer* toLayer, frame_t toFrame)
{ {
DocEvent ev(this); DocEvent ev(this);

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -107,6 +107,14 @@ namespace app {
os::ColorSpaceRef osColorSpace() const { return m_osColorSpace; } os::ColorSpaceRef osColorSpace() const { return m_osColorSpace; }
//////////////////////////////////////////////////////////////////////
// Modifications with notifications
// Use this function to change the layer visibility and notify all
// DocObservers about this change (e.g. so the Editor can be
// invalidated/redrawn, MovingPixelsState can drop pixels, etc.)
void setLayerVisibilityWithNotifications(Layer* layer, const bool visible);
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Notifications // Notifications
@ -116,6 +124,8 @@ namespace app {
void notifySpritePixelsModified(Sprite* sprite, const gfx::Region& region, frame_t frame); void notifySpritePixelsModified(Sprite* sprite, const gfx::Region& region, frame_t frame);
void notifyExposeSpritePixels(Sprite* sprite, const gfx::Region& region); void notifyExposeSpritePixels(Sprite* sprite, const gfx::Region& region);
void notifyLayerMergedDown(Layer* srcLayer, Layer* targetLayer); void notifyLayerMergedDown(Layer* srcLayer, Layer* targetLayer);
void notifyBeforeLayerVisibilityChange(Layer* layer, bool newState);
void notifyAfterLayerVisibilityChange(Layer* layer);
void notifyCelMoved(Layer* fromLayer, frame_t fromFrame, Layer* toLayer, frame_t toFrame); void notifyCelMoved(Layer* fromLayer, frame_t fromFrame, Layer* toLayer, frame_t toFrame);
void notifyCelCopied(Layer* fromLayer, frame_t fromFrame, Layer* toLayer, frame_t toFrame); void notifyCelCopied(Layer* fromLayer, frame_t fromFrame, Layer* toLayer, frame_t toFrame);
void notifySelectionChanged(); void notifySelectionChanged();

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -97,6 +97,10 @@ namespace app {
// The collapsed/expanded flag of a specific layer changed. // The collapsed/expanded flag of a specific layer changed.
virtual void onLayerCollapsedChanged(DocEvent& ev) { } virtual void onLayerCollapsedChanged(DocEvent& ev) { }
// The visibility flag of a specific layer is going to change/changed.
virtual void onBeforeLayerVisibilityChange(DocEvent& ev, bool newState) { }
virtual void onAfterLayerVisibilityChange(DocEvent& ev) { }
// The tileset was remapped (e.g. when tiles are re-ordered). // The tileset was remapped (e.g. when tiles are re-ordered).
virtual void onRemapTileset(DocEvent& ev, const doc::Remap& remap) { } virtual void onRemapTileset(DocEvent& ev, const doc::Remap& remap) { }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -355,7 +355,7 @@ bool AseFormat::onSave(FileOp* fop)
bool require_new_palette_chunk = false; bool require_new_palette_chunk = false;
for (Palette* pal : sprite->getPalettes()) { for (Palette* pal : sprite->getPalettes()) {
if (pal->size() != 256 || pal->hasAlpha()) { if (pal->size() > 256 || pal->hasAlpha()) {
require_new_palette_chunk = true; require_new_palette_chunk = true;
break; break;
} }
@ -393,9 +393,12 @@ bool AseFormat::onSave(FileOp* fop)
ase_file_write_palette_chunk(f, &frame_header, ase_file_write_palette_chunk(f, &frame_header,
pal, palFrom, palTo); pal, palFrom, palTo);
} }
else {
// Write color chunk for backward compatibility only // Use old color chunk only when the palette has 256 or less
ase_file_write_color2_chunk(f, &frame_header, pal); // colors, and we don't need the alpha channel (as this chunk
// is smaller than the new palette chunk).
ase_file_write_color2_chunk(f, &frame_header, pal);
}
} }
// Write extra chunks in the first frame // Write extra chunks in the first frame
@ -676,6 +679,7 @@ static void ase_file_write_color2_chunk(FILE* f, dio::AsepriteFrameHeader* frame
// First packet // First packet
fputc(0, f); // skip 0 colors fputc(0, f); // skip 0 colors
ASSERT(pal->size() <= 256); // For >256 we use the palette chunk
fputc(pal->size() == 256 ? 0: pal->size(), f); // number of colors fputc(pal->size() == 256 ? 0: pal->size(), f); // number of colors
for (c=0; c<pal->size(); c++) { for (c=0; c<pal->size(); c++) {
color = pal->getEntry(c); color = pal->getEntry(c);
@ -1692,8 +1696,12 @@ static void ase_ungroup_all(LayerGroup* group)
for (Layer* child : list) { for (Layer* child : list) {
if (child->isGroup()) { if (child->isGroup()) {
ase_ungroup_all(static_cast<LayerGroup*>(child)); auto* childGroup = static_cast<LayerGroup*>(child);
group->removeLayer(child); ase_ungroup_all(childGroup);
group->removeLayer(childGroup);
ASSERT(childGroup->layersCount() == 0);
delete childGroup;
} }
else if (group != root) { else if (group != root) {
// Create a new name adding all group layer names // Create a new name adding all group layer names
@ -1711,11 +1719,6 @@ static void ase_ungroup_all(LayerGroup* group)
root->addLayer(child); root->addLayer(child);
} }
} }
if (group != root) {
ASSERT(group->layersCount() == 0);
delete group;
}
} }
} // namespace app } // namespace app

View File

@ -1122,13 +1122,13 @@ void FileOp::operate(IFileOpProgress* progress)
void FileOp::done() void FileOp::done()
{ {
// Finally done. // Finally done.
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
m_done = true; m_done = true;
} }
void FileOp::stop() void FileOp::stop()
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
if (!m_done) if (!m_done)
m_stop = true; m_stop = true;
} }
@ -1443,7 +1443,7 @@ void FileOp::setError(const char *format, ...)
// Concatenate the new error // Concatenate the new error
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
// Add a newline char automatically if it's needed // Add a newline char automatically if it's needed
if (!m_error.empty() && m_error.back() != '\n') if (!m_error.empty() && m_error.back() != '\n')
m_error.push_back('\n'); m_error.push_back('\n');
@ -1455,7 +1455,7 @@ void FileOp::setIncompatibilityError(const std::string& msg)
{ {
// Concatenate the new error // Concatenate the new error
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
// Add a newline char automatically if it's needed // Add a newline char automatically if it's needed
if (!m_incompatibilityError.empty() && m_incompatibilityError.back() != '\n') if (!m_incompatibilityError.empty() && m_incompatibilityError.back() != '\n')
m_incompatibilityError.push_back('\n'); m_incompatibilityError.push_back('\n');
@ -1465,7 +1465,7 @@ void FileOp::setIncompatibilityError(const std::string& msg)
void FileOp::setProgress(double progress) void FileOp::setProgress(double progress)
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
if (isSequence()) { if (isSequence()) {
m_progress = m_progress =
@ -1494,7 +1494,7 @@ double FileOp::progress() const
{ {
double progress; double progress;
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
progress = m_progress; progress = m_progress;
} }
return progress; return progress;
@ -1506,7 +1506,7 @@ bool FileOp::isDone() const
{ {
bool done; bool done;
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
done = m_done; done = m_done;
} }
return done; return done;

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018 David Capello // Copyright (C) 2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -329,7 +329,9 @@ int Layer_set_isEditable(lua_State* L)
int Layer_set_isVisible(lua_State* L) int Layer_set_isVisible(lua_State* L)
{ {
auto layer = get_docobj<Layer>(L, 1); auto layer = get_docobj<Layer>(L, 1);
layer->setVisible(lua_toboolean(L, 2)); const bool newState = lua_toboolean(L, 2);
Doc* doc = static_cast<Doc*>(layer->sprite()->document());
doc->setLayerVisibilityWithNotifications(layer, newState);
return 0; return 0;
} }

View File

@ -29,7 +29,7 @@ Task::~Task()
void Task::run(base::task::func_t&& func) void Task::run(base::task::func_t&& func)
{ {
std::lock_guard lock(m_token_mutex); const std::lock_guard lock(m_token_mutex);
m_task.on_execute(std::move(func)); m_task.on_execute(std::move(func));
m_token = &m_task.start(tasks_pool); m_token = &m_task.start(tasks_pool);
} }

View File

@ -34,29 +34,27 @@ namespace app {
} }
bool canceled() const { bool canceled() const {
std::lock_guard lock(m_token_mutex); const std::lock_guard lock(m_token_mutex);
if (m_token) if (m_token)
return m_token->canceled(); return m_token->canceled();
else return false;
return false;
} }
float progress() const { float progress() const {
std::lock_guard lock(m_token_mutex); const std::lock_guard lock(m_token_mutex);
if (m_token) if (m_token)
return m_token->progress(); return m_token->progress();
else return 0.0f;
return 0.0f;
} }
void cancel() { void cancel() {
std::lock_guard lock(m_token_mutex); const std::lock_guard lock(m_token_mutex);
if (m_token) if (m_token)
m_token->cancel(); m_token->cancel();
} }
void set_progress(float progress) { void set_progress(float progress) {
std::lock_guard lock(m_token_mutex); const std::lock_guard lock(m_token_mutex);
if (m_token) if (m_token)
m_token->set_progress(progress); m_token->set_progress(progress);
} }

View File

@ -49,7 +49,7 @@ public:
~Worker() { ~Worker() {
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
if (m_fop) if (m_fop)
m_fop->stop(); m_fop->stop();
} }
@ -57,7 +57,7 @@ public:
} }
void stop() const { void stop() const {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
if (m_fop) if (m_fop)
m_fop->stop(); m_fop->stop();
} }
@ -67,7 +67,7 @@ public:
} }
void updateProgress() { void updateProgress() {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
if (m_item.fileitem && m_item.fop) { if (m_item.fileitem && m_item.fop) {
double progress = m_item.fop->progress(); double progress = m_item.fop->progress();
if (progress > m_item.fileitem->getThumbnailProgress()) if (progress > m_item.fileitem->getThumbnailProgress())
@ -80,7 +80,7 @@ private:
ASSERT(!m_fop); ASSERT(!m_fop);
try { try {
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
m_fop = m_item.fop; m_fop = m_item.fop;
ASSERT(m_fop); ASSERT(m_fop);
} }
@ -167,7 +167,7 @@ private:
0, 0, 0, 0, thumbnailImage->width(), thumbnailImage->height()); 0, 0, 0, 0, thumbnailImage->width(), thumbnailImage->height());
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
m_item.fileitem->setThumbnail(thumbnail); m_item.fileitem->setThumbnail(thumbnail);
} }
} }
@ -191,13 +191,13 @@ private:
// Reset the m_item (first the fileitem so this worker is not // Reset the m_item (first the fileitem so this worker is not
// associated to this fileitem anymore, and then the FileOp). // associated to this fileitem anymore, and then the FileOp).
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
m_item.fileitem = nullptr; m_item.fileitem = nullptr;
} }
m_fop->done(); m_fop->done();
{ {
std::lock_guard lock(m_mutex); const std::lock_guard lock(m_mutex);
m_item.fop = nullptr; m_item.fop = nullptr;
delete m_fop; delete m_fop;
m_fop = nullptr; m_fop = nullptr;
@ -212,7 +212,7 @@ private:
bool success = true; bool success = true;
while (success) { while (success) {
{ {
std::lock_guard lock(m_mutex); // To access m_item const std::lock_guard lock(m_mutex); // To access m_item
success = m_queue.try_pop(m_item); success = m_queue.try_pop(m_item);
} }
if (success) if (success)
@ -253,7 +253,7 @@ ThumbnailGenerator::ThumbnailGenerator()
bool ThumbnailGenerator::checkWorkers() bool ThumbnailGenerator::checkWorkers()
{ {
std::lock_guard lock(m_workersAccess); const std::lock_guard lock(m_workersAccess);
bool doingWork = (!m_workers.empty()); bool doingWork = (!m_workers.empty());
for (WorkerList::iterator for (WorkerList::iterator
@ -339,14 +339,14 @@ void ThumbnailGenerator::stopAllWorkers()
} }
} }
std::lock_guard lock(m_workersAccess); const std::lock_guard lock(m_workersAccess);
for (const auto& worker : m_workers) for (const auto& worker : m_workers)
worker->stop(); worker->stop();
} }
void ThumbnailGenerator::startWorker() void ThumbnailGenerator::startWorker()
{ {
std::lock_guard lock(m_workersAccess); const std::lock_guard lock(m_workersAccess);
if (m_workers.size() < m_maxWorkers) { if (m_workers.size() < m_maxWorkers) {
m_workers.push_back(std::make_unique<Worker>(m_remainingItems)); m_workers.push_back(std::make_unique<Worker>(m_remainingItems));
} }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2023 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2018 David Capello // Copyright (C) 2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -23,7 +23,7 @@ namespace app {
// Wrapper to create a new transaction or get the current // Wrapper to create a new transaction or get the current
// transaction in the context. // transaction in the context.
class Tx { class [[nodiscard]] Tx {
public: public:
enum LockAction { enum LockAction {
DocIsLocked, // The doc is locked to be written DocIsLocked, // The doc is locked to be written

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -478,7 +478,16 @@ void DocView::onTotalFramesChanged(DocEvent& ev)
void DocView::onLayerRestacked(DocEvent& ev) void DocView::onLayerRestacked(DocEvent& ev)
{ {
m_editor->invalidate(); if (hasContentInActiveFrame(ev.layer()))
m_editor->invalidate();
}
void DocView::onAfterLayerVisibilityChange(DocEvent& ev)
{
// If there is no cel for this layer in the current frame, there is
// no need to redraw the editor
if (hasContentInActiveFrame(ev.layer()))
m_editor->invalidate();
} }
void DocView::onTilesetChanged(DocEvent& ev) void DocView::onTilesetChanged(DocEvent& ev)
@ -653,4 +662,19 @@ void DocView::onCancel(Context* ctx)
} }
} }
bool DocView::hasContentInActiveFrame(const doc::Layer* layer) const
{
if (!layer)
return false;
else if (layer->cel(m_editor->frame()))
return true;
else if (layer->isGroup()) {
for (const doc::Layer* child : static_cast<const doc::LayerGroup*>(layer)->layers()) {
if (hasContentInActiveFrame(child))
return true;
}
}
return false;
}
} // namespace app } // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2023 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -15,6 +15,10 @@
#include "app/ui/workspace_view.h" #include "app/ui/workspace_view.h"
#include "ui/box.h" #include "ui/box.h"
namespace doc {
class Layer;
}
namespace ui { namespace ui {
class View; class View;
} }
@ -86,6 +90,7 @@ namespace app {
void onAfterRemoveCel(DocEvent& ev) override; void onAfterRemoveCel(DocEvent& ev) override;
void onTotalFramesChanged(DocEvent& ev) override; void onTotalFramesChanged(DocEvent& ev) override;
void onLayerRestacked(DocEvent& ev) override; void onLayerRestacked(DocEvent& ev) override;
void onAfterLayerVisibilityChange(DocEvent& ev) override;
void onTilesetChanged(DocEvent& ev) override; void onTilesetChanged(DocEvent& ev) override;
// InputChainElement impl // InputChainElement impl
@ -105,6 +110,8 @@ namespace app {
bool onProcessMessage(ui::Message* msg) override; bool onProcessMessage(ui::Message* msg) override;
private: private:
bool hasContentInActiveFrame(const doc::Layer* layer) const;
Type m_type; Type m_type;
Doc* m_document; Doc* m_document;
ui::View* m_view; ui::View* m_view;

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2022 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -96,8 +96,9 @@ void DrawingState::initToolLoop(Editor* editor,
// For selection inks we don't use a "the selected layer" for // For selection inks we don't use a "the selected layer" for
// preview purposes, because we want the selection feedback to be at // preview purposes, because we want the selection feedback to be at
// the top of all layers. // the top of all layers.
Layer* previewLayer = (m_toolLoop->getInk()->isSelection() ? nullptr: Layer* previewLayer = (m_toolLoop->getInk()->isSelection() ||
m_toolLoop->getLayer()); m_toolLoop->getInk()->isSlice() ?
nullptr : m_toolLoop->getLayer());
// Prepare preview image (the destination image will be our preview // Prepare preview image (the destination image will be our preview
// in the tool-loop time, so we can see what we are drawing) // in the tool-loop time, so we can see what we are drawing)

View File

@ -2438,6 +2438,12 @@ void Editor::onRemoveSlice(DocEvent& ev)
} }
} }
void Editor::onBeforeLayerVisibilityChange(DocEvent& ev, bool newState)
{
if (m_state)
m_state->onBeforeLayerVisibilityChange(this, ev.layer(), newState);
}
void Editor::setCursor(const gfx::Point& mouseDisplayPos) void Editor::setCursor(const gfx::Point& mouseDisplayPos)
{ {
Rect vp = View::getView(this)->viewportBounds(); Rect vp = View::getView(this)->viewportBounds();

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -351,6 +351,7 @@ namespace app {
void onAddTag(DocEvent& ev) override; void onAddTag(DocEvent& ev) override;
void onRemoveTag(DocEvent& ev) override; void onRemoveTag(DocEvent& ev) override;
void onRemoveSlice(DocEvent& ev) override; void onRemoveSlice(DocEvent& ev) override;
void onBeforeLayerVisibilityChange(DocEvent& ev, bool newState) override;
// ActiveToolObserver impl // ActiveToolObserver impl
void onActiveToolChange(tools::Tool* tool) override; void onActiveToolChange(tools::Tool* tool) override;

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2023 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello // Copyright (C) 2001-2016 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -26,6 +26,7 @@ namespace ui {
} }
namespace doc { namespace doc {
class Layer;
class Tag; class Tag;
} }
@ -145,6 +146,11 @@ namespace app {
// collection. // collection.
virtual void onBeforeRemoveLayer(Editor* editor) { } virtual void onBeforeRemoveLayer(Editor* editor) { }
// Called when the visibility of a specific layer is changed.
virtual void onBeforeLayerVisibilityChange(Editor* editor,
doc::Layer* layer,
bool newState) { }
private: private:
DISABLE_COPYING(EditorState); DISABLE_COPYING(EditorState);
}; };

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2023 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -578,6 +578,25 @@ bool MovingPixelsState::acceptQuickTool(tools::Tool* tool)
tool->getInk(0)->isZoom()); tool->getInk(0)->isZoom());
} }
void MovingPixelsState::onBeforeLayerVisibilityChange(Editor* editor,
doc::Layer* layer,
bool newState)
{
if (!isActiveDocument())
return;
// If the layer visibility of any selected layer changes, we just
// drop the pixels (it's the easiest way to avoid modifying hidden
// pixels).
if (m_pixelsMovement) {
const Site& site = m_pixelsMovement->site();
if (site.layer() == layer ||
site.range().contains(layer)) {
dropPixels();
}
}
}
// Before executing any command, we drop the pixels (go back to standby). // Before executing any command, we drop the pixels (go back to standby).
void MovingPixelsState::onBeforeCommandExecution(CommandExecutionEvent& ev) void MovingPixelsState::onBeforeCommandExecution(CommandExecutionEvent& ev)
{ {

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -21,7 +21,7 @@
#include "ui/timer.h" #include "ui/timer.h"
namespace doc { namespace doc {
class Image; class Layer;
} }
namespace app { namespace app {
@ -54,35 +54,37 @@ namespace app {
void updateTransformation(const Transformation& t); void updateTransformation(const Transformation& t);
// EditorState // EditorState
virtual void onEnterState(Editor* editor) override; void onEnterState(Editor* editor) override;
virtual void onEditorGotFocus(Editor* editor) override; void onEditorGotFocus(Editor* editor) override;
virtual LeaveAction onLeaveState(Editor* editor, EditorState* newState) override; LeaveAction onLeaveState(Editor* editor, EditorState* newState) override;
virtual void onActiveToolChange(Editor* editor, tools::Tool* tool) override; void onActiveToolChange(Editor* editor, tools::Tool* tool) override;
virtual bool onMouseDown(Editor* editor, ui::MouseMessage* msg) override; bool onMouseDown(Editor* editor, ui::MouseMessage* msg) override;
virtual bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override; bool onMouseUp(Editor* editor, ui::MouseMessage* msg) override;
virtual bool onMouseMove(Editor* editor, ui::MouseMessage* msg) override; bool onMouseMove(Editor* editor, ui::MouseMessage* msg) override;
virtual bool onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos) override; bool onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos) override;
virtual bool onKeyDown(Editor* editor, ui::KeyMessage* msg) override; bool onKeyDown(Editor* editor, ui::KeyMessage* msg) override;
virtual bool onKeyUp(Editor* editor, ui::KeyMessage* msg) override; bool onKeyUp(Editor* editor, ui::KeyMessage* msg) override;
virtual bool onUpdateStatusBar(Editor* editor) override; bool onUpdateStatusBar(Editor* editor) override;
virtual bool acceptQuickTool(tools::Tool* tool) override; bool acceptQuickTool(tools::Tool* tool) override;
virtual bool requireBrushPreview() override { return false; } bool requireBrushPreview() override { return false; }
void onBeforeLayerVisibilityChange(Editor* editor, doc::Layer* layer, bool newState) override;
// EditorObserver // EditorObserver
virtual void onDestroyEditor(Editor* editor) override; void onDestroyEditor(Editor* editor) override;
virtual void onBeforeFrameChanged(Editor* editor) override; void onBeforeFrameChanged(Editor* editor) override;
virtual void onBeforeLayerChanged(Editor* editor) override; void onBeforeLayerChanged(Editor* editor) override;
// TimelineObserver // TimelineObserver
virtual void onBeforeRangeChanged(Timeline* timeline) override; void onBeforeRangeChanged(Timeline* timeline) override;
// ContextBarObserver // ContextBarObserver
virtual void onDropPixels(ContextBarObserver::DropAction action) override; void onDropPixels(ContextBarObserver::DropAction action) override;
// PixelsMovementDelegate // PixelsMovementDelegate
virtual void onPivotChange() override; void onPivotChange() override;
virtual Transformation getTransformation(Editor* editor) override; Transformation getTransformation(Editor* editor) override;
private: private:
// DelayedMouseMoveDelegate impl // DelayedMouseMoveDelegate impl

View File

@ -787,9 +787,10 @@ void PixelsMovement::stampImage(bool finalStamp)
cels.push_back(currentCel); cels.push_back(currentCel);
} }
if (currentCel && currentCel->layer() && if (currentCel &&
currentCel->layer() &&
currentCel->layer()->isImage() && currentCel->layer()->isImage() &&
!currentCel->layer()->isEditableHierarchy()) { !currentCel->layer()->canEditPixels()) {
Transformation initialCelPos(gfx::Rect(m_initialMask0->bounds()), m_currentData.cornerThick()); Transformation initialCelPos(gfx::Rect(m_initialMask0->bounds()), m_currentData.cornerThick());
redrawExtraImage(&initialCelPos); redrawExtraImage(&initialCelPos);
stampExtraCelImage(); stampExtraCelImage();

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2021 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -75,6 +75,8 @@ namespace app {
const Mask* mask, const Mask* mask,
const char* operationName); const char* operationName);
const Site& site() { return m_site; }
HandleType handle() const { return m_handle; } HandleType handle() const { return m_handle; }
bool canHandleFrameChange() const { return m_canHandleFrameChange; } bool canHandleFrameChange() const { return m_canHandleFrameChange; }

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2019-2023 Igara Studio S.A. // Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -452,8 +452,8 @@ protected:
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// For drawing // For drawing
class ToolLoopImpl : public ToolLoopBase, class ToolLoopImpl final : public ToolLoopBase,
public EditorObserver { public EditorObserver {
Context* m_context; Context* m_context;
bool m_filled; bool m_filled;
bool m_previewFilled; bool m_previewFilled;
@ -517,6 +517,9 @@ public:
} }
} }
// 'isSelectionPreview = true' if the intention is to show a preview
// of Selection tools or Slice tool.
const bool isSelectionPreview = m_ink->isSelection() || m_ink->isSlice();
m_expandCelCanvas.reset(new ExpandCelCanvas( m_expandCelCanvas.reset(new ExpandCelCanvas(
site, m_layer, site, m_layer,
m_docPref.tiled.mode(), m_docPref.tiled.mode(),
@ -530,10 +533,10 @@ public:
(m_layer->isTilemap() && (m_layer->isTilemap() &&
site.tilemapMode() == TilemapMode::Pixels && site.tilemapMode() == TilemapMode::Pixels &&
site.tilesetMode() == TilesetMode::Manual && site.tilesetMode() == TilesetMode::Manual &&
!m_ink->isSelection() ? ExpandCelCanvas::TilesetPreview: !isSelectionPreview ? ExpandCelCanvas::TilesetPreview:
ExpandCelCanvas::None) | ExpandCelCanvas::None) |
(m_ink->isSelection() ? ExpandCelCanvas::SelectionPreview: (isSelectionPreview ? ExpandCelCanvas::SelectionPreview:
ExpandCelCanvas::None)))); ExpandCelCanvas::None))));
if (!m_floodfillSrcImage) if (!m_floodfillSrcImage)
m_floodfillSrcImage = const_cast<Image*>(getSrcImage()); m_floodfillSrcImage = const_cast<Image*>(getSrcImage());
@ -555,7 +558,7 @@ public:
m_sprayWidth = m_toolPref.spray.width(); m_sprayWidth = m_toolPref.spray.width();
m_spraySpeed = m_toolPref.spray.speed(); m_spraySpeed = m_toolPref.spray.speed();
if (m_ink->isSelection()) { if (isSelectionPreview) {
m_useMask = false; m_useMask = false;
} }
else { else {
@ -563,7 +566,7 @@ public:
} }
// Start with an empty mask if the user is selecting with "default selection mode" // Start with an empty mask if the user is selecting with "default selection mode"
if (m_ink->isSelection() && if (isSelectionPreview &&
(!m_document->isMaskVisible() || (!m_document->isMaskVisible() ||
(int(getModifiers()) & int(tools::ToolLoopModifiers::kReplaceSelection)))) { (int(getModifiers()) & int(tools::ToolLoopModifiers::kReplaceSelection)))) {
Mask emptyMask; Mask emptyMask;
@ -592,6 +595,9 @@ public:
m_editor->remove_observer(this); m_editor->remove_observer(this);
#endif #endif
// getSrcImage() is a virtual member function but ToolLoopImpl is
// marked as final to avoid not calling a derived version from
// this destructor.
if (m_floodfillSrcImage != getSrcImage()) if (m_floodfillSrcImage != getSrcImage())
delete m_floodfillSrcImage; delete m_floodfillSrcImage;
} }
@ -954,7 +960,7 @@ tools::ToolLoop* create_tool_loop_for_script(
#ifdef ENABLE_UI #ifdef ENABLE_UI
class PreviewToolLoopImpl : public ToolLoopBase { class PreviewToolLoopImpl final : public ToolLoopBase {
Image* m_image; Image* m_image;
public: public:

View File

@ -110,7 +110,7 @@ namespace app {
if (it != m_styles.end()) if (it != m_styles.end())
return it->second; return it->second;
else else
return nullptr; return getDefaultStyle();
} }
SkinPartPtr getPartById(const std::string& id) const { SkinPartPtr getPartById(const std::string& id) const {

View File

@ -696,7 +696,9 @@ bool Timeline::onProcessMessage(Message* msg)
bool newVisibleState = !allLayersVisible(); bool newVisibleState = !allLayersVisible();
for (Layer* topLayer : m_sprite->root()->layers()) { for (Layer* topLayer : m_sprite->root()->layers()) {
if (topLayer->isVisible() != newVisibleState) { if (topLayer->isVisible() != newVisibleState) {
topLayer->setVisible(newVisibleState); m_document->setLayerVisibilityWithNotifications(
topLayer, newVisibleState);
if (topLayer->isGroup()) if (topLayer->isGroup())
regenRows = true; regenRows = true;
} }
@ -825,11 +827,11 @@ bool Timeline::onProcessMessage(Message* msg)
for (Row& row : m_rows) { for (Row& row : m_rows) {
Layer* l = row.layer(); Layer* l = row.layer();
if (l->hasFlags(LayerFlags::Internal_WasVisible)) { if (l->hasFlags(LayerFlags::Internal_WasVisible)) {
l->setVisible(true); m_document->setLayerVisibilityWithNotifications(l, true);
l->switchFlags(LayerFlags::Internal_WasVisible, false); l->switchFlags(LayerFlags::Internal_WasVisible, false);
} }
else { else {
l->setVisible(false); m_document->setLayerVisibilityWithNotifications(l, false);
} }
} }
} }
@ -838,7 +840,7 @@ bool Timeline::onProcessMessage(Message* msg)
for (Row& row : m_rows) { for (Row& row : m_rows) {
Layer* l = row.layer(); Layer* l = row.layer();
l->switchFlags(LayerFlags::Internal_WasVisible, l->isVisible()); l->switchFlags(LayerFlags::Internal_WasVisible, l->isVisible());
l->setVisible(false); m_document->setLayerVisibilityWithNotifications(l, false);
} }
} }
@ -2016,6 +2018,14 @@ void Timeline::onLayerCollapsedChanged(DocEvent& ev)
invalidate(); invalidate();
} }
void Timeline::onAfterLayerVisibilityChange(DocEvent& ev)
{
layer_t layerIdx = getLayerIndex(ev.layer());
if (layerIdx >= 0)
invalidateRect(getPartBounds(Hit(PART_ROW_EYE_ICON, layerIdx))
.offset(origin()));
}
void Timeline::onStateChanged(Editor* editor) void Timeline::onStateChanged(Editor* editor)
{ {
m_aniControls.updateUsingEditor(editor); m_aniControls.updateUsingEditor(editor);
@ -4475,12 +4485,10 @@ void Timeline::setLayerVisibleFlag(const layer_t l, const bool state)
if (!layer) if (!layer)
return; return;
bool redrawEditors = false;
bool regenRows = false; bool regenRows = false;
if (layer->isVisible() != state) { if (layer->isVisible() != state) {
layer->setVisible(state); m_document->setLayerVisibilityWithNotifications(layer, state);
redrawEditors = true;
// Regenerate rows because might change the flag of the children // Regenerate rows because might change the flag of the children
// (the flag is propagated to the children in m_inheritedFlags). // (the flag is propagated to the children in m_inheritedFlags).
@ -4493,9 +4501,8 @@ void Timeline::setLayerVisibleFlag(const layer_t l, const bool state)
layer = layer->parent(); layer = layer->parent();
while (layer) { while (layer) {
if (!layer->isVisible()) { if (!layer->isVisible()) {
layer->setVisible(true); m_document->setLayerVisibilityWithNotifications(layer, true);
regenRows = true; regenRows = true;
redrawEditors = true;
} }
layer = layer->parent(); layer = layer->parent();
} }
@ -4506,9 +4513,6 @@ void Timeline::setLayerVisibleFlag(const layer_t l, const bool state)
regenerateRows(); regenerateRows();
invalidate(); invalidate();
} }
if (redrawEditors)
m_document->notifyGeneralUpdate();
} }
void Timeline::setLayerEditableFlag(const layer_t l, const bool state) void Timeline::setLayerEditableFlag(const layer_t l, const bool state)

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
@ -163,6 +163,7 @@ namespace app {
void onTagChange(DocEvent& ev) override; void onTagChange(DocEvent& ev) override;
void onTagRename(DocEvent& ev) override; void onTagRename(DocEvent& ev) override;
void onLayerCollapsedChanged(DocEvent& ev) override; void onLayerCollapsedChanged(DocEvent& ev) override;
void onAfterLayerVisibilityChange(DocEvent& ev) override;
// app::Context slots. // app::Context slots.
void onBeforeCommandExecution(CommandExecutionEvent& ev); void onBeforeCommandExecution(CommandExecutionEvent& ev);

View File

@ -1,5 +1,5 @@
// Aseprite // Aseprite
// Copyright (C) 2020 Igara Studio S.A. // Copyright (C) 2020-2024 Igara Studio S.A.
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
@ -45,14 +45,34 @@ Layer* candidate_if_layer_is_deleted(
bool layer_is_locked(Editor* editor) bool layer_is_locked(Editor* editor)
{ {
Layer* layer = editor->layer(); Layer* layer = editor->layer();
if (layer && !layer->isEditableHierarchy()) { if (!layer)
return false;
#ifdef ENABLE_UI #ifdef ENABLE_UI
if (auto statusBar = StatusBar::instance()) auto statusBar = StatusBar::instance();
#endif
if (!layer->isVisibleHierarchy()) {
#ifdef ENABLE_UI
if (statusBar) {
statusBar->showTip( statusBar->showTip(
1000, fmt::format(Strings::statusbar_tips_layer_locked(), layer->name())); 1000, fmt::format(Strings::statusbar_tips_layer_x_is_hidden(),
layer->name()));
}
#endif #endif
return true; return true;
} }
if (!layer->isEditableHierarchy()) {
#ifdef ENABLE_UI
if (statusBar) {
statusBar->showTip(
1000, fmt::format(Strings::statusbar_tips_layer_locked(), layer->name()));
}
#endif
return true;
}
return false; return false;
} }

View File

@ -1,15 +1,15 @@
// Aseprite // Aseprite
// Copyright (C) 2018-2020 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello // Copyright (C) 2001-2018 David Capello
// //
// This program is distributed under the terms of // This program is distributed under the terms of
// the End-User License Agreement for Aseprite. // the End-User License Agreement for Aseprite.
#ifdef __ASEPRITE_CONFIG_H #ifdef ASEPRITE_CONFIG_H_INCLUDED
#error You cannot use config.h two times #error You cannot use config.h two times
#endif #endif
#define __ASEPRITE_CONFIG_H #define ASEPRITE_CONFIG_H_INCLUDED
// In MSVC // In MSVC
#ifdef _MSC_VER #ifdef _MSC_VER

View File

@ -75,8 +75,7 @@ namespace doc {
Image* originalImage() const { Image* originalImage() const {
if (m_backupImage) if (m_backupImage)
return m_backupImage.get(); return m_backupImage.get();
else return m_image.get();
return m_image.get();
} }
private: private:

View File

@ -1,5 +1,5 @@
// Aseprite Document Library // Aseprite Document Library
// Copyright (c) 2018-2023 Igara Studio S.A. // Copyright (c) 2018-2024 Igara Studio S.A.
// Copyright (c) 2001-2015 David Capello // Copyright (c) 2001-2015 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -53,13 +53,11 @@ namespace doc {
if (rgba_geta(a) == 0) { if (rgba_geta(a) == 0) {
if (rgba_geta(b) == 0) if (rgba_geta(b) == 0)
return true; return true;
else
return false;
}
else if (rgba_geta(b) == 0)
return false; return false;
else }
return a == b; if (rgba_geta(b) == 0)
return false;
return a == b;
} }
}; };
@ -98,13 +96,11 @@ namespace doc {
if (graya_geta(a) == 0) { if (graya_geta(a) == 0) {
if (graya_geta(b) == 0) if (graya_geta(b) == 0)
return true; return true;
else
return false;
}
else if (graya_geta(b) == 0)
return false; return false;
else }
return a == b; if (graya_geta(b) == 0)
return false;
return a == b;
} }
}; };

View File

@ -1,5 +1,5 @@
// Aseprite Document Library // Aseprite Document Library
// Copyright (c) 2022 Igara Studio S.A. // Copyright (c) 2022-2024 Igara Studio S.A.
// Copyright (c) 2017 David Capello // Copyright (c) 2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -72,14 +72,12 @@ namespace doc {
T* operator*() { T* operator*() {
if (m_it != m_end) if (m_it != m_end)
return m_it->value(); return m_it->value();
else return nullptr;
return nullptr;
} }
T* operator->() { T* operator->() {
if (m_it != m_end) if (m_it != m_end)
return m_it->value(); return m_it->value();
else return nullptr;
return nullptr;
} }
private: private:
iterator m_it, m_next; iterator m_it, m_next;
@ -154,8 +152,7 @@ namespace doc {
it->value() && it->value() &&
frame >= it->frame()) frame >= it->frame())
return it->value(); return it->value();
else return nullptr;
return nullptr;
} }
iterator begin() { return m_keys.begin(); } iterator begin() { return m_keys.begin(); }
@ -185,15 +182,13 @@ namespace doc {
frame_t fromFrame() const { frame_t fromFrame() const {
if (!m_keys.empty()) if (!m_keys.empty())
return m_keys.front().frame(); return m_keys.front().frame();
else return -1;
return -1;
} }
frame_t toFrame() const { frame_t toFrame() const {
if (!m_keys.empty()) if (!m_keys.empty())
return m_keys.back().frame(); return m_keys.back().frame();
else return -1;
return -1;
} }
Range range(const frame_t from, Range range(const frame_t from,

View File

@ -53,7 +53,7 @@ const ObjectId Object::id() const
// The first time the ID is request, we store the object in the // The first time the ID is request, we store the object in the
// "objects" hash table. // "objects" hash table.
if (!m_id) { if (!m_id) {
std::lock_guard lock(g_mutex); const std::lock_guard lock(g_mutex);
m_id = ++newId; m_id = ++newId;
objects.insert(std::make_pair(m_id, const_cast<Object*>(this))); objects.insert(std::make_pair(m_id, const_cast<Object*>(this)));
} }
@ -62,7 +62,7 @@ const ObjectId Object::id() const
void Object::setId(ObjectId id) void Object::setId(ObjectId id)
{ {
std::lock_guard lock(g_mutex); const std::lock_guard lock(g_mutex);
if (m_id) { if (m_id) {
auto it = objects.find(m_id); auto it = objects.find(m_id);
@ -101,7 +101,7 @@ void Object::setVersion(ObjectVersion version)
Object* get_object(ObjectId id) Object* get_object(ObjectId id)
{ {
std::lock_guard lock(g_mutex); const std::lock_guard lock(g_mutex);
auto it = objects.find(id); auto it = objects.find(id);
if (it != objects.end()) if (it != objects.end())
return it->second; return it->second;

View File

@ -73,8 +73,7 @@ namespace doc {
ASSERT(i >= 0); ASSERT(i >= 0);
if (i >= 0 && i < size()) if (i >= 0 && i < size())
return m_colors[i]; return m_colors[i];
else return 0;
return 0;
} }
color_t getEntry(int i) const { color_t getEntry(int i) const {
return entry(i); return entry(i);

View File

@ -435,7 +435,8 @@ bool Playback::decrementRepeat(const frame_t frameDelta)
PLAY_TRACE(" Repeat tag", tag->name(), " frame=", m_frame, PLAY_TRACE(" Repeat tag", tag->name(), " frame=", m_frame,
"repeat=", m_playing.back()->repeat, "repeat=", m_playing.back()->repeat,
"forward=", m_playing.back()->forward); "forward=", m_playing.back()->forward);
return true; // Tag has only 1 frame, then don't move the playback cue.
return tag->frames() > 1;
} }
else { else {
// Remove tag from played // Remove tag from played

View File

@ -282,6 +282,48 @@ TEST(Playback, SimplePingPong3)
EXPECT_FALSE(play.isStopped()); EXPECT_FALSE(play.isStopped());
} }
TEST(Playback, SimplePingPong4)
{
// A
// <>
// 0
Tag* a = make_tag("A", 0, 0, AniDir::PING_PONG, 1);
auto sprite = make_sprite(1, { a });
Playback play(sprite.get(), 0, Playback::Mode::PlayAll);
expect_frames(play, {0,0});
EXPECT_TRUE(play.isStopped());
}
TEST(Playback, SimplePingPong5)
{
// A
// <>
// 0
Tag* a = make_tag("A", 0, 0, AniDir::PING_PONG, 3);
auto sprite = make_sprite(1, { a });
Playback play(sprite.get(), 0, Playback::Mode::PlayAll);
expect_frames(play, {0,0,0,0});
EXPECT_TRUE(play.isStopped());
}
TEST(Playback, SimplePingPong6)
{
// A
// <>
// 0
Tag* a = make_tag("A", 0, 0, AniDir::PING_PONG, 0);
auto sprite = make_sprite(1, { a });
Playback play(sprite.get(), 0, Playback::Mode::PlayAll);
expect_frames(play, {0,0,0});
EXPECT_TRUE(play.isStopped());
}
TEST(Playback, SimplePingPong3Repeats) TEST(Playback, SimplePingPong3Repeats)
{ {
// A // A
@ -297,6 +339,7 @@ TEST(Playback, SimplePingPong3Repeats)
EXPECT_FALSE(play.isStopped()); EXPECT_FALSE(play.isStopped());
} }
TEST(Playback, TagOneFrame) TEST(Playback, TagOneFrame)
{ {
// A // A

View File

@ -54,8 +54,7 @@ namespace doc {
//ASSERT(index >= 0 && index < size()); //ASSERT(index >= 0 && index < size());
if (index >= 0 && index < size()) if (index >= 0 && index < size())
return m_map[index]; return m_map[index];
else return index; // No remap
return index; // No remap
} }
void merge(const Remap& other); void merge(const Remap& other);

View File

@ -9,7 +9,6 @@
#define DOC_TAG_H_INCLUDED #define DOC_TAG_H_INCLUDED
#pragma once #pragma once
#include "base/disable_copying.h"
#include "doc/anidir.h" #include "doc/anidir.h"
#include "doc/frame.h" #include "doc/frame.h"
#include "doc/object.h" #include "doc/object.h"

View File

@ -84,8 +84,7 @@ namespace doc {
ImageRef get(const tile_index ti) const { ImageRef get(const tile_index ti) const {
if (ti >= 0 && ti < size()) if (ti >= 0 && ti < size())
return m_tiles[ti].image; return m_tiles[ti].image;
else return ImageRef(nullptr);
return ImageRef(nullptr);
} }
void set(const tile_index ti, void set(const tile_index ti,
const ImageRef& image); const ImageRef& image);
@ -93,8 +92,7 @@ namespace doc {
UserData& getTileData(const tile_index ti) const { UserData& getTileData(const tile_index ti) const {
if (ti >= 0 && ti < size()) if (ti >= 0 && ti < size())
return const_cast<UserData&>(m_tiles[ti].data); return const_cast<UserData&>(m_tiles[ti].data);
else return kNoUserData;
return kNoUserData;
} }
void setTileData(const tile_index ti, void setTileData(const tile_index ti,
const UserData& userData); const UserData& userData);

View File

@ -38,8 +38,7 @@ namespace doc {
Tileset* get(const tileset_index tsi) const { Tileset* get(const tileset_index tsi) const {
if (tsi < size()) if (tsi < size())
return m_tilesets[tsi]; return m_tilesets[tsi];
else return nullptr;
return nullptr;
} }
tileset_index getIndex(const Tileset *tileset) { tileset_index getIndex(const Tileset *tileset) {

View File

@ -52,17 +52,13 @@ namespace fixmath {
errno = ERANGE; errno = ERANGE;
return -0x7FFFFFFF; return -0x7FFFFFFF;
} }
else return result;
return result;
} }
else { if ((x > 0) && (y > 0)) {
if ((x > 0) && (y > 0)) { errno = ERANGE;
errno = ERANGE; return 0x7FFFFFFF;
return 0x7FFFFFFF;
}
else
return result;
} }
return result;
} }
inline fixed fixsub(fixed x, fixed y) { inline fixed fixsub(fixed x, fixed y) {
@ -73,17 +69,13 @@ namespace fixmath {
errno = ERANGE; errno = ERANGE;
return -0x7FFFFFFF; return -0x7FFFFFFF;
} }
else return result;
return result;
} }
else { if ((x > 0) && (y < 0)) {
if ((x > 0) && (y < 0)) { errno = ERANGE;
errno = ERANGE; return 0x7FFFFFFF;
return 0x7FFFFFFF;
}
else
return result;
} }
return result;
} }
inline fixed fixmul(fixed x, fixed y) { inline fixed fixmul(fixed x, fixed y) {
@ -95,16 +87,14 @@ namespace fixmath {
errno = ERANGE; errno = ERANGE;
return (x < 0) ? -0x7FFFFFFF : 0x7FFFFFFF; return (x < 0) ? -0x7FFFFFFF : 0x7FFFFFFF;
} }
else return ftofix(fixtof(x) / fixtof(y));
return ftofix(fixtof(x) / fixtof(y));
} }
inline int fixfloor(fixed x) { inline int fixfloor(fixed x) {
/* (x >> 16) is not portable */ /* (x >> 16) is not portable */
if (x >= 0) if (x >= 0)
return (x >> 16); return (x >> 16);
else return ~((~x) >> 16);
return ~((~x) >> 16);
} }
inline int fixceil(fixed x) { inline int fixceil(fixed x) {

View File

@ -21,12 +21,12 @@ BEGIN
BEGIN BEGIN
VALUE "Comments", "https://www.aseprite.org/" VALUE "Comments", "https://www.aseprite.org/"
VALUE "CompanyName", "Igara Studio S.A." VALUE "CompanyName", "Igara Studio S.A."
VALUE "FileDescription", "Aseprite - Animated sprites editor & pixel art tool" VALUE "FileDescription", "Aseprite"
VALUE "FileVersion", "1,3,0,0" VALUE "FileVersion", "1,3,0,0"
VALUE "InternalName", "aseprite" VALUE "InternalName", "aseprite"
VALUE "LegalCopyright", "Copyright (C) 2001-2024 Igara Studio S.A." VALUE "LegalCopyright", "Copyright (C) 2001-2024 Igara Studio S.A."
VALUE "OriginalFilename", "aseprite.exe" VALUE "OriginalFilename", "aseprite.exe"
VALUE "ProductName", "ASEPRITE" VALUE "ProductName", "Aseprite"
VALUE "ProductVersion", "1,3,0,0" VALUE "ProductVersion", "1,3,0,0"
END END
END END

View File

@ -72,10 +72,9 @@ namespace render {
if (n == 2) if (n == 2)
return D2[i*2 + j]; return D2[i*2 + j];
else return
return + 4*Dn(i%(n/2), j%(n/2), n/2)
+ 4*Dn(i%(n/2), j%(n/2), n/2) + Dn(i/(n/2), j/(n/2), 2);
+ Dn(i/(n/2), j/(n/2), 2);
} }
}; };

View File

@ -76,16 +76,14 @@ namespace render {
inline int Zoom::remove(int x) const { inline int Zoom::remove(int x) const {
if (x < 0) if (x < 0)
return (x * m_den / m_num) - 1; return (x * m_den / m_num) - 1;
else return (x * m_den / m_num);
return (x * m_den / m_num);
} }
inline int Zoom::removeCeiling(int x) const { inline int Zoom::removeCeiling(int x) const {
int v = x * m_den; int v = x * m_den;
if (x < 0) if (x < 0)
return (v / m_num); return (v / m_num);
else return (v / m_num) + (v % m_num != 0);
return (v / m_num) + (v % m_num != 0);
} }
inline gfx::Rect Zoom::apply(const gfx::Rect& r) const { inline gfx::Rect Zoom::apply(const gfx::Rect& r) const {

View File

@ -134,7 +134,7 @@ public:
unloadLib(); unloadLib();
} }
bool initialized() const { bool isInitialized() const {
return m_initialized; return m_initialized;
} }
@ -239,7 +239,7 @@ SteamAPI* SteamAPI::instance()
} }
SteamAPI::SteamAPI() SteamAPI::SteamAPI()
: m_impl(new Impl) : m_impl(std::make_unique<Impl>())
{ {
ASSERT(g_instance == nullptr); ASSERT(g_instance == nullptr);
g_instance = this; g_instance = this;
@ -247,15 +247,13 @@ SteamAPI::SteamAPI()
SteamAPI::~SteamAPI() SteamAPI::~SteamAPI()
{ {
delete m_impl;
ASSERT(g_instance == this); ASSERT(g_instance == this);
g_instance = nullptr; g_instance = nullptr;
} }
bool SteamAPI::initialized() const bool SteamAPI::isInitialized() const
{ {
return m_impl->initialized(); return m_impl->isInitialized();
} }
void SteamAPI::runCallbacks() void SteamAPI::runCallbacks()

View File

@ -1,5 +1,5 @@
// Aseprite Steam Wrapper // Aseprite Steam Wrapper
// Copyright (c) 2020 Igara Studio S.A. // Copyright (c) 2020-2024 Igara Studio S.A.
// Copyright (c) 2016 David Capello // Copyright (c) 2016 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -9,6 +9,8 @@
#define STEAM_STEAM_H_INCLUDED #define STEAM_STEAM_H_INCLUDED
#pragma once #pragma once
#include <memory>
namespace steam { namespace steam {
class SteamAPI { class SteamAPI {
@ -18,7 +20,7 @@ public:
SteamAPI(); SteamAPI();
~SteamAPI(); ~SteamAPI();
bool initialized() const; bool isInitialized() const;
void runCallbacks(); void runCallbacks();
bool writeScreenshot(void* rgbBuffer, bool writeScreenshot(void* rgbBuffer,
@ -27,7 +29,7 @@ public:
private: private:
class Impl; class Impl;
Impl* m_impl; std::unique_ptr<Impl> m_impl;
}; };
} // namespace steam } // namespace steam

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2018-2023 Igara Studio S.A. // Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -125,6 +125,9 @@ Grid::Info Grid::getChildInfo(Widget* child)
void Grid::setStyle(Style* style) void Grid::setStyle(Style* style)
{ {
ASSERT(style);
if (!style)
style = Theme::getDefaultStyle();
Widget::setStyle(style); Widget::setStyle(style);
setGap(style->gap()); setGap(style->gap());
} }

View File

@ -8,7 +8,6 @@
#define UI_PAINT_H_INCLUDED #define UI_PAINT_H_INCLUDED
#pragma once #pragma once
#include "base/disable_copying.h"
#include "gfx/color.h" #include "gfx/color.h"
#include "os/paint.h" #include "os/paint.h"

View File

@ -142,6 +142,9 @@ Theme::~Theme()
set_theme(nullptr, guiscale()); set_theme(nullptr, guiscale());
} }
// static
ui::Style Theme::m_defaultStyle(nullptr);
void Theme::regenerateTheme() void Theme::regenerateTheme()
{ {
set_mouse_cursor(kNoCursor); set_mouse_cursor(kNoCursor);

View File

@ -1,5 +1,5 @@
// Aseprite UI Library // Aseprite UI Library
// Copyright (C) 2020-2022 Igara Studio S.A. // Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello // Copyright (C) 2001-2017 David Capello
// //
// This file is released under the terms of the MIT license. // This file is released under the terms of the MIT license.
@ -141,6 +141,8 @@ namespace ui {
static void drawTextBox(Graphics* g, const Widget* textbox, static void drawTextBox(Graphics* g, const Widget* textbox,
int* w, int* h, gfx::Color bg, gfx::Color fg); int* w, int* h, gfx::Color bg, gfx::Color fg);
static ui::Style* getDefaultStyle() { return &m_defaultStyle; }
protected: protected:
virtual void onRegenerateTheme() = 0; virtual void onRegenerateTheme() = 0;
@ -165,6 +167,8 @@ namespace ui {
gfx::Size& sizeHint, gfx::Size& sizeHint,
gfx::Border& borderHint, gfx::Border& borderHint,
gfx::Rect& textHint, int& textAlign); gfx::Rect& textHint, int& textAlign);
static ui::Style m_defaultStyle;
}; };
} // namespace ui } // namespace ui

View File

@ -199,7 +199,9 @@ void Widget::setTheme(Theme* theme)
void Widget::setStyle(Style* style) void Widget::setStyle(Style* style)
{ {
assert_ui_thread(); assert_ui_thread();
ASSERT(style);
if (!style)
style = Theme::getDefaultStyle();
m_style = style; m_style = style;
m_border = m_theme->calcBorder(this, style); m_border = m_theme->calcBorder(this, style);
m_bgColor = m_theme->calcBgColor(this, style); m_bgColor = m_theme->calcBgColor(this, style);

View File

@ -142,8 +142,7 @@ namespace ui {
gfx::Color bgColor() const { gfx::Color bgColor() const {
if (gfx::geta(m_bgColor) == 0 && m_parent) if (gfx::geta(m_bgColor) == 0 && m_parent)
return m_parent->bgColor(); return m_parent->bgColor();
else return m_bgColor;
return m_bgColor;
} }
// Sets the background color of the widget // Sets the background color of the widget

View File

@ -8,9 +8,7 @@
#define UPDATER_USER_AGENT_H_INCLUDED #define UPDATER_USER_AGENT_H_INCLUDED
#pragma once #pragma once
#include "base/disable_copying.h" #include <string>
#include <iosfwd>
namespace updater { namespace updater {

View File

@ -1,4 +1,4 @@
-- Copyright (C) 2023 Igara Studio S.A. -- Copyright (C) 2023-2024 Igara Studio S.A.
-- --
-- This file is released under the terms of the MIT license. -- This file is released under the terms of the MIT license.
-- Read LICENSE.txt for more information. -- Read LICENSE.txt for more information.
@ -8,7 +8,11 @@
-- --
dofile('./test_utils.lua') dofile('./test_utils.lua')
do -- You can use this code to fill file-tests-props.aseprite properties,
-- but we did this just one time in the past, now we want to check
-- that we can read the properties correctly from the file, e.g. to
-- check the correct endianness of the platform.
if false then
local spr = Sprite{ fromFile="sprites/file-tests-props.aseprite" } local spr = Sprite{ fromFile="sprites/file-tests-props.aseprite" }
-- Set sprite custom properties -- Set sprite custom properties
@ -16,6 +20,7 @@ do
spr.properties.b = 1 spr.properties.b = 1
spr.properties.c = "hi" spr.properties.c = "hi"
spr.properties.d = 2.3 spr.properties.d = 2.3
spr.properties.e = 8.123456e-12
spr.properties("ext").a = {"one", "two", "three"} spr.properties("ext").a = {"one", "two", "three"}
-- Set layer custom properties -- Set layer custom properties
spr.layers[2].properties.a = "i'm the layer 3" spr.layers[2].properties.a = "i'm the layer 3"
@ -24,11 +29,11 @@ do
spr.layers[1].tileset.properties.a = "i'm a tilemap" spr.layers[1].tileset.properties.a = "i'm a tilemap"
spr.layers[1].tileset.properties.b = 11 spr.layers[1].tileset.properties.b = 11
spr.layers[1].tileset.properties("ext").a = "text from extension" spr.layers[1].tileset.properties("ext").a = "text from extension"
-- Create some tiles with properties -- Set tiles custom properties
local tile = spr:newTile(spr.layers[1].tileset, 1) local tile = spr.layers[1].tileset:tile(1)
tile.properties.a = 320 tile.properties.a = 320
tile.properties.b = 330 tile.properties.b = 330
tile = spr:newTile(spr.layers[1].tileset, 2) tile = spr.layers[1].tileset:tile(2)
tile.properties.a = 640 tile.properties.a = 640
tile.properties.b = 650 tile.properties.b = 650
-- Set tags custom properties -- Set tags custom properties
@ -42,19 +47,25 @@ do
spr.slices[1].properties = {a=Point(3,4), b=Size(10,20)} spr.slices[1].properties = {a=Point(3,4), b=Size(10,20)}
spr.slices[1].properties("ext", {a=Rectangle(10,20,30,40)}) spr.slices[1].properties("ext", {a=Rectangle(10,20,30,40)})
spr:saveAs("sprites/file-tests-props.aseprite")
spr:close()
end
do
-- Test load/save file (and keep the properties intact)
local spr = Sprite{ fromFile="sprites/file-tests-props.aseprite" }
assert(#spr.properties == 5)
spr:saveAs("_test_userdata_codec_1.aseprite") spr:saveAs("_test_userdata_codec_1.aseprite")
spr:close() spr:close()
local origSpr = Sprite{ fromFile="sprites/file-tests-props.aseprite" } local spr = Sprite{ fromFile="_test_userdata_codec_1.aseprite" }
spr = Sprite{ fromFile="_test_userdata_codec_1.aseprite" } assert(#spr.properties == 5)
assert_sprites_eq(origSpr, spr)
origSpr:close()
assert(#spr.properties == 4)
assert(#spr.properties("ext") == 1) assert(#spr.properties("ext") == 1)
assert(spr.properties.a == true) assert(spr.properties.a == true)
assert(spr.properties.b == 1) assert(spr.properties.b == 1)
assert(spr.properties.c == "hi") assert(spr.properties.c == "hi")
assert(spr.properties.d == 2.3) assert(spr.properties.d == 2.3)
assert(spr.properties.e == 8.123456e-12)
assert(spr.properties("ext").a[1] == "one") assert(spr.properties("ext").a[1] == "one")
assert(spr.properties("ext").a[2] == "two") assert(spr.properties("ext").a[2] == "two")
assert(spr.properties("ext").a[3] == "three") assert(spr.properties("ext").a[3] == "three")

View File

@ -28,10 +28,11 @@
* `4f-index-4x4.aseprite`: Indexed, 4 frames, 1 layer, mask color set * `4f-index-4x4.aseprite`: Indexed, 4 frames, 1 layer, mask color set
to index 0. to index 0.
* `file-tests-props.aseprite`: Indexed, 64x64, 6 frames, 4 layers (one * `file-tests-props.aseprite`: Indexed, 64x64, 6 frames, 4 layers (one
of them is a tilemap), 13 cels, 1 tag. of them is a tilemap), 13 cels, 1 tag, pre-defined user data
properties of all kinds in several sprite elements.
* `slices.aseprite`: Indexed, 4x4, background layer, 2 slices. * `slices.aseprite`: Indexed, 4x4, background layer, 2 slices.
* `slices-moving.aseprite`: Indexed, 4x4, 1 linked cel in 4 frames, * `slices-moving.aseprite`: Indexed, 4x4, 1 linked cel in 4 frames,
background layer, 1 slice with 4 keyframes (each keyframe with a background layer, 1 slice with 4 keyframes (each keyframe with a
different position/size). different position/size).
* `2x2tilemap2x2tile.aseprite`: RGB, 6x6, 2x2 tilemap layer, 5 tiles tileset, * `2x2tilemap2x2tile.aseprite`: RGB, 6x6, 2x2 tilemap layer, 5 tiles tileset,
2x2 tile size, 1 frame. 2x2 tile size, 1 frame.

2
third_party/json11 vendored

@ -1 +1 @@
Subproject commit e5868fff1fc5128077ed7699bdaa20ae47c47eca Subproject commit 92ba6ce0fa1f1c8fd8783b6930b52539b3861888