mirror of https://github.com/aseprite/aseprite.git
Compare commits
26 Commits
626e408bcd
...
ca8727c6e1
| Author | SHA1 | Date |
|---|---|---|
|
|
ca8727c6e1 | |
|
|
debab653fa | |
|
|
6e9024d54d | |
|
|
1fa7fd0831 | |
|
|
ab6b040e83 | |
|
|
bc312a37b3 | |
|
|
3129fda977 | |
|
|
6cb61fb41e | |
|
|
aa817a8d2a | |
|
|
40031f83d8 | |
|
|
90282dbc40 | |
|
|
1227f9c49c | |
|
|
d61ae919ad | |
|
|
b2b2583176 | |
|
|
b535212642 | |
|
|
229a3cdf65 | |
|
|
b3814ec912 | |
|
|
e88f3bb413 | |
|
|
eaa2bdf0af | |
|
|
57309e5aa5 | |
|
|
4bb9239f50 | |
|
|
cef92c1a38 | |
|
|
22e72ab5cb | |
|
|
80fa065bd5 | |
|
|
de1ccb24dd | |
|
|
7d91c4b9d9 |
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
8
build.sh
8
build.sh
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -621,6 +621,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
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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
2
laf
|
|
@ -1 +1 @@
|
|||
Subproject commit a2bb9ec7fb98354279a2c49870a4a47a67a8e86e
|
||||
Subproject commit 01571537bc6002a2e039a66497837365c394d7fa
|
||||
|
|
@ -180,8 +180,8 @@ if(ENABLE_ASEPRITE_EXE)
|
|||
|
||||
if(WIN32)
|
||||
set(main_resources
|
||||
main/resources_win32.rc
|
||||
main/settings.manifest)
|
||||
main/win/resources_win32.rc
|
||||
main/win/settings.manifest)
|
||||
endif()
|
||||
|
||||
add_executable(${main_target}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ void ResourceFinder::includeDesktopDir(const char* filename)
|
|||
#ifdef _WIN32
|
||||
|
||||
std::vector<wchar_t> buf(MAX_PATH);
|
||||
HRESULT hr = SHGetFolderPath(NULL, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_DEFAULT, &buf[0]);
|
||||
HRESULT hr = SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, &buf[0]);
|
||||
if (hr == S_OK) {
|
||||
addPath(base::join_path(base::to_utf8(&buf[0]), filename));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -127,12 +127,13 @@ struct Dialog {
|
|||
int showRef = LUA_REFNIL;
|
||||
lua_State* L = nullptr;
|
||||
|
||||
Dialog(const ui::Window::Type windowType, const std::string& title)
|
||||
Dialog(const ui::Window::Type windowType, const std::string& title, bool sizeable)
|
||||
: window(windowType, title)
|
||||
, grid(2, false)
|
||||
, currentGrid(&grid)
|
||||
{
|
||||
window.addChild(&grid);
|
||||
window.setSizeable(sizeable);
|
||||
all_dialogs.push_back(this);
|
||||
}
|
||||
|
||||
|
|
@ -365,6 +366,7 @@ int Dialog_new(lua_State* L)
|
|||
// Get the title and the type of window (with or without title bar)
|
||||
ui::Window::Type windowType = ui::Window::WithTitleBar;
|
||||
std::string title = "Script";
|
||||
bool sizeable = true;
|
||||
if (lua_isstring(L, 1)) {
|
||||
title = lua_tostring(L, 1);
|
||||
}
|
||||
|
|
@ -378,9 +380,14 @@ int Dialog_new(lua_State* L)
|
|||
if (type != LUA_TNIL && lua_toboolean(L, -1))
|
||||
windowType = ui::Window::WithoutTitleBar;
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 1, "resizeable");
|
||||
if (type != LUA_TNIL && !lua_toboolean(L, -1))
|
||||
sizeable = false;
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
auto dlg = push_new<Dialog>(L, windowType, title);
|
||||
auto dlg = push_new<Dialog>(L, windowType, title, sizeable);
|
||||
|
||||
// The uservalue of the dialog userdata will contain a table that
|
||||
// stores all the callbacks to handle events. As these callbacks can
|
||||
|
|
@ -1509,6 +1516,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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
#include "app/console.h"
|
||||
#include "app/context.h"
|
||||
#include "app/context_observer.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/doc_undo.h"
|
||||
#include "app/script/docobj.h"
|
||||
#include "app/script/engine.h"
|
||||
#include "app/script/luacpp.h"
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@
|
|||
#include "doc/tag.h"
|
||||
#include "doc/tileset.h"
|
||||
#include "doc/tilesets.h"
|
||||
#include "undo/undo_state.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
|
@ -456,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())
|
||||
|
|
@ -1029,6 +1030,42 @@ int Sprite_set_useLayerUuids(lua_State* L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int Sprite_get_undoHistory(lua_State* L)
|
||||
{
|
||||
const auto* sprite = get_docobj<Sprite>(L, 1);
|
||||
const auto* doc = static_cast<Doc*>(sprite->document());
|
||||
const auto* history = doc->undoHistory();
|
||||
|
||||
if (!history) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const undo::UndoState* currentState = history->currentState();
|
||||
const undo::UndoState* s = history->firstState();
|
||||
const bool canRedo = history->canRedo();
|
||||
bool pastCurrent = !currentState && canRedo;
|
||||
|
||||
int undoSteps = 0;
|
||||
int redoSteps = 0;
|
||||
while (s) {
|
||||
if (pastCurrent && canRedo)
|
||||
redoSteps++;
|
||||
else if (currentState || !canRedo)
|
||||
undoSteps++;
|
||||
|
||||
if (s == currentState || !currentState)
|
||||
pastCurrent = true;
|
||||
|
||||
s = s->next();
|
||||
}
|
||||
|
||||
lua_newtable(L);
|
||||
setfield_integer(L, "undoSteps", undoSteps);
|
||||
setfield_integer(L, "redoSteps", redoSteps);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const luaL_Reg Sprite_methods[] = {
|
||||
{ "__eq", Sprite_eq },
|
||||
{ "resize", Sprite_resize },
|
||||
|
|
@ -1094,6 +1131,7 @@ const Property Sprite_properties[] = {
|
|||
{ "events", Sprite_get_events, nullptr },
|
||||
{ "tileManagementPlugin", Sprite_get_tileManagementPlugin, Sprite_set_tileManagementPlugin },
|
||||
{ "useLayerUuids", Sprite_get_useLayerUuids, Sprite_set_useLayerUuids },
|
||||
{ "undoHistory", Sprite_get_undoHistory, nullptr },
|
||||
{ nullptr, nullptr, nullptr }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -2626,6 +2636,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()
|
||||
|
|
|
|||
|
|
@ -286,9 +286,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 +439,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();
|
||||
}
|
||||
|
||||
|
|
@ -477,14 +470,13 @@ bool MovingPixelsState::onKeyDown(Editor* editor, KeyMessage* msg)
|
|||
|
||||
if (msg->scancode() == kKeyEnter || // TODO make this key customizable
|
||||
msg->scancode() == kKeyEnterPad || msg->scancode() == kKeyEsc) {
|
||||
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);
|
||||
m_pixelsMovement->discardImage(PixelsMovement::DontCommitChanges);
|
||||
m_discarded = true;
|
||||
}
|
||||
|
||||
dropPixels();
|
||||
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -443,12 +443,7 @@ bool WritingTextState::onSetCursor(Editor* editor, const gfx::Point& mouseScreen
|
|||
return true;
|
||||
}
|
||||
|
||||
bool WritingTextState::onKeyDown(Editor*, KeyMessage*)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WritingTextState::onKeyUp(Editor*, KeyMessage* msg)
|
||||
bool WritingTextState::onKeyDown(Editor*, KeyMessage* msg)
|
||||
{
|
||||
// Cancel loop pressing Esc key
|
||||
if (msg->scancode() == ui::kKeyEsc) {
|
||||
|
|
@ -457,7 +452,17 @@ bool WritingTextState::onKeyUp(Editor*, KeyMessage* msg)
|
|||
// Drop text pressing Enter key
|
||||
else if (msg->scancode() == ui::kKeyEnter) {
|
||||
drop();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WritingTextState::onKeyUp(Editor*, KeyMessage* msg)
|
||||
{
|
||||
// Note: We cannot process kKeyEnter key here to drop the text as it
|
||||
// could be received after the Enter key is pressed in the IME
|
||||
// dialog to accept the composition (not to accept the text). So we
|
||||
// process kKeyEnter in onKeyDown().
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -4576,7 +4576,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 +4602,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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>aseprite</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>Document.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Aseprite Sprite</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>ase</string>
|
||||
<string>bmp</string>
|
||||
<string>flc</string>
|
||||
<string>fli</string>
|
||||
<string>gif</string>
|
||||
<string>ico</string>
|
||||
<string>jpeg</string>
|
||||
<string>jpg</string>
|
||||
<string>pcx</string>
|
||||
<string>png</string>
|
||||
<string>tga</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>Document.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Aseprite Sprite</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>aseprite-extension</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>Extension.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Aseprite Extension</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Aseprite</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>aseprite</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.aseprite.Aseprite</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Aseprite</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.3</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.3</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>Aseprite.icns</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.graphics-design</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2001-2025, Igara Studio S.A.
|
||||
All rights reserved.</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>NSRequiresAquaSystemAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,3 +228,69 @@ do
|
|||
c = app.open(fn)
|
||||
assert(c.tileManagementPlugin == nil)
|
||||
end
|
||||
|
||||
-- Undo History
|
||||
|
||||
function test_undo_history()
|
||||
local sprite = Sprite(1, 1)
|
||||
|
||||
assert(sprite.undoHistory.undoSteps == 0)
|
||||
assert(sprite.undoHistory.redoSteps == 0)
|
||||
|
||||
sprite:resize(10, 10)
|
||||
|
||||
assert(sprite.undoHistory.undoSteps == 1)
|
||||
assert(sprite.undoHistory.redoSteps == 0)
|
||||
|
||||
sprite:resize(10, 15)
|
||||
|
||||
assert(sprite.undoHistory.undoSteps == 2)
|
||||
assert(sprite.undoHistory.redoSteps == 0)
|
||||
|
||||
sprite:resize(10, 30)
|
||||
|
||||
assert(sprite.undoHistory.undoSteps == 3)
|
||||
assert(sprite.undoHistory.redoSteps == 0)
|
||||
|
||||
app.undo()
|
||||
assert(sprite.undoHistory.undoSteps == 2)
|
||||
assert(sprite.undoHistory.redoSteps == 1)
|
||||
|
||||
app.undo()
|
||||
assert(sprite.undoHistory.undoSteps == 1)
|
||||
assert(sprite.undoHistory.redoSteps == 2)
|
||||
|
||||
app.redo()
|
||||
assert(sprite.undoHistory.undoSteps == 2)
|
||||
assert(sprite.undoHistory.redoSteps == 1)
|
||||
|
||||
app.undo()
|
||||
app.undo()
|
||||
|
||||
assert(sprite.undoHistory.undoSteps == 0)
|
||||
assert(sprite.undoHistory.redoSteps == 3)
|
||||
|
||||
sprite:resize(10, 30)
|
||||
|
||||
if (app.preferences.undo.allow_nonlinear_history) then
|
||||
assert(sprite.undoHistory.undoSteps == 4)
|
||||
assert(sprite.undoHistory.redoSteps == 0)
|
||||
else
|
||||
assert(sprite.undoHistory.undoSteps == 1)
|
||||
assert(sprite.undoHistory.redoSteps == 0)
|
||||
end
|
||||
end
|
||||
|
||||
do
|
||||
local prevSetting = app.preferences.undo.allow_nonlinear_history
|
||||
app.preferences.undo.allow_nonlinear_history = true
|
||||
test_undo_history()
|
||||
app.preferences.undo.allow_nonlinear_history = prevSetting
|
||||
end
|
||||
|
||||
do
|
||||
local prevSetting = app.preferences.undo.allow_nonlinear_history
|
||||
app.preferences.undo.allow_nonlinear_history = false
|
||||
test_undo_history()
|
||||
app.preferences.undo.allow_nonlinear_history = prevSetting
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 8607be393dfd81614611897a6e3026fe94a3966c
|
||||
Subproject commit d14c2d1764f800d31b51893fb3d1e05d77a9280b
|
||||
Loading…
Reference in New Issue