Compare commits

...

29 Commits

Author SHA1 Message Date
دانتي باولا 2bffe5b2a3
Merge 27f19959bd into 5f7cc42333 2025-08-26 16:48:14 -03:00
David Capello 5f7cc42333 Remove unnecessary call to regenerateCols() in Timeline::refresh()
Related to:
https://github.com/aseprite/aseprite/pull/5367#issuecomment-3225287017
2025-08-26 15:39:55 -03:00
Christian Kaiser 8e75cfc4c7 [lua] Refresh timeline when changing layer collapsed status (fix #5366) 2025-08-26 15:35:28 -03:00
David Capello 983b07383f [lua] Fix default autofit for a Dialog() when autofit is not specified (#5176 / #5321) 2025-08-26 11:23:48 -03:00
Christian Kaiser c57554646b Escape characters in the console that we can't show properly (fix #5324) 2025-08-26 11:21:08 -03:00
Christian Kaiser 74953174d6 [lua] Added `autofit` and `sizeHint` properties to Dialog (fix #5176) 2025-08-26 11:05:06 -03:00
Christian Kaiser 49fa35237a Activate the native window when asking the user to save sprite changes (fix #3542 / #5318) 2025-08-26 09:57:51 -03:00
Christian Kaiser 0ccf9dcc4f [lua] Add app.tip (#5316 / #5348) 2025-08-26 09:00:40 -03:00
David Capello 194f8424a8 Add right-click to configure the cancel selection button (#5102 / #5145) 2025-08-26 07:55:14 -03:00
Joshua Lopez debab653fa Fix marquee tool escape deselection (fix #5102) 2025-08-25 16:51:03 -03:00
hwabis 6e9024d54d Fix mouse wheel zooming not working with zoom tool (quick) 2025-08-25 16:35:14 -03:00
David Capello 1fa7fd0831 Minor UI fixes for dialogs with user data
This patch unifies the behavior of all dialogs with user data to
expand the window vertically onToggleUserData().
2025-08-25 16:20:37 -03:00
David Capello ab6b040e83 Rename Mask::m_freeze_count -> Mask::m_freezes 2025-08-25 15:30:23 -03:00
Gaspar Capello bc312a37b3 Fix Color Management regression (fix #5333)
Before this fix, the assigned color space was always sRGB (default),
so this color space was used for the rest of the rendering.
Now the color space for the back layer of the display
has been made explicit.
2025-08-25 13:10:25 -03:00
Christian Kaiser 3129fda977 [lua] Add missing "DIAGONAL" FlipType (fix #5359) 2025-08-17 14:58:26 -03:00
David Capello 6cb61fb41e Remove deprecated issue template 2025-08-14 14:17:56 -03:00
David Capello aa817a8d2a
Update issue templates 2025-08-14 14:11:47 -03:00
David Capello 40031f83d8 [lua] Fix typo in Sprite:newCel() error 2025-08-13 13:06:05 -03:00
David Capello 90282dbc40 Fix cast error in HarfBuzz library between function types
This includes the following patch:
60c6b7786d
2025-08-11 15:29:58 -03:00
Martín Capello 1227f9c49c Refactor getLayerIndexFromSprite into LayerGroup 2025-08-11 14:47:56 -03:00
Martín Capello d61ae919ad Fix crash dropping file on timeline (fix #5289) 2025-08-11 14:47:56 -03:00
David Capello b2b2583176 [lua] Update scripting API version to 35 2025-08-07 20:18:10 -03:00
David Capello b535212642 Update laf module 2025-08-07 20:14:43 -03:00
Christian Kaiser 229a3cdf65 [lua] Add onchecked parameter to Commands 2025-08-07 18:57:28 -03:00
Christian Kaiser b3814ec912 Unify ContextBar updates when moving pixels, tooltips (fix #5329) 2025-08-06 20:20:42 -03:00
David Capello e88f3bb413 Show error if curl/unzip tools aren't available (fix #5309) 2025-08-06 14:44:21 -03:00
Christian Kaiser eaa2bdf0af [lua] Process mnemonics consistently for plugins (fix #5250) 2025-08-04 15:58:29 -03:00
Christian Kaiser 57309e5aa5 Allow gif encoding to be stopped (fix #2619) 2025-08-01 20:46:32 -03:00
Liebranca 27f19959bd Fix tilemap layer copy/paste 2025-07-24 00:01:20 -03:00
44 changed files with 534 additions and 122 deletions

View File

@ -1,9 +0,0 @@
Describe your bug report or feature request here
...
...
...
### Aseprite and System version
* Aseprite version: version number, installer/portable/Steam/beta/dev/commit-hash
* System: Windows/macOS/Linux, version, distribution

32
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,32 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug, triage
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots or a screen recording to help explain your problem.
**Aseprite & System (please complete the following information):**
- Aseprite: [version number, installer/portable/Steam/beta/dev/commit-hash]
- System: [Windows/macOS/Linux, version, distribution]
- Extensions: [List the extensions you have installed]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,29 @@
---
name: Feature request
about: Suggest an idea for Aseprite
title: ''
labels: feature, triage
assignees: ''
---
**Did other user suggested a similar idea?**
- [ ] No
- [ ] Yes/Links to similar ideas
> You can try to find a similar feature requests before in:
> - GitHub issues: https://github.com/aseprite/aseprite/issues?q=label%3Afeature
> - Community site: https://community.aseprite.org/c/features/7
> - Steam community: https://steamcommunity.com/app/431730/discussions/1/
> In case you find a similar feature request, making a comment there will be useful to give some traction and show interest in the feature.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -428,9 +428,17 @@ if [ ! -d "$skia_library_dir" ] ; then
skia_url=$(bash laf/misc/skia-url.sh $skia_build)
skia_file=$(basename $skia_url)
if [ ! -f "$skia_dir/$skia_file" ] ; then
if ! command -v curl >/dev/null 2>&1 ; then
echo "Error: 'curl' command line tool is not available in PATH"
exit 1
fi
curl --ssl-revoke-best-effort -L -o "$skia_dir/$skia_file" "$skia_url"
fi
if [ ! -d "$skia_library_dir" ] ; then
if ! command -v unzip >/dev/null 2>&1 ; then
echo "Error: 'unzip' command line tool is not available in PATH"
exit 1
fi
unzip -n -d "$skia_dir" "$skia_dir/$skia_file"
fi
else

View File

@ -146,6 +146,10 @@
<value id="KEEP_AS_IS" value="1" />
<value id="RAW_IMAGE" value="2" />
</enum>
<enum id="CancelSelection">
<value id="DISCARD" value="0" />
<value id="DESELECT" value="1" />
</enum>
</types>
<global>
@ -325,6 +329,7 @@
<option id="force_rotsprite" type="bool" default="false" />
<option id="multicel_when_layers_or_frames" type="bool" default="true" />
<option id="snap_to_grid" type="bool" default="true" />
<option id="cancel_selection" type="CancelSelection" default="CancelSelection::DISCARD" />
</section>
<section id="quantization">
<option id="with_alpha" type="bool" default="true" />

View File

@ -554,6 +554,8 @@ amount = Amount:
flatten = Merge layers
[context_bar]
discard_changes = Discard Changes
deselect = Deselect
center = Center
fit_screen = Fit Screen
back = Back
@ -582,7 +584,7 @@ pixel_perfect = Pixel-perfect
linear_gradient = Linear Gradient
radial_gradient = Radial Gradient
drop_pixel = Drop pixels here (Enter)
cancel_drag = Cancel drag and drop (Esc)
cancel_drag = Cancel drag and drop (Esc)\nRight-click: Configure action
auto_select_layer = Auto Select Layer
all = All
none = None
@ -621,6 +623,14 @@ current_layer = Current Layer
first_ref_layer = First Reference Layer
pick = Pick:
sample = Sample:
position_label = P:
rotation_label = R:
position_x = X Position
position_y = Y Position
size_width = Width
size_height = Height
rotation_angle = Angle
rotation_skew = Skew
[convolution_matrix]
reload_stock = &Reload Stock

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Aseprite -->
<!-- Copyright (C) 2019-2024 Igara Studio S.A. -->
<!-- Copyright (C) 2019-2025 Igara Studio S.A. -->
<!-- Copyright (C) 2017-2018 David Capello -->
<gui>
<window id="slice_properties" text="@.title" help="slices#slice-properties">
<vbox>
<vbox expansive="true">
<grid id="properties_grid" columns="3">
<label id="label1" text="@.name" />
<entry id="name" maxsize="256" magnet="true" cell_align="horizontal" expansive="true" />
<button id="user_data" icon="icon_user_data" maxsize="32" tooltip="@.user_data_tooltip" />
</grid>
<grid columns="2">
<separator horizontal="true" cell_hspan="2" />
<grid columns="3" expansive="true">
<separator horizontal="true" cell_hspan="3" />
<box />
<hbox homogeneous="true">
@ -20,6 +20,7 @@
<label text="@.width" />
<label text="@.height" />
</hbox>
<boxfiller cell_align="horizontal" />
<label text="@.bounds" />
<hbox homogeneous="true">
@ -28,6 +29,7 @@
<expr id="bounds_w" />
<expr id="bounds_h" />
</hbox>
<boxfiller />
<check text="@.center" id="center" />
<hbox homogeneous="true">
@ -36,16 +38,18 @@
<expr id="center_w" />
<expr id="center_h" />
</hbox>
<boxfiller />
<check text="@.pivot" id="pivot" />
<hbox>
<expr id="pivot_x" />
<expr id="pivot_y" />
</hbox>
<boxfiller />
<separator horizontal="true" cell_hspan="2" />
<hbox cell_hspan="2">
<boxfiller cell_align="vertical" cell_hspan="3" />
<separator horizontal="true" cell_hspan="3" cell_align="horizontal" />
<hbox cell_hspan="3">
<boxfiller />
<hbox homogeneous="true">
<button text="@general.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Aseprite -->
<!-- Copyright (C) 2019-2021 Igara Studio S.A. -->
<!-- Copyright (C) 2019-2025 Igara Studio S.A. -->
<!-- Copyright (C) 2015-2018 David Capello -->
<gui>
<window id="tag_properties" text="@.title">
@ -23,13 +23,14 @@
<check text="@.repeat" id="limit_repeat" />
<vbox id="repeat_placeholder" cell_hspan="2" />
</grid>
<boxfiller />
<grid columns="2">
<separator horizontal="true" cell_hspan="2" minwidth="180" />
<separator horizontal="true" cell_align="horizontal" cell_hspan="2" minwidth="180" />
<box horizontal="true" homogeneous="true" cell_hspan="2" cell_align="right">
<hbox homogeneous="true" cell_hspan="2" cell_align="right">
<button text="@general.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />
<button text="@general.cancel" closewindow="true" />
</box>
</hbox>
</grid>
</vbox>
</window>

2
laf

@ -1 +1 @@
Subproject commit 8ec4b553f1618f7a4b47cdcf4cfc2663266111ac
Subproject commit 39f6dca9c623e2d005a5c5609afa97ac749b4e33

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020-2023 Igara Studio S.A.
// Copyright (C) 2020-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -101,12 +101,12 @@ public:
if (countCels() > 0) {
m_userDataView.configureAndSet((m_cel ? m_cel->data()->userData() : UserData()),
g_window->propertiesGrid());
propertiesGrid());
}
else if (!m_cel)
m_userDataView.setVisible(false, false);
g_window->expandWindow(gfx::Size(g_window->bounds().w, g_window->sizeHint().h));
expandWindow(gfx::Size(bounds().w, sizeHint().h));
updateFromCel();
}
@ -281,7 +281,7 @@ private:
{
if (countCels() > 0) {
m_userDataView.toggleVisibility();
g_window->expandWindow(gfx::Size(g_window->bounds().w, g_window->sizeHint().h));
expandWindow(gfx::Size(bounds().w, sizeHint().h));
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2020-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -166,7 +166,7 @@ public:
m_document->add_observer(this);
if (countLayers() > 0) {
m_userDataView.configureAndSet(m_layer->userData(), g_window->propertiesGrid());
m_userDataView.configureAndSet(m_layer->userData(), propertiesGrid());
if (m_remapAfterConfigure) {
remapWindow();
centerWindow();
@ -368,8 +368,7 @@ private:
{
if (m_layer) {
m_userDataView.toggleVisibility();
g_window->remapWindow();
manager()->invalidate();
expandWindow(gfx::Size(bounds().w, sizeHint().h));
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -188,8 +188,7 @@ private:
void onToggleUserData()
{
m_userDataView.toggleVisibility();
remapWindow();
manager()->invalidate();
expandWindow(gfx::Size(bounds().w, sizeHint().h));
}
void onTilesedDuplicated(const Tileset* tilesetClone)

View File

@ -81,7 +81,7 @@ public:
~ConsoleWindow() { TRACE_CON("CON: ~ConsoleWindow this=", this); }
void addMessage(const std::string& msg)
void addMessage(std::string msg)
{
if (!m_hasText) {
m_hasText = true;
@ -93,6 +93,17 @@ public:
gfx::Point pt = m_view.viewScroll();
const bool autoScroll = (pt.y >= maxSize.h - visible.h);
// Escape characters we can't show properly
for (size_t i = 0; i < msg.size(); i++) {
switch (msg[i]) {
case '\a':
case '\b':
case '\r':
case '\t':
case '\v': msg[i] = ' ';
}
}
m_textbox.setText(m_textbox.text() + msg);
if (autoScroll) {

View File

@ -1095,6 +1095,9 @@ public:
gifframe_t nframes = totalFrames();
for (gifframe_t gifFrame = 0; gifFrame < nframes; ++gifFrame) {
ASSERT(frame_it != frame_end);
if (m_fop->isStop())
break;
frame_t frame = *frame_it;
++frame_it;

View File

@ -10,6 +10,6 @@
// Increment this value if the scripting API is modified between two
// released Aseprite versions.
#define API_VERSION 34
#define API_VERSION 35
#endif

View File

@ -34,10 +34,10 @@
#include "app/tools/tool_loop_manager.h"
#include "app/tx.h"
#include "app/ui/context_bar.h"
#include "app/ui/doc_view.h"
#include "app/ui/editor/editor.h"
#include "app/ui/editor/tool_loop_impl.h"
#include "app/ui/main_window.h"
#include "app/ui/status_bar.h"
#include "app/ui/timeline/timeline.h"
#include "app/ui_context.h"
#include "base/fs.h"
@ -498,6 +498,44 @@ int App_useTool(lua_State* L)
return 0;
}
int App_tip(lua_State* L)
{
const auto* ctx = App::instance()->context();
if (!ctx || !ctx->isUIAvailable() || !StatusBar::instance())
return 0; // No UI to show the tooltip
std::string text;
double duration = 2.0;
if (lua_istable(L, 1)) {
int type = lua_getfield(L, 1, "text");
if (type == LUA_TSTRING)
text = lua_tostring(L, -1);
lua_pop(L, 1);
type = lua_getfield(L, 1, "duration");
if (type == LUA_TNUMBER)
duration = lua_tonumber(L, -1);
lua_pop(L, 1);
}
else {
if (!lua_isstring(L, 1))
return luaL_error(L, "app.tip text parameter must be a string");
text = lua_tostring(L, 1);
if (lua_isnumber(L, 2))
duration = lua_tonumber(L, 2);
}
if (text.empty())
return luaL_error(L, "app.tip text cannot be empty");
int msecs = std::clamp<int>(duration * 1000.0, 500, 30000);
StatusBar::instance()->showTip(msecs, text);
return 0;
}
int App_get_events(lua_State* L)
{
push_app_events(L);
@ -820,6 +858,7 @@ const luaL_Reg App_methods[] = {
{ "alert", App_alert },
{ "refresh", App_refresh },
{ "useTool", App_useTool },
{ "tip", App_tip },
{ nullptr, nullptr }
};

View File

@ -60,6 +60,8 @@ namespace app { namespace script {
using namespace ui;
static constexpr const int kDefaultAutofit = ui::LEFT | ui::TOP;
namespace {
class DialogWindow : public WindowWithHand {
@ -107,6 +109,7 @@ struct Dialog {
std::map<std::string, ui::Widget*> dataWidgets;
std::map<std::string, ui::Widget*> labelWidgets;
int currentRadioGroup = 0;
int autofit = kDefaultAutofit;
// Member used to hold current state about the creation of a tabs
// widget. After creation it is reset to null to be ready for the
@ -193,6 +196,13 @@ struct Dialog {
it->second->setText(text);
}
void setAutofit(int align)
{
// Accept both 0 or a valid subset of align parameters.
if (align == 0 || (align & (ui::LEFT | ui::RIGHT | ui::TOP | ui::BOTTOM)))
autofit = align;
}
Display* parentDisplay() const
{
Display* parentDisplay = window.parentDisplay();
@ -231,8 +241,10 @@ struct Dialog {
window.display()->nativeWindow()->setFrame(frame);
}
else {
gfx::Rect oldBounds(window.bounds());
window.setBounds(rc);
window.invalidate();
parentDisplay()->invalidateRect(oldBounds);
}
}
@ -367,6 +379,7 @@ int Dialog_new(lua_State* L)
ui::Window::Type windowType = ui::Window::WithTitleBar;
std::string title = "Script";
bool sizeable = true;
int autofit = kDefaultAutofit;
if (lua_isstring(L, 1)) {
title = lua_tostring(L, 1);
}
@ -385,9 +398,16 @@ int Dialog_new(lua_State* L)
if (type != LUA_TNIL && !lua_toboolean(L, -1))
sizeable = false;
lua_pop(L, 1);
type = lua_getfield(L, 1, "autofit");
if (type != LUA_TNIL) {
autofit = lua_tointeger(L, -1);
}
lua_pop(L, 1);
}
auto dlg = push_new<Dialog>(L, windowType, title, sizeable);
dlg->setAutofit(autofit);
// The uservalue of the dialog userdata will contain a table that
// stores all the callbacks to handle events. As these callbacks can
@ -1516,6 +1536,10 @@ int Dialog_modify(lua_State* L)
type = lua_getfield(L, 2, "text");
if (const char* s = lua_tostring(L, -1)) {
widget->setText(s);
// Re-process mnemonics for buttons
if (widget->type() == WidgetType::kButtonWidget)
widget->processMnemonicFromText();
relayout = true;
}
lua_pop(L, 1);
@ -1650,8 +1674,26 @@ int Dialog_modify(lua_State* L)
if (relayout && !dlg->window.isResizing()) {
dlg->window.layout();
gfx::Rect bounds(dlg->window.bounds().w, dlg->window.sizeHint().h);
dlg->window.expandWindow(bounds.size());
if (dlg->autofit > 0) {
gfx::Rect oldBounds = dlg->window.bounds();
gfx::Size resize(oldBounds.size());
if (dlg->autofit & ui::TOP || dlg->autofit & ui::BOTTOM)
resize.h = dlg->window.sizeHint().h;
if (dlg->autofit & ui::LEFT || dlg->autofit & ui::RIGHT)
resize.w = dlg->window.sizeHint().w;
gfx::Size difference = resize - oldBounds.size();
const auto& bounds = dlg->getWindowBounds();
gfx::Rect newBounds(bounds.x, bounds.y, resize.w, resize.h);
if (dlg->autofit & ui::BOTTOM)
newBounds.y = bounds.y - difference.h;
if (dlg->autofit & ui::RIGHT)
newBounds.x = bounds.x - difference.w;
dlg->setWindowBounds(newBounds);
}
}
}
lua_pushvalue(L, 1);
@ -1873,6 +1915,27 @@ int Dialog_get_bounds(lua_State* L)
return 1;
}
int Dialog_get_sizeHint(lua_State* L)
{
auto dlg = get_obj<Dialog>(L, 1);
push_new<gfx::Size>(L, dlg->window.sizeHint());
return 1;
}
int Dialog_get_autofit(lua_State* L)
{
auto dlg = get_obj<Dialog>(L, 1);
lua_pushinteger(L, dlg->autofit);
return 1;
}
int Dialog_set_autofit(lua_State* L)
{
auto dlg = get_obj<Dialog>(L, 1);
dlg->setAutofit(lua_tointeger(L, 2));
return 0;
}
int Dialog_set_bounds(lua_State* L)
{
auto dlg = get_obj<Dialog>(L, 1);
@ -1914,9 +1977,11 @@ const luaL_Reg Dialog_methods[] = {
};
const Property Dialog_properties[] = {
{ "data", Dialog_get_data, Dialog_set_data },
{ "bounds", Dialog_get_bounds, Dialog_set_bounds },
{ nullptr, nullptr, nullptr }
{ "data", Dialog_get_data, Dialog_set_data },
{ "bounds", Dialog_get_bounds, Dialog_set_bounds },
{ "autofit", Dialog_get_autofit, Dialog_set_autofit },
{ "sizeHint", Dialog_get_sizeHint, nullptr },
{ nullptr, nullptr, nullptr }
};
} // anonymous namespace

View File

@ -452,6 +452,7 @@ Engine::Engine() : L(luaL_newstate()), m_delegate(nullptr), m_printLastResult(fa
lua_setglobal(L, "FlipType");
setfield_integer(L, "HORIZONTAL", doc::algorithm::FlipType::FlipHorizontal);
setfield_integer(L, "VERTICAL", doc::algorithm::FlipType::FlipVertical);
setfield_integer(L, "DIAGONAL", doc::algorithm::FlipType::FlipDiagonal);
lua_pop(L, 1);
lua_newtable(L);

View File

@ -22,6 +22,7 @@
#include "app/script/luacpp.h"
#include "app/script/userdata.h"
#include "app/tx.h"
#include "app/ui/timeline/timeline.h"
#include "doc/layer.h"
#include "doc/layer_tilemap.h"
#include "doc/sprite.h"
@ -355,6 +356,9 @@ int Layer_set_isCollapsed(lua_State* L)
{
auto layer = get_docobj<Layer>(L, 1);
layer->setCollapsed(lua_toboolean(L, 2));
if (auto* timeline = App::instance()->timeline())
timeline->refresh();
return 0;
}
@ -362,6 +366,9 @@ int Layer_set_isExpanded(lua_State* L)
{
auto layer = get_docobj<Layer>(L, 1);
layer->setCollapsed(!lua_toboolean(L, 2));
if (auto* timeline = App::instance()->timeline())
timeline->refresh();
return 0;
}

View File

@ -29,11 +29,16 @@ struct Plugin {
class PluginCommand : public Command {
public:
PluginCommand(const std::string& id, const std::string& title, int onclickRef, int onenabledRef)
PluginCommand(const std::string& id,
const std::string& title,
int onclickRef,
int onenabledRef,
int oncheckedRef)
: Command(id.c_str(), CmdUIOnlyFlag)
, m_title(title)
, m_onclickRef(onclickRef)
, m_onenabledRef(onenabledRef)
, m_oncheckedRef(oncheckedRef)
{
}
@ -72,28 +77,44 @@ protected:
bool onEnabled(Context* context) override
{
if (m_onenabledRef) {
script::Engine* engine = App::instance()->scriptEngine();
lua_State* L = engine->luaState();
lua_rawgeti(L, LUA_REGISTRYINDEX, m_onenabledRef);
if (lua_pcall(L, 0, 1, 0)) {
if (const char* s = lua_tostring(L, -1)) {
Console().printf("Error: %s", s);
return false;
}
}
else {
bool ret = lua_toboolean(L, -1);
lua_pop(L, 1);
return ret;
}
return callScriptRef(m_onenabledRef);
}
return true;
}
bool onChecked(Context* context) override
{
if (m_oncheckedRef) {
return callScriptRef(m_oncheckedRef);
}
return false;
}
private:
bool callScriptRef(int ref)
{
ASSERT(ref);
script::Engine* engine = App::instance()->scriptEngine();
lua_State* L = engine->luaState();
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
if (lua_pcall(L, 0, 1, 0)) {
if (const char* s = lua_tostring(L, -1)) {
Console().printf("Error: %s", s);
return false;
}
}
else {
bool ret = lua_toboolean(L, -1);
lua_pop(L, 1);
return ret;
}
}
std::string m_title;
int m_onclickRef;
int m_onenabledRef;
int m_oncheckedRef;
};
void deleteCommandIfExistent(Extension* ext, const std::string& id)
@ -126,6 +147,7 @@ int Plugin_newCommand(lua_State* L)
if (lua_istable(L, 2)) {
std::string id, title, group;
int onenabledRef = 0;
int oncheckedRef = 0;
lua_getfield(L, 2, "id");
if (const char* s = lua_tostring(L, -1)) {
@ -156,6 +178,14 @@ int Plugin_newCommand(lua_State* L)
lua_pop(L, 1);
}
type = lua_getfield(L, 2, "onchecked");
if (type == LUA_TFUNCTION) {
oncheckedRef = luaL_ref(L, LUA_REGISTRYINDEX); // does a pop
}
else {
lua_pop(L, 1);
}
type = lua_getfield(L, 2, "onclick");
if (type == LUA_TFUNCTION) {
int onclickRef = luaL_ref(L, LUA_REGISTRYINDEX);
@ -164,7 +194,7 @@ int Plugin_newCommand(lua_State* L)
// overwriting a previous registered command)
deleteCommandIfExistent(plugin->ext, id);
auto cmd = new PluginCommand(id, title, onclickRef, onenabledRef);
auto cmd = new PluginCommand(id, title, onclickRef, onenabledRef, oncheckedRef);
Commands::instance()->add(cmd);
plugin->ext->addCommand(id);
@ -172,6 +202,7 @@ int Plugin_newCommand(lua_State* L)
if (!group.empty() && App::instance()->isGui()) { // On CLI menus do not make sense
if (auto appMenus = AppMenus::instance()) {
auto menuItem = std::make_unique<AppMenuItem>(title, id);
menuItem->processMnemonicFromText();
appMenus->addMenuItemIntoGroup(group, std::move(menuItem));
}
}

View File

@ -457,7 +457,7 @@ int Sprite_newCel(lua_State* L)
auto sprite = get_docobj<Sprite>(L, 1);
auto layerBase = get_docobj<Layer>(L, 2);
if (!layerBase->isImage())
return luaL_error(L, "unexpected kinf of layer in Sprite:newCel()");
return luaL_error(L, "unexpected kind of layer in Sprite:newCel()");
frame_t frame = get_frame_number_from_arg(L, 3);
if (frame < 0 || frame > sprite->lastFrame())

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -388,6 +388,7 @@ FOR_ENUM(app::gen::TimelinePosition)
FOR_ENUM(app::gen::ToGrayAlgorithm)
FOR_ENUM(app::gen::WindowColorProfile)
FOR_ENUM(app::gen::AlphaRange)
FOR_ENUM(app::gen::CancelSelection)
FOR_ENUM(app::tools::ColorFromTo)
FOR_ENUM(app::tools::DynamicSensor)
FOR_ENUM(app::tools::FreehandAlgorithm)

View File

@ -1001,12 +1001,12 @@ public:
m_angle.setSuffix("°");
m_skew.setSuffix("°");
addChild(new Label("P:"));
addChild(new Label(Strings::context_bar_position_label()));
addChild(&m_x);
addChild(&m_y);
addChild(&m_w);
addChild(&m_h);
addChild(new Label("R:"));
addChild(new Label(Strings::context_bar_rotation_label()));
addChild(&m_angle);
addChild(&m_skew);
@ -1047,6 +1047,16 @@ public:
m_skew.Change.connect([this] { onChangeSkew(); });
}
void setupTooltips(TooltipManager* tooltipManager)
{
tooltipManager->addTooltipFor(&m_x, Strings::context_bar_position_x(), BOTTOM);
tooltipManager->addTooltipFor(&m_y, Strings::context_bar_position_y(), BOTTOM);
tooltipManager->addTooltipFor(&m_w, Strings::context_bar_size_width(), BOTTOM);
tooltipManager->addTooltipFor(&m_h, Strings::context_bar_size_height(), BOTTOM);
tooltipManager->addTooltipFor(&m_angle, Strings::context_bar_rotation_angle(), BOTTOM);
tooltipManager->addTooltipFor(&m_skew, Strings::context_bar_rotation_skew(), BOTTOM);
}
void update(const Transformation& t)
{
auto rc = t.bounds();
@ -1397,6 +1407,7 @@ public:
}
obs::signal<void(ContextBarObserver::DropAction)> DropPixels;
obs::signal<void(ContextBarObserver::DropAction, const gfx::Point&)> ConfigureDropPixels;
protected:
void onItemChange(Item* item) override
@ -1408,6 +1419,21 @@ protected:
case 1: DropPixels(ContextBarObserver::CancelDrag); break;
}
}
void onRightClick(Item* item) override
{
ButtonSet::onRightClick(item);
const gfx::Rect rc = item->bounds();
const gfx::Point pt(rc.x, rc.y2());
auto action = ContextBarObserver::DropPixels;
switch (selectedItem()) {
case 0: action = ContextBarObserver::DropPixels; break;
case 1: action = ContextBarObserver::CancelDrag; break;
}
ConfigureDropPixels(action, pt);
}
};
class ContextBar::EyedropperField : public HBox {
@ -1942,6 +1968,8 @@ ContextBar::ContextBar(TooltipManager* tooltipManager, ColorBar* colorBar)
m_keysConn = KeyboardShortcuts::instance()->UserChange.connect(
[this, tooltipManager] { setupTooltips(tooltipManager); });
m_dropPixelsConn = m_dropPixels->DropPixels.connect(&ContextBar::onDropPixels, this);
m_configureDropPixelsConn =
m_dropPixels->ConfigureDropPixels.connect(&ContextBar::onConfigureDropPixels, this);
setActiveBrush(createBrushFromPreferences());
@ -2086,6 +2114,14 @@ void ContextBar::onDropPixels(ContextBarObserver::DropAction action)
notify_observers(&ContextBarObserver::onDropPixels, action);
}
void ContextBar::onConfigureDropPixels(ContextBarObserver::DropAction action, const gfx::Point& pt)
{
notify_observers<ContextBarObserver::DropAction, const gfx::Point&>(
&ContextBarObserver::onConfigureDropPixels,
action,
pt);
}
void ContextBar::updateSliceFields(const Site& site)
{
if (site.sprite())
@ -2626,6 +2662,7 @@ void ContextBar::setupTooltips(TooltipManager* tooltipManager)
m_dropPixels->setupTooltips(tooltipManager);
m_symmetry->setupTooltips(tooltipManager);
m_sliceFields->setupTooltips(tooltipManager);
m_transformation->setupTooltips(tooltipManager);
}
void ContextBar::registerCommands()

View File

@ -129,6 +129,7 @@ private:
void onFgOrBgColorChange(doc::Brush::ImageColor imageColor);
void onOpacityRangeChange();
void onDropPixels(ContextBarObserver::DropAction action);
void onConfigureDropPixels(ContextBarObserver::DropAction action, const gfx::Point& pt);
void updateSliceFields(const Site& site);
// ActiveToolObserver impl
@ -213,6 +214,7 @@ private:
obs::scoped_connection m_alphaRangeConn;
obs::scoped_connection m_keysConn;
obs::scoped_connection m_dropPixelsConn;
obs::scoped_connection m_configureDropPixelsConn;
obs::scoped_connection m_sizeConn;
obs::scoped_connection m_angleConn;
obs::scoped_connection m_opacityConn;

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2025 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@ -8,6 +9,8 @@
#define APP_CONTEXT_BAR_OBSERVER_H_INCLUDED
#pragma once
#include "gfx/fwd.h"
namespace app {
class ContextBarObserver {
@ -16,6 +19,7 @@ public:
virtual ~ContextBarObserver() {}
virtual void onDropPixels(DropAction action) {}
virtual void onConfigureDropPixels(DropAction action, const gfx::Point& pt) {}
};
} // namespace app

View File

@ -42,6 +42,7 @@
#include "doc/sprite.h"
#include "fmt/format.h"
#include "ui/alert.h"
#include "ui/display.h"
#include "ui/menu.h"
#include "ui/message.h"
#include "ui/shortcut.h"
@ -306,6 +307,11 @@ bool DocView::onCloseView(Workspace* workspace, bool quitting)
// See if the sprite has changes
while (m_document->isModified()) {
if (quitting) {
// Make sure the window is active so we can see the message when we close the app.
display()->nativeWindow()->activate();
}
// ask what want to do the user with the changes in the sprite
int ret = Alert::show(Strings::alerts_save_sprite_changes(
m_document->name(),

View File

@ -2748,7 +2748,10 @@ void Editor::setZoomAndCenterInMouse(const Zoom& zoom,
}
}
void Editor::pasteImage(const Image* image, const Mask* mask, const gfx::Point* position)
void Editor::pasteImage(const Image* image,
const Mask* mask,
const gfx::Point* position,
const Tileset* srcTileset)
{
ASSERT(image);
@ -2840,6 +2843,10 @@ void Editor::pasteImage(const Image* image, const Mask* mask, const gfx::Point*
PixelsMovementPtr pixelsMovement(
new PixelsMovement(UIContext::instance(), site, image, &mask2, "Paste"));
// Adjust image when copying between tilemap layers
if (site.tilemapMode() == TilemapMode::Tiles && srcTileset != nullptr)
pixelsMovement->remapTilesForPaste(srcTileset);
setState(EditorStatePtr(new MovingPixelsState(this, NULL, pixelsMovement, NoHandle)));
}

View File

@ -244,7 +244,8 @@ public:
void pasteImage(const Image* image,
const Mask* mask = nullptr,
const gfx::Point* position = nullptr);
const gfx::Point* position = nullptr,
const Tileset* srcTileset = nullptr);
void startSelectionTransformation(const gfx::Point& move, double angle);
void startFlipTransformation(doc::algorithm::FlipType flipType);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -22,6 +22,7 @@
#include "app/commands/commands.h"
#include "app/commands/move_thing.h"
#include "app/console.h"
#include "app/i18n/strings.h"
#include "app/modules/gui.h"
#include "app/pref/preferences.h"
#include "app/tools/ink.h"
@ -48,6 +49,7 @@
#include "fmt/format.h"
#include "gfx/rect.h"
#include "ui/manager.h"
#include "ui/menu.h"
#include "ui/message.h"
#include "ui/system.h"
#include "ui/view.h"
@ -286,9 +288,6 @@ bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
UIContext* ctx = UIContext::instance();
ctx->setActiveView(editor->getDocView());
ContextBar* contextBar = App::instance()->contextBar();
contextBar->updateForMovingPixels(getTransformation(editor));
// Start scroll loop
if (editor->checkForScroll(msg) || editor->checkForZoom(msg))
return true;
@ -442,10 +441,6 @@ void MovingPixelsState::onCommitMouseMove(Editor* editor, const gfx::PointF& spr
// Drag the image to that position
m_pixelsMovement->moveImage(spritePos, moveModifier);
// Update context bar and status bar
ContextBar* contextBar = App::instance()->contextBar();
contextBar->updateForMovingPixels(transformation);
m_editor->updateStatusBar();
}
@ -475,16 +470,13 @@ bool MovingPixelsState::onKeyDown(Editor* editor, KeyMessage* msg)
// FineControl now (e.g. if we pressed another modifier key).
m_lockedKeyAction = KeyAction::None;
if (msg->scancode() == kKeyEnter || // TODO make this key customizable
msg->scancode() == kKeyEnterPad || msg->scancode() == kKeyEsc) {
// TODO make these keys customizable
if (msg->scancode() == kKeyEsc) {
cancelDrag();
return true;
}
if (msg->scancode() == kKeyEnter || msg->scancode() == kKeyEnterPad) {
dropPixels();
// The escape key drop pixels and deselect the mask.
if (msg->scancode() == kKeyEsc) { // TODO make this key customizable
Command* cmd = Commands::instance()->byId(CommandId::DeselectMask());
UIContext::instance()->executeCommandFromMenuOrShortcut(cmd);
}
return true;
}
@ -529,6 +521,10 @@ bool MovingPixelsState::onUpdateStatusBar(Editor* editor)
const Transformation& transform(getTransformation(editor));
gfx::Size imageSize = m_pixelsMovement->getInitialImageSize();
// Update the context bar along with the status bar
ContextBar* contextBar = App::instance()->contextBar();
contextBar->updateForMovingPixels(transform);
int w = int(transform.bounds().w);
int h = int(transform.bounds().h);
int gcd = base::gcd(w, h);
@ -796,14 +792,64 @@ void MovingPixelsState::onDropPixels(ContextBarObserver::DropAction action)
switch (action) {
case ContextBarObserver::DropPixels: dropPixels(); break;
case ContextBarObserver::CancelDrag: cancelDrag(); break;
}
}
case ContextBarObserver::CancelDrag:
void MovingPixelsState::cancelDrag()
{
if (!m_pixelsMovement || m_discarded)
return;
switch (Preferences::instance().selection.cancelSelection()) {
case gen::CancelSelection::DISCARD:
m_pixelsMovement->discardImage(PixelsMovement::DontCommitChanges);
m_discarded = true;
// Quit from MovingPixelsState, back to standby.
m_editor->backToPreviousState();
dropPixels();
break;
case gen::CancelSelection::DESELECT: {
dropPixels();
Command* cmd = Commands::instance()->byId(CommandId::DeselectMask());
UIContext::instance()->executeCommandFromMenuOrShortcut(cmd);
break;
}
}
}
void MovingPixelsState::onConfigureDropPixels(ContextBarObserver::DropAction action,
const gfx::Point& pt)
{
if (!isActiveEditor())
return;
switch (action) {
case ContextBarObserver::DropPixels:
// Do nothing
break;
case ContextBarObserver::CancelDrag: {
Menu menu;
MenuItem discardChanges(Strings::context_bar_discard_changes());
MenuItem deselect(Strings::context_bar_deselect());
menu.addChild(&discardChanges);
menu.addChild(&deselect);
auto& opt = Preferences::instance().selection.cancelSelection;
discardChanges.setSelected(opt() == gen::CancelSelection::DISCARD);
deselect.setSelected(opt() == gen::CancelSelection::DESELECT);
discardChanges.Click.connect([&opt] { opt(gen::CancelSelection::DISCARD); });
deselect.Click.connect([&opt] { opt(gen::CancelSelection::DESELECT); });
ContextBar* contextBar = App::instance()->contextBar();
menu.showPopup(pt, contextBar->display());
break;
}
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -77,6 +77,7 @@ public:
// ContextBarObserver
void onDropPixels(ContextBarObserver::DropAction action) override;
void onConfigureDropPixels(ContextBarObserver::DropAction action, const gfx::Point& pt) override;
// PixelsMovementDelegate
void onPivotChange() override;
@ -95,6 +96,7 @@ private:
void setTransparentColor(bool opaque, const app::Color& color);
void dropPixels();
void cancelDrag();
bool isActiveDocument() const;
bool isActiveEditor() const;

View File

@ -12,6 +12,7 @@
#include "app/ui/editor/pixels_movement.h"
#include "app/app.h"
#include "app/cmd/add_tile.h"
#include "app/cmd/clear_mask.h"
#include "app/cmd/deselect_mask.h"
#include "app/cmd/set_mask.h"
@ -1356,6 +1357,39 @@ void PixelsMovement::drawTransformedTilemap(const Transformation& transformation
draw_row(dst->height() - 1, src->height() - 1, 1);
}
void PixelsMovement::remapTilesForPaste(const Tileset* srcTileset)
{
// Check that tile size matches before doing anything
LayerTilemap* dstLayer = static_cast<LayerTilemap*>(m_site.layer());
Tileset* dstTileset = dstLayer->tileset();
const doc::Grid& srcGrid = srcTileset->grid();
const doc::Grid& dstGrid = dstTileset->grid();
if (srcGrid.tileSize() != dstGrid.tileSize())
throw base::Exception("Tile size does not match.");
// Map source to destination
tile_index dstSz = dstTileset->size();
for (int y = 0; y < m_originalImage->height(); ++y) {
for (int x = 0; x < m_originalImage->width(); ++x) {
// Blank tile can be skipped
const color_t srcTi = m_originalImage->getPixel(x, y);
if (!srcTi)
continue;
// Add tile to destination if missing
tile_index dstTi;
const ImageRef t = srcTileset->get(srcTi);
if (!dstTileset->findTileIndex(t, dstTi)) {
m_tx(new cmd::AddTile(dstTileset, t, srcTileset->getTileData(srcTi)));
dstTi = dstSz++;
}
// Update tile index in image
m_originalImage->putPixel(x, y, dstTi);
}
}
}
void PixelsMovement::onPivotChange()
{
set_pivot_from_preferences(m_currentData);

View File

@ -130,6 +130,9 @@ public:
const Transformation& getTransformation() const { return m_currentData; }
void setTransformation(const Transformation& t);
// For copy/paste between tilemap layers
void remapTilesForPaste(const Tileset* srcTileset);
private:
void setTransformationBase(const Transformation& t);
void adjustPivot();

View File

@ -74,7 +74,11 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
double dz = delta.x + delta.y;
WheelAction wheelAction = WheelAction::None;
if (KeyboardShortcuts::instance()->hasMouseWheelCustomization()) {
if (tools::Tool* quickTool = App::instance()->activeToolManager()->quickTool();
quickTool && quickTool->getId() == tools::WellKnownInks::Zoom) {
wheelAction = WheelAction::Zoom;
}
else if (KeyboardShortcuts::instance()->hasMouseWheelCustomization()) {
if (!Preferences::instance().editor.zoomWithSlide() && msg->preciseWheel())
wheelAction = WheelAction::VScroll;
else

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
@ -181,8 +181,7 @@ void SliceWindow::onPivotChange()
void SliceWindow::onToggleUserData()
{
m_userDataView.toggleVisibility();
remapWindow();
manager()->invalidate();
expandWindow(gfx::Size(bounds().w, sizeHint().h));
}
void SliceWindow::onModifyField(ui::Entry* entry, const Mods mods)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
@ -132,8 +132,7 @@ void TagWindow::onRepeatChange()
void TagWindow::onToggleUserData()
{
m_userDataView.toggleVisibility();
remapWindow();
manager()->invalidate();
expandWindow(gfx::Size(bounds().w, sizeHint().h));
}
} // namespace app

View File

@ -4321,6 +4321,12 @@ void Timeline::clearAndInvalidateRange()
}
}
void Timeline::refresh()
{
regenerateRows();
invalidate();
}
app::gen::GlobalPref::Timeline& Timeline::timelinePref() const
{
return Preferences::instance().timeline;
@ -4576,7 +4582,7 @@ void Timeline::onDrop(ui::DragEvent& e)
// Determine at which frame and layer the content was dropped on.
frame_t frame = m_frame;
layer_t layerIndex = getLayerIndex(m_layer);
layer_t layerIndex = m_sprite->root()->getLayerIndex(m_layer);
InsertionPoint insert = InsertionPoint::BeforeLayer;
DroppedOn droppedOn = DroppedOn::Unspecified;
TRACE("m_dropRange.type() %d\n", m_dropRange.type());
@ -4602,7 +4608,7 @@ void Timeline::onDrop(ui::DragEvent& e)
break;
case Range::kLayers:
droppedOn = DroppedOn::Layer;
if (m_dropTarget.vhit != DropTarget::VeryBottom) {
if (m_dropTarget.vhit != DropTarget::VeryBottom && !m_dropRange.selectedLayers().empty()) {
auto* selectedLayer = *m_dropRange.selectedLayers().begin();
layerIndex = getLayerIndex(selectedLayer);
}

View File

@ -155,6 +155,8 @@ public:
void clearAndInvalidateRange();
void refresh();
protected:
bool onProcessMessage(ui::Message* msg) override;
void onInitTheme(ui::InitThemeEvent& ev) override;

View File

@ -10,6 +10,7 @@
#endif
#include "app/app.h"
#include "app/cmd/add_tileset.h"
#include "app/cmd/clear_mask.h"
#include "app/cmd/deselect_mask.h"
#include "app/cmd/set_mask.h"
@ -584,7 +585,10 @@ void Clipboard::paste(Context* ctx, const bool interactive, const gfx::Point* po
// TODO add post-command parameters (issue #2324)
// Change to MovingTilemapState
editor->pasteImage(m_data->tilemap.get(), m_data->mask.get(), position);
editor->pasteImage(m_data->tilemap.get(),
m_data->mask.get(),
position,
m_data->tileset.get());
}
else {
// TODO non-interactive version (for scripts)
@ -763,7 +767,14 @@ void Clipboard::paste(Context* ctx, const bool interactive, const gfx::Point* po
afterThis = dstSpr->root()->lastLayer();
Layer* newLayer = nullptr;
if (srcLayer->isImage())
if (srcLayer->isTilemap()) {
Tileset* srcTileset = static_cast<LayerTilemap*>(srcLayer)->tileset();
Tileset* tilesetCopy = Tileset::MakeCopyCopyingImagesForSprite(srcTileset, dstSpr);
const tileset_index tsi = dstSpr->tilesets()->size();
tx(new cmd::AddTileset(dstSpr, tilesetCopy));
newLayer = new LayerTilemap(dstSpr, tsi);
}
else if (srcLayer->isImage())
newLayer = new LayerImage(dstSpr);
else if (srcLayer->isGroup())
newLayer = new LayerGroup(dstSpr);

View File

@ -597,4 +597,22 @@ void LayerGroup::displaceFrames(frame_t fromThis, frame_t delta)
layer->displaceFrames(fromThis, delta);
}
layer_t LayerGroup::getLayerIndex(const Layer* layer, layer_t& index) const
{
for (Layer* child : this->layers()) {
if ((child->isGroup() && static_cast<LayerGroup*>(child)->getLayerIndex(layer, index) != -1) ||
(child == layer)) {
return index;
}
index++;
}
return -1;
}
layer_t LayerGroup::getLayerIndex(const Layer* layer) const
{
layer_t index = 0;
return this->getLayerIndex(layer, index);
}
} // namespace doc

View File

@ -236,9 +236,13 @@ public:
bool isBrowsable() const override { return isGroup() && isExpanded() && !m_layers.empty(); }
layer_t getLayerIndex(const Layer* layer) const;
private:
void destroyAllLayers();
layer_t getLayerIndex(const Layer* layer, layer_t& index) const;
LayerList m_layers;
};

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -46,24 +46,16 @@ void for_each_mask_pixel(Mask& a, const Mask& b, Func f)
Mask::Mask() : Object(ObjectType::Mask)
{
initialize();
}
Mask::Mask(const Mask& mask) : Object(mask)
{
initialize();
copyFrom(&mask);
}
Mask::~Mask()
{
ASSERT(m_freeze_count == 0);
}
void Mask::initialize()
{
m_freeze_count = 0;
m_bounds = gfx::Rect(0, 0, 0, 0);
ASSERT(m_freezes == 0);
}
int Mask::getMemSize() const
@ -78,17 +70,17 @@ void Mask::setName(const char* name)
void Mask::freeze()
{
ASSERT(m_freeze_count >= 0);
m_freeze_count++;
ASSERT(m_freezes >= 0);
m_freezes++;
}
void Mask::unfreeze()
{
ASSERT(m_freeze_count > 0);
m_freeze_count--;
ASSERT(m_freezes > 0);
m_freezes--;
// Shrink just in case
if (m_freeze_count == 0)
if (m_freezes == 0)
shrink();
}
@ -110,7 +102,7 @@ bool Mask::isRectangular() const
void Mask::copyFrom(const Mask* sourceMask)
{
ASSERT(m_freeze_count == 0);
ASSERT(m_freezes == 0);
clear();
setName(sourceMask->name().c_str());
@ -245,10 +237,10 @@ void Mask::intersect(const doc::Mask& mask)
void Mask::add(const gfx::Rect& bounds)
{
if (m_freeze_count == 0)
if (m_freezes == 0)
reserve(bounds);
// m_bitmap can be nullptr if we have m_freeze_count > 0
// m_bitmap can be nullptr if we have m_freezes > 0
if (!m_bitmap)
return;
@ -490,7 +482,7 @@ void Mask::reserve(const gfx::Rect& bounds)
void Mask::shrink()
{
// If the mask is frozen we avoid the shrinking
if (m_freeze_count > 0)
if (m_freezes > 0)
return;
#define SHRINK_SIDE(u_begin, u_op, u_final, u_add, v_begin, v_op, v_final, v_add, U, V, var) \

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2020-2024 Igara Studio S.A.
// Copyright (c) 2020-2025 Igara Studio S.A.
// Copyright (c) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -65,7 +65,7 @@ public:
void unfreeze();
// Returns true if the mask is frozen (See freeze/unfreeze functions).
bool isFrozen() const { return m_freeze_count > 0; }
bool isFrozen() const { return m_freezes > 0; }
// Returns true if the mask is a rectangular region.
bool isRectangular() const;
@ -107,9 +107,7 @@ public:
void offsetOrigin(int dx, int dy);
private:
void initialize();
int m_freeze_count;
int m_freezes = 0;
std::string m_name; // Mask name
gfx::Rect m_bounds; // Region bounds
ImageRef m_bitmap; // Bitmapped image mask

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -87,7 +87,8 @@ void Display::configureBackLayer()
layerSurface->width() != displaySurface->width() ||
layerSurface->height() != displaySurface->height()) {
layerSurface = os::System::instance()->makeSurface(displaySurface->width(),
displaySurface->height());
displaySurface->height(),
displaySurface->colorSpace());
layer->setSurface(layerSurface);
}
}

@ -1 +1 @@
Subproject commit 8607be393dfd81614611897a6e3026fe94a3966c
Subproject commit d14c2d1764f800d31b51893fb3d1e05d77a9280b