mirror of
https://github.com/aseprite/aseprite.git
synced 2024-10-04 13:59:46 +00:00
Merge branch 'aseprite:main' into main
This commit is contained in:
commit
e995f83ec4
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
if: ${{ runner.os == 'Linux' || runner.os == 'macOS' }}
|
||||
with:
|
||||
key: ${{ matrix.os }}-${{ matrix.enable_ui }}-${{ matrix.build_type }}
|
||||
- uses: turtlesec-no/get-ninja@main
|
||||
- uses: aseprite/get-ninja@main
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
if: runner.os == 'Windows'
|
||||
- name: Workaround for windows-2022 and cmake 3.25.0
|
||||
|
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -24,9 +24,6 @@
|
||||
[submodule "third_party/libpng"]
|
||||
path = third_party/libpng
|
||||
url = https://github.com/aseprite/libpng.git
|
||||
[submodule "src/clip"]
|
||||
path = src/clip
|
||||
url = https://github.com/aseprite/clip.git
|
||||
[submodule "src/observable"]
|
||||
path = src/observable
|
||||
url = https://github.com/aseprite/observable.git
|
||||
@ -81,3 +78,6 @@
|
||||
[submodule "third_party/qoi"]
|
||||
path = third_party/qoi
|
||||
url = https://github.com/aseprite/qoi.git
|
||||
[submodule "third_party/tinyxml2"]
|
||||
path = third_party/tinyxml2
|
||||
url = https://github.com/aseprite/tinyxml2.git
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Aseprite
|
||||
# Copyright (C) 2018-2022 Igara Studio S.A.
|
||||
# Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
# Copyright (C) 2001-2018 David Capello
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
@ -163,7 +163,7 @@ set(PIXMAN_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/pixman)
|
||||
set(FREETYPE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/freetype2)
|
||||
set(HARFBUZZ_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/harfbuzz)
|
||||
set(SIMPLEINI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/simpleini)
|
||||
set(TINYXML_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/tinyxml)
|
||||
set(TINYXML_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/tinyxml2)
|
||||
set(ZLIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/zlib)
|
||||
|
||||
# Search in the "cmake" directory for additional CMake modules.
|
||||
@ -225,12 +225,12 @@ endif()
|
||||
include_directories(${PNG_INCLUDE_DIRS})
|
||||
add_definitions(-DPNG_NO_MMX_CODE) # Do not use MMX optimizations in PNG code
|
||||
|
||||
# tinyxml
|
||||
# tinyxml2
|
||||
if(USE_SHARED_TINYXML)
|
||||
find_library(TINYXML_LIBRARY NAMES tinyxml)
|
||||
find_path(TINYXML_INCLUDE_DIR NAMES tinyxml.h)
|
||||
find_library(TINYXML_LIBRARY NAMES tinyxml2)
|
||||
find_path(TINYXML_INCLUDE_DIR NAMES tinyxml2.h)
|
||||
else()
|
||||
set(TINYXML_LIBRARY tinyxml)
|
||||
set(TINYXML_LIBRARY tinyxml2)
|
||||
set(TINYXML_INCLUDE_DIR ${TINYXML_DIR})
|
||||
endif()
|
||||
include_directories(${TINYXML_INCLUDE_DIR})
|
||||
@ -293,7 +293,7 @@ if(USE_SHARED_CMARK)
|
||||
find_path(CMARK_INCLUDE_DIRS NAMES cmark.h)
|
||||
else()
|
||||
add_definitions(-DCMARK_STATIC_DEFINE)
|
||||
set(CMARK_LIBRARIES cmark_static)
|
||||
set(CMARK_LIBRARIES cmark)
|
||||
endif()
|
||||
|
||||
if(REQUIRE_CURL)
|
||||
|
@ -1139,10 +1139,10 @@ freely, subject to the following restrictions:
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
```
|
||||
|
||||
# [tinyxml](http://www.grinninglizard.com/tinyxml/)
|
||||
# [tinyxml2](https://github.com/leethomason/tinyxml2)
|
||||
|
||||
```
|
||||
TinyXML is released under the zlib license:
|
||||
Original code by Lee Thomason (www.grinninglizard.com)
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
@ -1162,12 +1162,6 @@ must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
|
||||
--
|
||||
|
||||
TinyXML was originally written by Lee Thomason. Lee reviews changes
|
||||
and releases new versions, with the help of Yves Berquin, Andrew
|
||||
Ellerton, and the tinyXml community.
|
||||
```
|
||||
|
||||
# [ucdn](https://github.com/grigorig/ucdn)
|
||||
|
2
laf
2
laf
@ -1 +1 @@
|
||||
Subproject commit 6d19cc2e890576b07399e3d157da83d04acbc99b
|
||||
Subproject commit 695bb59d1a7de36747236f3f46854b614725da77
|
@ -95,12 +95,6 @@ set(OBSERVABLE_TESTS OFF CACHE BOOL "Compile observable tests")
|
||||
add_subdirectory(observable)
|
||||
include_directories(observable)
|
||||
|
||||
# Disable clip examples and tests
|
||||
set(CLIP_EXAMPLES OFF CACHE BOOL "Compile clip examples")
|
||||
set(CLIP_TESTS OFF CACHE BOOL "Compile clip tests")
|
||||
set(CLIP_X11_PNG_LIBRARY "${PNG_LIBRARY}")
|
||||
add_subdirectory(clip)
|
||||
|
||||
# Disable undo tests
|
||||
set(UNDO_TESTS OFF CACHE BOOL "Compile undo tests")
|
||||
add_subdirectory(undo)
|
||||
@ -123,6 +117,10 @@ if(REQUIRE_CURL)
|
||||
add_subdirectory(net)
|
||||
endif()
|
||||
|
||||
# We need the updater library to check for updates (when
|
||||
# ENABLE_UPDATER) or for the app.os object (ENABLE_SCRIPTING).
|
||||
add_subdirectory(updater)
|
||||
|
||||
if(GEN_EXE)
|
||||
add_executable(gen IMPORTED)
|
||||
set_target_properties(gen PROPERTIES IMPORTED_LOCATION ${GEN_EXE})
|
||||
@ -133,10 +131,6 @@ else()
|
||||
set(GEN_DEP gen)
|
||||
endif()
|
||||
|
||||
if(ENABLE_UPDATER)
|
||||
add_subdirectory(updater)
|
||||
endif()
|
||||
|
||||
if(ENABLE_STEAM)
|
||||
add_subdirectory(steam)
|
||||
endif()
|
||||
|
@ -101,23 +101,20 @@ add_definitions(-DLIBARCHIVE_STATIC)
|
||||
######################################################################
|
||||
# app-lib target
|
||||
|
||||
add_library(app-lib ${generated_files})
|
||||
|
||||
# These specific-platform files should be in an external library
|
||||
# (e.g. "base" or "os").
|
||||
set(app_platform_files)
|
||||
if(WIN32)
|
||||
set(app_platform_files
|
||||
font_path_win.cpp)
|
||||
target_sources(app-lib PRIVATE font_path_win.cpp)
|
||||
elseif(APPLE)
|
||||
set(app_platform_files
|
||||
font_path_osx.mm)
|
||||
target_sources(app-lib PRIVATE font_path_osx.mm)
|
||||
else()
|
||||
set(app_platform_files
|
||||
font_path_unix.cpp)
|
||||
target_sources(app-lib PRIVATE font_path_unix.cpp)
|
||||
endif()
|
||||
|
||||
set(data_recovery_files)
|
||||
if(ENABLE_DATA_RECOVERY)
|
||||
set(data_recovery_files
|
||||
target_sources(app-lib PRIVATE
|
||||
crash/backup_observer.cpp
|
||||
crash/data_recovery.cpp
|
||||
crash/read_document.cpp
|
||||
@ -126,7 +123,7 @@ if(ENABLE_DATA_RECOVERY)
|
||||
ui/data_recovery_view.cpp)
|
||||
endif()
|
||||
|
||||
set(file_formats
|
||||
target_sources(app-lib PRIVATE
|
||||
file/ase_format.cpp
|
||||
file/bmp_format.cpp
|
||||
file/css_format.cpp
|
||||
@ -140,30 +137,31 @@ set(file_formats
|
||||
file/svg_format.cpp
|
||||
file/tga_format.cpp)
|
||||
if(ENABLE_WEBP)
|
||||
list(APPEND file_formats file/webp_format.cpp)
|
||||
target_sources(app-lib PRIVATE
|
||||
file/webp_format.cpp)
|
||||
endif()
|
||||
if(ENABLE_PSD)
|
||||
list(APPEND file_formats file/psd_format.cpp)
|
||||
target_sources(app-lib PRIVATE
|
||||
file/psd_format.cpp)
|
||||
endif()
|
||||
|
||||
set(scripting_files)
|
||||
if(ENABLE_SCRIPTING)
|
||||
set(scripting_files_ui)
|
||||
if(ENABLE_UI)
|
||||
set(scripting_files_ui
|
||||
target_sources(app-lib PRIVATE
|
||||
commands/cmd_developer_console.cpp
|
||||
commands/cmd_open_script_folder.cpp
|
||||
commands/debugger.cpp
|
||||
ui/devconsole_view.cpp)
|
||||
endif()
|
||||
if(ENABLE_WEBSOCKET)
|
||||
set(scripting_files_ws
|
||||
target_sources(app-lib PRIVATE
|
||||
script/websocket_class.cpp)
|
||||
endif()
|
||||
set(scripting_files
|
||||
target_sources(app-lib PRIVATE
|
||||
commands/cmd_run_script.cpp
|
||||
script/app_command_object.cpp
|
||||
script/app_fs_object.cpp
|
||||
script/app_os_object.cpp
|
||||
script/app_object.cpp
|
||||
script/app_theme_object.cpp
|
||||
script/brush_class.cpp
|
||||
@ -219,14 +217,11 @@ if(ENABLE_SCRIPTING)
|
||||
script/values.cpp
|
||||
script/version_class.cpp
|
||||
script/window_class.cpp
|
||||
shell.cpp
|
||||
${scripting_files_ws}
|
||||
${scripting_files_ui})
|
||||
shell.cpp)
|
||||
endif()
|
||||
|
||||
set(ui_app_files)
|
||||
if(ENABLE_UI)
|
||||
set(ui_app_files
|
||||
target_sources(app-lib PRIVATE
|
||||
app_brushes.cpp
|
||||
app_menus.cpp
|
||||
closed_docs.cpp
|
||||
@ -436,34 +431,30 @@ if(ENABLE_UI)
|
||||
ui_context.cpp
|
||||
widget_loader.cpp)
|
||||
if(ENABLE_NEWS)
|
||||
set(ui_app_files
|
||||
target_sources(app-lib PRIVATE
|
||||
res/http_loader.cpp
|
||||
ui/news_listbox.cpp
|
||||
${ui_app_files})
|
||||
ui/news_listbox.cpp)
|
||||
endif()
|
||||
if(ENABLE_DRM)
|
||||
set(ui_app_files
|
||||
target_sources(app-lib PRIVATE
|
||||
ui/enter_license.cpp
|
||||
ui/aseprite_update.cpp
|
||||
${ui_app_files})
|
||||
ui/aseprite_update.cpp)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(send_crash_files)
|
||||
if(ENABLE_SENTRY)
|
||||
set(send_crash_files sentry_wrapper.cpp)
|
||||
target_sources(app-lib PRIVATE sentry_wrapper.cpp)
|
||||
else()
|
||||
set(send_crash_files send_crash.cpp)
|
||||
target_sources(app-lib PRIVATE send_crash.cpp)
|
||||
endif()
|
||||
|
||||
add_library(app-lib
|
||||
target_sources(app-lib PRIVATE
|
||||
active_site_handler.cpp
|
||||
app.cpp
|
||||
check_update.cpp
|
||||
cli/app_options.cpp
|
||||
cli/cli_open_file.cpp
|
||||
cli/cli_processor.cpp
|
||||
${file_formats}
|
||||
cli/default_cli_delegate.cpp
|
||||
cli/preview_cli_delegate.cpp
|
||||
cmd.cpp
|
||||
@ -699,17 +690,12 @@ add_library(app-lib
|
||||
util/range_utils.cpp
|
||||
util/readable_time.cpp
|
||||
util/resize_image.cpp
|
||||
util/shader_helpers.cpp
|
||||
util/tile_flags_utils.cpp
|
||||
util/tileset_utils.cpp
|
||||
util/wrap_point.cpp
|
||||
xml_document.cpp
|
||||
xml_exception.cpp
|
||||
${send_crash_files}
|
||||
${ui_app_files}
|
||||
${app_platform_files}
|
||||
${data_recovery_files}
|
||||
${scripting_files}
|
||||
${generated_files})
|
||||
xml_exception.cpp)
|
||||
|
||||
if(TARGET generated_version)
|
||||
add_dependencies(app-lib generated_version)
|
||||
@ -731,6 +717,7 @@ target_link_libraries(app-lib
|
||||
laf-os
|
||||
ui-lib
|
||||
ver-lib
|
||||
updater-lib
|
||||
undo
|
||||
${CMARK_LIBRARIES}
|
||||
${TINYXML_LIBRARY}
|
||||
@ -770,10 +757,6 @@ if(ENABLE_SCRIPTING)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(ENABLE_UPDATER)
|
||||
target_link_libraries(app-lib updater-lib)
|
||||
endif()
|
||||
|
||||
if(ENABLE_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
|
||||
|
@ -267,9 +267,19 @@ int App::initialize(const AppOptions& options)
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
m_isGui = options.startUI() && !options.previewCLI();
|
||||
|
||||
// Notify the scripting engine that we're going to enter to GUI
|
||||
// mode, this is useful so we can mark the stdin file handle as
|
||||
// closed so no script can hang the program if it tries to read from
|
||||
// stdin when the GUI is running.
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (m_isGui)
|
||||
m_engine->notifyRunningGui();
|
||||
#endif
|
||||
#else
|
||||
m_isGui = false;
|
||||
#endif
|
||||
|
||||
m_isShell = options.startShell();
|
||||
m_coreModules = std::make_unique<CoreModules>();
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -23,6 +23,8 @@
|
||||
#include "doc/image.h"
|
||||
#include "doc/image_impl.h"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace app {
|
||||
@ -30,15 +32,16 @@ namespace app {
|
||||
using namespace doc;
|
||||
using namespace base::serialization;
|
||||
using namespace base::serialization::little_endian;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace {
|
||||
|
||||
ImageRef load_xml_image(const TiXmlElement* imageElem)
|
||||
ImageRef load_xml_image(const XMLElement* imageElem)
|
||||
{
|
||||
ImageRef image;
|
||||
int w, h;
|
||||
if (imageElem->QueryIntAttribute("width", &w) != TIXML_SUCCESS ||
|
||||
imageElem->QueryIntAttribute("height", &h) != TIXML_SUCCESS ||
|
||||
if (imageElem->QueryIntAttribute("width", &w) != XML_SUCCESS ||
|
||||
imageElem->QueryIntAttribute("height", &h) != XML_SUCCESS ||
|
||||
w < 0 || w > 9999 ||
|
||||
h < 0 || h > 9999)
|
||||
return image;
|
||||
@ -109,7 +112,7 @@ ImageRef load_xml_image(const TiXmlElement* imageElem)
|
||||
return image;
|
||||
}
|
||||
|
||||
void save_xml_image(TiXmlElement* imageElem, const Image* image)
|
||||
void save_xml_image(XMLElement* imageElem, const Image* image)
|
||||
{
|
||||
int w = image->width();
|
||||
int h = image->height();
|
||||
@ -167,8 +170,7 @@ void save_xml_image(TiXmlElement* imageElem, const Image* image)
|
||||
|
||||
std::string data_base64;
|
||||
base::encode_base64(data, data_base64);
|
||||
TiXmlText textElem(data_base64.c_str());
|
||||
imageElem->InsertEndChild(textElem);
|
||||
imageElem->InsertNewText(data_base64.c_str());
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
@ -305,11 +307,11 @@ static const int kBrushFlags =
|
||||
|
||||
void AppBrushes::load(const std::string& filename)
|
||||
{
|
||||
XmlDocumentRef doc = app::open_xml(filename);
|
||||
TiXmlHandle handle(doc.get());
|
||||
TiXmlElement* brushElem = handle
|
||||
.FirstChild("brushes")
|
||||
.FirstChild("brush").ToElement();
|
||||
XMLDocumentRef doc = app::open_xml(filename);
|
||||
XMLHandle handle(doc.get());
|
||||
XMLElement* brushElem = handle
|
||||
.FirstChildElement("brushes")
|
||||
.FirstChildElement("brush").ToElement();
|
||||
|
||||
while (brushElem) {
|
||||
// flags
|
||||
@ -339,9 +341,9 @@ void AppBrushes::load(const std::string& filename)
|
||||
|
||||
// Brush image
|
||||
ImageRef image, mask;
|
||||
if (TiXmlElement* imageElem = brushElem->FirstChildElement("image"))
|
||||
if (XMLElement* imageElem = brushElem->FirstChildElement("image"))
|
||||
image = load_xml_image(imageElem);
|
||||
if (TiXmlElement* maskElem = brushElem->FirstChildElement("mask"))
|
||||
if (XMLElement* maskElem = brushElem->FirstChildElement("mask"))
|
||||
mask = load_xml_image(maskElem);
|
||||
|
||||
if (image) {
|
||||
@ -351,14 +353,14 @@ void AppBrushes::load(const std::string& filename)
|
||||
}
|
||||
|
||||
// Colors
|
||||
if (TiXmlElement* fgcolorElem = brushElem->FirstChildElement("fgcolor")) {
|
||||
if (XMLElement* fgcolorElem = brushElem->FirstChildElement("fgcolor")) {
|
||||
if (auto value = fgcolorElem->Attribute("value")) {
|
||||
fgColor = app::Color::fromString(value);
|
||||
flags |= int(BrushSlot::Flags::FgColor);
|
||||
}
|
||||
}
|
||||
|
||||
if (TiXmlElement* bgcolorElem = brushElem->FirstChildElement("bgcolor")) {
|
||||
if (XMLElement* bgcolorElem = brushElem->FirstChildElement("bgcolor")) {
|
||||
if (auto value = bgcolorElem->Attribute("value")) {
|
||||
bgColor = app::Color::fromString(value);
|
||||
flags |= int(BrushSlot::Flags::BgColor);
|
||||
@ -366,14 +368,14 @@ void AppBrushes::load(const std::string& filename)
|
||||
}
|
||||
|
||||
// Ink
|
||||
if (TiXmlElement* inkTypeElem = brushElem->FirstChildElement("inktype")) {
|
||||
if (XMLElement* inkTypeElem = brushElem->FirstChildElement("inktype")) {
|
||||
if (auto value = inkTypeElem->Attribute("value")) {
|
||||
inkType = app::tools::string_id_to_ink_type(value);
|
||||
flags |= int(BrushSlot::Flags::InkType);
|
||||
}
|
||||
}
|
||||
|
||||
if (TiXmlElement* inkOpacityElem = brushElem->FirstChildElement("inkopacity")) {
|
||||
if (XMLElement* inkOpacityElem = brushElem->FirstChildElement("inkopacity")) {
|
||||
if (auto value = inkOpacityElem->Attribute("value")) {
|
||||
inkOpacity = base::convert_to<int>(std::string(value));
|
||||
flags |= int(BrushSlot::Flags::InkOpacity);
|
||||
@ -381,7 +383,7 @@ void AppBrushes::load(const std::string& filename)
|
||||
}
|
||||
|
||||
// Shade
|
||||
if (TiXmlElement* shadeElem = brushElem->FirstChildElement("shade")) {
|
||||
if (XMLElement* shadeElem = brushElem->FirstChildElement("shade")) {
|
||||
if (auto value = shadeElem->Attribute("value")) {
|
||||
shade = shade_from_string(value);
|
||||
flags |= int(BrushSlot::Flags::Shade);
|
||||
@ -389,7 +391,7 @@ void AppBrushes::load(const std::string& filename)
|
||||
}
|
||||
|
||||
// Pixel-perfect
|
||||
if (TiXmlElement* pixelPerfectElem = brushElem->FirstChildElement("pixelperfect")) {
|
||||
if (XMLElement* pixelPerfectElem = brushElem->FirstChildElement("pixelperfect")) {
|
||||
pixelPerfect = bool_attr(pixelPerfectElem, "value", false);
|
||||
flags |= int(BrushSlot::Flags::PixelPerfect);
|
||||
}
|
||||
@ -414,13 +416,13 @@ void AppBrushes::load(const std::string& filename)
|
||||
|
||||
void AppBrushes::save(const std::string& filename) const
|
||||
{
|
||||
XmlDocumentRef doc(new TiXmlDocument());
|
||||
TiXmlElement brushesElem("brushes");
|
||||
|
||||
//<?xml version="1.0" encoding="utf-8"?>
|
||||
auto doc = std::make_unique<XMLDocument>();
|
||||
XMLElement* brushesElem = doc->NewElement("brushes");
|
||||
|
||||
doc->InsertEndChild(doc->NewDeclaration("xml version=\"1.0\" encoding=\"utf-8\""));
|
||||
doc->InsertEndChild(brushesElem);
|
||||
for (const auto& slot : m_slots) {
|
||||
TiXmlElement brushElem("brush");
|
||||
XMLElement* brushElem = brushesElem->InsertNewChildElement("brush");
|
||||
if (slot.locked()) {
|
||||
// Flags
|
||||
int flags = int(slot.flags());
|
||||
@ -435,32 +437,30 @@ void AppBrushes::save(const std::string& filename) const
|
||||
ASSERT(slot.brush());
|
||||
|
||||
if (flags & int(BrushSlot::Flags::BrushType)) {
|
||||
brushElem.SetAttribute(
|
||||
brushElem->SetAttribute(
|
||||
"type", brush_type_to_string_id(slot.brush()->type()).c_str());
|
||||
}
|
||||
|
||||
if (flags & int(BrushSlot::Flags::BrushSize)) {
|
||||
brushElem.SetAttribute("size", slot.brush()->size());
|
||||
brushElem->SetAttribute("size", slot.brush()->size());
|
||||
}
|
||||
|
||||
if (flags & int(BrushSlot::Flags::BrushAngle)) {
|
||||
brushElem.SetAttribute("angle", slot.brush()->angle());
|
||||
brushElem->SetAttribute("angle", slot.brush()->angle());
|
||||
}
|
||||
|
||||
if (slot.brush()->type() == kImageBrushType &&
|
||||
slot.brush()->originalImage()) {
|
||||
TiXmlElement elem("image");
|
||||
save_xml_image(&elem, slot.brush()->originalImage());
|
||||
brushElem.InsertEndChild(elem);
|
||||
XMLElement* elem = brushElem->InsertNewChildElement("image");
|
||||
save_xml_image(elem, slot.brush()->originalImage());
|
||||
|
||||
if (slot.brush()->maskBitmap()) {
|
||||
TiXmlElement maskElem("mask");
|
||||
save_xml_image(&maskElem, slot.brush()->maskBitmap());
|
||||
brushElem.InsertEndChild(maskElem);
|
||||
XMLElement* maskElem = brushElem->InsertNewChildElement("mask");
|
||||
save_xml_image(maskElem, slot.brush()->maskBitmap());
|
||||
}
|
||||
|
||||
// Image color
|
||||
brushElem.SetAttribute(
|
||||
brushElem->SetAttribute(
|
||||
"imagecolor",
|
||||
(flags & int(BrushSlot::Flags::ImageColor)) ? "true": "false");
|
||||
}
|
||||
@ -468,53 +468,42 @@ void AppBrushes::save(const std::string& filename) const
|
||||
|
||||
// Colors
|
||||
if (flags & int(BrushSlot::Flags::FgColor)) {
|
||||
TiXmlElement elem("fgcolor");
|
||||
elem.SetAttribute("value", slot.fgColor().toString().c_str());
|
||||
brushElem.InsertEndChild(elem);
|
||||
XMLElement* elem = brushElem->InsertNewChildElement("fgcolor");
|
||||
elem->SetAttribute("value", slot.fgColor().toString().c_str());
|
||||
}
|
||||
|
||||
if (flags & int(BrushSlot::Flags::BgColor)) {
|
||||
TiXmlElement elem("bgcolor");
|
||||
elem.SetAttribute("value", slot.bgColor().toString().c_str());
|
||||
brushElem.InsertEndChild(elem);
|
||||
XMLElement* elem = brushElem->InsertNewChildElement("bgcolor");
|
||||
elem->SetAttribute("value", slot.bgColor().toString().c_str());
|
||||
}
|
||||
|
||||
// Ink
|
||||
if (flags & int(BrushSlot::Flags::InkType)) {
|
||||
TiXmlElement elem("inktype");
|
||||
elem.SetAttribute(
|
||||
XMLElement* elem = brushElem->InsertNewChildElement("inktype");
|
||||
elem->SetAttribute(
|
||||
"value", app::tools::ink_type_to_string_id(slot.inkType()).c_str());
|
||||
brushElem.InsertEndChild(elem);
|
||||
}
|
||||
|
||||
if (flags & int(BrushSlot::Flags::InkOpacity)) {
|
||||
TiXmlElement elem("inkopacity");
|
||||
elem.SetAttribute("value", slot.inkOpacity());
|
||||
brushElem.InsertEndChild(elem);
|
||||
XMLElement* elem = brushElem->InsertNewChildElement("inkopacity");
|
||||
elem->SetAttribute("value", slot.inkOpacity());
|
||||
}
|
||||
|
||||
// Shade
|
||||
if (flags & int(BrushSlot::Flags::Shade)) {
|
||||
TiXmlElement elem("shade");
|
||||
elem.SetAttribute("value", shade_to_string(slot.shade()).c_str());
|
||||
brushElem.InsertEndChild(elem);
|
||||
XMLElement* elem = brushElem->InsertNewChildElement("shade");
|
||||
elem->SetAttribute("value", shade_to_string(slot.shade()).c_str());
|
||||
}
|
||||
|
||||
// Pixel-perfect
|
||||
if (flags & int(BrushSlot::Flags::PixelPerfect)) {
|
||||
TiXmlElement elem("pixelperfect");
|
||||
elem.SetAttribute("value", slot.pixelPerfect() ? "true": "false");
|
||||
brushElem.InsertEndChild(elem);
|
||||
XMLElement* elem = brushElem->InsertNewChildElement("pixelperfect");
|
||||
elem->SetAttribute("value", slot.pixelPerfect() ? "true": "false");
|
||||
}
|
||||
}
|
||||
|
||||
brushesElem.InsertEndChild(brushElem);
|
||||
}
|
||||
|
||||
TiXmlDeclaration declaration("1.0", "utf-8", "");
|
||||
doc->InsertEndChild(declaration);
|
||||
doc->InsertEndChild(brushesElem);
|
||||
save_xml(doc, filename);
|
||||
save_xml(doc.get(), filename);
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -35,7 +35,7 @@
|
||||
#include "ui/ui.h"
|
||||
#include "ver/info.h"
|
||||
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
@ -47,6 +47,7 @@
|
||||
|
||||
namespace app {
|
||||
|
||||
using namespace tinyxml2;
|
||||
using namespace ui;
|
||||
|
||||
namespace {
|
||||
@ -343,8 +344,8 @@ void AppMenus::reload()
|
||||
{
|
||||
MENUS_TRACE("MENUS: AppMenus::reload()");
|
||||
|
||||
XmlDocumentRef doc(GuiXml::instance()->doc());
|
||||
TiXmlHandle handle(doc.get());
|
||||
XMLDocument* doc = GuiXml::instance()->doc();
|
||||
XMLHandle handle(doc);
|
||||
const char* path = GuiXml::instance()->filename();
|
||||
|
||||
////////////////////////////////////////
|
||||
@ -454,9 +455,9 @@ void AppMenus::reload()
|
||||
|
||||
LOG("MENU: Loading commands keyboard shortcuts from %s\n", path);
|
||||
|
||||
TiXmlElement* xmlKey = handle
|
||||
.FirstChild("gui")
|
||||
.FirstChild("keyboard").ToElement();
|
||||
XMLElement* xmlKey = handle
|
||||
.FirstChildElement("gui")
|
||||
.FirstChildElement("keyboard").ToElement();
|
||||
|
||||
// From a fresh start, load the default keys
|
||||
KeyboardShortcuts::instance()->clear();
|
||||
@ -716,15 +717,15 @@ void AppMenus::removeMenuItemFromGroup(Widget* menuItem)
|
||||
});
|
||||
}
|
||||
|
||||
Menu* AppMenus::loadMenuById(TiXmlHandle& handle, const char* id)
|
||||
Menu* AppMenus::loadMenuById(XMLHandle& handle, const char* id)
|
||||
{
|
||||
ASSERT(id != NULL);
|
||||
|
||||
// <gui><menus><menu>
|
||||
TiXmlElement* xmlMenu = handle
|
||||
.FirstChild("gui")
|
||||
.FirstChild("menus")
|
||||
.FirstChild("menu").ToElement();
|
||||
XMLElement* xmlMenu = handle
|
||||
.FirstChildElement("gui")
|
||||
.FirstChildElement("menus")
|
||||
.FirstChildElement("menu").ToElement();
|
||||
while (xmlMenu) {
|
||||
const char* menuId = xmlMenu->Attribute("id");
|
||||
|
||||
@ -739,12 +740,12 @@ Menu* AppMenus::loadMenuById(TiXmlHandle& handle, const char* id)
|
||||
throw base::Exception("Error loading menu '%s'\nReinstall the application.", id);
|
||||
}
|
||||
|
||||
Menu* AppMenus::convertXmlelemToMenu(TiXmlElement* elem)
|
||||
Menu* AppMenus::convertXmlelemToMenu(XMLElement* elem)
|
||||
{
|
||||
Menu* menu = new Menu();
|
||||
menu->setText(m_xmlTranslator(elem, "text"));
|
||||
|
||||
TiXmlElement* child = elem->FirstChildElement();
|
||||
XMLElement* child = elem->FirstChildElement();
|
||||
while (child) {
|
||||
Widget* menuitem = convertXmlelemToMenuitem(child, menu);
|
||||
if (menuitem)
|
||||
@ -759,7 +760,7 @@ Menu* AppMenus::convertXmlelemToMenu(TiXmlElement* elem)
|
||||
return menu;
|
||||
}
|
||||
|
||||
Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem, Menu* menu)
|
||||
Widget* AppMenus::convertXmlelemToMenuitem(XMLElement* elem, Menu* menu)
|
||||
{
|
||||
const char* id = elem->Attribute("id");
|
||||
const char* group = elem->Attribute("group");
|
||||
@ -791,7 +792,7 @@ Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem, Menu* menu)
|
||||
// load params
|
||||
Params params;
|
||||
if (command) {
|
||||
TiXmlElement* xmlParam = elem->FirstChildElement("param");
|
||||
XMLElement* xmlParam = elem->FirstChildElement("param");
|
||||
while (xmlParam) {
|
||||
const char* param_name = xmlParam->Attribute("name");
|
||||
const char* param_value = xmlParam->Attribute("value");
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -20,8 +20,10 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
class TiXmlElement;
|
||||
class TiXmlHandle;
|
||||
namespace tinyxml2 {
|
||||
class XMLElement;
|
||||
class XMLHandle;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
class Command;
|
||||
@ -73,9 +75,9 @@ namespace app {
|
||||
template<typename Pred>
|
||||
void removeMenuItemFromGroup(Pred pred);
|
||||
|
||||
Menu* loadMenuById(TiXmlHandle& handle, const char *id);
|
||||
Menu* convertXmlelemToMenu(TiXmlElement* elem);
|
||||
Widget* convertXmlelemToMenuitem(TiXmlElement* elem, Menu* menu);
|
||||
Menu* loadMenuById(tinyxml2::XMLHandle& handle, const char *id);
|
||||
Menu* convertXmlelemToMenu(tinyxml2::XMLElement* elem);
|
||||
Widget* convertXmlelemToMenuitem(tinyxml2::XMLElement* elem, Menu* menu);
|
||||
void applyShortcutToMenuitemsWithCommand(Menu* menu, Command* command, const Params& params,
|
||||
const KeyPtr& key);
|
||||
void syncNativeMenuItemKeyShortcuts(Menu* menu);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (c) 2023 Igara Studio S.A.
|
||||
// Copyright (c) 2023-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -156,9 +156,7 @@ void ChangeBrushCommand::onExecute(Context* context)
|
||||
|
||||
// Create a copy of the brush (to avoid modifying the original
|
||||
// brush from the AppBrushes stock)
|
||||
BrushRef newBrush = std::make_shared<Brush>(*brush);
|
||||
newBrush->setImage(newImg.get(),
|
||||
newMsk.get());
|
||||
BrushRef newBrush = brush->cloneWithExistingImages(newImg, newMsk);
|
||||
contextBar->setActiveBrush(newBrush);
|
||||
}
|
||||
else {
|
||||
@ -210,9 +208,7 @@ void ChangeBrushCommand::onExecute(Context* context)
|
||||
break;
|
||||
}
|
||||
|
||||
BrushRef newBrush = std::make_shared<Brush>(*brush);
|
||||
newBrush->setImage(newImg.get(),
|
||||
newMsk.get());
|
||||
BrushRef newBrush = brush->cloneWithExistingImages(newImg, newMsk);
|
||||
contextBar->setActiveBrush(newBrush);
|
||||
}
|
||||
else {
|
||||
@ -297,10 +293,7 @@ void ChangeBrushCommand::onExecute(Context* context)
|
||||
|
||||
ImageRef newImg2(crop_image(newImg.get(), cropBounds, bg));
|
||||
ImageRef newMsk2(crop_image(newMsk.get(), cropBounds, bg));
|
||||
|
||||
BrushRef newBrush = std::make_shared<Brush>(*brush);
|
||||
newBrush->setImage(newImg.get(),
|
||||
newMsk.get());
|
||||
BrushRef newBrush = brush->cloneWithExistingImages(newImg2, newMsk2);
|
||||
contextBar->setActiveBrush(newBrush);
|
||||
}
|
||||
break;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -484,7 +484,8 @@ protected:
|
||||
std::string onGetFriendlyName() const override;
|
||||
|
||||
private:
|
||||
bool m_useUI;
|
||||
bool m_showDlg;
|
||||
bool m_showProgress;
|
||||
doc::PixelFormat m_format;
|
||||
render::Dithering m_dithering;
|
||||
doc::RgbMapAlgorithm m_rgbmap;
|
||||
@ -494,7 +495,8 @@ private:
|
||||
ChangePixelFormatCommand::ChangePixelFormatCommand()
|
||||
: Command(CommandId::ChangePixelFormat(), CmdUIOnlyFlag)
|
||||
{
|
||||
m_useUI = true;
|
||||
m_showDlg = true;
|
||||
m_showProgress = true;
|
||||
m_format = IMAGE_RGB;
|
||||
m_dithering = render::Dithering();
|
||||
m_rgbmap = doc::RgbMapAlgorithm::DEFAULT;
|
||||
@ -503,15 +505,20 @@ ChangePixelFormatCommand::ChangePixelFormatCommand()
|
||||
|
||||
void ChangePixelFormatCommand::onLoadParams(const Params& params)
|
||||
{
|
||||
m_useUI = false;
|
||||
m_showDlg = false;
|
||||
m_showProgress = true;
|
||||
|
||||
std::string format = params.get("format");
|
||||
if (format == "rgb") m_format = IMAGE_RGB;
|
||||
else if (format == "grayscale" ||
|
||||
format == "gray") m_format = IMAGE_GRAYSCALE;
|
||||
else if (format == "indexed") m_format = IMAGE_INDEXED;
|
||||
else
|
||||
m_useUI = true;
|
||||
else {
|
||||
m_showDlg = true;
|
||||
}
|
||||
|
||||
if (params.has_param("ui"))
|
||||
m_showDlg = m_showProgress = params.get_as<bool>("ui");
|
||||
|
||||
std::string dithering = params.get("dithering");
|
||||
if (dithering == "ordered")
|
||||
@ -587,7 +594,7 @@ bool ChangePixelFormatCommand::onEnabled(Context* context)
|
||||
if (!sprite)
|
||||
return false;
|
||||
|
||||
if (m_useUI)
|
||||
if (m_showDlg)
|
||||
return true;
|
||||
|
||||
if (sprite->pixelFormat() == IMAGE_INDEXED &&
|
||||
@ -600,7 +607,7 @@ bool ChangePixelFormatCommand::onEnabled(Context* context)
|
||||
|
||||
bool ChangePixelFormatCommand::onChecked(Context* context)
|
||||
{
|
||||
if (m_useUI)
|
||||
if (m_showDlg)
|
||||
return false;
|
||||
|
||||
const ContextReader reader(context);
|
||||
@ -622,7 +629,7 @@ void ChangePixelFormatCommand::onExecute(Context* context)
|
||||
bool flatten = false;
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
if (m_useUI) {
|
||||
if (context->isUIAvailable() && m_showDlg) {
|
||||
ColorModeWindow window(Editor::activeEditor());
|
||||
|
||||
window.remapWindow();
|
||||
@ -646,12 +653,12 @@ void ChangePixelFormatCommand::onExecute(Context* context)
|
||||
#endif // ENABLE_UI
|
||||
|
||||
// No conversion needed
|
||||
if (context->activeDocument()->sprite()->pixelFormat() == m_format)
|
||||
Doc* doc = context->activeDocument();
|
||||
if (doc->sprite()->pixelFormat() == m_format)
|
||||
return;
|
||||
|
||||
{
|
||||
const ContextReader reader(context);
|
||||
SpriteJob job(reader, Strings::color_mode_title().c_str());
|
||||
SpriteJob job(context, doc, Strings::color_mode_title(), m_showProgress);
|
||||
Sprite* sprite(job.sprite());
|
||||
|
||||
// TODO this was moved in the main UI thread because
|
||||
@ -662,16 +669,17 @@ void ChangePixelFormatCommand::onExecute(Context* context)
|
||||
// https://github.com/aseprite/aseprite/issues/509
|
||||
// https://github.com/aseprite/aseprite/issues/378
|
||||
if (flatten) {
|
||||
Tx tx(Tx::LockDoc, context, doc);
|
||||
const bool newBlend = Preferences::instance().experimental.newBlend();
|
||||
SelectedLayers selLayers;
|
||||
for (auto layer : sprite->root()->layers())
|
||||
selLayers.insert(layer);
|
||||
job.tx()(new cmd::FlattenLayers(sprite, selLayers, newBlend));
|
||||
tx(new cmd::FlattenLayers(sprite, selLayers, newBlend));
|
||||
}
|
||||
|
||||
job.startJobWithCallback(
|
||||
[this, &job, sprite] {
|
||||
job.tx()(
|
||||
[this, &job, sprite](Tx& tx) {
|
||||
tx(
|
||||
new cmd::SetPixelFormat(
|
||||
sprite, m_format,
|
||||
m_dithering,
|
||||
@ -690,7 +698,7 @@ std::string ChangePixelFormatCommand::onGetFriendlyName() const
|
||||
{
|
||||
std::string conversion;
|
||||
|
||||
if (!m_useUI) {
|
||||
if (!m_showDlg) {
|
||||
switch (m_format) {
|
||||
case IMAGE_RGB:
|
||||
conversion = Strings::commands_ChangePixelFormat_RGB();
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -93,10 +93,7 @@ bool ColorQuantizationCommand::onEnabled(Context* ctx)
|
||||
|
||||
void ColorQuantizationCommand::onExecute(Context* ctx)
|
||||
{
|
||||
#ifdef ENABLE_UI
|
||||
const bool ui = (params().ui() && ctx->isUIAvailable());
|
||||
#endif
|
||||
|
||||
auto& pref = Preferences::instance();
|
||||
bool withAlpha = params().withAlpha();
|
||||
int maxColors = params().maxColors();
|
||||
@ -177,45 +174,46 @@ void ColorQuantizationCommand::onExecute(Context* ctx)
|
||||
return;
|
||||
|
||||
try {
|
||||
ContextReader reader(ctx);
|
||||
Doc* doc = site.document();
|
||||
Sprite* sprite = site.sprite();
|
||||
frame_t frame = site.frame();
|
||||
const Palette* curPalette = site.sprite()->palette(frame);
|
||||
Palette tmpPalette(frame, entries.picks());
|
||||
|
||||
SpriteJob job(reader, "Color Quantization");
|
||||
SpriteJob job(ctx, doc, "Color Quantization", ui);
|
||||
const bool newBlend = pref.experimental.newBlend();
|
||||
job.startJobWithCallback(
|
||||
[sprite, withAlpha, &tmpPalette, &job, newBlend, algorithm]{
|
||||
[sprite, withAlpha, curPalette, &tmpPalette, &job, &entries,
|
||||
newBlend, algorithm, createPal, site, frame](Tx& tx) {
|
||||
render::create_palette_from_sprite(
|
||||
sprite, 0, sprite->lastFrame(),
|
||||
withAlpha, &tmpPalette,
|
||||
&job, // SpriteJob is a render::TaskDelegate
|
||||
newBlend,
|
||||
algorithm);
|
||||
|
||||
std::unique_ptr<Palette> newPalette(
|
||||
new Palette(createPal ? tmpPalette:
|
||||
*site.palette()));
|
||||
|
||||
if (createPal) {
|
||||
entries = PalettePicks(newPalette->size());
|
||||
entries.all();
|
||||
}
|
||||
|
||||
int i = 0, j = 0;
|
||||
for (bool state : entries) {
|
||||
if (state)
|
||||
newPalette->setEntry(i, tmpPalette.getEntry(j++));
|
||||
++i;
|
||||
}
|
||||
|
||||
if (*curPalette != *newPalette)
|
||||
tx(new cmd::SetPalette(sprite, frame, newPalette.get()));
|
||||
});
|
||||
job.waitJob();
|
||||
if (job.isCanceled())
|
||||
return;
|
||||
|
||||
std::unique_ptr<Palette> newPalette(
|
||||
new Palette(createPal ? tmpPalette:
|
||||
*site.palette()));
|
||||
|
||||
if (createPal) {
|
||||
entries = PalettePicks(newPalette->size());
|
||||
entries.all();
|
||||
}
|
||||
|
||||
int i = 0, j = 0;
|
||||
for (bool state : entries) {
|
||||
if (state)
|
||||
newPalette->setEntry(i, tmpPalette.getEntry(j++));
|
||||
++i;
|
||||
}
|
||||
|
||||
if (*curPalette != *newPalette)
|
||||
job.tx()(new cmd::SetPalette(sprite, frame, newPalette.get()));
|
||||
}
|
||||
catch (const base::Exception& e) {
|
||||
Console::showException(e);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -11,6 +11,7 @@
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/commands/cmd_export_sprite_sheet.h"
|
||||
#include "app/console.h"
|
||||
#include "app/context.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/doc.h"
|
||||
@ -141,6 +142,17 @@ ConstraintType constraint_type_from_params(const ExportSpriteSheetParams& params
|
||||
|
||||
#endif // ENABLE_UI
|
||||
|
||||
void destroy_doc(Context* ctx, Doc* doc)
|
||||
{
|
||||
try {
|
||||
DocDestroyer destroyer(ctx, doc, 500);
|
||||
destroyer.destroyDocument();
|
||||
}
|
||||
catch (const LockedDocException& ex) {
|
||||
Console::showException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
Doc* generate_sprite_sheet_from_params(
|
||||
DocExporter& exporter,
|
||||
Context* ctx,
|
||||
@ -500,8 +512,7 @@ public:
|
||||
auto ctx = UIContext::instance();
|
||||
ctx->setActiveDocument(m_site.document());
|
||||
|
||||
DocDestroyer destroyer(ctx, m_spriteSheet.release(), 100);
|
||||
destroyer.destroyDocument();
|
||||
destroy_doc(ctx, m_spriteSheet.release());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1014,8 +1025,7 @@ private:
|
||||
auto ctx = UIContext::instance();
|
||||
ctx->setActiveDocument(m_site.document());
|
||||
|
||||
DocDestroyer destroyer(ctx, m_spriteSheet.release(), 100);
|
||||
destroyer.destroyDocument();
|
||||
destroy_doc(ctx, m_spriteSheet.release());
|
||||
m_editor = nullptr;
|
||||
}
|
||||
return;
|
||||
@ -1066,8 +1076,7 @@ private:
|
||||
return;
|
||||
|
||||
if (token.canceled()) {
|
||||
DocDestroyer destroyer(&tmpCtx, newDocument, 100);
|
||||
destroyer.destroyDocument();
|
||||
destroy_doc(&tmpCtx, newDocument);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1090,8 +1099,7 @@ private:
|
||||
// old one. IN this case the newDocument contains a back
|
||||
// buffer (ImageBufferPtr) that will be discarded.
|
||||
m_executionID != executionID) {
|
||||
DocDestroyer destroyer(context, newDocument, 100);
|
||||
destroyer.destroyDocument();
|
||||
destroy_doc(context, newDocument);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1137,8 +1145,7 @@ private:
|
||||
|
||||
m_spriteSheet->notifyGeneralUpdate();
|
||||
|
||||
DocDestroyer destroyer(context, newDocument, 100);
|
||||
destroyer.destroyDocument();
|
||||
destroy_doc(context, newDocument);
|
||||
}
|
||||
|
||||
waitGenTaskAndDelete();
|
||||
@ -1186,8 +1193,9 @@ public:
|
||||
ExportSpriteSheetJob(
|
||||
DocExporter& exporter,
|
||||
const Site& site,
|
||||
const ExportSpriteSheetParams& params)
|
||||
: Job(Strings::export_sprite_sheet_generating().c_str())
|
||||
const ExportSpriteSheetParams& params,
|
||||
const bool showProgress)
|
||||
: Job(Strings::export_sprite_sheet_generating(), showProgress)
|
||||
, m_exporter(exporter)
|
||||
, m_site(site)
|
||||
, m_params(params) { }
|
||||
@ -1366,7 +1374,9 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
|
||||
std::unique_ptr<Doc> newDocument;
|
||||
#ifdef ENABLE_UI
|
||||
if (context->isUIAvailable()) {
|
||||
ExportSpriteSheetJob job(exporter, site, params);
|
||||
ExportSpriteSheetJob job(exporter, site, params,
|
||||
// Progress bar can be disabled with ui=false
|
||||
params.ui());
|
||||
job.startJob();
|
||||
job.waitJob();
|
||||
|
||||
@ -1379,8 +1389,10 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
|
||||
statusbar->showTip(1000, Strings::export_sprite_sheet_generated());
|
||||
|
||||
// Save the exported sprite sheet as a recent file
|
||||
if (newDocument->isAssociatedToFile())
|
||||
if (newDocument->isAssociatedToFile() &&
|
||||
should_add_file_to_recents(context, params)) {
|
||||
App::instance()->recentFiles()->addRecentFile(newDocument->filename());
|
||||
}
|
||||
|
||||
// Copy background and grid preferences
|
||||
DocumentPreferences& newDocPref(
|
||||
@ -1407,8 +1419,7 @@ void ExportSpriteSheetCommand::onExecute(Context* context)
|
||||
newDocument.release();
|
||||
}
|
||||
else {
|
||||
DocDestroyer destroyer(context, newDocument.release(), 100);
|
||||
destroyer.destroyDocument();
|
||||
destroy_doc(context, newDocument.release());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2022 Igara Studio S.A.
|
||||
// Copyright (C) 2022-2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -19,6 +19,7 @@ namespace app {
|
||||
|
||||
struct ExportSpriteSheetParams : public NewParams {
|
||||
Param<bool> ui { this, true, "ui" };
|
||||
Param<bool> recent { this, true, "recent" };
|
||||
Param<bool> askOverwrite { this, true, { "askOverwrite", "ask-overwrite" } };
|
||||
Param<app::SpriteSheetType> type { this, app::SpriteSheetType::None, "type" };
|
||||
Param<int> columns { this, 0, "columns" };
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -13,6 +13,7 @@
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/commands/new_params.h"
|
||||
#include "app/console.h"
|
||||
#include "app/context.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/doc_access.h"
|
||||
@ -264,8 +265,13 @@ private:
|
||||
releaseEditor();
|
||||
|
||||
if (m_fileOpened) {
|
||||
DocDestroyer destroyer(m_context, oldDocument, 100);
|
||||
destroyer.destroyDocument();
|
||||
try {
|
||||
DocDestroyer destroyer(m_context, oldDocument, 500);
|
||||
destroyer.destroyDocument();
|
||||
}
|
||||
catch (const LockedDocException& ex) {
|
||||
Console::showException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -14,11 +14,12 @@
|
||||
#include "app/color.h"
|
||||
#include "app/color_utils.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/console.h"
|
||||
#include "app/context.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/ini_file.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/ini_file.h"
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/ui/color_bar.h"
|
||||
@ -260,22 +261,21 @@ void MaskByColorCommand::maskPreview(const ContextReader& reader)
|
||||
reader.sprite(), image,
|
||||
xpos, ypos,
|
||||
m_selMode->selectionMode()));
|
||||
{
|
||||
ContextWriter writer(reader);
|
||||
|
||||
ContextWriter writer(reader);
|
||||
|
||||
#ifdef SHOW_BOUNDARIES_GEN_PERFORMANCE
|
||||
base::Chrono chrono;
|
||||
base::Chrono chrono;
|
||||
#endif
|
||||
|
||||
writer.document()->generateMaskBoundaries(mask.get());
|
||||
writer.document()->generateMaskBoundaries(mask.get());
|
||||
|
||||
#ifdef SHOW_BOUNDARIES_GEN_PERFORMANCE
|
||||
double time = chrono.elapsed();
|
||||
m_window->setText("Mask by Color (" + base::convert_to<std::string>(time) + ")");
|
||||
double time = chrono.elapsed();
|
||||
m_window->setText("Mask by Color (" + base::convert_to<std::string>(time) + ")");
|
||||
#endif
|
||||
|
||||
update_screen_for_document(writer.document());
|
||||
}
|
||||
update_screen_for_document(writer.document());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -36,8 +36,8 @@ namespace app {
|
||||
|
||||
class OpenFileJob : public Job, public IFileOpProgress {
|
||||
public:
|
||||
OpenFileJob(FileOp* fop)
|
||||
: Job(Strings::open_file_loading().c_str())
|
||||
OpenFileJob(FileOp* fop, const bool showProgress)
|
||||
: Job(Strings::open_file_loading(), showProgress)
|
||||
, m_fop(fop)
|
||||
{
|
||||
}
|
||||
@ -76,6 +76,7 @@ private:
|
||||
|
||||
OpenFileCommand::OpenFileCommand()
|
||||
: Command(CommandId::OpenFile(), CmdRecordableFlag)
|
||||
, m_ui(true)
|
||||
, m_repeatCheckbox(false)
|
||||
, m_oneFrame(false)
|
||||
, m_seqDecision(gen::SequenceDecision::ASK)
|
||||
@ -86,6 +87,12 @@ void OpenFileCommand::onLoadParams(const Params& params)
|
||||
{
|
||||
m_filename = params.get("filename");
|
||||
m_folder = params.get("folder"); // Initial folder
|
||||
|
||||
if (params.has_param("ui"))
|
||||
m_ui = params.get_as<bool>("ui");
|
||||
else
|
||||
m_ui = true;
|
||||
|
||||
m_repeatCheckbox = params.get_as<bool>("repeat_checkbox");
|
||||
m_oneFrame = params.get_as<bool>("oneframe");
|
||||
|
||||
@ -220,7 +227,7 @@ void OpenFileCommand::onExecute(Context* context)
|
||||
m_usedFiles.push_back(fn);
|
||||
}
|
||||
|
||||
OpenFileJob task(fop.get());
|
||||
OpenFileJob task(fop.get(), m_ui);
|
||||
task.showProgressWindow();
|
||||
|
||||
// Post-load processing, it is called from the GUI because may require user intervention.
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2016-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -36,6 +36,7 @@ namespace app {
|
||||
private:
|
||||
std::string m_filename;
|
||||
std::string m_folder;
|
||||
bool m_ui;
|
||||
bool m_repeatCheckbox;
|
||||
bool m_oneFrame;
|
||||
base::paths m_usedFiles;
|
||||
|
@ -784,10 +784,15 @@ public:
|
||||
m_context->activeDocument() &&
|
||||
m_context->activeDocument()->sprite() &&
|
||||
m_context->activeDocument()->sprite()->gridBounds() != gridBounds()) {
|
||||
ContextWriter writer(m_context);
|
||||
Tx tx(writer, Strings::commands_GridSettings(), ModifyDocument);
|
||||
tx(new cmd::SetGridBounds(writer.sprite(), gridBounds()));
|
||||
tx.commit();
|
||||
try {
|
||||
ContextWriter writer(m_context, 1000);
|
||||
Tx tx(writer, Strings::commands_GridSettings(), ModifyDocument);
|
||||
tx(new cmd::SetGridBounds(writer.sprite(), gridBounds()));
|
||||
tx.commit();
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
Console::showException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
m_curPref->show.grid(gridVisible()->isSelected());
|
||||
@ -1312,9 +1317,20 @@ private:
|
||||
if (language()->getItemCount() > 0)
|
||||
return;
|
||||
|
||||
// Select current language by lang ID
|
||||
// Check if the current language exists, in other case select English.
|
||||
Strings* strings = Strings::instance();
|
||||
std::string curLang = strings->currentLanguage();
|
||||
bool found = false;
|
||||
for (const LangInfo& lang : strings->availableLanguages()) {
|
||||
if (lang.id == curLang) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
curLang = Strings::kDefLanguage;
|
||||
|
||||
// Select current language by lang ID
|
||||
for (const LangInfo& lang : strings->availableLanguages()) {
|
||||
int i = language()->addItem(new LangItem(lang));
|
||||
if (lang.id == curLang)
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -13,7 +13,6 @@
|
||||
#include "app/cmd/set_cel_bounds.h"
|
||||
#include "app/commands/cmd_rotate.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/doc_api.h"
|
||||
#include "app/doc_range.h"
|
||||
#include "app/i18n/strings.h"
|
||||
@ -45,10 +44,12 @@ class RotateJob : public SpriteJob {
|
||||
|
||||
public:
|
||||
|
||||
RotateJob(const ContextReader& reader,
|
||||
RotateJob(Context* ctx, Doc* doc,
|
||||
const std::string& jobName,
|
||||
int angle, const CelList& cels, bool rotateSprite)
|
||||
: SpriteJob(reader, jobName.c_str())
|
||||
int angle, const CelList& cels,
|
||||
const bool rotateSprite,
|
||||
const bool showProgress)
|
||||
: SpriteJob(ctx, doc, jobName, showProgress)
|
||||
, m_cels(cels)
|
||||
, m_rotateSprite(rotateSprite) {
|
||||
m_angle = angle;
|
||||
@ -80,8 +81,8 @@ protected:
|
||||
}
|
||||
|
||||
// [working thread]
|
||||
void onJob() override {
|
||||
DocApi api = document()->getApi(tx());
|
||||
void onSpriteJob(Tx& tx) override {
|
||||
DocApi api = document()->getApi(tx);
|
||||
|
||||
// 1) Rotate cel positions
|
||||
for (Cel* cel : m_cels) {
|
||||
@ -93,7 +94,7 @@ protected:
|
||||
gfx::RectF bounds = cel->boundsF();
|
||||
rotate_rect(bounds);
|
||||
if (cel->boundsF() != bounds)
|
||||
tx()(new cmd::SetCelBoundsF(cel, bounds));
|
||||
tx(new cmd::SetCelBoundsF(cel, bounds));
|
||||
}
|
||||
else {
|
||||
gfx::Rect bounds = cel->bounds();
|
||||
@ -168,12 +169,18 @@ protected:
|
||||
RotateCommand::RotateCommand()
|
||||
: Command(CommandId::Rotate(), CmdRecordableFlag)
|
||||
{
|
||||
m_ui = true;
|
||||
m_flipMask = false;
|
||||
m_angle = 0;
|
||||
}
|
||||
|
||||
void RotateCommand::onLoadParams(const Params& params)
|
||||
{
|
||||
if (params.has_param("ui"))
|
||||
m_ui = params.get_as<bool>("ui");
|
||||
else
|
||||
m_ui = true;
|
||||
|
||||
std::string target = params.get("target");
|
||||
m_flipMask = (target == "mask");
|
||||
|
||||
@ -192,6 +199,7 @@ void RotateCommand::onExecute(Context* context)
|
||||
{
|
||||
{
|
||||
Site site = context->activeSite();
|
||||
Doc* doc = site.document();
|
||||
CelList cels;
|
||||
bool rotateSprite = false;
|
||||
|
||||
@ -203,7 +211,7 @@ void RotateCommand::onExecute(Context* context)
|
||||
// If we want to rotate the visible mask, we can go to
|
||||
// MovingPixelsState (even when the range is enabled, because
|
||||
// now PixelsMovement support ranges).
|
||||
if (site.document()->isMaskVisible()) {
|
||||
if (doc->isMaskVisible()) {
|
||||
// Select marquee tool
|
||||
if (tools::Tool* tool = App::instance()->toolBox()
|
||||
->getToolById(tools::WellKnownTools::RectangularMarquee)) {
|
||||
@ -237,13 +245,12 @@ void RotateCommand::onExecute(Context* context)
|
||||
rotateSprite = true;
|
||||
}
|
||||
|
||||
ContextReader reader(context);
|
||||
{
|
||||
RotateJob job(reader, friendlyName(), m_angle, cels, rotateSprite);
|
||||
RotateJob job(context, doc, friendlyName(), m_angle, cels, rotateSprite, m_ui);
|
||||
job.startJob();
|
||||
job.waitJob();
|
||||
}
|
||||
update_screen_for_document(reader.document());
|
||||
update_screen_for_document(doc);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -27,6 +28,7 @@ namespace app {
|
||||
std::string onGetFriendlyName() const override;
|
||||
|
||||
private:
|
||||
bool m_ui;
|
||||
bool m_flipMask;
|
||||
int m_angle;
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -49,8 +49,8 @@ namespace app {
|
||||
|
||||
class SaveFileJob : public Job, public IFileOpProgress {
|
||||
public:
|
||||
SaveFileJob(FileOp* fop)
|
||||
: Job(Strings::save_file_saving().c_str())
|
||||
SaveFileJob(FileOp* fop, const bool showProgressBar)
|
||||
: Job(Strings::save_file_saving(), showProgressBar)
|
||||
, m_fop(fop)
|
||||
{
|
||||
}
|
||||
@ -239,7 +239,7 @@ void SaveFileBaseCommand::saveDocumentInBackground(
|
||||
if (resizeOnTheFly == ResizeOnTheFly::On)
|
||||
fop->setOnTheFlyScale(scale);
|
||||
|
||||
SaveFileJob job(fop.get());
|
||||
SaveFileJob job(fop.get(), params().ui());
|
||||
job.showProgressWindow();
|
||||
|
||||
if (fop->hasError()) {
|
||||
@ -257,7 +257,7 @@ void SaveFileBaseCommand::saveDocumentInBackground(
|
||||
document->impossibleToBackToSavedState();
|
||||
}
|
||||
else {
|
||||
if (context->isUIAvailable() && params().ui())
|
||||
if (should_add_file_to_recents(context, params()))
|
||||
App::instance()->recentFiles()->addRecentFile(filename);
|
||||
|
||||
if (markAsSaved == MarkAsSaved::On) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2021-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2021-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -23,6 +23,7 @@ namespace app {
|
||||
|
||||
struct SaveFileParams : public NewParams {
|
||||
Param<bool> ui { this, true, { "ui", "useUI" } };
|
||||
Param<bool> recent { this, true, "recent" };
|
||||
Param<std::string> filename { this, std::string(), "filename" };
|
||||
Param<std::string> filenameFormat { this, std::string(), { "filenameFormat", "filename-format" } };
|
||||
Param<std::string> tag { this, std::string(), { "tag", "frame-tag" } };
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -9,31 +9,32 @@
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/cmd/add_tileset.h"
|
||||
#include "app/cmd/assign_color_profile.h"
|
||||
#include "app/cmd/convert_color_profile.h"
|
||||
#include "app/cmd/add_tileset.h"
|
||||
#include "app/cmd/remove_tileset.h"
|
||||
#include "app/cmd/set_pixel_ratio.h"
|
||||
#include "app/cmd/set_user_data.h"
|
||||
#include "app/color.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/console.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/doc_api.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/util/tileset_utils.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/ui/color_button.h"
|
||||
#include "app/ui/user_data_view.h"
|
||||
#include "app/ui/skin/skin_theme.h"
|
||||
#include "app/ui/user_data_view.h"
|
||||
#include "app/util/pixel_ratio.h"
|
||||
#include "app/util/tileset_utils.h"
|
||||
#include "base/mem_utils.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/palette.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "doc/user_data.h"
|
||||
#include "doc/tilesets.h"
|
||||
#include "doc/user_data.h"
|
||||
#include "fmt/format.h"
|
||||
#include "os/color_space.h"
|
||||
#include "os/system.h"
|
||||
@ -351,12 +352,17 @@ void SpritePropertiesCommand::onExecute(Context* context)
|
||||
[&](){
|
||||
selectedColorProfile = window.colorProfile()->getSelectedItemIndex();
|
||||
|
||||
ContextWriter writer(context);
|
||||
Sprite* sprite(writer.sprite());
|
||||
Tx tx(writer, Strings::sprite_properties_assign_color_profile());
|
||||
tx(new cmd::AssignColorProfile(
|
||||
sprite, colorSpaces[selectedColorProfile]->gfxColorSpace()));
|
||||
tx.commit();
|
||||
try {
|
||||
ContextWriter writer(context);
|
||||
Sprite* sprite(writer.sprite());
|
||||
Tx tx(writer, Strings::sprite_properties_assign_color_profile());
|
||||
tx(new cmd::AssignColorProfile(
|
||||
sprite, colorSpaces[selectedColorProfile]->gfxColorSpace()));
|
||||
tx.commit();
|
||||
}
|
||||
catch (const base::Exception& e) {
|
||||
Console::showException(e);
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
});
|
||||
@ -364,12 +370,17 @@ void SpritePropertiesCommand::onExecute(Context* context)
|
||||
[&](){
|
||||
selectedColorProfile = window.colorProfile()->getSelectedItemIndex();
|
||||
|
||||
ContextWriter writer(context);
|
||||
Sprite* sprite(writer.sprite());
|
||||
Tx tx(writer, Strings::sprite_properties_convert_color_profile());
|
||||
tx(new cmd::ConvertColorProfile(
|
||||
sprite, colorSpaces[selectedColorProfile]->gfxColorSpace()));
|
||||
tx.commit();
|
||||
try {
|
||||
ContextWriter writer(context);
|
||||
Sprite* sprite(writer.sprite());
|
||||
Tx tx(writer, Strings::sprite_properties_convert_color_profile());
|
||||
tx(new cmd::ConvertColorProfile(
|
||||
sprite, colorSpaces[selectedColorProfile]->gfxColorSpace()));
|
||||
tx.commit();
|
||||
}
|
||||
catch (const base::Exception& e) {
|
||||
Console::showException(e);
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -80,8 +80,12 @@ class SpriteSizeJob : public SpriteJob {
|
||||
|
||||
public:
|
||||
|
||||
SpriteSizeJob(const ContextReader& reader, int new_width, int new_height, ResizeMethod resize_method)
|
||||
: SpriteJob(reader, Strings::sprite_size_title().c_str()) {
|
||||
SpriteSizeJob(Context* ctx, Doc* doc,
|
||||
const int new_width,
|
||||
const int new_height,
|
||||
const ResizeMethod resize_method,
|
||||
const bool showProgress)
|
||||
: SpriteJob(ctx, doc, Strings::sprite_size_title(), showProgress) {
|
||||
m_new_width = new_width;
|
||||
m_new_height = new_height;
|
||||
m_resize_method = resize_method;
|
||||
@ -90,8 +94,8 @@ public:
|
||||
protected:
|
||||
|
||||
// [working thread]
|
||||
void onJob() override {
|
||||
DocApi api = writer().document()->getApi(tx());
|
||||
void onSpriteJob(Tx& tx) override {
|
||||
DocApi api = document()->getApi(tx);
|
||||
Tilesets* tilesets = sprite()->tilesets();
|
||||
|
||||
int img_count = 0;
|
||||
@ -147,7 +151,7 @@ protected:
|
||||
++progress;
|
||||
++idx;
|
||||
}
|
||||
tx()(new cmd::ReplaceTileset(sprite(), tsi, newTileset));
|
||||
tx(new cmd::ReplaceTileset(sprite(), tsi, newTileset));
|
||||
|
||||
// Cancel all the operation?
|
||||
if (isCanceled())
|
||||
@ -170,11 +174,11 @@ protected:
|
||||
cel->y()*scale.h,
|
||||
canvasSize.w,
|
||||
canvasSize.h);
|
||||
tx()(new cmd::SetCelBoundsF(cel, newBounds));
|
||||
tx(new cmd::SetCelBoundsF(cel, newBounds));
|
||||
}
|
||||
else {
|
||||
resize_cel_image(
|
||||
tx(), cel, scale,
|
||||
tx, cel, scale,
|
||||
m_resize_method,
|
||||
cel->layer()->isReference() ?
|
||||
-cel->boundsF().origin():
|
||||
@ -239,7 +243,7 @@ protected:
|
||||
newKey.setPivot(gfx::Point(scale_x(newKey.pivot().x),
|
||||
scale_y(newKey.pivot().y)));
|
||||
|
||||
tx()(new cmd::SetSliceKey(slice, k.frame(), newKey));
|
||||
tx(new cmd::SetSliceKey(slice, k.frame(), newKey));
|
||||
}
|
||||
}
|
||||
|
||||
@ -370,11 +374,10 @@ bool SpriteSizeCommand::onEnabled(Context* context)
|
||||
|
||||
void SpriteSizeCommand::onExecute(Context* context)
|
||||
{
|
||||
#ifdef ENABLE_UI
|
||||
const bool ui = (params().ui() && context->isUIAvailable());
|
||||
#endif
|
||||
const ContextReader reader(context);
|
||||
const Sprite* sprite(reader.sprite());
|
||||
const Site site = context->activeSite();
|
||||
Doc* doc = site.document();
|
||||
Sprite* sprite = site.sprite();
|
||||
auto& params = this->params();
|
||||
|
||||
double ratio = sprite->width() / double(sprite->height());
|
||||
@ -461,13 +464,13 @@ void SpriteSizeCommand::onExecute(Context* context)
|
||||
new_height = std::clamp(new_height, 1, DOC_SPRITE_MAX_HEIGHT);
|
||||
|
||||
{
|
||||
SpriteSizeJob job(reader, new_width, new_height, resize_method);
|
||||
SpriteSizeJob job(context, doc, new_width, new_height, resize_method, ui);
|
||||
job.startJob();
|
||||
job.waitJob();
|
||||
}
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
update_screen_for_document(reader.document());
|
||||
update_screen_for_document(doc);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -9,6 +9,7 @@
|
||||
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "app/context.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
@ -152,6 +153,20 @@ namespace app {
|
||||
T m_params;
|
||||
};
|
||||
|
||||
// Common logic to know if we should add a file to recent files. We
|
||||
// offer two params: "ui" and "recent", if "recent" is specified, we
|
||||
// do what it says. In other case "ui" is like the default value of
|
||||
// "recent", i.e. if there is ui=true, we add to recent, if there is
|
||||
// ui=false, we don't add it.
|
||||
template<typename T>
|
||||
inline bool should_add_file_to_recents(const Context* ctx,
|
||||
const T& params) {
|
||||
ASSERT(ctx);
|
||||
return (ctx->isUIAvailable()
|
||||
&& ((params.recent.isSet() && params.recent()) ||
|
||||
(!params.recent.isSet() && params.ui())));
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -191,8 +191,8 @@ DocDiff compare_docs(const Doc* a,
|
||||
if (aLay->type() != bLay->type() ||
|
||||
aLay->name() != bLay->name() ||
|
||||
aLay->userData() != bLay->userData() ||
|
||||
((int(aLay->flags()) & int(LayerFlags::PersistentFlagsMask)) !=
|
||||
(int(bLay->flags()) & int(LayerFlags::PersistentFlagsMask))) ||
|
||||
((int(aLay->flags()) & int(LayerFlags::StructuralFlagsMask)) !=
|
||||
(int(bLay->flags()) & int(LayerFlags::StructuralFlagsMask))) ||
|
||||
(aLay->isImage() && bLay->isImage() &&
|
||||
(((const LayerImage*)aLay)->opacity() != ((const LayerImage*)bLay)->opacity())) ||
|
||||
(aLay->isTilemap() && bLay->isTilemap() &&
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -21,6 +21,8 @@
|
||||
#include "fmt/format.h"
|
||||
#include "gfx/color.h"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <set>
|
||||
@ -29,6 +31,7 @@ namespace app {
|
||||
|
||||
using namespace base;
|
||||
using namespace doc;
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace {
|
||||
|
||||
@ -89,7 +92,7 @@ template<typename Container,
|
||||
typename ChildNameGetterFunc,
|
||||
typename UpdateXmlChildFunc>
|
||||
void update_xml_collection(const Container& container,
|
||||
TiXmlElement* xmlParent,
|
||||
XMLElement* xmlParent,
|
||||
const char* childElemName,
|
||||
const char* idAttrName,
|
||||
ChildNameGetterFunc childNameGetter,
|
||||
@ -98,12 +101,12 @@ void update_xml_collection(const Container& container,
|
||||
if (!xmlParent)
|
||||
return;
|
||||
|
||||
TiXmlElement* xmlNext = nullptr;
|
||||
XMLElement* xmlNext = nullptr;
|
||||
std::set<std::string> existent;
|
||||
|
||||
// Update existent children
|
||||
for (TiXmlElement* xmlChild=(xmlParent->FirstChild(childElemName) ?
|
||||
xmlParent->FirstChild(childElemName)->ToElement(): nullptr);
|
||||
for (XMLElement* xmlChild=(xmlParent->FirstChildElement(childElemName) ?
|
||||
xmlParent->FirstChildElement(childElemName): nullptr);
|
||||
xmlChild;
|
||||
xmlChild=xmlNext) {
|
||||
xmlNext = xmlChild->NextSiblingElement();
|
||||
@ -126,35 +129,33 @@ void update_xml_collection(const Container& container,
|
||||
// Delete this <child> element (as the child was removed from the
|
||||
// original container)
|
||||
if (!found)
|
||||
xmlParent->RemoveChild(xmlChild);
|
||||
xmlParent->DeleteChild(xmlChild);
|
||||
}
|
||||
|
||||
// Add new children
|
||||
for (const auto& child : container) {
|
||||
std::string thisChildName = childNameGetter(child);
|
||||
if (existent.find(thisChildName) == existent.end()) {
|
||||
TiXmlElement xmlChild(childElemName);
|
||||
xmlChild.SetAttribute(idAttrName, thisChildName.c_str());
|
||||
updateXmlChild(child, &xmlChild);
|
||||
|
||||
xmlParent->InsertEndChild(xmlChild);
|
||||
XMLElement* xmlChild = xmlParent->InsertNewChildElement(childElemName);
|
||||
xmlChild->SetAttribute(idAttrName, thisChildName.c_str());
|
||||
updateXmlChild(child, xmlChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_xml_part_from_slice_key(const doc::SliceKey* key, TiXmlElement* xmlPart)
|
||||
void update_xml_part_from_slice_key(const doc::SliceKey* key, XMLElement* xmlPart)
|
||||
{
|
||||
xmlPart->SetAttribute("x", key->bounds().x);
|
||||
xmlPart->SetAttribute("y", key->bounds().y);
|
||||
if (!key->hasCenter()) {
|
||||
xmlPart->SetAttribute("w", key->bounds().w);
|
||||
xmlPart->SetAttribute("h", key->bounds().h);
|
||||
if (xmlPart->Attribute("w1")) xmlPart->RemoveAttribute("w1");
|
||||
if (xmlPart->Attribute("w2")) xmlPart->RemoveAttribute("w2");
|
||||
if (xmlPart->Attribute("w3")) xmlPart->RemoveAttribute("w3");
|
||||
if (xmlPart->Attribute("h1")) xmlPart->RemoveAttribute("h1");
|
||||
if (xmlPart->Attribute("h2")) xmlPart->RemoveAttribute("h2");
|
||||
if (xmlPart->Attribute("h3")) xmlPart->RemoveAttribute("h3");
|
||||
if (xmlPart->Attribute("w1")) xmlPart->DeleteAttribute("w1");
|
||||
if (xmlPart->Attribute("w2")) xmlPart->DeleteAttribute("w2");
|
||||
if (xmlPart->Attribute("w3")) xmlPart->DeleteAttribute("w3");
|
||||
if (xmlPart->Attribute("h1")) xmlPart->DeleteAttribute("h1");
|
||||
if (xmlPart->Attribute("h2")) xmlPart->DeleteAttribute("h2");
|
||||
if (xmlPart->Attribute("h3")) xmlPart->DeleteAttribute("h3");
|
||||
}
|
||||
else {
|
||||
xmlPart->SetAttribute("w1", key->center().x);
|
||||
@ -163,8 +164,8 @@ void update_xml_part_from_slice_key(const doc::SliceKey* key, TiXmlElement* xmlP
|
||||
xmlPart->SetAttribute("h1", key->center().y);
|
||||
xmlPart->SetAttribute("h2", key->center().h);
|
||||
xmlPart->SetAttribute("h3", key->bounds().h - key->center().y2());
|
||||
if (xmlPart->Attribute("w")) xmlPart->RemoveAttribute("w");
|
||||
if (xmlPart->Attribute("h")) xmlPart->RemoveAttribute("h");
|
||||
if (xmlPart->Attribute("w")) xmlPart->DeleteAttribute("w");
|
||||
if (xmlPart->Attribute("h")) xmlPart->DeleteAttribute("h");
|
||||
}
|
||||
|
||||
if (key->hasPivot()) {
|
||||
@ -172,17 +173,17 @@ void update_xml_part_from_slice_key(const doc::SliceKey* key, TiXmlElement* xmlP
|
||||
xmlPart->SetAttribute("focusy", key->pivot().y);
|
||||
}
|
||||
else {
|
||||
if (xmlPart->Attribute("focusx")) xmlPart->RemoveAttribute("focusx");
|
||||
if (xmlPart->Attribute("focusy")) xmlPart->RemoveAttribute("focusy");
|
||||
if (xmlPart->Attribute("focusx")) xmlPart->DeleteAttribute("focusx");
|
||||
if (xmlPart->Attribute("focusy")) xmlPart->DeleteAttribute("focusy");
|
||||
}
|
||||
}
|
||||
|
||||
void update_xml_slice(const doc::Slice* slice, TiXmlElement* xmlSlice)
|
||||
void update_xml_slice(const doc::Slice* slice, XMLElement* xmlSlice)
|
||||
{
|
||||
if (!slice->userData().text().empty())
|
||||
xmlSlice->SetAttribute("text", slice->userData().text().c_str());
|
||||
else if (xmlSlice->Attribute("text"))
|
||||
xmlSlice->RemoveAttribute("text");
|
||||
xmlSlice->DeleteAttribute("text");
|
||||
xmlSlice->SetAttribute("color", color_to_hex(slice->userData().color()).c_str());
|
||||
|
||||
// Update <key> elements
|
||||
@ -192,7 +193,7 @@ void update_xml_slice(const doc::Slice* slice, TiXmlElement* xmlSlice)
|
||||
[](const Keyframes<SliceKey>::Key& key) -> std::string {
|
||||
return base::convert_to<std::string>(key.frame());
|
||||
},
|
||||
[](const Keyframes<SliceKey>::Key& key, TiXmlElement* xmlKey) {
|
||||
[](const Keyframes<SliceKey>::Key& key, XMLElement* xmlKey) {
|
||||
SliceKey* sliceKey = key.value();
|
||||
|
||||
xmlKey->SetAttribute("x", sliceKey->bounds().x);
|
||||
@ -207,10 +208,10 @@ void update_xml_slice(const doc::Slice* slice, TiXmlElement* xmlSlice)
|
||||
xmlKey->SetAttribute("ch", sliceKey->center().h);
|
||||
}
|
||||
else {
|
||||
if (xmlKey->Attribute("cx")) xmlKey->RemoveAttribute("cx");
|
||||
if (xmlKey->Attribute("cy")) xmlKey->RemoveAttribute("cy");
|
||||
if (xmlKey->Attribute("cw")) xmlKey->RemoveAttribute("cw");
|
||||
if (xmlKey->Attribute("ch")) xmlKey->RemoveAttribute("ch");
|
||||
if (xmlKey->Attribute("cx")) xmlKey->DeleteAttribute("cx");
|
||||
if (xmlKey->Attribute("cy")) xmlKey->DeleteAttribute("cy");
|
||||
if (xmlKey->Attribute("cw")) xmlKey->DeleteAttribute("cw");
|
||||
if (xmlKey->Attribute("ch")) xmlKey->DeleteAttribute("ch");
|
||||
}
|
||||
|
||||
if (sliceKey->hasPivot()) {
|
||||
@ -218,8 +219,8 @@ void update_xml_slice(const doc::Slice* slice, TiXmlElement* xmlSlice)
|
||||
xmlKey->SetAttribute("py", sliceKey->pivot().y);
|
||||
}
|
||||
else {
|
||||
if (xmlKey->Attribute("px")) xmlKey->RemoveAttribute("px");
|
||||
if (xmlKey->Attribute("py")) xmlKey->RemoveAttribute("py");
|
||||
if (xmlKey->Attribute("px")) xmlKey->DeleteAttribute("px");
|
||||
if (xmlKey->Attribute("py")) xmlKey->DeleteAttribute("py");
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -230,12 +231,12 @@ void load_aseprite_data_file(const std::string& dataFilename,
|
||||
doc::Document* doc,
|
||||
app::Color& defaultSliceColor)
|
||||
{
|
||||
XmlDocumentRef xmlDoc = open_xml(dataFilename);
|
||||
TiXmlHandle handle(xmlDoc.get());
|
||||
XMLDocumentRef xmlDoc = open_xml(dataFilename);
|
||||
XMLHandle handle(xmlDoc.get());
|
||||
|
||||
TiXmlElement* xmlSlices = handle
|
||||
.FirstChild("sprite")
|
||||
.FirstChild("slices").ToElement();
|
||||
XMLElement* xmlSlices = handle
|
||||
.FirstChildElement("sprite")
|
||||
.FirstChildElement("slices").ToElement();
|
||||
|
||||
// Load slices/parts from theme.xml file
|
||||
if (xmlSlices &&
|
||||
@ -243,13 +244,13 @@ void load_aseprite_data_file(const std::string& dataFilename,
|
||||
std::string themeFileName = xmlSlices->Attribute("theme");
|
||||
|
||||
// Open theme XML file
|
||||
XmlDocumentRef xmlThemeDoc = open_xml(
|
||||
XMLDocumentRef xmlThemeDoc = open_xml(
|
||||
base::join_path(base::get_file_path(dataFilename), themeFileName));
|
||||
TiXmlHandle themeHandle(xmlThemeDoc.get());
|
||||
for (TiXmlElement* xmlPart = themeHandle
|
||||
.FirstChild("theme")
|
||||
.FirstChild("parts")
|
||||
.FirstChild("part").ToElement();
|
||||
XMLHandle themeHandle(xmlThemeDoc.get());
|
||||
for (XMLElement* xmlPart = themeHandle
|
||||
.FirstChildElement("theme")
|
||||
.FirstChildElement("parts")
|
||||
.FirstChildElement("part").ToElement();
|
||||
xmlPart;
|
||||
xmlPart=xmlPart->NextSiblingElement()) {
|
||||
const char* partId = xmlPart->Attribute("id");
|
||||
@ -300,8 +301,8 @@ void load_aseprite_data_file(const std::string& dataFilename,
|
||||
}
|
||||
// Load slices from <slice> elements
|
||||
else if (xmlSlices) {
|
||||
for (TiXmlElement* xmlSlice=(xmlSlices->FirstChild("slice") ?
|
||||
xmlSlices->FirstChild("slice")->ToElement(): nullptr);
|
||||
for (XMLElement* xmlSlice=(xmlSlices->FirstChildElement("slice") ?
|
||||
xmlSlices->FirstChildElement("slice")->ToElement(): nullptr);
|
||||
xmlSlice;
|
||||
xmlSlice=xmlSlice->NextSiblingElement()) {
|
||||
const char* sliceId = xmlSlice->Attribute("id");
|
||||
@ -332,8 +333,8 @@ void load_aseprite_data_file(const std::string& dataFilename,
|
||||
}
|
||||
slice->userData().setColor(color);
|
||||
|
||||
for (TiXmlElement* xmlKey=(xmlSlice->FirstChild("key") ?
|
||||
xmlSlice->FirstChild("key")->ToElement(): nullptr);
|
||||
for (XMLElement* xmlKey=(xmlSlice->FirstChildElement("key") ?
|
||||
xmlSlice->FirstChildElement("key")->ToElement(): nullptr);
|
||||
xmlKey;
|
||||
xmlKey=xmlKey->NextSiblingElement()) {
|
||||
if (!xmlKey->Attribute("frame"))
|
||||
@ -373,12 +374,12 @@ void load_aseprite_data_file(const std::string& dataFilename,
|
||||
#ifdef ENABLE_SAVE
|
||||
void save_aseprite_data_file(const std::string& dataFilename, const doc::Document* doc)
|
||||
{
|
||||
XmlDocumentRef xmlDoc = open_xml(dataFilename);
|
||||
TiXmlHandle handle(xmlDoc.get());
|
||||
XMLDocumentRef xmlDoc = open_xml(dataFilename);
|
||||
XMLHandle handle(xmlDoc.get());
|
||||
|
||||
TiXmlElement* xmlSlices = handle
|
||||
.FirstChild("sprite")
|
||||
.FirstChild("slices").ToElement();
|
||||
XMLElement* xmlSlices = handle
|
||||
.FirstChildElement("sprite")
|
||||
.FirstChildElement("slices").ToElement();
|
||||
|
||||
// Update theme.xml file
|
||||
if (xmlSlices &&
|
||||
@ -386,13 +387,13 @@ void save_aseprite_data_file(const std::string& dataFilename, const doc::Documen
|
||||
// Open theme XML file
|
||||
std::string themeFileName = base::join_path(
|
||||
base::get_file_path(dataFilename), xmlSlices->Attribute("theme"));
|
||||
XmlDocumentRef xmlThemeDoc = open_xml(themeFileName);
|
||||
XMLDocumentRef xmlThemeDoc = open_xml(themeFileName);
|
||||
|
||||
TiXmlHandle themeHandle(xmlThemeDoc.get());
|
||||
TiXmlElement* xmlParts =
|
||||
XMLHandle themeHandle(xmlThemeDoc.get());
|
||||
XMLElement* xmlParts =
|
||||
themeHandle
|
||||
.FirstChild("theme")
|
||||
.FirstChild("parts").ToElement();
|
||||
.FirstChildElement("theme")
|
||||
.FirstChildElement("parts").ToElement();
|
||||
|
||||
update_xml_collection(
|
||||
doc->sprite()->slices(),
|
||||
@ -403,13 +404,13 @@ void save_aseprite_data_file(const std::string& dataFilename, const doc::Documen
|
||||
else
|
||||
return std::string();
|
||||
},
|
||||
[](Slice* slice, TiXmlElement* xmlSlice) {
|
||||
[](Slice* slice, XMLElement* xmlSlice) {
|
||||
ASSERT(slice->getByFrame(0));
|
||||
update_xml_part_from_slice_key(slice->getByFrame(0), xmlSlice);
|
||||
});
|
||||
|
||||
// Save theme.xml file
|
||||
save_xml(xmlThemeDoc, themeFileName);
|
||||
save_xml(xmlThemeDoc.get(), themeFileName);
|
||||
}
|
||||
// <slices> without "theme" attribute
|
||||
else if (xmlSlices) {
|
||||
@ -422,7 +423,7 @@ void save_aseprite_data_file(const std::string& dataFilename, const doc::Documen
|
||||
update_xml_slice);
|
||||
|
||||
// Save .aseprite-data file
|
||||
save_xml(xmlDoc, dataFilename);
|
||||
save_xml(xmlDoc.get(), dataFilename);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -28,8 +28,8 @@ namespace app {
|
||||
GuiXml();
|
||||
|
||||
// Returns the tinyxml document instance.
|
||||
XmlDocumentRef doc() {
|
||||
return m_doc;
|
||||
tinyxml2::XMLDocument* doc() {
|
||||
return m_doc.get();
|
||||
}
|
||||
|
||||
// Returns the name of the gui.xml file.
|
||||
@ -38,7 +38,7 @@ namespace app {
|
||||
}
|
||||
|
||||
private:
|
||||
XmlDocumentRef m_doc;
|
||||
XMLDocumentRef m_doc;
|
||||
friend class std::unique_ptr<GuiXml>;
|
||||
};
|
||||
|
||||
|
@ -25,7 +25,8 @@
|
||||
namespace app {
|
||||
|
||||
static Strings* singleton = nullptr;
|
||||
static const char* kDefLanguage = "en";
|
||||
|
||||
const char* Strings::kDefLanguage = "en";
|
||||
|
||||
// static
|
||||
void Strings::createInstance(Preferences& pref,
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2023 Igara Studio S.A.
|
||||
// Copyright (C) 2023-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2016-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -25,6 +25,8 @@ namespace app {
|
||||
// Singleton class to load and access "strings/en.ini" file.
|
||||
class Strings : public app::gen::Strings<app::Strings> {
|
||||
public:
|
||||
static const char* kDefLanguage;
|
||||
|
||||
static void createInstance(Preferences& pref,
|
||||
Extensions& exts);
|
||||
static Strings* instance();
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -11,11 +12,13 @@
|
||||
#include "app/i18n/xml_translator.h"
|
||||
|
||||
#include "app/i18n/strings.h"
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
std::string XmlTranslator::operator()(const TiXmlElement* elem,
|
||||
using namespace tinyxml2;
|
||||
|
||||
std::string XmlTranslator::operator()(const XMLElement* elem,
|
||||
const char* attrName)
|
||||
{
|
||||
const char* value = elem->Attribute(attrName);
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,13 +11,15 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
class TiXmlElement;
|
||||
namespace tinyxml2 {
|
||||
class XMLElement;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
|
||||
class XmlTranslator {
|
||||
public:
|
||||
std::string operator()(const TiXmlElement* elem,
|
||||
std::string operator()(const tinyxml2::XMLElement* elem,
|
||||
const char* attrName);
|
||||
|
||||
void clearStringIdPrefix();
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2021 Igara Studio S.A.
|
||||
// Copyright (C) 2021-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -33,18 +33,19 @@ int Job::runningJobs()
|
||||
return g_runningJobs;
|
||||
}
|
||||
|
||||
Job::Job(const char* jobName)
|
||||
Job::Job(const std::string& jobName,
|
||||
const bool showProgress)
|
||||
{
|
||||
m_last_progress = 0.0;
|
||||
m_done_flag = false;
|
||||
m_canceled_flag = false;
|
||||
|
||||
if (App::instance()->isGui()) {
|
||||
if (showProgress && App::instance()->isGui()) {
|
||||
m_alert_window = ui::Alert::create(
|
||||
fmt::format(Strings::alerts_job_working(), jobName));
|
||||
m_alert_window->addProgress();
|
||||
|
||||
m_timer.reset(new ui::Timer(kMonitoringPeriod, m_alert_window.get()));
|
||||
m_timer = std::make_unique<ui::Timer>(kMonitoringPeriod, m_alert_window.get());
|
||||
m_timer->Tick.connect(&Job::onMonitoringTick, this);
|
||||
m_timer->start();
|
||||
}
|
||||
@ -53,7 +54,7 @@ Job::Job(const char* jobName)
|
||||
Job::~Job()
|
||||
{
|
||||
if (App::instance()->isGui()) {
|
||||
ASSERT(!m_timer->isRunning());
|
||||
ASSERT(!m_timer || !m_timer->isRunning());
|
||||
|
||||
if (m_alert_window)
|
||||
m_alert_window->closeWindow(NULL);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2021 Igara Studio S.A.
|
||||
// Copyright (C) 2021-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -15,6 +15,7 @@
|
||||
#include <atomic>
|
||||
#include <exception>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace app {
|
||||
@ -23,7 +24,10 @@ namespace app {
|
||||
public:
|
||||
static int runningJobs();
|
||||
|
||||
Job(const char* jobName);
|
||||
Job(const std::string& jobName, bool showProgress);
|
||||
Job() = delete;
|
||||
Job(const Job&) = delete;
|
||||
Job& operator==(const Job&) = delete;
|
||||
virtual ~Job();
|
||||
|
||||
// Starts the job calling onJob() event in another thread and
|
||||
@ -67,12 +71,6 @@ namespace app {
|
||||
bool m_done_flag;
|
||||
bool m_canceled_flag;
|
||||
std::exception_ptr m_error;
|
||||
|
||||
// these methods are privated and not defined
|
||||
Job();
|
||||
Job(const Job&);
|
||||
Job& operator==(const Job&);
|
||||
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2022-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2022-2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -87,19 +87,9 @@ ShaderRenderer::ShaderRenderer()
|
||||
m_properties.renderBgOnScreen = true;
|
||||
m_properties.requiresRgbaBackbuffer = true;
|
||||
|
||||
auto makeShader = [](const char* code) {
|
||||
auto result = SkRuntimeEffect::MakeForShader(SkString(code));
|
||||
if (!result.errorText.isEmpty()) {
|
||||
LOG(ERROR, "Shader error: %s\n", result.errorText.c_str());
|
||||
std::printf("Shader error: %s\n", result.errorText.c_str());
|
||||
throw std::runtime_error("Cannot compile shaders for ShaderRenderer");
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
m_bgEffect = makeShader(kBgShaderCode).effect;
|
||||
m_indexedEffect = makeShader(kIndexedShaderCode).effect;
|
||||
m_grayscaleEffect = makeShader(kGrayscaleShaderCode).effect;
|
||||
m_bgEffect = make_shader(kBgShaderCode);
|
||||
m_indexedEffect = make_shader(kIndexedShaderCode);
|
||||
m_grayscaleEffect = make_shader(kGrayscaleShaderCode);
|
||||
}
|
||||
|
||||
ShaderRenderer::~ShaderRenderer() = default;
|
||||
@ -416,21 +406,11 @@ void ShaderRenderer::drawImage(SkCanvas* canvas,
|
||||
const int opacity,
|
||||
const doc::BlendMode blendMode)
|
||||
{
|
||||
auto skData = SkData::MakeWithoutCopy(
|
||||
(const void*)srcImage->getPixelAddress(0, 0),
|
||||
srcImage->rowBytes() * srcImage->height());
|
||||
auto skImg = make_skimage_for_docimage(srcImage);
|
||||
|
||||
switch (srcImage->colorMode()) {
|
||||
|
||||
case doc::ColorMode::RGB: {
|
||||
auto skImg = SkImage::MakeRasterData(
|
||||
SkImageInfo::Make(srcImage->width(),
|
||||
srcImage->height(),
|
||||
kRGBA_8888_SkColorType,
|
||||
kUnpremul_SkAlphaType),
|
||||
skData,
|
||||
srcImage->rowBytes());
|
||||
|
||||
SkPaint p;
|
||||
p.setAlpha(opacity);
|
||||
p.setBlendMode(to_skia(blendMode));
|
||||
@ -443,15 +423,6 @@ void ShaderRenderer::drawImage(SkCanvas* canvas,
|
||||
}
|
||||
|
||||
case doc::ColorMode::GRAYSCALE: {
|
||||
// We use kR8G8_unorm_SkColorType to access gray and alpha
|
||||
auto skImg = SkImage::MakeRasterData(
|
||||
SkImageInfo::Make(srcImage->width(),
|
||||
srcImage->height(),
|
||||
kR8G8_unorm_SkColorType,
|
||||
kOpaque_SkAlphaType),
|
||||
skData,
|
||||
srcImage->rowBytes());
|
||||
|
||||
SkRuntimeShaderBuilder builder(m_grayscaleEffect);
|
||||
builder.child("iImg") = skImg->makeRawShader(SkSamplingOptions(SkFilterMode::kNearest));
|
||||
|
||||
@ -471,15 +442,6 @@ void ShaderRenderer::drawImage(SkCanvas* canvas,
|
||||
}
|
||||
|
||||
case doc::ColorMode::INDEXED: {
|
||||
// We use kAlpha_8_SkColorType to access to the index value through the alpha channel
|
||||
auto skImg = SkImage::MakeRasterData(
|
||||
SkImageInfo::Make(srcImage->width(),
|
||||
srcImage->height(),
|
||||
kAlpha_8_SkColorType,
|
||||
kUnpremul_SkAlphaType),
|
||||
skData,
|
||||
srcImage->rowBytes());
|
||||
|
||||
// Use the palette data as an "width x height" image where
|
||||
// width=number of palette colors, and height=1
|
||||
const size_t palSize = sizeof(color_t) * m_palette.size();
|
||||
|
@ -10,6 +10,6 @@
|
||||
|
||||
// Increment this value if the scripting API is modified between two
|
||||
// released Aseprite versions.
|
||||
#define API_VERSION 27
|
||||
#define API_VERSION 28
|
||||
|
||||
#endif
|
||||
|
121
src/app/script/app_os_object.cpp
Normal file
121
src/app/script/app_os_object.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/script/engine.h"
|
||||
#include "app/script/luacpp.h"
|
||||
#include "base/config.h"
|
||||
#include "base/platform.h"
|
||||
#include "updater/user_agent.h"
|
||||
|
||||
namespace app {
|
||||
namespace script {
|
||||
|
||||
namespace {
|
||||
|
||||
struct AppOS { };
|
||||
|
||||
int AppOS_get_name(lua_State* L)
|
||||
{
|
||||
#if LAF_WINDOWS
|
||||
lua_pushstring(L, "Windows");
|
||||
#elif LAF_MACOS
|
||||
lua_pushstring(L, "macOS");
|
||||
#elif LAF_LINUX
|
||||
lua_pushstring(L, "Linux");
|
||||
#else
|
||||
lua_pushnil(L);
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AppOS_get_version(lua_State* L)
|
||||
{
|
||||
base::Platform p = base::get_platform();
|
||||
push_version(L, p.osVer);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AppOS_get_fullName(lua_State* L)
|
||||
{
|
||||
lua_pushstring(L, updater::getFullOSString().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AppOS_get_windows(lua_State* L)
|
||||
{
|
||||
lua_pushboolean(L, base::Platform::os == base::Platform::OS::Windows);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AppOS_get_macos(lua_State* L)
|
||||
{
|
||||
lua_pushboolean(L, base::Platform::os == base::Platform::OS::macOS);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AppOS_get_linux(lua_State* L)
|
||||
{
|
||||
lua_pushboolean(L, base::Platform::os == base::Platform::OS::Linux);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AppOS_get_x64(lua_State* L)
|
||||
{
|
||||
lua_pushboolean(L, base::Platform::arch == base::Platform::Arch::x64);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AppOS_get_x86(lua_State* L)
|
||||
{
|
||||
lua_pushboolean(L, base::Platform::arch == base::Platform::Arch::x86);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AppOS_get_arm64(lua_State* L)
|
||||
{
|
||||
lua_pushboolean(L, base::Platform::arch == base::Platform::Arch::arm64);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const Property AppOS_properties[] = {
|
||||
{ "name", AppOS_get_name, nullptr },
|
||||
{ "version", AppOS_get_version, nullptr },
|
||||
{ "fullName", AppOS_get_fullName, nullptr },
|
||||
{ "windows", AppOS_get_windows, nullptr },
|
||||
{ "macos", AppOS_get_macos, nullptr },
|
||||
{ "linux", AppOS_get_linux, nullptr },
|
||||
{ "x64", AppOS_get_x64, nullptr },
|
||||
{ "x86", AppOS_get_x86, nullptr },
|
||||
{ "arm64", AppOS_get_arm64, nullptr },
|
||||
{ nullptr, nullptr, nullptr }
|
||||
};
|
||||
|
||||
const luaL_Reg AppOS_methods[] = {
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
DEF_MTNAME(AppOS);
|
||||
|
||||
void register_app_os_object(lua_State* L)
|
||||
{
|
||||
REG_CLASS(L, AppOS);
|
||||
REG_CLASS_PROPERTIES(L, AppOS);
|
||||
|
||||
lua_getglobal(L, "app");
|
||||
lua_pushstring(L, "os");
|
||||
push_new<AppOS>(L);
|
||||
lua_rawset(L, -3);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
} // namespace script
|
||||
} // namespace app
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -36,7 +36,7 @@ BrushRef Brush_new(lua_State* L, int index)
|
||||
if (auto brush2 = may_get_obj<BrushObj>(L, index)) {
|
||||
ASSERT(brush2->brush);
|
||||
if (brush2->brush)
|
||||
brush.reset(new Brush(*brush2->brush));
|
||||
brush = brush2->brush->cloneWithNewImages();
|
||||
}
|
||||
else if (auto image = may_get_image_from_arg(L, index)) {
|
||||
if (image) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -42,6 +42,14 @@
|
||||
#include <stack>
|
||||
#include <string>
|
||||
|
||||
// We use our own fopen() that supports Unicode filename on Windows
|
||||
// extern "C"
|
||||
FILE* lua_user_fopen(const char* fname,
|
||||
const char* mode)
|
||||
{
|
||||
return base::open_file_raw(fname, mode);
|
||||
}
|
||||
|
||||
namespace app {
|
||||
namespace script {
|
||||
|
||||
@ -129,31 +137,35 @@ int dofile(lua_State *L)
|
||||
return dofilecont(L, 0, 0);
|
||||
}
|
||||
|
||||
lua_CFunction orig_loadfile = nullptr;
|
||||
int loadfile(lua_State *L)
|
||||
{
|
||||
ASSERT(orig_loadfile);
|
||||
if (!orig_loadfile)
|
||||
return luaL_error(L, "no original loadfile()?");
|
||||
|
||||
// fname is not optional if we are running in GUI mode as it blocks
|
||||
// the program.
|
||||
if (auto app = App::instance();
|
||||
app && app->isGui() && !lua_isstring(L, 1)) {
|
||||
return luaL_error(L, "loadfile() for stdin cannot be used running in GUI mode");
|
||||
}
|
||||
|
||||
return orig_loadfile(L);
|
||||
}
|
||||
|
||||
int os_clock(lua_State* L)
|
||||
{
|
||||
lua_pushnumber(L, luaClock.elapsed());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int unsupported(lua_State* L)
|
||||
{
|
||||
// debug.getinfo(1, "n").name
|
||||
lua_getglobal(L, "debug");
|
||||
lua_getfield(L, -1, "getinfo");
|
||||
lua_remove(L, -2);
|
||||
lua_pushinteger(L, 1);
|
||||
lua_pushstring(L, "n");
|
||||
lua_call(L, 2, 1);
|
||||
lua_getfield(L, -1, "name");
|
||||
return luaL_error(L, "unsupported function '%s'",
|
||||
lua_tostring(L, -1));
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void register_app_object(lua_State* L);
|
||||
void register_app_pixel_color_object(lua_State* L);
|
||||
void register_app_fs_object(lua_State* L);
|
||||
void register_app_os_object(lua_State* L);
|
||||
void register_app_command_object(lua_State* L);
|
||||
void register_app_preferences_object(lua_State* L);
|
||||
void register_json_object(lua_State* L);
|
||||
@ -207,13 +219,6 @@ void register_websocket_class(lua_State* L);
|
||||
|
||||
void set_app_params(lua_State* L, const Params& params);
|
||||
|
||||
// We use our own fopen() that supports Unicode filename on Windows
|
||||
extern "C" FILE* lua_user_fopen(const char* fname,
|
||||
const char* mode)
|
||||
{
|
||||
return base::open_file_raw(fname, mode);
|
||||
}
|
||||
|
||||
Engine::Engine()
|
||||
: L(luaL_newstate())
|
||||
, m_delegate(nullptr)
|
||||
@ -226,40 +231,25 @@ Engine::Engine()
|
||||
// Standard Lua libraries
|
||||
luaL_openlibs(L);
|
||||
|
||||
// Overwrite Lua functions
|
||||
// Secure Lua functions
|
||||
overwrite_unsecure_functions(L);
|
||||
|
||||
// Overwrite Lua functions with custom implementations
|
||||
lua_register(L, "print", print);
|
||||
lua_register(L, "dofile", dofile);
|
||||
|
||||
lua_getglobal(L, "os");
|
||||
for (const char* name : { "remove", "rename", "exit", "tmpname" }) {
|
||||
lua_pushcfunction(L, unsupported);
|
||||
lua_setfield(L, -2, name);
|
||||
if (!orig_loadfile) {
|
||||
lua_getglobal(L, "loadfile");
|
||||
orig_loadfile = lua_tocfunction(L, -1);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
lua_register(L, "loadfile", loadfile);
|
||||
|
||||
lua_getglobal(L, "os");
|
||||
lua_pushcfunction(L, os_clock);
|
||||
lua_setfield(L, -2, "clock");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Wrap io.open()
|
||||
lua_getglobal(L, "io");
|
||||
lua_getfield(L, -1, "open");
|
||||
lua_pushcclosure(L, secure_io_open, 1);
|
||||
lua_setfield(L, -2, "open");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Wrap os.execute()
|
||||
lua_getglobal(L, "os");
|
||||
lua_getfield(L, -1, "execute");
|
||||
lua_pushcclosure(L, secure_os_execute, 1);
|
||||
lua_setfield(L, -2, "execute");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Wrap package.loadlib()
|
||||
lua_getglobal(L, "package");
|
||||
lua_getfield(L, -1, "loadlib");
|
||||
lua_pushcclosure(L, secure_package_loadlib, 1);
|
||||
lua_setfield(L, -2, "loadlib");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Enhance require() function for plugins
|
||||
custom_require_function(L);
|
||||
|
||||
@ -270,6 +260,7 @@ Engine::Engine()
|
||||
register_app_object(L);
|
||||
register_app_pixel_color_object(L);
|
||||
register_app_fs_object(L);
|
||||
register_app_os_object(L);
|
||||
register_app_command_object(L);
|
||||
register_app_preferences_object(L);
|
||||
register_json_object(L);
|
||||
@ -548,6 +539,24 @@ void Engine::destroy()
|
||||
L = nullptr;
|
||||
}
|
||||
|
||||
void Engine::notifyRunningGui()
|
||||
{
|
||||
// Mark stdin file handle as closed so the following statements
|
||||
// don't hang the program:
|
||||
// - io.lines()
|
||||
// - io.read('a')
|
||||
// - io.stdin:read('a')
|
||||
lua_getglobal(L, "io");
|
||||
lua_getfield(L, -1, "stdin");
|
||||
|
||||
auto p = ((luaL_Stream*)luaL_checkudata(L, -1, LUA_FILEHANDLE));
|
||||
ASSERT(p);
|
||||
p->f = nullptr;
|
||||
p->closef = nullptr;
|
||||
|
||||
lua_pop(L, 2);
|
||||
}
|
||||
|
||||
void Engine::printLastResult()
|
||||
{
|
||||
m_printLastResult = true;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -97,6 +97,9 @@ namespace app {
|
||||
m_delegate = delegate;
|
||||
}
|
||||
|
||||
// Called if the GUI is going to be started.
|
||||
void notifyRunningGui();
|
||||
|
||||
void printLastResult();
|
||||
bool evalCode(const std::string& code,
|
||||
const std::string& filename = std::string());
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -9,11 +9,12 @@
|
||||
#define APP_SCRIPT_LUACPP_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
extern "C" {
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
#include "lauxlib.h"
|
||||
}
|
||||
// We're compiling Lua with C++ support to handle error with
|
||||
// exceptions, so there is no need of extern "C" { ... } these
|
||||
// includes.
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
#include "lauxlib.h"
|
||||
|
||||
#include "base/debug.h"
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -32,9 +32,43 @@
|
||||
namespace app {
|
||||
namespace script {
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
namespace {
|
||||
|
||||
int secure_io_open(lua_State* L);
|
||||
int secure_io_popen(lua_State* L);
|
||||
int secure_io_lines(lua_State* L);
|
||||
int secure_io_input(lua_State* L);
|
||||
int secure_io_output(lua_State* L);
|
||||
int secure_os_execute(lua_State* L);
|
||||
int secure_package_loadlib(lua_State* L);
|
||||
|
||||
enum {
|
||||
io_open,
|
||||
io_popen,
|
||||
io_lines,
|
||||
io_input,
|
||||
io_output,
|
||||
os_execute,
|
||||
package_loadlib,
|
||||
};
|
||||
|
||||
static struct {
|
||||
const char* package;
|
||||
const char* funcname;
|
||||
lua_CFunction newfunc;
|
||||
lua_CFunction origfunc = nullptr;
|
||||
} replaced_functions[] = {
|
||||
{ "io", "open", secure_io_open },
|
||||
{ "io", "popen", secure_io_popen },
|
||||
{ "io", "lines", secure_io_lines },
|
||||
{ "io", "input", secure_io_input },
|
||||
{ "io", "output", secure_io_output },
|
||||
{ "os", "execute", secure_os_execute },
|
||||
{ "package", "loadlib", secure_package_loadlib },
|
||||
};
|
||||
|
||||
#ifdef ENABLE_UI
|
||||
|
||||
// Map from .lua file name -> sha1
|
||||
std::unordered_map<std::string, std::string> g_keys;
|
||||
|
||||
@ -66,13 +100,24 @@ std::string get_script_filename(lua_State* L)
|
||||
return script;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
#endif // ENABLE_UI
|
||||
|
||||
int unsupported(lua_State* L)
|
||||
{
|
||||
// debug.getinfo(1, "n").name
|
||||
lua_getglobal(L, "debug");
|
||||
lua_getfield(L, -1, "getinfo");
|
||||
lua_remove(L, -2);
|
||||
lua_pushinteger(L, 1);
|
||||
lua_pushstring(L, "n");
|
||||
lua_call(L, 2, 1);
|
||||
lua_getfield(L, -1, "name");
|
||||
return luaL_error(L, "unsupported function '%s'",
|
||||
lua_tostring(L, -1));
|
||||
}
|
||||
|
||||
int secure_io_open(lua_State* L)
|
||||
{
|
||||
int n = lua_gettop(L);
|
||||
|
||||
std::string absFilename = base::get_absolute_path(luaL_checkstring(L, 1));
|
||||
|
||||
FileAccessMode mode = FileAccessMode::Read; // Read is the default access
|
||||
@ -85,53 +130,111 @@ int secure_io_open(lua_State* L)
|
||||
return luaL_error(L, "the script doesn't have access to file '%s'",
|
||||
absFilename.c_str());
|
||||
}
|
||||
|
||||
lua_pushvalue(L, lua_upvalueindex(1));
|
||||
lua_pushstring(L, absFilename.c_str());
|
||||
for (int i=2; i<=n; ++i)
|
||||
lua_pushvalue(L, i);
|
||||
lua_call(L, n, 1);
|
||||
return 1;
|
||||
return replaced_functions[io_open].origfunc(L);
|
||||
}
|
||||
|
||||
int secure_os_execute(lua_State* L)
|
||||
int secure_io_popen(lua_State* L)
|
||||
{
|
||||
int n = lua_gettop(L);
|
||||
if (n == 0)
|
||||
return 0;
|
||||
|
||||
const char* cmd = lua_tostring(L, 1);
|
||||
const char* cmd = luaL_checkstring(L, 1);
|
||||
if (!ask_access(L, cmd, FileAccessMode::Execute, ResourceType::Command)) {
|
||||
// Stop script
|
||||
return luaL_error(L, "the script doesn't have access to execute the command: '%s'",
|
||||
cmd);
|
||||
}
|
||||
return replaced_functions[io_popen].origfunc(L);
|
||||
}
|
||||
|
||||
lua_pushvalue(L, lua_upvalueindex(1));
|
||||
for (int i=1; i<=n; ++i)
|
||||
lua_pushvalue(L, i);
|
||||
lua_call(L, n, 1);
|
||||
return 1;
|
||||
int secure_io_lines(lua_State* L)
|
||||
{
|
||||
if (auto fn = lua_tostring(L, 1)) {
|
||||
std::string absFilename = base::get_absolute_path(fn);
|
||||
|
||||
if (!ask_access(L, absFilename.c_str(), FileAccessMode::Read, ResourceType::File)) {
|
||||
return luaL_error(L, "the script doesn't have access to file '%s'",
|
||||
absFilename.c_str());
|
||||
}
|
||||
}
|
||||
return replaced_functions[io_lines].origfunc(L);
|
||||
}
|
||||
|
||||
int secure_io_input(lua_State* L)
|
||||
{
|
||||
if (auto fn = lua_tostring(L, 1)) {
|
||||
std::string absFilename = base::get_absolute_path(fn);
|
||||
|
||||
if (!ask_access(L, absFilename.c_str(), FileAccessMode::Read, ResourceType::File)) {
|
||||
return luaL_error(L, "the script doesn't have access to file '%s'",
|
||||
absFilename.c_str());
|
||||
}
|
||||
}
|
||||
return replaced_functions[io_input].origfunc(L);
|
||||
}
|
||||
|
||||
int secure_io_output(lua_State* L)
|
||||
{
|
||||
if (auto fn = lua_tostring(L, 1)) {
|
||||
std::string absFilename = base::get_absolute_path(fn);
|
||||
|
||||
if (!ask_access(L, absFilename.c_str(), FileAccessMode::Write, ResourceType::File)) {
|
||||
return luaL_error(L, "the script doesn't have access to file '%s'",
|
||||
absFilename.c_str());
|
||||
}
|
||||
}
|
||||
return replaced_functions[io_output].origfunc(L);
|
||||
}
|
||||
|
||||
int secure_os_execute(lua_State* L)
|
||||
{
|
||||
const char* cmd = luaL_checkstring(L, 1);
|
||||
if (!ask_access(L, cmd, FileAccessMode::Execute, ResourceType::Command)) {
|
||||
// Stop script
|
||||
return luaL_error(L, "the script doesn't have access to execute the command: '%s'",
|
||||
cmd);
|
||||
}
|
||||
return replaced_functions[os_execute].origfunc(L);
|
||||
}
|
||||
|
||||
int secure_package_loadlib(lua_State* L)
|
||||
{
|
||||
int n = lua_gettop(L);
|
||||
if (n == 0)
|
||||
return 0;
|
||||
|
||||
const char* cmd = lua_tostring(L, 1);
|
||||
const char* cmd = luaL_checkstring(L, 1);
|
||||
if (!ask_access(L, cmd, FileAccessMode::LoadLib, ResourceType::File)) {
|
||||
// Stop script
|
||||
return luaL_error(L, "the script doesn't have access to execute the command: '%s'",
|
||||
cmd);
|
||||
}
|
||||
return replaced_functions[package_loadlib].origfunc(L);
|
||||
}
|
||||
|
||||
lua_pushvalue(L, lua_upvalueindex(1));
|
||||
for (int i=1; i<=n; ++i)
|
||||
lua_pushvalue(L, i);
|
||||
lua_call(L, n, 1);
|
||||
return 1;
|
||||
} // anonymous namespace
|
||||
|
||||
void overwrite_unsecure_functions(lua_State* L)
|
||||
{
|
||||
// Remove unsupported functions
|
||||
lua_getglobal(L, "os");
|
||||
for (const char* name : { "remove", "rename", "exit", "tmpname" }) {
|
||||
lua_pushcfunction(L, unsupported);
|
||||
lua_setfield(L, -2, name);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Replace functions with our own implementations (that ask for
|
||||
// permissions first).
|
||||
for (auto& item : replaced_functions) {
|
||||
lua_getglobal(L, item.package);
|
||||
|
||||
// Get old function
|
||||
if (!item.origfunc) {
|
||||
lua_getfield(L, -1, item.funcname);
|
||||
item.origfunc = lua_tocfunction(L, -1);
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// Push and set the new function
|
||||
lua_pushcfunction(L, item.newfunc);
|
||||
lua_setfield(L, -2, item.funcname);
|
||||
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
|
||||
bool ask_access(lua_State* L,
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2021-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2021-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -33,9 +33,7 @@ namespace script {
|
||||
WebSocket,
|
||||
};
|
||||
|
||||
int secure_io_open(lua_State* L);
|
||||
int secure_os_execute(lua_State* L);
|
||||
int secure_package_loadlib(lua_State* L);
|
||||
void overwrite_unsecure_functions(lua_State* L);
|
||||
|
||||
bool ask_access(lua_State* L,
|
||||
const char* filename,
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,26 +11,50 @@
|
||||
|
||||
#include "app/sprite_job.h"
|
||||
|
||||
#include "base/log.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
SpriteJob::SpriteJob(const ContextReader& reader, const char* jobName)
|
||||
: Job(jobName)
|
||||
, m_writer(reader, 500)
|
||||
, m_document(m_writer.document())
|
||||
, m_sprite(m_writer.sprite())
|
||||
, m_tx(m_writer, jobName, ModifyDocument)
|
||||
SpriteJob::SpriteJob(Context* ctx, Doc* doc,
|
||||
const std::string& jobName,
|
||||
const bool showProgress)
|
||||
: Job(jobName, showProgress)
|
||||
, m_doc(doc)
|
||||
, m_sprite(doc->sprite())
|
||||
, m_tx(Tx::DontLockDoc, ctx, doc, jobName, ModifyDocument)
|
||||
, m_lockAction(Tx::LockDoc)
|
||||
{
|
||||
// Try to write-lock the document to see if we have to lock the
|
||||
// document in the background thread.
|
||||
auto lockResult = m_doc->writeLock(500);
|
||||
if (lockResult != Doc::LockResult::Fail) {
|
||||
if (lockResult == Doc::LockResult::Reentrant)
|
||||
m_lockAction = Tx::DontLockDoc;
|
||||
m_doc->unlock(lockResult);
|
||||
}
|
||||
}
|
||||
|
||||
SpriteJob::~SpriteJob()
|
||||
{
|
||||
if (!isCanceled())
|
||||
m_tx.commit();
|
||||
try {
|
||||
if (!isCanceled())
|
||||
m_tx.commit();
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
LOG(ERROR, "Error committing changes: %s\n", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
void SpriteJob::onSpriteJob(Tx& tx)
|
||||
{
|
||||
if (m_callback)
|
||||
m_callback(tx);
|
||||
}
|
||||
|
||||
void SpriteJob::onJob()
|
||||
{
|
||||
m_callback();
|
||||
Tx subtx(m_lockAction, m_ctx, m_doc);
|
||||
onSpriteJob(subtx);
|
||||
}
|
||||
|
||||
bool SpriteJob::continueTask()
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
// Copyright (C) 2017-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -15,19 +16,33 @@
|
||||
#include "render/task_delegate.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace app {
|
||||
|
||||
class Context;
|
||||
|
||||
// Creates a Job to run a task in a background thread. At the same
|
||||
// time it creates a new Tx in the main thread (to group all sub-Txs)
|
||||
// without locking the sprite. You have to lock the sprite with a sub
|
||||
// Tx (the onSpriteJob(Tx) already has the sprite locked for write
|
||||
// access).
|
||||
//
|
||||
// This class takes care to lock the sprite in the background thread
|
||||
// for write access, or to avoid re-locking the sprite in case it's
|
||||
// already locked from the main thread (where SpriteJob was created,
|
||||
// generally true when we're running a script).
|
||||
class SpriteJob : public Job,
|
||||
public render::TaskDelegate {
|
||||
public:
|
||||
SpriteJob(const ContextReader& reader, const char* jobName);
|
||||
SpriteJob(Context* ctx, Doc* doc,
|
||||
const std::string& jobName,
|
||||
const bool showProgress);
|
||||
~SpriteJob();
|
||||
|
||||
ContextWriter& writer() { return m_writer; }
|
||||
Doc* document() const { return m_document; }
|
||||
Doc* document() const { return m_doc; }
|
||||
Sprite* sprite() const { return m_sprite; }
|
||||
Tx& tx() { return m_tx; }
|
||||
|
||||
template<typename T>
|
||||
void startJobWithCallback(T&& callback) {
|
||||
@ -36,6 +51,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
virtual void onSpriteJob(Tx& tx);
|
||||
|
||||
// Job impl
|
||||
void onJob() override;
|
||||
|
||||
@ -44,15 +61,21 @@ private:
|
||||
bool continueTask() override;
|
||||
void notifyTaskProgress(double progress) override;
|
||||
|
||||
ContextWriter m_writer;
|
||||
Doc* m_document;
|
||||
Context* m_ctx;
|
||||
Doc* m_doc;
|
||||
Sprite* m_sprite;
|
||||
Tx m_tx;
|
||||
|
||||
// What action to do with the sub-Tx inside the background thread.
|
||||
// This is required to check if the sprite is already locked for
|
||||
// write access in the main thread, in that case we don't need to
|
||||
// lock it again from the background thread.
|
||||
Tx::LockAction m_lockAction;
|
||||
|
||||
// Default implementation calls the given function in
|
||||
// startJob(). Anyway you can just extended the SpriteJob and
|
||||
// override onJob().
|
||||
std::function<void()> m_callback;
|
||||
std::function<void(Tx&)> m_callback;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -29,6 +29,8 @@
|
||||
#include "doc/image_impl.h"
|
||||
#include "doc/mask.h"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
@ -41,6 +43,7 @@ namespace app {
|
||||
namespace tools {
|
||||
|
||||
using namespace gfx;
|
||||
using namespace tinyxml2;
|
||||
|
||||
const char* WellKnownTools::RectangularMarquee = "rectangular_marquee";
|
||||
const char* WellKnownTools::Lasso = "lasso";
|
||||
@ -206,11 +209,14 @@ void ToolBox::loadTools()
|
||||
{
|
||||
LOG("TOOL: Loading tools...\n");
|
||||
|
||||
XmlDocumentRef doc(GuiXml::instance()->doc());
|
||||
TiXmlHandle handle(doc.get());
|
||||
XMLDocument* doc = GuiXml::instance()->doc();
|
||||
XMLHandle handle(doc);
|
||||
|
||||
// For each group
|
||||
TiXmlElement* xmlGroup = handle.FirstChild("gui").FirstChild("tools").FirstChild("group").ToElement();
|
||||
XMLElement* xmlGroup = handle
|
||||
.FirstChildElement("gui")
|
||||
.FirstChildElement("tools")
|
||||
.FirstChildElement("group").ToElement();
|
||||
while (xmlGroup) {
|
||||
const char* groupId = xmlGroup->Attribute("id");
|
||||
if (!groupId)
|
||||
@ -233,8 +239,8 @@ void ToolBox::loadTools()
|
||||
}
|
||||
|
||||
// For each tool
|
||||
TiXmlNode* xmlToolNode = xmlGroup->FirstChild("tool");
|
||||
TiXmlElement* xmlTool = xmlToolNode ? xmlToolNode->ToElement(): NULL;
|
||||
XMLNode* xmlToolNode = xmlGroup->FirstChildElement("tool");
|
||||
XMLElement* xmlTool = (xmlToolNode ? xmlToolNode->ToElement(): nullptr);
|
||||
while (xmlTool) {
|
||||
const char* toolId = xmlTool->Attribute("id");
|
||||
std::string toolText = m_xmlTranslator(xmlTool, "text");
|
||||
@ -272,7 +278,7 @@ void ToolBox::loadTools()
|
||||
LOG("TOOL: Done. %d tools, %d groups.\n", m_tools.size(), m_groups.size());
|
||||
}
|
||||
|
||||
void ToolBox::loadToolProperties(TiXmlElement* xmlTool, Tool* tool, int button, const std::string& suffix)
|
||||
void ToolBox::loadToolProperties(XMLElement* xmlTool, Tool* tool, int button, const std::string& suffix)
|
||||
{
|
||||
const char* tool_id = tool->getId().c_str();
|
||||
const char* fill = xmlTool->Attribute(("fill_"+suffix).c_str());
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -16,7 +16,9 @@
|
||||
#include "app/i18n/xml_translator.h"
|
||||
#include "app/tools/tool.h"
|
||||
|
||||
class TiXmlElement;
|
||||
namespace tinyxml2 {
|
||||
class XMLElement;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
namespace tools {
|
||||
@ -113,7 +115,7 @@ namespace app {
|
||||
|
||||
private:
|
||||
void loadTools();
|
||||
void loadToolProperties(TiXmlElement* xmlTool, Tool* tool, int button, const std::string& suffix);
|
||||
void loadToolProperties(tinyxml2::XMLElement* xmlTool, Tool* tool, int button, const std::string& suffix);
|
||||
|
||||
std::map<std::string, Ink*> m_inks;
|
||||
std::map<std::string, Controller*> m_controllers;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -44,7 +44,8 @@ using namespace filters;
|
||||
ToolLoopManager::ToolLoopManager(ToolLoop* toolLoop)
|
||||
: m_toolLoop(toolLoop)
|
||||
, m_canceled(false)
|
||||
, m_brush0(*toolLoop->getBrush())
|
||||
, m_brushSize0(toolLoop->getBrush()->size())
|
||||
, m_brushAngle0(toolLoop->getBrush()->angle())
|
||||
, m_dynamics(toolLoop->getDynamics())
|
||||
{
|
||||
}
|
||||
@ -358,8 +359,8 @@ Stroke::Pt ToolLoopManager::getSpriteStrokePt(const Pointer& pointer)
|
||||
{
|
||||
// Convert the screen point to a sprite point
|
||||
Stroke::Pt spritePoint = pointer.point();
|
||||
spritePoint.size = m_brush0.size();
|
||||
spritePoint.angle = m_brush0.angle();
|
||||
spritePoint.size = m_brushSize0;
|
||||
spritePoint.angle = m_brushAngle0;
|
||||
|
||||
// Center the input to some grid point if needed
|
||||
snapToGrid(spritePoint);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -95,7 +95,8 @@ private:
|
||||
Pointer m_lastPointer;
|
||||
gfx::Region m_dirtyArea;
|
||||
gfx::Region m_nextDirtyArea;
|
||||
doc::Brush m_brush0;
|
||||
const int m_brushSize0;
|
||||
const int m_brushAngle0;
|
||||
DynamicsOptions m_dynamics;
|
||||
gfx::PointF m_stabilizerCenter;
|
||||
};
|
||||
|
@ -467,7 +467,9 @@ os::SurfaceRef BrushPopup::createSurfaceForBrush(const BrushRef& origBrush,
|
||||
BrushRef brush = origBrush;
|
||||
if (brush) {
|
||||
if (brush->type() != kImageBrushType && brush->size() > kMaxSize) {
|
||||
brush.reset(new Brush(*brush));
|
||||
// Clone with shared images, as setSize() will re-create the
|
||||
// images and the brush is no kImageBrushType anyway.
|
||||
brush = brush->cloneWithSharedImages();
|
||||
brush->setSize(kMaxSize);
|
||||
}
|
||||
// Show the original image in the popup (without the image colors
|
||||
|
@ -655,35 +655,22 @@ bool ColorSelector::buildEffects()
|
||||
|
||||
if (!m_mainEffect) {
|
||||
if (const char* code = getMainAreaShader())
|
||||
m_mainEffect = buildEffect(code);
|
||||
m_mainEffect = make_shader(code);
|
||||
}
|
||||
|
||||
if (!m_bottomEffect) {
|
||||
if (const char* code = getBottomBarShader())
|
||||
m_bottomEffect = buildEffect(code);
|
||||
m_bottomEffect = make_shader(code);
|
||||
}
|
||||
|
||||
if (!m_alphaEffect) {
|
||||
if (const char* code = getAlphaBarShader())
|
||||
m_alphaEffect = buildEffect(code);
|
||||
m_alphaEffect = make_shader(code);
|
||||
}
|
||||
|
||||
return (m_mainEffect && m_bottomEffect && m_alphaEffect);
|
||||
}
|
||||
|
||||
sk_sp<SkRuntimeEffect> ColorSelector::buildEffect(const char* code)
|
||||
{
|
||||
auto result = SkRuntimeEffect::MakeForShader(SkString(code));
|
||||
if (!result.errorText.isEmpty()) {
|
||||
LOG(ERROR, "Shader error: %s\n", result.errorText.c_str());
|
||||
std::printf("Shader error: %s\n", result.errorText.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
else {
|
||||
return result.effect;
|
||||
}
|
||||
}
|
||||
|
||||
void ColorSelector::resetBottomEffect()
|
||||
{
|
||||
m_bottomEffect.reset();
|
||||
|
@ -121,7 +121,6 @@ namespace app {
|
||||
#if SK_ENABLE_SKSL
|
||||
static const char* getAlphaBarShader();
|
||||
bool buildEffects();
|
||||
sk_sp<SkRuntimeEffect> buildEffect(const char* code);
|
||||
#endif
|
||||
|
||||
// Internal flag used to lock the modification of m_color.
|
||||
|
@ -1770,7 +1770,9 @@ private:
|
||||
}
|
||||
|
||||
void updateLayout() {
|
||||
const bool visible = (m_doc && !m_doc->sprite()->slices().empty());
|
||||
const bool visible = (m_doc &&
|
||||
m_doc->sprite() &&
|
||||
!m_doc->sprite()->slices().empty());
|
||||
const bool relayout = (visible != m_combobox.isVisible() ||
|
||||
visible != m_action.isVisible());
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -166,7 +166,9 @@ private:
|
||||
break;
|
||||
|
||||
auto mouseMsg = static_cast<MouseMessage*>(msg);
|
||||
const gfx::Rect rc = bounds();
|
||||
gfx::Rect rc = bounds();
|
||||
rc.shrink(border());
|
||||
rc.shrink(gfx::Border(3, 0, 3, 1) * guiscale());
|
||||
float u = (mouseMsg->position().x - rc.x) / float(rc.w);
|
||||
u = std::clamp(u, 0.0f, 1.0f);
|
||||
switch (capture) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2017-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -14,6 +14,7 @@
|
||||
#include "app/cmd/set_mask_position.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/console.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/ui/editor/editor.h"
|
||||
@ -80,12 +81,15 @@ EditorState::LeaveAction MovingSelectionState::onLeaveState(Editor* editor, Edit
|
||||
doc->generateMaskBoundaries();
|
||||
}
|
||||
else {
|
||||
{
|
||||
try {
|
||||
ContextWriter writer(UIContext::instance(), 1000);
|
||||
Tx tx(writer, "Move Selection Edges", DoesntModifyDocument);
|
||||
tx(new cmd::SetMaskPosition(doc, newOrigin));
|
||||
tx.commit();
|
||||
}
|
||||
catch (const base::Exception& e) {
|
||||
Console::showException(e);
|
||||
}
|
||||
doc->resetTransformation();
|
||||
}
|
||||
doc->notifyGeneralUpdate();
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/expand_cel_canvas.h"
|
||||
#include "app/util/layer_utils.h"
|
||||
#include "doc/brush.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/layer.h"
|
||||
@ -189,6 +190,33 @@ public:
|
||||
ASSERT(m_ink);
|
||||
ASSERT(m_controller);
|
||||
|
||||
// If the user right-clicks with a custom/image brush we change
|
||||
// the image's colors of the brush to the background color.
|
||||
//
|
||||
// This is different from SwitchColors that makes a new brush
|
||||
// switching fg <-> bg colors, so here we have some extra
|
||||
// functionality with custom brushes (quickly convert the custom
|
||||
// brush with a plain color, or in other words, replace the custom
|
||||
// brush area with the background color).
|
||||
if (m_brush->type() == kImageBrushType && m_button == Right) {
|
||||
// We've to recalculate the background color to use for the
|
||||
// brush using the specific brush image pixel format/color mode,
|
||||
// as we cannot use m_primaryColor or m_bgColor here because
|
||||
// those are in the sprite pixel format/color mode.
|
||||
const color_t brushColor =
|
||||
color_utils::color_for_target_mask(
|
||||
Preferences::instance().colorBar.bgColor(),
|
||||
ColorTarget(ColorTarget::TransparentLayer,
|
||||
m_brush->image()->pixelFormat(),
|
||||
-1));
|
||||
|
||||
// Clone the brush with new images to avoid modifying the
|
||||
// current brush used in left-click / brush preview.
|
||||
BrushRef newBrush = m_brush->cloneWithNewImages();
|
||||
newBrush->setImageColor(Brush::ImageColor::BothColors, brushColor);
|
||||
m_brush = newBrush;
|
||||
}
|
||||
|
||||
if (m_tilesMode) {
|
||||
// Use FloodFillPointShape or TilePointShape in tiles mode
|
||||
if (!m_pointShape->isFloodFill()) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -56,7 +56,17 @@ ExportFileWindow::ExportFileWindow(const Doc* doc)
|
||||
fill_layers_combobox(m_doc->sprite(), layers(), m_docPref.saveCopy.layer(), m_docPref.saveCopy.layerIndex());
|
||||
fill_frames_combobox(m_doc->sprite(), frames(), m_docPref.saveCopy.frameTag());
|
||||
fill_anidir_combobox(anidir(), m_docPref.saveCopy.aniDir());
|
||||
pixelRatio()->setSelected(m_docPref.saveCopy.applyPixelRatio());
|
||||
|
||||
if (doc->sprite()->hasPixelRatio()) {
|
||||
pixelRatio()->setSelected(m_docPref.saveCopy.applyPixelRatio());
|
||||
}
|
||||
else {
|
||||
// Hide "Apply pixel ratio" checkbox when there is no pixel aspect
|
||||
// ratio to apply.
|
||||
pixelRatio()->setSelected(false);
|
||||
pixelRatio()->setVisible(false);
|
||||
}
|
||||
|
||||
forTwitter()->setSelected(m_docPref.saveCopy.forTwitter());
|
||||
adjustResize()->setVisible(false);
|
||||
playSubtags()->setSelected(m_docPref.saveCopy.playSubtags());
|
||||
@ -218,7 +228,9 @@ void ExportFileWindow::updateAniDir()
|
||||
void ExportFileWindow::updatePlaySubtags()
|
||||
{
|
||||
std::string framesValue = this->framesValue();
|
||||
playSubtags()->setVisible(framesValue != kSelectedFrames);
|
||||
playSubtags()->setVisible(framesValue != kSelectedFrames &&
|
||||
// We hide the option if there is no tag
|
||||
!m_doc->sprite()->tags().empty());
|
||||
layout();
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -29,6 +30,8 @@ namespace app {
|
||||
virtual bool onCanPaste(Context* ctx) = 0;
|
||||
virtual bool onCanClear(Context* ctx) = 0;
|
||||
|
||||
// These commands are executed from Context::executeCommand()
|
||||
// which catch any exception that is thrown.
|
||||
virtual bool onCut(Context* ctx) = 0;
|
||||
virtual bool onCopy(Context* ctx) = 0;
|
||||
virtual bool onPaste(Context* ctx) = 0;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -31,6 +31,8 @@
|
||||
#include "ui/accelerator.h"
|
||||
#include "ui/message.h"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
@ -39,6 +41,8 @@
|
||||
|
||||
#define I18N_KEY(a) app::Strings::keyboard_shortcuts_##a()
|
||||
|
||||
using namespace tinyxml2;
|
||||
|
||||
namespace {
|
||||
|
||||
struct KeyShortcutAction {
|
||||
@ -138,7 +142,7 @@ namespace {
|
||||
return g_wheel_actions;
|
||||
}
|
||||
|
||||
const char* get_shortcut(TiXmlElement* elem) {
|
||||
const char* get_shortcut(XMLElement* elem) {
|
||||
const char* shortcut = NULL;
|
||||
|
||||
#ifdef _WIN32
|
||||
@ -613,13 +617,13 @@ void KeyboardShortcuts::clear()
|
||||
m_keys.clear();
|
||||
}
|
||||
|
||||
void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
|
||||
void KeyboardShortcuts::importFile(XMLElement* rootElement, KeySource source)
|
||||
{
|
||||
// <keyboard><commands><key>
|
||||
TiXmlHandle handle(rootElement);
|
||||
TiXmlElement* xmlKey = handle
|
||||
.FirstChild("commands")
|
||||
.FirstChild("key").ToElement();
|
||||
XMLHandle handle(rootElement);
|
||||
XMLElement* xmlKey = handle
|
||||
.FirstChildElement("commands")
|
||||
.FirstChildElement("key").ToElement();
|
||||
while (xmlKey) {
|
||||
const char* command_name = xmlKey->Attribute("command");
|
||||
const char* command_key = get_shortcut(xmlKey);
|
||||
@ -637,7 +641,7 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
|
||||
// Read params
|
||||
Params params;
|
||||
|
||||
TiXmlElement* xmlParam = xmlKey->FirstChildElement("param");
|
||||
XMLElement* xmlParam = xmlKey->FirstChildElement("param");
|
||||
while (xmlParam) {
|
||||
const char* param_name = xmlParam->Attribute("name");
|
||||
const char* param_value = xmlParam->Attribute("value");
|
||||
@ -677,8 +681,8 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
|
||||
// Load keyboard shortcuts for tools
|
||||
// <keyboard><tools><key>
|
||||
xmlKey = handle
|
||||
.FirstChild("tools")
|
||||
.FirstChild("key").ToElement();
|
||||
.FirstChildElement("tools")
|
||||
.FirstChildElement("key").ToElement();
|
||||
while (xmlKey) {
|
||||
const char* tool_id = xmlKey->Attribute("tool");
|
||||
const char* tool_key = get_shortcut(xmlKey);
|
||||
@ -705,8 +709,8 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
|
||||
// Load keyboard shortcuts for quicktools
|
||||
// <keyboard><quicktools><key>
|
||||
xmlKey = handle
|
||||
.FirstChild("quicktools")
|
||||
.FirstChild("key").ToElement();
|
||||
.FirstChildElement("quicktools")
|
||||
.FirstChildElement("key").ToElement();
|
||||
while (xmlKey) {
|
||||
const char* tool_id = xmlKey->Attribute("tool");
|
||||
const char* tool_key = get_shortcut(xmlKey);
|
||||
@ -733,8 +737,8 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
|
||||
// Load special keyboard shortcuts for sprite editor customization
|
||||
// <keyboard><actions><key>
|
||||
xmlKey = handle
|
||||
.FirstChild("actions")
|
||||
.FirstChild("key").ToElement();
|
||||
.FirstChildElement("actions")
|
||||
.FirstChildElement("key").ToElement();
|
||||
while (xmlKey) {
|
||||
const char* action_id = xmlKey->Attribute("action");
|
||||
const char* action_key = get_shortcut(xmlKey);
|
||||
@ -768,8 +772,8 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
|
||||
// Load special keyboard shortcuts for mouse wheel customization
|
||||
// <keyboard><wheel><key>
|
||||
xmlKey = handle
|
||||
.FirstChild("wheel")
|
||||
.FirstChild("key").ToElement();
|
||||
.FirstChildElement("wheel")
|
||||
.FirstChildElement("key").ToElement();
|
||||
while (xmlKey) {
|
||||
const char* action_id = xmlKey->Attribute("action");
|
||||
const char* action_key = get_shortcut(xmlKey);
|
||||
@ -796,8 +800,8 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
|
||||
// Load special keyboard shortcuts to simulate mouse wheel actions
|
||||
// <keyboard><drag><key>
|
||||
xmlKey = handle
|
||||
.FirstChild("drag")
|
||||
.FirstChild("key").ToElement();
|
||||
.FirstChildElement("drag")
|
||||
.FirstChildElement("key").ToElement();
|
||||
while (xmlKey) {
|
||||
const char* action_id = xmlKey->Attribute("action");
|
||||
const char* action_key = get_shortcut(xmlKey);
|
||||
@ -835,26 +839,25 @@ void KeyboardShortcuts::importFile(TiXmlElement* rootElement, KeySource source)
|
||||
|
||||
void KeyboardShortcuts::importFile(const std::string& filename, KeySource source)
|
||||
{
|
||||
XmlDocumentRef doc = app::open_xml(filename);
|
||||
TiXmlHandle handle(doc.get());
|
||||
TiXmlElement* xmlKey = handle.FirstChild("keyboard").ToElement();
|
||||
XMLDocumentRef doc = app::open_xml(filename);
|
||||
XMLHandle handle(doc.get());
|
||||
XMLElement* xmlKey = handle.FirstChildElement("keyboard").ToElement();
|
||||
|
||||
importFile(xmlKey, source);
|
||||
}
|
||||
|
||||
void KeyboardShortcuts::exportFile(const std::string& filename)
|
||||
{
|
||||
XmlDocumentRef doc(new TiXmlDocument());
|
||||
auto doc = std::make_unique<XMLDocument>();
|
||||
XMLElement* keyboard = doc->NewElement("keyboard");
|
||||
XMLElement* commands = keyboard->InsertNewChildElement("commands");
|
||||
XMLElement* tools = keyboard->InsertNewChildElement("tools");
|
||||
XMLElement* quicktools = keyboard->InsertNewChildElement("quicktools");
|
||||
XMLElement* actions = keyboard->InsertNewChildElement("actions");
|
||||
XMLElement* wheel = keyboard->InsertNewChildElement("wheel");
|
||||
XMLElement* drag = keyboard->InsertNewChildElement("drag");
|
||||
|
||||
TiXmlElement keyboard("keyboard");
|
||||
TiXmlElement commands("commands");
|
||||
TiXmlElement tools("tools");
|
||||
TiXmlElement quicktools("quicktools");
|
||||
TiXmlElement actions("actions");
|
||||
TiXmlElement wheel("wheel");
|
||||
TiXmlElement drag("drag");
|
||||
|
||||
keyboard.SetAttribute("version", XML_KEYBOARD_FILE_VERSION);
|
||||
keyboard->SetAttribute("version", XML_KEYBOARD_FILE_VERSION);
|
||||
|
||||
exportKeys(commands, KeyType::Command);
|
||||
exportKeys(tools, KeyType::Tool);
|
||||
@ -863,20 +866,12 @@ void KeyboardShortcuts::exportFile(const std::string& filename)
|
||||
exportKeys(wheel, KeyType::WheelAction);
|
||||
exportKeys(drag, KeyType::DragAction);
|
||||
|
||||
keyboard.InsertEndChild(commands);
|
||||
keyboard.InsertEndChild(tools);
|
||||
keyboard.InsertEndChild(quicktools);
|
||||
keyboard.InsertEndChild(actions);
|
||||
keyboard.InsertEndChild(wheel);
|
||||
keyboard.InsertEndChild(drag);
|
||||
|
||||
TiXmlDeclaration declaration("1.0", "utf-8", "");
|
||||
doc->InsertEndChild(declaration);
|
||||
doc->InsertEndChild(doc->NewDeclaration("xml version=\"1.0\" encoding=\"utf-8\""));
|
||||
doc->InsertEndChild(keyboard);
|
||||
save_xml(doc, filename);
|
||||
save_xml(doc.get(), filename);
|
||||
}
|
||||
|
||||
void KeyboardShortcuts::exportKeys(TiXmlElement& parent, KeyType type)
|
||||
void KeyboardShortcuts::exportKeys(XMLElement* parent, KeyType type)
|
||||
{
|
||||
for (KeyPtr& key : m_keys) {
|
||||
// Save only user defined accelerators.
|
||||
@ -893,17 +888,17 @@ void KeyboardShortcuts::exportKeys(TiXmlElement& parent, KeyType type)
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcuts::exportAccel(TiXmlElement& parent, const Key* key, const ui::Accelerator& accel, bool removed)
|
||||
void KeyboardShortcuts::exportAccel(XMLElement* parent, const Key* key, const ui::Accelerator& accel, bool removed)
|
||||
{
|
||||
TiXmlElement elem("key");
|
||||
XMLElement* elem = parent->InsertNewChildElement("key");
|
||||
|
||||
switch (key->type()) {
|
||||
|
||||
case KeyType::Command: {
|
||||
elem.SetAttribute("command", key->command()->id().c_str());
|
||||
elem->SetAttribute("command", key->command()->id().c_str());
|
||||
|
||||
if (key->keycontext() != KeyContext::Any) {
|
||||
elem.SetAttribute("context",
|
||||
elem->SetAttribute("context",
|
||||
base::convert_to<std::string>(key->keycontext()).c_str());
|
||||
}
|
||||
|
||||
@ -911,48 +906,45 @@ void KeyboardShortcuts::exportAccel(TiXmlElement& parent, const Key* key, const
|
||||
if (param.second.empty())
|
||||
continue;
|
||||
|
||||
TiXmlElement paramElem("param");
|
||||
paramElem.SetAttribute("name", param.first.c_str());
|
||||
paramElem.SetAttribute("value", param.second.c_str());
|
||||
elem.InsertEndChild(paramElem);
|
||||
XMLElement* paramElem = elem->InsertNewChildElement("param");
|
||||
paramElem->SetAttribute("name", param.first.c_str());
|
||||
paramElem->SetAttribute("value", param.second.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case KeyType::Tool:
|
||||
case KeyType::Quicktool:
|
||||
elem.SetAttribute("tool", key->tool()->getId().c_str());
|
||||
elem->SetAttribute("tool", key->tool()->getId().c_str());
|
||||
break;
|
||||
|
||||
case KeyType::Action:
|
||||
elem.SetAttribute("action",
|
||||
elem->SetAttribute("action",
|
||||
base::convert_to<std::string>(key->action()).c_str());
|
||||
if (key->keycontext() != KeyContext::Any)
|
||||
elem.SetAttribute("context",
|
||||
elem->SetAttribute("context",
|
||||
base::convert_to<std::string>(key->keycontext()).c_str());
|
||||
break;
|
||||
|
||||
case KeyType::WheelAction:
|
||||
elem.SetAttribute("action",
|
||||
base::convert_to<std::string>(key->wheelAction()));
|
||||
elem->SetAttribute("action",
|
||||
base::convert_to<std::string>(key->wheelAction()).c_str());
|
||||
break;
|
||||
|
||||
case KeyType::DragAction:
|
||||
elem.SetAttribute("action",
|
||||
base::convert_to<std::string>(key->wheelAction()));
|
||||
elem.SetAttribute("vector",
|
||||
fmt::format("{},{}",
|
||||
key->dragVector().x,
|
||||
key->dragVector().y));
|
||||
elem->SetAttribute("action",
|
||||
base::convert_to<std::string>(key->wheelAction()).c_str());
|
||||
elem->SetAttribute("vector",
|
||||
fmt::format("{},{}",
|
||||
key->dragVector().x,
|
||||
key->dragVector().y).c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
elem.SetAttribute("shortcut", accel.toString().c_str());
|
||||
elem->SetAttribute("shortcut", accel.toString().c_str());
|
||||
|
||||
if (removed)
|
||||
elem.SetAttribute("removed", "true");
|
||||
|
||||
parent.InsertEndChild(elem);
|
||||
elem->SetAttribute("removed", "true");
|
||||
}
|
||||
|
||||
void KeyboardShortcuts::reset()
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -12,7 +12,9 @@
|
||||
#include "app/ui/key.h"
|
||||
#include "obs/signal.h"
|
||||
|
||||
class TiXmlElement;
|
||||
namespace tinyxml2 {
|
||||
class XMLElement;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
|
||||
@ -38,7 +40,7 @@ namespace app {
|
||||
const bool cloneKeys);
|
||||
|
||||
void clear();
|
||||
void importFile(TiXmlElement* rootElement, KeySource source);
|
||||
void importFile(tinyxml2::XMLElement* rootElement, KeySource source);
|
||||
void importFile(const std::string& filename, KeySource source);
|
||||
void exportFile(const std::string& filename);
|
||||
void reset();
|
||||
@ -78,8 +80,8 @@ namespace app {
|
||||
obs::signal<void()> UserChange;
|
||||
|
||||
private:
|
||||
void exportKeys(TiXmlElement& parent, KeyType type);
|
||||
void exportAccel(TiXmlElement& parent, const Key* key, const ui::Accelerator& accel, bool removed);
|
||||
void exportKeys(tinyxml2::XMLElement* parent, KeyType type);
|
||||
void exportAccel(tinyxml2::XMLElement* parent, const Key* key, const ui::Accelerator& accel, bool removed);
|
||||
|
||||
mutable Keys m_keys;
|
||||
};
|
||||
|
@ -27,15 +27,16 @@
|
||||
#include "ui/view.h"
|
||||
#include "ver/info.h"
|
||||
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <sstream>
|
||||
|
||||
namespace app {
|
||||
|
||||
using namespace ui;
|
||||
using namespace app::skin;
|
||||
using namespace tinyxml2;
|
||||
using namespace ui;
|
||||
|
||||
namespace {
|
||||
|
||||
@ -267,7 +268,7 @@ void NewsListBox::parseFile(const std::string& filename)
|
||||
{
|
||||
View* view = View::getView(this);
|
||||
|
||||
XmlDocumentRef doc;
|
||||
XMLDocumentRef doc;
|
||||
try {
|
||||
doc = open_xml(filename);
|
||||
}
|
||||
@ -278,18 +279,18 @@ void NewsListBox::parseFile(const std::string& filename)
|
||||
return;
|
||||
}
|
||||
|
||||
TiXmlHandle handle(doc.get());
|
||||
TiXmlElement* itemXml = handle
|
||||
.FirstChild("rss")
|
||||
.FirstChild("channel")
|
||||
.FirstChild("item").ToElement();
|
||||
XMLHandle handle(doc.get());
|
||||
XMLElement* itemXml = handle
|
||||
.FirstChildElement("rss")
|
||||
.FirstChildElement("channel")
|
||||
.FirstChildElement("item").ToElement();
|
||||
|
||||
int count = 0;
|
||||
|
||||
while (itemXml) {
|
||||
TiXmlElement* titleXml = itemXml->FirstChildElement("title");
|
||||
TiXmlElement* descXml = itemXml->FirstChildElement("description");
|
||||
TiXmlElement* linkXml = itemXml->FirstChildElement("link");
|
||||
XMLElement* titleXml = itemXml->FirstChildElement("title");
|
||||
XMLElement* descXml = itemXml->FirstChildElement("description");
|
||||
XMLElement* linkXml = itemXml->FirstChildElement("link");
|
||||
if (titleXml && titleXml->GetText() &&
|
||||
descXml && descXml->GetText() &&
|
||||
linkXml && linkXml->GetText()) {
|
||||
@ -316,10 +317,10 @@ void NewsListBox::parseFile(const std::string& filename)
|
||||
itemXml = itemXml->NextSiblingElement();
|
||||
}
|
||||
|
||||
TiXmlElement* linkXml = handle
|
||||
.FirstChild("rss")
|
||||
.FirstChild("channel")
|
||||
.FirstChild("link").ToElement();
|
||||
XMLElement* linkXml = handle
|
||||
.FirstChildElement("rss")
|
||||
.FirstChildElement("channel")
|
||||
.FirstChildElement("link").ToElement();
|
||||
if (linkXml && linkXml->GetText())
|
||||
addChild(
|
||||
new NewsItem(linkXml->GetText(), Strings::news_listbox_more(), ""));
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -40,7 +40,7 @@
|
||||
#include "ui/intern.h"
|
||||
#include "ui/ui.h"
|
||||
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
@ -52,6 +52,7 @@ namespace app {
|
||||
namespace skin {
|
||||
|
||||
using namespace gfx;
|
||||
using namespace tinyxml2;
|
||||
using namespace ui;
|
||||
|
||||
// TODO For backward compatibility, in future versions we should remove this (extensions are preferred)
|
||||
@ -77,7 +78,8 @@ class app::skin::SkinTheme::BackwardCompatibility {
|
||||
// Loaded XML <style> element from the original theme (cloned
|
||||
// elements). Must be in order to insert them in the same order in
|
||||
// the selected theme.
|
||||
std::vector<std::unique_ptr<TiXmlElement>> m_styles;
|
||||
XMLDocumentRef m_stylesDoc;
|
||||
std::vector<XMLElement*> m_styles;
|
||||
|
||||
public:
|
||||
void copyingStyles() {
|
||||
@ -85,13 +87,17 @@ public:
|
||||
}
|
||||
|
||||
// Called for each <style> element found in theme.xml.
|
||||
void onStyle(TiXmlElement* xmlStyle) {
|
||||
void onStyle(XMLElement* xmlStyle) {
|
||||
// Loading <style> from the default theme
|
||||
if (m_state == State::LoadingStyles)
|
||||
m_styles.emplace_back((TiXmlElement*)xmlStyle->Clone());
|
||||
if (m_state == State::LoadingStyles) {
|
||||
if (!m_stylesDoc)
|
||||
m_stylesDoc = std::make_unique<XMLDocument>();
|
||||
m_styles.emplace_back(
|
||||
xmlStyle->DeepClone(m_stylesDoc.get())->ToElement());
|
||||
}
|
||||
}
|
||||
|
||||
void removeExistentStyles(TiXmlElement* xmlStyle) {
|
||||
void removeExistentStyles(XMLElement* xmlStyle) {
|
||||
if (m_state != State::CopyingStyles)
|
||||
return;
|
||||
|
||||
@ -117,7 +123,7 @@ public:
|
||||
// Copies all missing <style> elements to the new theme. xmlStyles
|
||||
// is the <styles> element from the theme.xml of the selected theme
|
||||
// (non the default one).
|
||||
void copyMissingStyles(TiXmlNode* xmlStyles) {
|
||||
void copyMissingStyles(XMLNode* xmlStyles) {
|
||||
if (m_state != State::CopyingStyles)
|
||||
return;
|
||||
|
||||
@ -125,8 +131,8 @@ public:
|
||||
LOG(VERBOSE, "THEME: Copying <style id='%s'> from default theme\n",
|
||||
style->Attribute("id"));
|
||||
|
||||
// InsertEndChild() clones the node
|
||||
xmlStyles->InsertEndChild(*style.get());
|
||||
xmlStyles->InsertEndChild(
|
||||
style->DeepClone(xmlStyles->GetDocument()));
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -153,7 +159,7 @@ static const char* g_cursor_names[kCursorTypes] = {
|
||||
};
|
||||
|
||||
static FontData* load_font(std::map<std::string, FontData*>& fonts,
|
||||
const TiXmlElement* xmlFont,
|
||||
const XMLElement* xmlFont,
|
||||
const std::string& xmlFilename)
|
||||
{
|
||||
const char* fontRef = xmlFont->Attribute("font");
|
||||
@ -237,8 +243,8 @@ static FontData* load_font(std::map<std::string, FontData*>& fonts,
|
||||
font.release();
|
||||
|
||||
// Fallback font
|
||||
const TiXmlElement* xmlFallback =
|
||||
(const TiXmlElement*)xmlFont->FirstChild("fallback");
|
||||
const XMLElement* xmlFallback =
|
||||
(const XMLElement*)xmlFont->FirstChildElement("fallback");
|
||||
if (xmlFallback) {
|
||||
FontData* fallback = load_font(fonts, xmlFallback, xmlFilename);
|
||||
if (fallback) {
|
||||
@ -345,12 +351,12 @@ void SkinTheme::loadFontData()
|
||||
if (!rf.findFirst())
|
||||
throw base::Exception("File %s not found", fontsFilename.c_str());
|
||||
|
||||
XmlDocumentRef doc = open_xml(rf.filename());
|
||||
TiXmlHandle handle(doc.get());
|
||||
XMLDocumentRef doc = open_xml(rf.filename());
|
||||
XMLHandle handle(doc.get());
|
||||
|
||||
TiXmlElement* xmlFont = handle
|
||||
.FirstChild("fonts")
|
||||
.FirstChild("font").ToElement();
|
||||
XMLElement* xmlFont = handle
|
||||
.FirstChildElement("fonts")
|
||||
.FirstChildElement("font").ToElement();
|
||||
while (xmlFont) {
|
||||
load_font(m_fonts, xmlFont, rf.filename());
|
||||
xmlFont = xmlFont->NextSiblingElement();
|
||||
@ -420,15 +426,15 @@ void SkinTheme::loadXml(BackwardCompatibility* backward)
|
||||
// Load the skin XML
|
||||
std::string xml_filename(base::join_path(m_path, "theme.xml"));
|
||||
|
||||
XmlDocumentRef doc = open_xml(xml_filename);
|
||||
TiXmlHandle handle(doc.get());
|
||||
XMLDocumentRef doc = open_xml(xml_filename);
|
||||
XMLHandle handle(doc.get());
|
||||
|
||||
// Load Preferred scaling
|
||||
m_preferredScreenScaling = -1;
|
||||
m_preferredUIScaling = -1;
|
||||
{
|
||||
TiXmlElement* xmlTheme = handle
|
||||
.FirstChild("theme").ToElement();
|
||||
XMLElement* xmlTheme = handle
|
||||
.FirstChildElement("theme").ToElement();
|
||||
if (xmlTheme) {
|
||||
const char* screenScaling = xmlTheme->Attribute("screenscaling");
|
||||
const char* uiScaling = xmlTheme->Attribute("uiscaling");
|
||||
@ -441,10 +447,10 @@ void SkinTheme::loadXml(BackwardCompatibility* backward)
|
||||
|
||||
// Load fonts
|
||||
{
|
||||
TiXmlElement* xmlFont = handle
|
||||
.FirstChild("theme")
|
||||
.FirstChild("fonts")
|
||||
.FirstChild("font").ToElement();
|
||||
XMLElement* xmlFont = handle
|
||||
.FirstChildElement("theme")
|
||||
.FirstChildElement("fonts")
|
||||
.FirstChildElement("font").ToElement();
|
||||
while (xmlFont) {
|
||||
const char* idStr = xmlFont->Attribute("id");
|
||||
FontData* fontData = load_font(m_fonts, xmlFont, xml_filename);
|
||||
@ -485,10 +491,10 @@ void SkinTheme::loadXml(BackwardCompatibility* backward)
|
||||
|
||||
// Load dimension
|
||||
{
|
||||
TiXmlElement* xmlDim = handle
|
||||
.FirstChild("theme")
|
||||
.FirstChild("dimensions")
|
||||
.FirstChild("dim").ToElement();
|
||||
XMLElement* xmlDim = handle
|
||||
.FirstChildElement("theme")
|
||||
.FirstChildElement("dimensions")
|
||||
.FirstChildElement("dim").ToElement();
|
||||
while (xmlDim) {
|
||||
std::string id = xmlDim->Attribute("id");
|
||||
uint32_t value = strtol(xmlDim->Attribute("value"), NULL, 10);
|
||||
@ -502,10 +508,10 @@ void SkinTheme::loadXml(BackwardCompatibility* backward)
|
||||
|
||||
// Load colors
|
||||
{
|
||||
TiXmlElement* xmlColor = handle
|
||||
.FirstChild("theme")
|
||||
.FirstChild("colors")
|
||||
.FirstChild("color").ToElement();
|
||||
XMLElement* xmlColor = handle
|
||||
.FirstChildElement("theme")
|
||||
.FirstChildElement("colors")
|
||||
.FirstChildElement("color").ToElement();
|
||||
while (xmlColor) {
|
||||
std::string id = xmlColor->Attribute("id");
|
||||
uint32_t value = strtol(xmlColor->Attribute("value")+1, NULL, 16);
|
||||
@ -523,10 +529,10 @@ void SkinTheme::loadXml(BackwardCompatibility* backward)
|
||||
|
||||
// Load parts
|
||||
{
|
||||
TiXmlElement* xmlPart = handle
|
||||
.FirstChild("theme")
|
||||
.FirstChild("parts")
|
||||
.FirstChild("part").ToElement();
|
||||
XMLElement* xmlPart = handle
|
||||
.FirstChildElement("theme")
|
||||
.FirstChildElement("parts")
|
||||
.FirstChildElement("part").ToElement();
|
||||
while (xmlPart) {
|
||||
// Get the tool-icon rectangle
|
||||
const char* part_id = xmlPart->Attribute("id");
|
||||
@ -616,10 +622,10 @@ void SkinTheme::loadXml(BackwardCompatibility* backward)
|
||||
|
||||
// Load styles
|
||||
{
|
||||
TiXmlElement* xmlStyle = handle
|
||||
.FirstChild("theme")
|
||||
.FirstChild("styles")
|
||||
.FirstChild("style").ToElement();
|
||||
XMLElement* xmlStyle = handle
|
||||
.FirstChildElement("theme")
|
||||
.FirstChildElement("styles")
|
||||
.FirstChildElement("style").ToElement();
|
||||
|
||||
if (!xmlStyle) // Without styles?
|
||||
throw base::Exception("There are no styles");
|
||||
@ -752,7 +758,7 @@ void SkinTheme::loadXml(BackwardCompatibility* backward)
|
||||
}
|
||||
}
|
||||
|
||||
TiXmlElement* xmlLayer = xmlStyle->FirstChildElement();
|
||||
XMLElement* xmlLayer = xmlStyle->FirstChildElement();
|
||||
while (xmlLayer) {
|
||||
const std::string layerName = xmlLayer->Value();
|
||||
|
||||
|
@ -1347,7 +1347,7 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
else if (mouseMsg->left()) {
|
||||
Command* command = Commands::instance()
|
||||
->byId(CommandId::FrameTagProperties());
|
||||
UIContext::instance()->executeCommand(command, params);
|
||||
m_context->executeCommand(command, params);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -1389,13 +1389,18 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
if (tag) {
|
||||
if ((m_state == STATE_RESIZING_TAG_LEFT && tag->fromFrame() != m_resizeTagData.from) ||
|
||||
(m_state == STATE_RESIZING_TAG_RIGHT && tag->toFrame() != m_resizeTagData.to)) {
|
||||
ContextWriter writer(UIContext::instance());
|
||||
Tx tx(writer, Strings::commands_FrameTagProperties());
|
||||
tx(new cmd::SetTagRange(
|
||||
tag,
|
||||
(m_state == STATE_RESIZING_TAG_LEFT ? m_resizeTagData.from: tag->fromFrame()),
|
||||
(m_state == STATE_RESIZING_TAG_RIGHT ? m_resizeTagData.to: tag->toFrame())));
|
||||
tx.commit();
|
||||
try {
|
||||
ContextWriter writer(m_context);
|
||||
Tx tx(writer, Strings::commands_FrameTagProperties());
|
||||
tx(new cmd::SetTagRange(
|
||||
tag,
|
||||
(m_state == STATE_RESIZING_TAG_LEFT ? m_resizeTagData.from: tag->fromFrame()),
|
||||
(m_state == STATE_RESIZING_TAG_RIGHT ? m_resizeTagData.to: tag->toFrame())));
|
||||
tx.commit();
|
||||
}
|
||||
catch (const base::Exception& e) {
|
||||
Console::showException(e);
|
||||
}
|
||||
|
||||
regenerateRows();
|
||||
}
|
||||
@ -1431,7 +1436,7 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
Command* command = Commands::instance()
|
||||
->byId(CommandId::LayerProperties());
|
||||
|
||||
UIContext::instance()->executeCommand(command);
|
||||
m_context->executeCommand(command);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1441,7 +1446,7 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
Params params;
|
||||
params.set("frame", "current");
|
||||
|
||||
UIContext::instance()->executeCommand(command, params);
|
||||
m_context->executeCommand(command, params);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1449,7 +1454,7 @@ bool Timeline::onProcessMessage(Message* msg)
|
||||
Command* command = Commands::instance()
|
||||
->byId(CommandId::CelProperties());
|
||||
|
||||
UIContext::instance()->executeCommand(command);
|
||||
m_context->executeCommand(command);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2088,7 +2093,10 @@ void Timeline::setCursor(ui::Message* msg, const Hit& hit)
|
||||
ui::set_mouse_cursor(kSizeECursor);
|
||||
}
|
||||
else if (hit.part == PART_RANGE_OUTLINE) {
|
||||
ui::set_mouse_cursor(kMoveCursor);
|
||||
if (is_copy_key_pressed(msg))
|
||||
ui::set_mouse_cursor(kArrowPlusCursor);
|
||||
else
|
||||
ui::set_mouse_cursor(kMoveCursor);
|
||||
}
|
||||
else if (hit.part == PART_SEPARATOR) {
|
||||
ui::set_mouse_cursor(kSizeWECursor);
|
||||
|
94
src/app/util/shader_helpers.cpp
Normal file
94
src/app/util/shader_helpers.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#if SK_ENABLE_SKSL
|
||||
|
||||
#include "app/util/shader_helpers.h"
|
||||
|
||||
#include "base/exception.h"
|
||||
#include "doc/image.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include "include/effects/SkRuntimeEffect.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
sk_sp<SkRuntimeEffect> make_shader(const char* code)
|
||||
{
|
||||
auto result = SkRuntimeEffect::MakeForShader(SkString(code));
|
||||
if (!result.errorText.isEmpty()) {
|
||||
std::string error = fmt::format("Error compiling shader.\nError: {}\n",
|
||||
result.errorText.c_str());
|
||||
LOG(ERROR, error.c_str());
|
||||
std::printf("%s", error.c_str());
|
||||
throw base::Exception(error);
|
||||
}
|
||||
return result.effect;
|
||||
}
|
||||
|
||||
SkImageInfo get_skimageinfo_for_docimage(const doc::Image* img)
|
||||
{
|
||||
switch (img->colorMode()) {
|
||||
|
||||
case doc::ColorMode::RGB:
|
||||
return SkImageInfo::Make(img->width(),
|
||||
img->height(),
|
||||
kRGBA_8888_SkColorType,
|
||||
kUnpremul_SkAlphaType);
|
||||
|
||||
case doc::ColorMode::GRAYSCALE:
|
||||
// We use kR8G8_unorm_SkColorType to access gray and alpha
|
||||
return SkImageInfo::Make(img->width(),
|
||||
img->height(),
|
||||
kR8G8_unorm_SkColorType,
|
||||
kOpaque_SkAlphaType);
|
||||
|
||||
case doc::ColorMode::INDEXED: {
|
||||
// We use kAlpha_8_SkColorType to access to the index value through the alpha channel
|
||||
return SkImageInfo::Make(img->width(),
|
||||
img->height(),
|
||||
kAlpha_8_SkColorType,
|
||||
kUnpremul_SkAlphaType);
|
||||
|
||||
}
|
||||
}
|
||||
return SkImageInfo();
|
||||
}
|
||||
|
||||
sk_sp<SkImage> make_skimage_for_docimage(const doc::Image* img)
|
||||
{
|
||||
switch (img->colorMode()) {
|
||||
case doc::ColorMode::RGB:
|
||||
case doc::ColorMode::GRAYSCALE:
|
||||
case doc::ColorMode::INDEXED: {
|
||||
auto skData = SkData::MakeWithoutCopy(
|
||||
(const void*)img->getPixelAddress(0, 0),
|
||||
img->rowBytes() * img->height());
|
||||
|
||||
return SkImage::MakeRasterData(
|
||||
get_skimageinfo_for_docimage(img),
|
||||
skData,
|
||||
img->rowBytes());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<SkCanvas> make_skcanvas_for_docimage(const doc::Image* img)
|
||||
{
|
||||
return SkCanvas::MakeRasterDirect(
|
||||
get_skimageinfo_for_docimage(img),
|
||||
(void*)img->getPixelAddress(0, 0),
|
||||
img->rowBytes());
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif // SK_ENABLE_SKSL
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2022 Igara Studio S.A.
|
||||
// Copyright (C) 2022-2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
@ -8,18 +8,32 @@
|
||||
#define APP_UTIL_SHADER_HELPERS_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#if SK_ENABLE_SKSL
|
||||
#if LAF_SKIA
|
||||
|
||||
#include "app/color.h"
|
||||
#include "gfx/color.h"
|
||||
|
||||
#include "include/core/SkCanvas.h"
|
||||
#include "include/core/SkImage.h"
|
||||
#include "include/core/SkM44.h"
|
||||
#include "include/core/SkRefCnt.h"
|
||||
|
||||
// To include kRGB_to_HSL_sksl and kHSL_to_RGB_sksl
|
||||
#include "src/core/SkRuntimeEffectPriv.h"
|
||||
#if SK_ENABLE_SKSL
|
||||
#include "include/effects/SkRuntimeEffect.h"
|
||||
// To include kRGB_to_HSL_sksl and kHSL_to_RGB_sksl
|
||||
#include "src/core/SkRuntimeEffectPriv.h"
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace doc {
|
||||
class Image;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
|
||||
#if SK_ENABLE_SKSL
|
||||
|
||||
// rgb_to_hsl() and hsv_to_hsl() functions by Sam Hocevar licensed
|
||||
// under WTFPL (https://en.wikipedia.org/wiki/WTFPL)
|
||||
// Source:
|
||||
@ -74,8 +88,16 @@ inline SkV4 appColorHsl_to_SkV4(const app::Color& color) {
|
||||
float(color.getAlpha() / 255.0)};
|
||||
}
|
||||
|
||||
sk_sp<SkRuntimeEffect> make_shader(const char* code);
|
||||
|
||||
#endif // SK_ENABLE_SKSL
|
||||
|
||||
SkImageInfo get_skimageinfo_for_docimage(const doc::Image* img);
|
||||
sk_sp<SkImage> make_skimage_for_docimage(const doc::Image* img);
|
||||
std::unique_ptr<SkCanvas> make_skcanvas_for_docimage(const doc::Image* img);
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
||||
#endif // LAF_SKIA
|
||||
|
||||
#endif
|
||||
|
@ -34,7 +34,7 @@
|
||||
#include "os/system.h"
|
||||
#include "ui/ui.h"
|
||||
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@ -44,11 +44,12 @@
|
||||
|
||||
namespace app {
|
||||
|
||||
using namespace ui;
|
||||
using namespace app::skin;
|
||||
using namespace tinyxml2;
|
||||
using namespace ui;
|
||||
|
||||
static int convert_align_value_to_flags(const char *value);
|
||||
static int int_attr(const TiXmlElement* elem, const char* attribute_name, int default_value);
|
||||
static int int_attr(const XMLElement* elem, const char* attribute_name, int default_value);
|
||||
|
||||
WidgetLoader::WidgetLoader()
|
||||
: m_tooltipManager(NULL)
|
||||
@ -96,12 +97,12 @@ Widget* WidgetLoader::loadWidgetFromXmlFile(
|
||||
m_tooltipManager = NULL;
|
||||
m_xmlTranslator.setStringIdPrefix(widgetId.c_str());
|
||||
|
||||
XmlDocumentRef doc(open_xml(xmlFilename));
|
||||
TiXmlHandle handle(doc.get());
|
||||
XMLDocumentRef doc = open_xml(xmlFilename);
|
||||
XMLHandle handle(doc.get());
|
||||
|
||||
// Search the requested widget.
|
||||
TiXmlElement* xmlElement = handle
|
||||
.FirstChild("gui")
|
||||
XMLElement* xmlElement = handle
|
||||
.FirstChildElement("gui")
|
||||
.FirstChildElement().ToElement();
|
||||
|
||||
while (xmlElement) {
|
||||
@ -118,7 +119,7 @@ Widget* WidgetLoader::loadWidgetFromXmlFile(
|
||||
return widget;
|
||||
}
|
||||
|
||||
Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget* root, Widget* parent, Widget* widget)
|
||||
Widget* WidgetLoader::convertXmlElementToWidget(const XMLElement* elem, Widget* root, Widget* parent, Widget* widget)
|
||||
{
|
||||
const std::string elem_name = elem->Value();
|
||||
|
||||
@ -542,7 +543,7 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
|
||||
return widget;
|
||||
}
|
||||
|
||||
void WidgetLoader::fillWidgetWithXmlElementAttributes(const TiXmlElement* elem, Widget* root, Widget* widget)
|
||||
void WidgetLoader::fillWidgetWithXmlElementAttributes(const XMLElement* elem, Widget* root, Widget* widget)
|
||||
{
|
||||
const char* id = elem->Attribute("id");
|
||||
const char* tooltip_dir = elem->Attribute("tooltip_dir");
|
||||
@ -679,7 +680,7 @@ void WidgetLoader::fillWidgetWithXmlElementAttributes(const TiXmlElement* elem,
|
||||
widget->initTheme();
|
||||
}
|
||||
|
||||
void WidgetLoader::fillWidgetWithXmlElementAttributesWithChildren(const TiXmlElement* elem, ui::Widget* root, ui::Widget* widget)
|
||||
void WidgetLoader::fillWidgetWithXmlElementAttributesWithChildren(const XMLElement* elem, ui::Widget* root, ui::Widget* widget)
|
||||
{
|
||||
fillWidgetWithXmlElementAttributes(elem, root, widget);
|
||||
|
||||
@ -687,7 +688,7 @@ void WidgetLoader::fillWidgetWithXmlElementAttributesWithChildren(const TiXmlEle
|
||||
root = widget;
|
||||
|
||||
// Children
|
||||
const TiXmlElement* childElem = elem->FirstChildElement();
|
||||
const XMLElement* childElem = elem->FirstChildElement();
|
||||
while (childElem) {
|
||||
Widget* child = convertXmlElementToWidget(childElem, root, widget, NULL);
|
||||
if (child) {
|
||||
@ -772,7 +773,7 @@ static int convert_align_value_to_flags(const char *value)
|
||||
return flags;
|
||||
}
|
||||
|
||||
static int int_attr(const TiXmlElement* elem, const char* attribute_name, int default_value)
|
||||
static int int_attr(const XMLElement* elem, const char* attribute_name, int default_value)
|
||||
{
|
||||
const char* value = elem->Attribute(attribute_name);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -14,7 +15,9 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class TiXmlElement;
|
||||
namespace tinyxml2 {
|
||||
class XMLElement;
|
||||
}
|
||||
|
||||
namespace ui {
|
||||
class Widget;
|
||||
@ -30,7 +33,7 @@ namespace app {
|
||||
public:
|
||||
virtual ~IWidgetTypeCreator() { }
|
||||
virtual void dispose() = 0;
|
||||
virtual ui::Widget* createWidgetFromXml(const TiXmlElement* xmlElem) = 0;
|
||||
virtual ui::Widget* createWidgetFromXml(const tinyxml2::XMLElement* xmlElem) = 0;
|
||||
};
|
||||
|
||||
WidgetLoader();
|
||||
@ -62,9 +65,9 @@ namespace app {
|
||||
const std::string& widgetId,
|
||||
ui::Widget* widget);
|
||||
|
||||
ui::Widget* convertXmlElementToWidget(const TiXmlElement* elem, ui::Widget* root, ui::Widget* parent, ui::Widget* widget);
|
||||
void fillWidgetWithXmlElementAttributes(const TiXmlElement* elem, ui::Widget* root, ui::Widget* widget);
|
||||
void fillWidgetWithXmlElementAttributesWithChildren(const TiXmlElement* elem, ui::Widget* root, ui::Widget* widget);
|
||||
ui::Widget* convertXmlElementToWidget(const tinyxml2::XMLElement* elem, ui::Widget* root, ui::Widget* parent, ui::Widget* widget);
|
||||
void fillWidgetWithXmlElementAttributes(const tinyxml2::XMLElement* elem, ui::Widget* root, ui::Widget* widget);
|
||||
void fillWidgetWithXmlElementAttributesWithChildren(const tinyxml2::XMLElement* elem, ui::Widget* root, ui::Widget* widget);
|
||||
|
||||
typedef std::map<std::string, IWidgetTypeCreator*> TypeCreatorsMap;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -14,28 +14,28 @@
|
||||
#include "app/xml_exception.h"
|
||||
#include "base/file_handle.h"
|
||||
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
using namespace base;
|
||||
using namespace tinyxml2;
|
||||
|
||||
XmlDocumentRef open_xml(const std::string& filename)
|
||||
XMLDocumentRef open_xml(const std::string& filename)
|
||||
{
|
||||
FileHandle file(open_file(filename, "rb"));
|
||||
if (!file)
|
||||
throw Exception("Error loading file: " + filename);
|
||||
|
||||
// Try to load the XML file
|
||||
auto doc = std::make_shared<TiXmlDocument>();
|
||||
doc->SetValue(filename.c_str());
|
||||
if (!doc->LoadFile(file.get()))
|
||||
throw XmlException(doc.get());
|
||||
auto doc = std::make_unique<XMLDocument>();
|
||||
if (doc->LoadFile(file.get()) != XML_SUCCESS)
|
||||
throw XmlException(filename, doc.get());
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
void save_xml(XmlDocumentRef doc, const std::string& filename)
|
||||
void save_xml(XMLDocument* doc, const std::string& filename)
|
||||
{
|
||||
FileHandle file(open_file(filename, "wb"));
|
||||
if (!file) {
|
||||
@ -43,11 +43,11 @@ void save_xml(XmlDocumentRef doc, const std::string& filename)
|
||||
throw Exception("Error loading file: " + filename);
|
||||
}
|
||||
|
||||
if (!doc->SaveFile(file.get()))
|
||||
throw XmlException(doc.get());
|
||||
if (doc->SaveFile(file.get()) != XML_SUCCESS)
|
||||
throw XmlException(filename, doc);
|
||||
}
|
||||
|
||||
bool bool_attr(const TiXmlElement* elem, const char* attrName, bool defaultVal)
|
||||
bool bool_attr(const XMLElement* elem, const char* attrName, bool defaultVal)
|
||||
{
|
||||
const char* value = elem->Attribute(attrName);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -11,19 +11,21 @@
|
||||
|
||||
#include "base/exception.h"
|
||||
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace app {
|
||||
|
||||
typedef std::shared_ptr<TiXmlDocument> XmlDocumentRef;
|
||||
using XMLDocumentRef = std::unique_ptr<tinyxml2::XMLDocument>;
|
||||
|
||||
XmlDocumentRef open_xml(const std::string& filename);
|
||||
void save_xml(XmlDocumentRef doc, const std::string& filename);
|
||||
XMLDocumentRef open_xml(const std::string& filename);
|
||||
void save_xml(tinyxml2::XMLDocument* doc, const std::string& filename);
|
||||
|
||||
bool bool_attr(const TiXmlElement* elem, const char* attrName, bool defaultVal);
|
||||
bool bool_attr(const tinyxml2::XMLElement* elem,
|
||||
const char* attrName,
|
||||
bool defaultVal);
|
||||
|
||||
} // namespace app
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2023 Igara Studio S.A.
|
||||
// Copyright (C) 2023-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -12,17 +12,21 @@
|
||||
#include "app/xml_exception.h"
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
XmlException::XmlException(const TiXmlDocument* doc) throw()
|
||||
using namespace tinyxml2;
|
||||
|
||||
XmlException::XmlException(const std::string& filename,
|
||||
const XMLDocument* doc) noexcept
|
||||
{
|
||||
try {
|
||||
setMessage(
|
||||
fmt::format("Error in XML file '{}' (line {}, column {})\nError {}: {}",
|
||||
doc->Value(), doc->ErrorRow(), doc->ErrorCol(),
|
||||
doc->ErrorId(), doc->ErrorDesc()).c_str());
|
||||
fmt::format("Error in XML file '{}' (line {})\nError {}: {}",
|
||||
filename, doc->ErrorLineNum(),
|
||||
int(doc->ErrorID()),
|
||||
doc->ErrorStr()).c_str());
|
||||
}
|
||||
catch (...) {
|
||||
// No throw
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,13 +11,16 @@
|
||||
|
||||
#include "base/exception.h"
|
||||
|
||||
class TiXmlDocument;
|
||||
namespace tinyxml2 {
|
||||
class XMLDocument;
|
||||
}
|
||||
|
||||
namespace app {
|
||||
|
||||
class XmlException : public base::Exception {
|
||||
public:
|
||||
XmlException(const TiXmlDocument* doc) throw();
|
||||
XmlException(const std::string& filename,
|
||||
const tinyxml2::XMLDocument* doc) noexcept;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
1
src/clip
1
src/clip
@ -1 +0,0 @@
|
||||
Subproject commit 835cd0f7e7a964bb969482117856bc56a0ac12bf
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -48,32 +48,43 @@ Brush::Brush(BrushType type, int size, int angle)
|
||||
regenerate();
|
||||
}
|
||||
|
||||
Brush::Brush(const Brush& brush)
|
||||
{
|
||||
m_type = brush.m_type;
|
||||
m_size = brush.m_size;
|
||||
m_angle = brush.m_angle;
|
||||
m_image = brush.m_image;
|
||||
m_maskBitmap = brush.m_maskBitmap;
|
||||
m_pattern = brush.m_pattern;
|
||||
m_patternOrigin = brush.m_patternOrigin;
|
||||
m_gen = 0;
|
||||
|
||||
regenerate();
|
||||
}
|
||||
|
||||
Brush::~Brush()
|
||||
{
|
||||
clean();
|
||||
}
|
||||
|
||||
void Brush::setType(BrushType type)
|
||||
BrushRef Brush::cloneWithSharedImages() const
|
||||
{
|
||||
m_type = type;
|
||||
if (m_type != kImageBrushType)
|
||||
regenerate();
|
||||
BrushRef newBrush = std::make_shared<Brush>();
|
||||
newBrush->copyFieldsFromBrush(*this);
|
||||
return newBrush;
|
||||
}
|
||||
|
||||
BrushRef Brush::cloneWithNewImages() const
|
||||
{
|
||||
BrushRef newBrush = std::make_shared<Brush>();
|
||||
newBrush->copyFieldsFromBrush(*this);
|
||||
if (newBrush->m_image)
|
||||
newBrush->m_image.reset(Image::createCopy(newBrush->m_image.get()));
|
||||
if (newBrush->m_maskBitmap)
|
||||
newBrush->m_maskBitmap.reset(Image::createCopy(newBrush->m_maskBitmap.get()));
|
||||
return newBrush;
|
||||
}
|
||||
|
||||
BrushRef Brush::cloneWithExistingImages(const ImageRef& image,
|
||||
const ImageRef& maskBitmap) const
|
||||
{
|
||||
BrushRef newBrush = std::make_shared<Brush>();
|
||||
newBrush->copyFieldsFromBrush(*this);
|
||||
|
||||
newBrush->m_image = image;
|
||||
if (maskBitmap)
|
||||
newBrush->m_maskBitmap = maskBitmap;
|
||||
else
|
||||
clean();
|
||||
newBrush->regenerateMaskBitmap();
|
||||
|
||||
newBrush->resetBounds();
|
||||
return newBrush;
|
||||
}
|
||||
|
||||
void Brush::setSize(int size)
|
||||
@ -95,16 +106,8 @@ void Brush::setImage(const Image* image,
|
||||
m_image.reset(Image::createCopy(image));
|
||||
if (maskBitmap)
|
||||
m_maskBitmap.reset(Image::createCopy(maskBitmap));
|
||||
else {
|
||||
int w = image->width();
|
||||
int h = image->height();
|
||||
m_maskBitmap.reset(Image::create(IMAGE_BITMAP, w, h));
|
||||
LockImageBits<BitmapTraits> bits(m_maskBitmap.get());
|
||||
auto pos = bits.begin();
|
||||
for (int v=0; v<h; ++v)
|
||||
for (int u=0; u<w; ++u, ++pos)
|
||||
*pos = (get_pixel(image, u, v) != image->maskColor());
|
||||
}
|
||||
else
|
||||
regenerateMaskBitmap();
|
||||
|
||||
m_backupImage.reset();
|
||||
m_mainColor.reset();
|
||||
@ -234,7 +237,8 @@ static void replace_image_colors_indexed(
|
||||
}
|
||||
}
|
||||
|
||||
void Brush::setImageColor(ImageColor imageColor, color_t color)
|
||||
void Brush::setImageColor(const ImageColor imageColor,
|
||||
const color_t color)
|
||||
{
|
||||
ASSERT(m_image);
|
||||
if (!m_image)
|
||||
@ -249,10 +253,13 @@ void Brush::setImageColor(ImageColor imageColor, color_t color)
|
||||
|
||||
switch (imageColor) {
|
||||
case ImageColor::MainColor:
|
||||
m_mainColor = color_t(color);
|
||||
m_mainColor = color;
|
||||
break;
|
||||
case ImageColor::BackgroundColor:
|
||||
m_bgColor = color_t(color);
|
||||
m_bgColor = color;
|
||||
break;
|
||||
case ImageColor::BothColors:
|
||||
m_mainColor = m_bgColor = color;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -283,8 +290,11 @@ void Brush::setImageColor(ImageColor imageColor, color_t color)
|
||||
|
||||
void Brush::resetImageColors()
|
||||
{
|
||||
if (m_backupImage)
|
||||
if (m_backupImage) {
|
||||
m_image.reset(Image::createCopy(m_backupImage.get()));
|
||||
m_mainColor.reset();
|
||||
m_bgColor.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Brush::setCenter(const gfx::Point& center)
|
||||
@ -376,6 +386,22 @@ void Brush::regenerate()
|
||||
}
|
||||
}
|
||||
|
||||
void Brush::regenerateMaskBitmap()
|
||||
{
|
||||
ASSERT(m_image);
|
||||
if (!m_image)
|
||||
return;
|
||||
|
||||
int w = m_image->width();
|
||||
int h = m_image->height();
|
||||
m_maskBitmap.reset(Image::create(IMAGE_BITMAP, w, h));
|
||||
LockImageBits<BitmapTraits> bits(m_maskBitmap.get());
|
||||
auto pos = bits.begin();
|
||||
for (int v=0; v<h; ++v)
|
||||
for (int u=0; u<w; ++u, ++pos)
|
||||
*pos = (get_pixel(m_image.get(), u, v) != m_image->maskColor());
|
||||
}
|
||||
|
||||
void Brush::resetBounds()
|
||||
{
|
||||
m_center = gfx::Point(std::max(0, m_image->width()/2),
|
||||
@ -385,4 +411,19 @@ void Brush::resetBounds()
|
||||
m_image->height()));
|
||||
}
|
||||
|
||||
void Brush::copyFieldsFromBrush(const Brush& brush)
|
||||
{
|
||||
m_type = brush.m_type;
|
||||
m_size = brush.m_size;
|
||||
m_angle = brush.m_angle;
|
||||
m_image = brush.m_image;
|
||||
m_maskBitmap = brush.m_maskBitmap;
|
||||
m_bounds = brush.m_bounds;
|
||||
m_center = brush.m_center;
|
||||
m_pattern = brush.m_pattern;
|
||||
m_patternOrigin = brush.m_patternOrigin;
|
||||
m_patternImage = brush.m_patternImage;
|
||||
m_gen = 0;
|
||||
}
|
||||
|
||||
} // namespace doc
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -22,18 +22,34 @@
|
||||
|
||||
namespace doc {
|
||||
|
||||
class Brush;
|
||||
using BrushRef = std::shared_ptr<Brush>;
|
||||
|
||||
class Brush {
|
||||
public:
|
||||
static const int kMinBrushSize = 1;
|
||||
static const int kMaxBrushSize = 64;
|
||||
|
||||
enum class ImageColor { MainColor, BackgroundColor };
|
||||
enum class ImageColor { MainColor, BackgroundColor, BothColors };
|
||||
|
||||
Brush();
|
||||
Brush(BrushType type, int size, int angle);
|
||||
Brush(const Brush& brush);
|
||||
~Brush();
|
||||
|
||||
// Don't offer copy constructor/operator, use clone*() functions
|
||||
// instead.
|
||||
Brush(const Brush&) = delete;
|
||||
Brush& operator=(const Brush&) = delete;
|
||||
|
||||
// Cloned brushes can share the same image until
|
||||
// setSize()/Angle()/etc. (regenerate()) is called for the new
|
||||
// brush. In that case the original brush and the cloned one will
|
||||
// have a different image after all.
|
||||
BrushRef cloneWithSharedImages() const;
|
||||
BrushRef cloneWithNewImages() const;
|
||||
BrushRef cloneWithExistingImages(const ImageRef& image,
|
||||
const ImageRef& maskBitmap) const;
|
||||
|
||||
BrushType type() const { return m_type; }
|
||||
int size() const { return m_size; }
|
||||
int angle() const { return m_angle; }
|
||||
@ -48,7 +64,6 @@ namespace doc {
|
||||
const gfx::Rect& bounds() const { return m_bounds; }
|
||||
const gfx::Point& center() const { return m_center; }
|
||||
|
||||
void setType(BrushType type);
|
||||
void setSize(int size);
|
||||
void setAngle(int angle);
|
||||
void setImage(const Image* image,
|
||||
@ -81,7 +96,9 @@ namespace doc {
|
||||
private:
|
||||
void clean();
|
||||
void regenerate();
|
||||
void regenerateMaskBitmap();
|
||||
void resetBounds();
|
||||
void copyFieldsFromBrush(const Brush& brush);
|
||||
|
||||
BrushType m_type; // Type of brush
|
||||
int m_size; // Size (diameter)
|
||||
@ -101,8 +118,6 @@ namespace doc {
|
||||
std::optional<color_t> m_bgColor; // Background color
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<Brush> BrushRef;
|
||||
|
||||
} // namespace doc
|
||||
|
||||
#endif
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2019-2021 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -42,10 +42,13 @@ namespace doc {
|
||||
Reference = 64, // Is a reference layer
|
||||
|
||||
PersistentFlagsMask = 0xffff,
|
||||
|
||||
Internal_WasVisible = 0x10000, // Was visible in the alternative state (Alt+click)
|
||||
|
||||
BackgroundLayerFlags = LockMove | Background,
|
||||
|
||||
// Flags that change the modified flag of the document
|
||||
// (e.g. created by undoable actions).
|
||||
StructuralFlagsMask = Background | Reference,
|
||||
};
|
||||
|
||||
class Layer : public WithUserData {
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2021-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2021-2024 Igara Studio S.A.
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -185,8 +185,19 @@ void Playback::handleEnterFrame(const frame_t frameDelta, const bool firstTime)
|
||||
}
|
||||
else {
|
||||
addTag(t, false, forward);
|
||||
if (!firstTime)
|
||||
if (!firstTime) {
|
||||
goToFirstTagFrame(t);
|
||||
// Handle cases where inner tags will jump to different
|
||||
// frames several times recursively (e.g. one reverse
|
||||
// inside other reverse).
|
||||
//
|
||||
// Consideration for tests:
|
||||
// Playback.OnePingPongInsidePingPongReverse
|
||||
// Playback.OneReverseInsidePingPongReverse
|
||||
// Playback.OnePingPongReverseInsideReverse
|
||||
if (frame != m_frame)
|
||||
handleEnterFrame(frameDelta, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -457,10 +468,11 @@ bool Playback::decrementRepeat(const frame_t frameDelta)
|
||||
|
||||
// New frame outside the tag
|
||||
frame_t newFrame;
|
||||
if (rewind) {
|
||||
if (rewind && !m_playing.empty()) {
|
||||
newFrame = firstTagFrame(m_playing.back()->tag);
|
||||
}
|
||||
else {
|
||||
// Note that 'tag' means 'the last tag removed from m_playing'
|
||||
newFrame = (frameDelta * forward < 0 ? tag->fromFrame()-1: tag->toFrame()+1);
|
||||
}
|
||||
|
||||
@ -473,10 +485,100 @@ bool Playback::decrementRepeat(const frame_t frameDelta)
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
if (newFrame < 0)
|
||||
newFrame = m_sprite->lastFrame();
|
||||
else if (newFrame > m_sprite->lastFrame())
|
||||
newFrame = 0;
|
||||
if (newFrame < 0) {
|
||||
// m_playing.empty() should never happen, because the only
|
||||
// way to have "newFrame < 0" is if we have a tag on
|
||||
// m_playing in REVERSE or PING_PONG_REVERSE which frame 0
|
||||
// is contained into that tag.
|
||||
ASSERT(!m_playing.empty());
|
||||
if (m_playing.empty()) {
|
||||
newFrame = m_sprite->lastFrame();
|
||||
}
|
||||
else {
|
||||
// Special cases arise with PING_PONG_REVERSE aniDir and when
|
||||
// the begining of the tag range matches with the first frame of
|
||||
// the sprite.
|
||||
// Consideration for tests inside:
|
||||
// Playback.OnePingPongInsideOther
|
||||
// A A
|
||||
// >-------< >-------<
|
||||
// B B
|
||||
// <---> >---<
|
||||
// 0 1 2 3 4 0 1 2 3 4
|
||||
PlayTag* parentPlaying = m_playing.back().get();
|
||||
// When parentPlaying is PING_PONG_REVERSE
|
||||
// the next frame will be defined according:
|
||||
// 1. The playloop has more repetitions to decrement
|
||||
// --> go to the next frame of the 'tag'
|
||||
// 2. The playloop has no more repetitions to decrement
|
||||
// --> Start all the playloop again.
|
||||
if (parentPlaying->repeat > 1) {
|
||||
if (parentPlaying->tag->aniDir() == AniDir::PING_PONG_REVERSE)
|
||||
parentPlaying->invertForward();
|
||||
--parentPlaying->repeat;
|
||||
newFrame = tag->toFrame() + 1;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (newFrame > m_sprite->lastFrame()) {
|
||||
// If all the tags were played and
|
||||
// the 'tag' range == timeline range and
|
||||
// the 'tag' is PING_PONG_REVERSE -->
|
||||
// The playloop has to start on the last frame of
|
||||
// the timeline, or the first frame of the most nested
|
||||
// tag (on reverse direction).
|
||||
// Consideration for tests:
|
||||
// Playback.WithTagRepetitions
|
||||
// Playback.OnePingPongInsideOther
|
||||
// Playback.OnePingPongInsideOther14, 15, 18 and 19
|
||||
// A <-- last tag removed from 'm_playing', i.e. 'tag'
|
||||
// >-----<
|
||||
// B <-- most nested tag
|
||||
// ***-***
|
||||
// 0 1 2 3
|
||||
if (m_playing.empty() &&
|
||||
tag->aniDir() == AniDir::PING_PONG_REVERSE &&
|
||||
tag->fromFrame() == 0 &&
|
||||
tag->toFrame() == m_sprite->lastFrame()) {
|
||||
m_frame = m_sprite->lastFrame();
|
||||
handleEnterFrame(frameDelta, false);
|
||||
if (m_playing.size() > 1) {
|
||||
m_playing.back()->invertForward();
|
||||
goToFirstTagFrame(m_playing.back()->tag);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 'tag' is contained by other tag and the last frame of each tag
|
||||
// matches in the last frame of the sprite
|
||||
if (!m_playing.empty() &&
|
||||
tag->toFrame() == m_playing.back()->tag->toFrame()) {
|
||||
PlayTag* parentPlaying = m_playing.back().get();
|
||||
// The parentPlaying has no more repetitions to decrement
|
||||
// --> continue to remove the 'parentTag'
|
||||
if (parentPlaying->repeat <= 1)
|
||||
continue;
|
||||
// Consideration for test:
|
||||
// Playback.OnePingPongInsideOther
|
||||
if (parentPlaying->tag->aniDir() == AniDir::PING_PONG ||
|
||||
parentPlaying->tag->aniDir() == AniDir::PING_PONG_REVERSE) {
|
||||
parentPlaying->invertForward();
|
||||
newFrame = tag->fromFrame() - 1;
|
||||
}
|
||||
// Consideration for test:
|
||||
// Playback.OnePingPongInsideForward2
|
||||
else if (parentPlaying->tag->aniDir() == AniDir::FORWARD) {
|
||||
--parentPlaying->repeat;
|
||||
newFrame = parentPlaying->tag->fromFrame();
|
||||
}
|
||||
else
|
||||
newFrame = 0;
|
||||
}
|
||||
else
|
||||
newFrame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
m_frame = newFrame;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2021-2022 Igara Studio S.A.
|
||||
// Copyright (c) 2021-2024 Igara Studio S.A.
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -152,6 +152,47 @@ TEST(Playback, WithTagRepetitions)
|
||||
play = Playback(sprite.get(), 0, Playback::Mode::PlayAll);
|
||||
expect_frames(play, {0,1,2,1,2,3,0,0,0});
|
||||
EXPECT_TRUE(play.isStopped());
|
||||
|
||||
Tag* b = make_tag("B", 0, 3, AniDir::PING_PONG, 2);
|
||||
sprite = make_sprite(4, { b });
|
||||
play = Playback(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0,1,2,3,2,1,0,
|
||||
0,1,2,3,2,1,0,
|
||||
0,1,2,3,2,1,0});
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
|
||||
Tag* c = make_tag("C", 0, 3, AniDir::PING_PONG_REVERSE, 2);
|
||||
sprite = make_sprite(4, { c });
|
||||
play = Playback(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0,1,2,3,
|
||||
3,2,1,0,1,2,3,
|
||||
3,2,1,0,1,2,3});
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
|
||||
Tag* d = make_tag("D", 0, 3, AniDir::PING_PONG_REVERSE, 2);
|
||||
sprite = make_sprite(4, { d });
|
||||
play = Playback(sprite.get(), 1, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {1,0,1,2,3,
|
||||
3,2,1,0,1,2,3,
|
||||
3,2,1,0,1,2,3});
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
|
||||
Tag* e = make_tag("E", 0, 3, AniDir::PING_PONG_REVERSE, 1);
|
||||
sprite = make_sprite(4, { e });
|
||||
play = Playback(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0,
|
||||
3,2,1,0,
|
||||
3,2,1,0,
|
||||
3,2,1,0});
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
|
||||
Tag* f = make_tag("F", 0, 3, AniDir::REVERSE, 2);
|
||||
sprite = make_sprite(4, { f });
|
||||
play = Playback(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0,3,2,1,0,
|
||||
3,2,1,0, 3,2,1,0,
|
||||
3,2,1,0, 3,2,1,0});
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
|
||||
TEST(Playback, LoopTagInfinite)
|
||||
@ -464,39 +505,359 @@ TEST(Playback, PingPongWithInnerReverse)
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
|
||||
// OnePingPongInsideOther series
|
||||
static std::vector<int> goRight(const int a, const int b) {
|
||||
std::vector<int> out;
|
||||
if (a > b)
|
||||
return out;
|
||||
for (int i=a; i<=b ; ++i)
|
||||
out.push_back(i);
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::vector<int> goLeft(const int a, const int b) {
|
||||
std::vector<int> out;
|
||||
if (a > b)
|
||||
return out;
|
||||
for (int i=b; i>=a ; --i)
|
||||
out.push_back(i);
|
||||
return out;
|
||||
}
|
||||
|
||||
static void concat(std::vector<int>& a, const std::vector<int>& b)
|
||||
{
|
||||
for (size_t i=0; i<b.size(); ++i)
|
||||
a.push_back(b[i]);
|
||||
}
|
||||
|
||||
TEST(Playback, OnePingPongInsideOther)
|
||||
{
|
||||
// A
|
||||
// <------->
|
||||
// B
|
||||
// >---<
|
||||
// 0 1 2 3 4
|
||||
// A repeat = 2 ; B repeat = 2
|
||||
//
|
||||
// A A A
|
||||
// *-------* *-------* *-------*
|
||||
// B B B
|
||||
// *---* *---* *---*
|
||||
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
|
||||
const int lastFrame = 4;
|
||||
std::vector<AniDir> A_AniDirs = {AniDir::PING_PONG, AniDir::PING_PONG_REVERSE};
|
||||
std::vector<AniDir> B_AniDirs = {AniDir::PING_PONG, AniDir::PING_PONG_REVERSE};
|
||||
std::vector<int> A_Range = {0,lastFrame};
|
||||
std::vector<std::vector<int>> rangeBs = {{0,2}, {1,3}, {2,4}};
|
||||
std::vector<std::vector<int>> pingPongSeq1 = {{0,1,2,1,0}, {2,1,0,1,2}};
|
||||
std::vector<std::vector<int>> pingPongSeq2 = {{1,2,3,2,1}, {3,2,1,2,3}};
|
||||
std::vector<std::vector<int>> pingPongSeq3 = {{2,3,4,3,2}, {4,3,2,3,4}};
|
||||
std::vector<int> right012 = {0,1,2};
|
||||
|
||||
Tag* tagA = make_tag("A", 0, 4, AniDir::PING_PONG, 2);
|
||||
Tag* tagB = make_tag("B", 1, 3, AniDir::PING_PONG_REVERSE, 3);
|
||||
auto sprite = make_sprite(5, { tagA, tagB });
|
||||
for (auto A_aniDir : A_AniDirs) {
|
||||
for (auto B_aniDir : B_AniDirs) {
|
||||
for (auto B_Range : rangeBs) {
|
||||
std::vector<int> expected;
|
||||
std::vector<int> temp;
|
||||
// A A A
|
||||
// <-------> <-------> <------->
|
||||
// B B B
|
||||
// *---* *---* *---*
|
||||
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
|
||||
if (A_aniDir == doc::AniDir::PING_PONG) {
|
||||
|
||||
// Start
|
||||
temp = goRight(0, B_Range[0]-1);
|
||||
concat(expected, temp);
|
||||
|
||||
// Tag B playback
|
||||
if (B_Range[0] == 0)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[0] : right012);
|
||||
else if (B_Range[0] == 1)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[0] : pingPongSeq2[1]);
|
||||
else if (B_Range[0] == 2)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[0] : pingPongSeq3[1]);
|
||||
|
||||
// Reproduce right side of the tag A
|
||||
temp = goRight(B_Range[1]+1, lastFrame);
|
||||
concat(expected, temp);
|
||||
temp = goLeft(B_Range[1]+1, lastFrame-1);
|
||||
concat(expected, temp);
|
||||
|
||||
// Tag B playback (only if tag B last frame doesn't match with the tag A last frame
|
||||
if (B_Range[1] != A_Range[1]) {
|
||||
if (B_Range[1] == lastFrame - 1)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[1] : pingPongSeq2[0]);
|
||||
else if (B_Range[1] == lastFrame - 2)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[1] : pingPongSeq1[0]);
|
||||
}
|
||||
|
||||
// Reproduce right side of the tag A
|
||||
temp = goLeft(0, B_Range[0]-1);
|
||||
concat(expected, temp);
|
||||
// Sequence end
|
||||
}
|
||||
// A A A
|
||||
// >-------< >-------< >-------<
|
||||
// B B B
|
||||
// *---* *---* *---*
|
||||
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
|
||||
else {
|
||||
|
||||
// Start
|
||||
temp = goRight(0, B_Range[0]-1);
|
||||
concat(expected, temp);
|
||||
|
||||
// Tag B playback
|
||||
if (B_Range[0] == 0)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[0] : right012);
|
||||
else if (B_Range[0] == 1)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[0] : pingPongSeq2[1]);
|
||||
else if (B_Range[0] == 2)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[0] : pingPongSeq3[1]);
|
||||
|
||||
// Reproduce right side of the tag A
|
||||
temp = goRight(B_Range[1]+1, lastFrame);
|
||||
concat(expected, temp);
|
||||
// Sequence end
|
||||
|
||||
// New Start
|
||||
temp = goLeft(B_Range[1]+1, lastFrame);
|
||||
concat(expected, temp);
|
||||
|
||||
// Tag B playback
|
||||
if (B_Range[1] == lastFrame)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[1] : pingPongSeq3[0]);
|
||||
else if (B_Range[1] == lastFrame-1)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[1] : pingPongSeq2[0]);
|
||||
else if (B_Range[1] == lastFrame-2)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[1] : pingPongSeq1[0]);
|
||||
|
||||
// Reproduce left side of the tag A
|
||||
temp = goLeft(0, B_Range[0]-1);
|
||||
concat(expected, temp);
|
||||
temp = goRight(1, B_Range[0]-1);
|
||||
concat(expected, temp);
|
||||
|
||||
// Tag B playback (only if tag B first frame doesn't match with the tag A first frame
|
||||
if (B_Range[0] == 1)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[0] : pingPongSeq2[1]);
|
||||
else if (B_Range[0] == 2)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[0] : pingPongSeq3[1]);
|
||||
|
||||
|
||||
// Reproduce right side of the tag A
|
||||
temp = goRight(B_Range[1]+1, lastFrame);
|
||||
concat(expected, temp);
|
||||
// Sequence end
|
||||
}
|
||||
|
||||
// Test
|
||||
Tag* tagA = make_tag("A", 0, 4, A_aniDir, 2);
|
||||
Tag* tagB = make_tag("B", B_Range[0], B_Range[1], B_aniDir, 2);
|
||||
auto sprite = make_sprite(lastFrame + 1, { tagA, tagB });
|
||||
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
|
||||
expect_frames(play, expected);
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Playback, OnePingPongInsideOther1Repeat)
|
||||
{
|
||||
// A repeat = 1 ; B repeat = 1
|
||||
//
|
||||
// A A A
|
||||
// *-------* *-------* *-------*
|
||||
// B B B
|
||||
// *---* *---* *---*
|
||||
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
|
||||
|
||||
const int lastFrame = 4;
|
||||
std::vector<AniDir> A_AniDirs = {AniDir::PING_PONG, AniDir::PING_PONG_REVERSE};
|
||||
std::vector<AniDir> B_AniDirs = {AniDir::PING_PONG, AniDir::PING_PONG_REVERSE};
|
||||
std::vector<int> A_Range = {0,lastFrame};
|
||||
std::vector<std::vector<int>> rangeBs = {{0,2}, {1,3}, {2,4}};
|
||||
std::vector<std::vector<int>> pingPongSeq1 = {{0,1,2}, {2,1,0}};
|
||||
std::vector<std::vector<int>> pingPongSeq2 = {{1,2,3}, {3,2,1}};
|
||||
std::vector<std::vector<int>> pingPongSeq3 = {{2,3,4}, {4,3,2}};
|
||||
|
||||
for (auto A_aniDir : A_AniDirs) {
|
||||
for (auto B_aniDir : B_AniDirs) {
|
||||
for (auto B_Range : rangeBs) {
|
||||
std::vector<int> expected;
|
||||
std::vector<int> temp;
|
||||
// A A A
|
||||
// <-------> <-------> <------->
|
||||
// B B B
|
||||
// *---* *---* *---*
|
||||
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
|
||||
if (A_aniDir == doc::AniDir::PING_PONG) {
|
||||
|
||||
// Start
|
||||
temp = goRight(0, B_Range[0]-1);
|
||||
concat(expected, temp);
|
||||
// Tag B playback
|
||||
if (B_Range[0] == 0) {
|
||||
temp = {0};
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[0] : temp);
|
||||
}
|
||||
else if (B_Range[0] == 1)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[0] : pingPongSeq2[1]);
|
||||
else if (B_Range[0] == 2)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[0] : pingPongSeq3[1]);
|
||||
// Reproduce right side of the tag A
|
||||
temp = goRight(B_Range[1]+1, lastFrame);
|
||||
concat(expected, temp);
|
||||
// Sequence end
|
||||
|
||||
// Fresh sequence start
|
||||
temp = goRight(0, B_Range[0]-1);
|
||||
concat(expected, temp);
|
||||
// Tag B playback
|
||||
if (B_Range[0] == 0)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[0] : pingPongSeq1[1]);
|
||||
else if (B_Range[0] == 1)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[0] : pingPongSeq2[1]);
|
||||
else if (B_Range[0] == 2)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[0] : pingPongSeq3[1]);
|
||||
// Reproduce right side of the tag A
|
||||
temp = goRight(B_Range[1]+1, lastFrame);
|
||||
concat(expected, temp);
|
||||
// Sequence end
|
||||
|
||||
}
|
||||
// A A A
|
||||
// >-------< >-------< >-------<
|
||||
// B B B
|
||||
// *---* *---* *---*
|
||||
// 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4
|
||||
else {
|
||||
// Start
|
||||
temp = {0};
|
||||
// Tag B playback
|
||||
if (B_Range[0] == 0 && B_aniDir == doc::AniDir::PING_PONG)
|
||||
concat(expected, pingPongSeq1[0]);
|
||||
else
|
||||
concat(expected, temp);
|
||||
// Sequence end
|
||||
|
||||
// Fresh sequence start
|
||||
// Reproduce right side of the tag A
|
||||
temp = goLeft(B_Range[1]+1, lastFrame);
|
||||
concat(expected, temp);
|
||||
|
||||
// Tag B playback
|
||||
if (B_Range[1] == lastFrame)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq3[1] : pingPongSeq3[0]);
|
||||
else if (B_Range[1] == lastFrame-1)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq2[1] : pingPongSeq2[0]);
|
||||
else if (B_Range[1] == lastFrame-2)
|
||||
concat(expected, B_aniDir == doc::AniDir::PING_PONG ? pingPongSeq1[1] : pingPongSeq1[0]);
|
||||
|
||||
// Reproduce left side of the tag A
|
||||
temp = goLeft(0, B_Range[0]-1);
|
||||
concat(expected, temp);
|
||||
// Sequence end
|
||||
}
|
||||
|
||||
// Test
|
||||
Tag* tagA = make_tag("A", 0, 4, A_aniDir, 1);
|
||||
Tag* tagB = make_tag("B", B_Range[0], B_Range[1], B_aniDir, 1);
|
||||
auto sprite = make_sprite(lastFrame + 1, { tagA, tagB });
|
||||
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
|
||||
expect_frames(play, expected);
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Playback, OnePingPongInsideForward)
|
||||
{
|
||||
// A
|
||||
// -------->
|
||||
// B
|
||||
// <--->
|
||||
// 0 1 2 3 4
|
||||
|
||||
Tag* tagA = make_tag("A", 0, 4, AniDir::FORWARD, 2);
|
||||
Tag* tagB = make_tag("B", 2, 4, AniDir::PING_PONG, 2);
|
||||
auto sprite = make_sprite(5, { tagA, tagB });
|
||||
|
||||
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0,1 , 2,3,4,3,2,
|
||||
0,1 , 2,3,4,3,2});
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
|
||||
TEST(Playback, OnePingPongInsideForward2)
|
||||
{
|
||||
// A
|
||||
// -------->
|
||||
// B
|
||||
// <--->
|
||||
// 0 1 2 3 4 5
|
||||
|
||||
Tag* tagA = make_tag("A", 1, 5, AniDir::FORWARD, 2);
|
||||
Tag* tagB = make_tag("B", 3, 5, AniDir::PING_PONG, 2);
|
||||
auto sprite = make_sprite(6, { tagA, tagB });
|
||||
|
||||
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0, 3,2,1,2,3,2,1, 4, 1,2,3,2,1,2,3, 0,
|
||||
0, 3,2,1,2,3,2,1, 4, 1,2,3,2,1,2,3, 0, });
|
||||
expect_frames(play, {0 , 1,2 , 3,4,5,4,3, 1,2 , 3,4,5,4,3,
|
||||
0 , 1,2 , 3,4,5,4,3, 1,2 , 3,4,5,4,3});
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
|
||||
TEST(Playback, OnePingPongInsideOther3)
|
||||
TEST(Playback, OnePingPongInsidePingPongReverse)
|
||||
{
|
||||
// A
|
||||
// <------->
|
||||
// B
|
||||
// >---<
|
||||
// 0 1 2 3 4
|
||||
// A
|
||||
// >-------<
|
||||
// B
|
||||
// <--->
|
||||
// 0 1 2 3 4 5
|
||||
|
||||
Tag* tagA = make_tag("A", 0, 4, AniDir::PING_PONG, 3);
|
||||
Tag* tagB = make_tag("B", 1, 3, AniDir::PING_PONG_REVERSE, 2);
|
||||
auto sprite = make_sprite(5, { tagA, tagB });
|
||||
Tag* tagA = make_tag("A", 1, 5, AniDir::PING_PONG_REVERSE, 2);
|
||||
Tag* tagB = make_tag("B", 3, 5, AniDir::PING_PONG, 2);
|
||||
auto sprite = make_sprite(6, { tagA, tagB });
|
||||
|
||||
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0, 3,2,1,2,3, 4, 1,2,3,2,1, 0, 3,2,1,2,3, 4,
|
||||
0, 3,2,1,2,3, 4, 1,2,3,2,1, 0, 3,2,1,2,3, 4, 0 });
|
||||
expect_frames(play, {0 , 5,4,3,4,5 , 2,1,2 , 3,4,5,4,3,
|
||||
0 , 5,4,3,4,5 , 2,1,2 , 3,4,5,4,3});
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
|
||||
TEST(Playback, OneReverseInsidePingPongReverse)
|
||||
{
|
||||
// A
|
||||
// >-------<
|
||||
// B
|
||||
// <----
|
||||
// 0 1 2 3 4 5
|
||||
|
||||
Tag* tagA = make_tag("A", 1, 5, AniDir::PING_PONG_REVERSE, 2);
|
||||
Tag* tagB = make_tag("B", 3, 5, AniDir::REVERSE, 2);
|
||||
auto sprite = make_sprite(6, { tagA, tagB });
|
||||
|
||||
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0 , 3,4,5,3,4,5 , 2,1,2 , 5,4,3,5,4,3,
|
||||
0 , 3,4,5,3,4,5 , 2,1,2 , 5,4,3,5,4,3});
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
|
||||
TEST(Playback, OnePingPongReverseInsideReverse)
|
||||
{
|
||||
// A
|
||||
// <--------
|
||||
// B
|
||||
// >---<
|
||||
// 0 1 2 3 4 5
|
||||
|
||||
Tag* tagA = make_tag("A", 1, 5, AniDir::REVERSE, 2);
|
||||
Tag* tagB = make_tag("B", 3, 5, AniDir::PING_PONG_REVERSE, 2);
|
||||
auto sprite = make_sprite(6, { tagA, tagB });
|
||||
|
||||
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0 , 3,4,5,4,3 , 2,1, 3,4,5,4,3 , 2,1,
|
||||
0 , 3,4,5,4,3 , 2,1, 3,4,5,4,3 , 2,1});
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
|
||||
@ -536,6 +897,96 @@ TEST(Playback, TwoLoopsInCascadeReverse)
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
|
||||
TEST(Playback, TwoLoopsInCascadeReversePingPongReverse1)
|
||||
{
|
||||
// A
|
||||
// <----
|
||||
// B
|
||||
// >---<
|
||||
// 0 1 2 3 4
|
||||
|
||||
Tag* a = make_tag("A", 1, 3, AniDir::REVERSE, 2);
|
||||
Tag* b = make_tag("B", 2, 4, AniDir::PING_PONG_REVERSE, 2);
|
||||
auto sprite = make_sprite(5, { a, b });
|
||||
|
||||
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0, 3,2,1,3,2,1, 4,3,2,3,4,
|
||||
0, 3,2,1,3,2,1, 4,3,2,3,4, 0 });
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
|
||||
TEST(Playback, TwoLoopsInCascadeReversePingPongReverse2)
|
||||
{
|
||||
// A
|
||||
// <------
|
||||
// B
|
||||
// >---<
|
||||
// 0 1 2 3 4
|
||||
|
||||
Tag* a = make_tag("A", 0, 3, AniDir::REVERSE, 2);
|
||||
Tag* b = make_tag("B", 2, 4, AniDir::PING_PONG_REVERSE, 2);
|
||||
auto sprite = make_sprite(5, { a, b });
|
||||
|
||||
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0, 3,2,1,0, 4,3,2,3,4,
|
||||
3,2,1,0,3,2,1,0, 4,3,2,3,4 });
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
|
||||
TEST(Playback, TwoLoopsInCascadeReversePingPongReverse3)
|
||||
{
|
||||
// A
|
||||
// <--------
|
||||
// B
|
||||
// >---<
|
||||
// 0 1 2 3 4
|
||||
|
||||
Tag* a = make_tag("A", 0, 4, AniDir::REVERSE, 2);
|
||||
Tag* b = make_tag("B", 2, 4, AniDir::PING_PONG_REVERSE, 2);
|
||||
auto sprite = make_sprite(5, { a, b });
|
||||
|
||||
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0, 2,3,4,3,2, 1,0,
|
||||
2,3,4,3,2, 1,0, 2,3,4,3,2, 1,0,});
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
|
||||
TEST(Playback, TwoLoopsInCascadePingPongReverseReverse1)
|
||||
{
|
||||
// A
|
||||
// >-----<
|
||||
// B
|
||||
// <----
|
||||
// 0 1 2 3 4
|
||||
|
||||
Tag* a = make_tag("A", 0, 3, AniDir::PING_PONG_REVERSE, 2);
|
||||
Tag* b = make_tag("B", 2, 4, AniDir::REVERSE, 2);
|
||||
auto sprite = make_sprite(5, { a, b });
|
||||
|
||||
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0, 1,2,3, 4,3,2,4,3,2,
|
||||
3,2,1,0,1,2,3, 4,3,2,4,3,2 });
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
|
||||
TEST(Playback, TwoLoopsInCascadePingPongReverseReverse2)
|
||||
{
|
||||
// A
|
||||
// >---<
|
||||
// B
|
||||
// <----
|
||||
// 0 1 2 3 4
|
||||
|
||||
Tag* a = make_tag("A", 1, 3, AniDir::PING_PONG_REVERSE, 2);
|
||||
Tag* b = make_tag("B", 2, 4, AniDir::REVERSE, 2);
|
||||
auto sprite = make_sprite(5, { a, b });
|
||||
|
||||
Playback play(sprite.get(), 0, Playback::Mode::PlayInLoop);
|
||||
expect_frames(play, {0, 3,2,1,2,3, 4,3,2,4,3,2,
|
||||
0, 3,2,1,2,3, 4,3,2,4,3,2, 0 });
|
||||
EXPECT_FALSE(play.isStopped());
|
||||
}
|
||||
|
||||
TEST(Playback, ThreeLoopsInCascade)
|
||||
{
|
||||
// A
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -198,6 +198,11 @@ Sprite* Sprite::MakeStdTilemapSpriteWithTileset(const ImageSpec& spec,
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Main properties
|
||||
|
||||
bool Sprite::hasPixelRatio() const
|
||||
{
|
||||
return m_pixelRatio != PixelRatio(1, 1);
|
||||
}
|
||||
|
||||
void Sprite::setPixelFormat(PixelFormat format)
|
||||
{
|
||||
m_spec.setColorMode((ColorMode)format);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -93,6 +93,7 @@ namespace doc {
|
||||
PixelFormat pixelFormat() const { return (PixelFormat)m_spec.colorMode(); }
|
||||
ColorMode colorMode() const { return m_spec.colorMode(); }
|
||||
const PixelRatio& pixelRatio() const { return m_pixelRatio; }
|
||||
bool hasPixelRatio() const;
|
||||
gfx::Size size() const { return m_spec.size(); }
|
||||
gfx::Rect bounds() const { return m_spec.bounds(); }
|
||||
int width() const { return m_spec.width(); }
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Code Generator
|
||||
// Copyright (c) 2021-2023 Igara Studio S.A.
|
||||
// Copyright (c) 2021-2024 Igara Studio S.A.
|
||||
// Copyright (c) 2016-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -15,7 +15,7 @@
|
||||
#include "cfg/cfg.h"
|
||||
#include "gen/common.h"
|
||||
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <iostream>
|
||||
@ -27,11 +27,12 @@
|
||||
// All other translations will be considered work-in-progress.
|
||||
#define ENGLISH_ONLY 1
|
||||
|
||||
typedef std::vector<TiXmlElement*> XmlElements;
|
||||
using namespace tinyxml2;
|
||||
using XmlElements = std::vector<XMLElement*>;
|
||||
|
||||
static std::string find_first_id(TiXmlElement* elem)
|
||||
static std::string find_first_id(XMLElement* elem)
|
||||
{
|
||||
TiXmlElement* child = elem->FirstChildElement();
|
||||
XMLElement* child = elem->FirstChildElement();
|
||||
while (child) {
|
||||
const char* id = child->Attribute("id");
|
||||
if (id)
|
||||
@ -46,9 +47,9 @@ static std::string find_first_id(TiXmlElement* elem)
|
||||
return "";
|
||||
}
|
||||
|
||||
static void collect_elements_with_strings(TiXmlElement* elem, XmlElements& elems)
|
||||
static void collect_elements_with_strings(XMLElement* elem, XmlElements& elems)
|
||||
{
|
||||
TiXmlElement* child = elem->FirstChildElement();
|
||||
XMLElement* child = elem->FirstChildElement();
|
||||
while (child) {
|
||||
const char* text = child->Attribute("text");
|
||||
const char* tooltip = child->Attribute("tooltip");
|
||||
@ -111,19 +112,17 @@ public:
|
||||
for (const auto& fn : base::list_files(dir)) {
|
||||
std::string fullFn = base::join_path(dir, fn);
|
||||
base::FileHandle inputFile(base::open_file(fullFn, "rb"));
|
||||
std::unique_ptr<TiXmlDocument> doc(new TiXmlDocument());
|
||||
doc->SetValue(fullFn.c_str());
|
||||
if (!doc->LoadFile(inputFile.get())) {
|
||||
std::cerr << doc->Value() << ":"
|
||||
<< doc->ErrorRow() << ":"
|
||||
<< doc->ErrorCol() << ": "
|
||||
<< "error " << doc->ErrorId() << ": "
|
||||
<< doc->ErrorDesc() << "\n";
|
||||
auto doc = std::make_unique<XMLDocument>();
|
||||
if (doc->LoadFile(inputFile.get()) != XML_SUCCESS) {
|
||||
std::cerr << fullFn << ":"
|
||||
<< doc->ErrorLineNum() << ": "
|
||||
<< "error " << int(doc->ErrorID()) << ": "
|
||||
<< doc->ErrorStr() << "\n";
|
||||
|
||||
throw std::runtime_error("invalid input file");
|
||||
}
|
||||
|
||||
TiXmlHandle handle(doc.get());
|
||||
XMLHandle handle(doc.get());
|
||||
XmlElements widgets;
|
||||
|
||||
const char* warnings = doc->RootElement()->Attribute("i18nwarnings");
|
||||
@ -133,64 +132,63 @@ public:
|
||||
m_prefixId = find_first_id(doc->RootElement());
|
||||
|
||||
collect_elements_with_strings(doc->RootElement(), widgets);
|
||||
for (TiXmlElement* elem : widgets) {
|
||||
checkString(elem, elem->Attribute("text"));
|
||||
checkString(elem, elem->Attribute("tooltip"));
|
||||
for (XMLElement* elem : widgets) {
|
||||
checkString(fullFn, elem, elem->Attribute("text"));
|
||||
checkString(fullFn, elem, elem->Attribute("tooltip"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkStringsOnGuiFile(const std::string& fullFn) {
|
||||
base::FileHandle inputFile(base::open_file(fullFn, "rb"));
|
||||
std::unique_ptr<TiXmlDocument> doc(new TiXmlDocument());
|
||||
doc->SetValue(fullFn.c_str());
|
||||
if (!doc->LoadFile(inputFile.get())) {
|
||||
std::cerr << doc->Value() << ":"
|
||||
<< doc->ErrorRow() << ":"
|
||||
<< doc->ErrorCol() << ": "
|
||||
<< "error " << doc->ErrorId() << ": "
|
||||
<< doc->ErrorDesc() << "\n";
|
||||
auto doc = std::make_unique<XMLDocument>();
|
||||
if (doc->LoadFile(inputFile.get()) != XML_SUCCESS) {
|
||||
std::cerr << fullFn << ":"
|
||||
<< doc->ErrorLineNum() << ": "
|
||||
<< "error " << int(doc->ErrorID()) << ": "
|
||||
<< doc->ErrorStr() << "\n";
|
||||
|
||||
throw std::runtime_error("invalid input file");
|
||||
}
|
||||
|
||||
TiXmlHandle handle(doc.get());
|
||||
XMLHandle handle(doc.get());
|
||||
|
||||
// For each menu
|
||||
TiXmlElement* xmlMenu = handle
|
||||
.FirstChild("gui")
|
||||
.FirstChild("menus")
|
||||
.FirstChild("menu").ToElement();
|
||||
XMLElement* xmlMenu = handle
|
||||
.FirstChildElement("gui")
|
||||
.FirstChildElement("menus")
|
||||
.FirstChildElement("menu").ToElement();
|
||||
while (xmlMenu) {
|
||||
const char* menuId = xmlMenu->Attribute("id");
|
||||
if (menuId) {
|
||||
m_prefixId = menuId;
|
||||
XmlElements menus;
|
||||
collect_elements_with_strings(xmlMenu, menus);
|
||||
for (TiXmlElement* elem : menus)
|
||||
checkString(elem, elem->Attribute("text"));
|
||||
for (XMLElement* elem : menus)
|
||||
checkString(fullFn, elem, elem->Attribute("text"));
|
||||
}
|
||||
xmlMenu = xmlMenu->NextSiblingElement();
|
||||
}
|
||||
|
||||
// For each tool
|
||||
m_prefixId = "tools";
|
||||
TiXmlElement* xmlGroup = handle
|
||||
.FirstChild("gui")
|
||||
.FirstChild("tools")
|
||||
.FirstChild("group").ToElement();
|
||||
XMLElement* xmlGroup = handle
|
||||
.FirstChildElement("gui")
|
||||
.FirstChildElement("tools")
|
||||
.FirstChildElement("group").ToElement();
|
||||
while (xmlGroup) {
|
||||
XmlElements tools;
|
||||
collect_elements_with_strings(xmlGroup, tools);
|
||||
for (TiXmlElement* elem : tools) {
|
||||
checkString(elem, elem->Attribute("text"));
|
||||
checkString(elem, elem->Attribute("tooltip"));
|
||||
for (XMLElement* elem : tools) {
|
||||
checkString(fullFn, elem, elem->Attribute("text"));
|
||||
checkString(fullFn, elem, elem->Attribute("tooltip"));
|
||||
}
|
||||
xmlGroup = xmlGroup->NextSiblingElement();
|
||||
}
|
||||
}
|
||||
|
||||
void checkString(TiXmlElement* elem, const char* text) {
|
||||
void checkString(const std::string& filename,
|
||||
XMLElement* elem, const char* text) {
|
||||
if (!text)
|
||||
return; // Do nothing
|
||||
else if (text[0] == '@') {
|
||||
@ -212,9 +210,8 @@ public:
|
||||
const char* translated =
|
||||
cfg->getValue(section.c_str(), var.c_str(), nullptr);
|
||||
if (!translated || translated[0] == 0) {
|
||||
std::cerr << elem->GetDocument()->Value() << ":"
|
||||
<< elem->Row() << ":"
|
||||
<< elem->Column() << ": "
|
||||
std::cerr << filename << ":"
|
||||
<< elem->GetLineNum() << ": "
|
||||
<< "warning: <" << lang
|
||||
<< "> translation for a string ID wasn't found '"
|
||||
<< text << "' (" << section << "." << var << ")\n";
|
||||
@ -224,9 +221,8 @@ public:
|
||||
else if (text[0] != '!' &&
|
||||
has_alpha_char(text) &&
|
||||
!is_email(text)) {
|
||||
std::cerr << elem->GetDocument()->Value() << ":"
|
||||
<< elem->Row() << ":"
|
||||
<< elem->Column() << ": "
|
||||
std::cerr << filename << ":"
|
||||
<< elem->GetLineNum() << ": "
|
||||
<< "warning: raw string found '"
|
||||
<< text << "'\n";
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Code Generator
|
||||
// Copyright (c) 2021 Igara Studio S.A.
|
||||
// Copyright (c) 2021-2024 Igara Studio S.A.
|
||||
// Copyright (c) 2014-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -15,12 +15,13 @@
|
||||
#include "gen/strings_class.h"
|
||||
#include "gen/theme_class.h"
|
||||
#include "gen/ui_class.h"
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
typedef base::ProgramOptions PO;
|
||||
using PO = base::ProgramOptions;
|
||||
using namespace tinyxml2;
|
||||
|
||||
static void run(int argc, const char* argv[])
|
||||
{
|
||||
@ -38,21 +39,18 @@ static void run(int argc, const char* argv[])
|
||||
po.parse(argc, argv);
|
||||
|
||||
// Try to load the XML file
|
||||
std::unique_ptr<TiXmlDocument> doc;
|
||||
std::unique_ptr<XMLDocument> doc;
|
||||
|
||||
std::string inputFilename = po.value_of(inputOpt);
|
||||
if (!inputFilename.empty() &&
|
||||
base::get_file_extension(inputFilename) == "xml") {
|
||||
base::FileHandle inputFile(base::open_file(inputFilename, "rb"));
|
||||
doc.reset(new TiXmlDocument);
|
||||
doc->SetValue(inputFilename.c_str());
|
||||
if (!doc->LoadFile(inputFile.get())) {
|
||||
std::cerr << doc->Value() << ":"
|
||||
<< doc->ErrorRow() << ":"
|
||||
<< doc->ErrorCol() << ": "
|
||||
<< "error " << doc->ErrorId() << ": "
|
||||
<< doc->ErrorDesc() << "\n";
|
||||
|
||||
doc = std::make_unique<XMLDocument>();
|
||||
if (doc->LoadFile(inputFile.get()) != XML_SUCCESS) {
|
||||
std::cerr << inputFilename << ":"
|
||||
<< doc->ErrorLineNum() << ": "
|
||||
<< "error " << int(doc->ErrorID()) << ": "
|
||||
<< doc->ErrorStr() << "\n";
|
||||
throw std::runtime_error("invalid input file");
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Code Generator
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2014-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -17,9 +17,11 @@
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
typedef std::vector<TiXmlElement*> XmlElements;
|
||||
using namespace tinyxml2;
|
||||
|
||||
static void print_pref_class_def(TiXmlElement* elem, const std::string& className, const char* section, int indentSpaces)
|
||||
typedef std::vector<XMLElement*> XmlElements;
|
||||
|
||||
static void print_pref_class_def(XMLElement* elem, const std::string& className, const char* section, int indentSpaces)
|
||||
{
|
||||
std::string indent(indentSpaces, ' ');
|
||||
std::cout
|
||||
@ -39,7 +41,7 @@ static void print_pref_class_def(TiXmlElement* elem, const std::string& classNam
|
||||
<< indent << " Section* section(const char* id) override;\n"
|
||||
<< indent << " OptionBase* option(const char* id) override;\n";
|
||||
|
||||
TiXmlElement* child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL);
|
||||
XMLElement* child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): nullptr);
|
||||
while (child) {
|
||||
if (child->Value()) {
|
||||
std::string name = child->Value();
|
||||
@ -68,7 +70,7 @@ static void print_pref_class_def(TiXmlElement* elem, const std::string& classNam
|
||||
<< indent << "};\n";
|
||||
}
|
||||
|
||||
static void print_pref_class_impl(TiXmlElement* elem, const std::string& prefix, const std::string& className, const char* section)
|
||||
static void print_pref_class_impl(XMLElement* elem, const std::string& prefix, const std::string& className, const char* section)
|
||||
{
|
||||
std::cout
|
||||
<< "\n"
|
||||
@ -79,7 +81,7 @@ static void print_pref_class_impl(TiXmlElement* elem, const std::string& prefix,
|
||||
else
|
||||
std::cout << " : Section(name)\n";
|
||||
|
||||
TiXmlElement* child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL);
|
||||
XMLElement* child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): nullptr);
|
||||
while (child) {
|
||||
if (child->Value()) {
|
||||
std::string name = child->Value();
|
||||
@ -284,7 +286,7 @@ static void print_pref_class_impl(TiXmlElement* elem, const std::string& prefix,
|
||||
}
|
||||
}
|
||||
|
||||
void gen_pref_header(TiXmlDocument* doc, const std::string& inputFn)
|
||||
void gen_pref_header(XMLDocument* doc, const std::string& inputFn)
|
||||
{
|
||||
std::cout
|
||||
<< "// Don't modify, generated file from " << inputFn << "\n"
|
||||
@ -300,18 +302,18 @@ void gen_pref_header(TiXmlDocument* doc, const std::string& inputFn)
|
||||
<< "namespace app {\n"
|
||||
<< "namespace gen {\n";
|
||||
|
||||
TiXmlHandle handle(doc);
|
||||
TiXmlElement* elem = handle
|
||||
.FirstChild("preferences")
|
||||
.FirstChild("types")
|
||||
.FirstChild("enum").ToElement();
|
||||
XMLHandle handle(doc);
|
||||
XMLElement* elem = handle
|
||||
.FirstChildElement("preferences")
|
||||
.FirstChildElement("types")
|
||||
.FirstChildElement("enum").ToElement();
|
||||
while (elem) {
|
||||
if (!elem->Attribute("id")) throw std::runtime_error("missing 'id' attr in <enum>");
|
||||
std::cout
|
||||
<< "\n"
|
||||
<< " enum class " << elem->Attribute("id") << " {\n";
|
||||
|
||||
TiXmlElement* child = elem->FirstChildElement("value");
|
||||
XMLElement* child = elem->FirstChildElement("value");
|
||||
while (child) {
|
||||
if (!child->Attribute("id")) throw std::runtime_error("missing 'id' attr in <value>");
|
||||
if (!child->Attribute("value")) throw std::runtime_error("missing 'value' attr in <value>");
|
||||
@ -328,20 +330,20 @@ void gen_pref_header(TiXmlDocument* doc, const std::string& inputFn)
|
||||
}
|
||||
|
||||
elem = handle
|
||||
.FirstChild("preferences")
|
||||
.FirstChild("global").ToElement();
|
||||
.FirstChildElement("preferences")
|
||||
.FirstChildElement("global").ToElement();
|
||||
if (elem)
|
||||
print_pref_class_def(elem, "GlobalPref", NULL, 2);
|
||||
|
||||
elem = handle
|
||||
.FirstChild("preferences")
|
||||
.FirstChild("tool").ToElement();
|
||||
.FirstChildElement("preferences")
|
||||
.FirstChildElement("tool").ToElement();
|
||||
if (elem)
|
||||
print_pref_class_def(elem, "ToolPref", NULL, 2);
|
||||
|
||||
elem = handle
|
||||
.FirstChild("preferences")
|
||||
.FirstChild("document").ToElement();
|
||||
.FirstChildElement("preferences")
|
||||
.FirstChildElement("document").ToElement();
|
||||
if (elem)
|
||||
print_pref_class_def(elem, "DocPref", NULL, 2);
|
||||
|
||||
@ -353,7 +355,7 @@ void gen_pref_header(TiXmlDocument* doc, const std::string& inputFn)
|
||||
<< "#endif\n";
|
||||
}
|
||||
|
||||
void gen_pref_impl(TiXmlDocument* doc, const std::string& inputFn)
|
||||
void gen_pref_impl(XMLDocument* doc, const std::string& inputFn)
|
||||
{
|
||||
std::cout
|
||||
<< "// Don't modify, generated file from " << inputFn << "\n"
|
||||
@ -370,22 +372,22 @@ void gen_pref_impl(TiXmlDocument* doc, const std::string& inputFn)
|
||||
<< "namespace app {\n"
|
||||
<< "namespace gen {\n";
|
||||
|
||||
TiXmlHandle handle(doc);
|
||||
TiXmlElement* elem = handle
|
||||
.FirstChild("preferences")
|
||||
.FirstChild("global").ToElement();
|
||||
XMLHandle handle(doc);
|
||||
XMLElement* elem = handle
|
||||
.FirstChildElement("preferences")
|
||||
.FirstChildElement("global").ToElement();
|
||||
if (elem)
|
||||
print_pref_class_impl(elem, "", "GlobalPref", NULL);
|
||||
|
||||
elem = handle
|
||||
.FirstChild("preferences")
|
||||
.FirstChild("tool").ToElement();
|
||||
.FirstChildElement("preferences")
|
||||
.FirstChildElement("tool").ToElement();
|
||||
if (elem)
|
||||
print_pref_class_impl(elem, "", "ToolPref", NULL);
|
||||
|
||||
elem = handle
|
||||
.FirstChild("preferences")
|
||||
.FirstChild("document").ToElement();
|
||||
.FirstChildElement("preferences")
|
||||
.FirstChildElement("document").ToElement();
|
||||
if (elem)
|
||||
print_pref_class_impl(elem, "", "DocPref", NULL);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite Code Generator
|
||||
// Copyright (c) 2024 Igara Studio S.A.
|
||||
// Copyright (c) 2014 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -9,9 +10,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
void gen_pref_header(TiXmlDocument* doc, const std::string& inputFn);
|
||||
void gen_pref_impl(TiXmlDocument* doc, const std::string& inputFn);
|
||||
void gen_pref_header(tinyxml2::XMLDocument* doc, const std::string& inputFn);
|
||||
void gen_pref_impl(tinyxml2::XMLDocument* doc, const std::string& inputFn);
|
||||
|
||||
#endif
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite Code Generator
|
||||
// Copyright (c) 2024 Igara Studio S.A.
|
||||
// Copyright (c) 2015-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -13,7 +14,9 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
void gen_theme_class(TiXmlDocument* doc, const std::string& inputFn)
|
||||
using namespace tinyxml2;
|
||||
|
||||
void gen_theme_class(XMLDocument* doc, const std::string& inputFn)
|
||||
{
|
||||
std::vector<std::string> dimensions;
|
||||
std::vector<std::string> colors;
|
||||
@ -21,11 +24,11 @@ void gen_theme_class(TiXmlDocument* doc, const std::string& inputFn)
|
||||
std::vector<std::string> cursors;
|
||||
std::vector<std::string> styles;
|
||||
|
||||
TiXmlHandle handle(doc);
|
||||
TiXmlElement* elem = handle
|
||||
.FirstChild("theme")
|
||||
.FirstChild("dimensions")
|
||||
.FirstChild("dim").ToElement();
|
||||
XMLHandle handle(doc);
|
||||
XMLElement* elem = handle
|
||||
.FirstChildElement("theme")
|
||||
.FirstChildElement("dimensions")
|
||||
.FirstChildElement("dim").ToElement();
|
||||
while (elem) {
|
||||
const char* id = elem->Attribute("id");
|
||||
dimensions.push_back(id);
|
||||
@ -33,9 +36,9 @@ void gen_theme_class(TiXmlDocument* doc, const std::string& inputFn)
|
||||
}
|
||||
|
||||
elem = handle
|
||||
.FirstChild("theme")
|
||||
.FirstChild("colors")
|
||||
.FirstChild("color").ToElement();
|
||||
.FirstChildElement("theme")
|
||||
.FirstChildElement("colors")
|
||||
.FirstChildElement("color").ToElement();
|
||||
while (elem) {
|
||||
const char* id = elem->Attribute("id");
|
||||
colors.push_back(id);
|
||||
@ -43,9 +46,9 @@ void gen_theme_class(TiXmlDocument* doc, const std::string& inputFn)
|
||||
}
|
||||
|
||||
elem = handle
|
||||
.FirstChild("theme")
|
||||
.FirstChild("parts")
|
||||
.FirstChild("part").ToElement();
|
||||
.FirstChildElement("theme")
|
||||
.FirstChildElement("parts")
|
||||
.FirstChildElement("part").ToElement();
|
||||
while (elem) {
|
||||
const char* id = elem->Attribute("id");
|
||||
if (std::strncmp(id, "cursor_", 7) == 0) {
|
||||
@ -57,9 +60,9 @@ void gen_theme_class(TiXmlDocument* doc, const std::string& inputFn)
|
||||
}
|
||||
|
||||
elem = handle
|
||||
.FirstChild("theme")
|
||||
.FirstChild("styles")
|
||||
.FirstChild("style").ToElement();
|
||||
.FirstChildElement("theme")
|
||||
.FirstChildElement("styles")
|
||||
.FirstChildElement("style").ToElement();
|
||||
while (elem) {
|
||||
const char* id = elem->Attribute("id");
|
||||
styles.push_back(id);
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite Code Generator
|
||||
// Copyright (c) 2024 Igara Studio S.A.
|
||||
// Copyright (c) 2015-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -9,8 +10,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
void gen_theme_class(TiXmlDocument* doc, const std::string& inputFn);
|
||||
void gen_theme_class(tinyxml2::XMLDocument* doc, const std::string& inputFn);
|
||||
|
||||
#endif
|
||||
|
@ -17,7 +17,8 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
typedef std::vector<TiXmlElement*> XmlElements;
|
||||
using namespace tinyxml2;
|
||||
using XmlElements = std::vector<XMLElement*>;
|
||||
|
||||
namespace {
|
||||
|
||||
@ -36,15 +37,15 @@ struct Item {
|
||||
|
||||
}
|
||||
|
||||
static TiXmlElement* find_element_by_id(TiXmlElement* elem, const std::string& thisId)
|
||||
static XMLElement* find_element_by_id(XMLElement* elem, const std::string& thisId)
|
||||
{
|
||||
const char* id = elem->Attribute("id");
|
||||
if (id && id == thisId)
|
||||
return elem;
|
||||
|
||||
TiXmlElement* child = elem->FirstChildElement();
|
||||
XMLElement* child = elem->FirstChildElement();
|
||||
while (child) {
|
||||
TiXmlElement* match = find_element_by_id(child, thisId);
|
||||
XMLElement* match = find_element_by_id(child, thisId);
|
||||
if (match)
|
||||
return match;
|
||||
|
||||
@ -54,9 +55,9 @@ static TiXmlElement* find_element_by_id(TiXmlElement* elem, const std::string& t
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void collect_widgets_with_ids(TiXmlElement* elem, XmlElements& widgets)
|
||||
static void collect_widgets_with_ids(XMLElement* elem, XmlElements& widgets)
|
||||
{
|
||||
TiXmlElement* child = elem->FirstChildElement();
|
||||
XMLElement* child = elem->FirstChildElement();
|
||||
while (child) {
|
||||
const char* id = child->Attribute("id");
|
||||
if (id)
|
||||
@ -66,7 +67,7 @@ static void collect_widgets_with_ids(TiXmlElement* elem, XmlElements& widgets)
|
||||
}
|
||||
}
|
||||
|
||||
static Item convert_to_item(TiXmlElement* elem)
|
||||
static Item convert_to_item(XMLElement* elem)
|
||||
{
|
||||
static std::string parent;
|
||||
const std::string name = elem->Value();
|
||||
@ -182,7 +183,7 @@ static Item convert_to_item(TiXmlElement* elem)
|
||||
throw base::Exception("Unknown widget name: " + name);
|
||||
}
|
||||
|
||||
void gen_ui_class(TiXmlDocument* doc,
|
||||
void gen_ui_class(XMLDocument* doc,
|
||||
const std::string& inputFn,
|
||||
const std::string& widgetId)
|
||||
{
|
||||
@ -190,8 +191,8 @@ void gen_ui_class(TiXmlDocument* doc,
|
||||
<< "// Don't modify, generated file from " << inputFn << "\n"
|
||||
<< "\n";
|
||||
|
||||
TiXmlHandle handle(doc);
|
||||
TiXmlElement* elem = handle.FirstChild("gui").ToElement();
|
||||
XMLHandle handle(doc);
|
||||
XMLElement* elem = handle.FirstChildElement("gui").ToElement();
|
||||
elem = find_element_by_id(elem, widgetId);
|
||||
if (!elem) {
|
||||
std::cout << "#error Widget not found: " << widgetId << "\n";
|
||||
@ -202,7 +203,7 @@ void gen_ui_class(TiXmlDocument* doc,
|
||||
{
|
||||
XmlElements xmlWidgets;
|
||||
collect_widgets_with_ids(elem, xmlWidgets);
|
||||
for (TiXmlElement* elem : xmlWidgets) {
|
||||
for (XMLElement* elem : xmlWidgets) {
|
||||
const char* id = elem->Attribute("id");
|
||||
if (!id)
|
||||
continue;
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite Code Generator
|
||||
// Copyright (c) 2024 Igara Studio S.A.
|
||||
// Copyright (c) 2014-2016 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -9,9 +10,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
|
||||
void gen_ui_class(TiXmlDocument* doc,
|
||||
void gen_ui_class(tinyxml2::XMLDocument* doc,
|
||||
const std::string& inputFn,
|
||||
const std::string& widgetId);
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
# ASEPRITE
|
||||
# Copyright (C) 2020-2021 Igara Studio S.A.
|
||||
# Copyright (C) 2020-2024 Igara Studio S.A.
|
||||
# Copyright (C) 2001-2017 David Capello
|
||||
|
||||
set(UPDATER_LIB_SOURCES
|
||||
check_update.cpp
|
||||
user_agent.cpp)
|
||||
# By default the updater-lib will contain only the functions related
|
||||
# the user agent string.
|
||||
add_library(updater-lib user_agent.cpp)
|
||||
target_link_libraries(updater-lib laf-base ver-lib)
|
||||
|
||||
add_library(updater-lib ${UPDATER_LIB_SOURCES})
|
||||
|
||||
target_link_libraries(updater-lib
|
||||
laf-base
|
||||
net-lib
|
||||
ver-lib
|
||||
${TINYXML_LIBRARY})
|
||||
# Only when ENABLE_UPDATER is ON we'll enable the "check for update"
|
||||
# portion of the library.
|
||||
if(ENABLE_UPDATER)
|
||||
target_sources(updater-lib PRIVATE check_update.cpp)
|
||||
target_link_libraries(updater-lib net-lib ${TINYXML_LIBRARY})
|
||||
endif()
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -16,7 +16,7 @@
|
||||
#include "net/http_headers.h"
|
||||
#include "net/http_request.h"
|
||||
#include "net/http_response.h"
|
||||
#include "tinyxml.h"
|
||||
#include "tinyxml2.h"
|
||||
#include "updater/user_agent.h"
|
||||
#include "ver/info.h"
|
||||
|
||||
@ -26,6 +26,8 @@
|
||||
|
||||
namespace updater {
|
||||
|
||||
using namespace tinyxml2;
|
||||
|
||||
CheckUpdateResponse::CheckUpdateResponse()
|
||||
: m_type(Unknown)
|
||||
, m_waitDays(0.0)
|
||||
@ -44,11 +46,11 @@ CheckUpdateResponse::CheckUpdateResponse(const std::string& responseBody)
|
||||
: m_type(Unknown)
|
||||
, m_waitDays(0.0)
|
||||
{
|
||||
TiXmlDocument doc;
|
||||
XMLDocument doc;
|
||||
doc.Parse(responseBody.c_str());
|
||||
|
||||
TiXmlHandle handle(&doc);
|
||||
TiXmlElement* xmlUpdate = handle.FirstChild("update").ToElement();
|
||||
XMLHandle handle(&doc);
|
||||
XMLElement* xmlUpdate = handle.FirstChildElement("update").ToElement();
|
||||
if (!xmlUpdate) {
|
||||
// TODO show error?
|
||||
return;
|
||||
|
@ -19,45 +19,42 @@
|
||||
|
||||
namespace updater {
|
||||
|
||||
std::string getUserAgent()
|
||||
std::string getFullOSString()
|
||||
{
|
||||
base::Platform p = base::get_platform();
|
||||
std::stringstream userAgent;
|
||||
|
||||
// App name and version
|
||||
userAgent << get_app_name() << "/" << get_app_version() << " (";
|
||||
std::stringstream os;
|
||||
|
||||
#if LAF_WINDOWS
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Windows
|
||||
|
||||
userAgent << "Windows";
|
||||
os << "Windows";
|
||||
switch (p.windowsType) {
|
||||
case base::Platform::WindowsType::Server:
|
||||
userAgent << " Server";
|
||||
os << " Server";
|
||||
break;
|
||||
case base::Platform::WindowsType::NT:
|
||||
userAgent << " NT";
|
||||
os << " NT";
|
||||
break;
|
||||
}
|
||||
userAgent << " " << p.osVer.str();
|
||||
os << " " << p.osVer.str();
|
||||
|
||||
if (p.servicePack.major() > 0)
|
||||
userAgent << " SP" << p.servicePack.major();
|
||||
os << " SP" << p.servicePack.major();
|
||||
|
||||
if (p.isWow64)
|
||||
userAgent << "; WOW64";
|
||||
os << "; WOW64";
|
||||
|
||||
if (p.wineVer)
|
||||
userAgent << "; Wine " << p.wineVer;
|
||||
os << "; Wine " << p.wineVer;
|
||||
|
||||
#elif LAF_MACOS
|
||||
|
||||
userAgent << "macOS "
|
||||
<< p.osVer.major() << "."
|
||||
<< p.osVer.minor() << "."
|
||||
<< p.osVer.patch();
|
||||
os << "macOS "
|
||||
<< p.osVer.major() << "."
|
||||
<< p.osVer.minor() << "."
|
||||
<< p.osVer.patch();
|
||||
|
||||
#else
|
||||
|
||||
@ -65,14 +62,23 @@ std::string getUserAgent()
|
||||
// Unix like
|
||||
|
||||
if (!p.distroName.empty()) {
|
||||
userAgent << p.distroName;
|
||||
os << p.distroName;
|
||||
if (!p.distroVer.empty())
|
||||
userAgent << " " << p.distroVer;
|
||||
os << " " << p.distroVer;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
userAgent << ")";
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string getUserAgent()
|
||||
{
|
||||
std::stringstream userAgent;
|
||||
|
||||
// App name and version
|
||||
userAgent << get_app_name() << "/" << get_app_version()
|
||||
<< " (" << getFullOSString() << ")";
|
||||
return userAgent.str();
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2015 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -12,6 +13,7 @@
|
||||
|
||||
namespace updater {
|
||||
|
||||
std::string getFullOSString();
|
||||
std::string getUserAgent();
|
||||
|
||||
} // namespace updater
|
||||
|
@ -8,21 +8,29 @@ local sep = fs.pathSeparator
|
||||
|
||||
assert('' == fs.filePath('first.png'))
|
||||
assert('path' == fs.filePath('path/second.png'))
|
||||
assert('C:\\path' == fs.filePath('C:\\path\\third.png'))
|
||||
if app.os.windows then
|
||||
assert('C:\\path' == fs.filePath('C:\\path\\third.png'))
|
||||
end
|
||||
|
||||
assert('first.png' == fs.fileName('first.png'))
|
||||
assert('second.png' == fs.fileName('path/second.png'))
|
||||
assert('third.png' == fs.fileName('C:\\path\\third.png'))
|
||||
if app.os.windows then
|
||||
assert('third.png' == fs.fileName('C:\\path\\third.png'))
|
||||
end
|
||||
|
||||
assert('png' == fs.fileExtension('path/file.png'))
|
||||
|
||||
assert('first' == fs.fileTitle('first.png'))
|
||||
assert('second' == fs.fileTitle('path/second.png'))
|
||||
assert('third' == fs.fileTitle('C:\\path\\third.png'))
|
||||
if app.os.windows then
|
||||
assert('third' == fs.fileTitle('C:\\path\\third.png'))
|
||||
end
|
||||
|
||||
assert('first' == fs.filePathAndTitle('first.png'))
|
||||
assert('path/second' == fs.filePathAndTitle('path/second.png'))
|
||||
assert('C:\\path\\third' == fs.filePathAndTitle('C:\\path\\third.png'))
|
||||
if app.os.windows then
|
||||
assert('C:\\path\\third' == fs.filePathAndTitle('C:\\path\\third.png'))
|
||||
end
|
||||
|
||||
assert('hi/bye' == fs.joinPath('hi/', 'bye'))
|
||||
assert('hi/bye' .. sep .. 'smth.png' == fs.joinPath('hi/', 'bye', 'smth.png'))
|
||||
|
@ -1,4 +1,4 @@
|
||||
-- Copyright (C) 2019 Igara Studio S.A.
|
||||
-- Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
--
|
||||
-- This file is released under the terms of the MIT license.
|
||||
-- Read LICENSE.txt for more information.
|
||||
@ -51,6 +51,7 @@ do
|
||||
assert(b.patternOrigin.y == 0)
|
||||
end
|
||||
|
||||
-- Image brush
|
||||
do
|
||||
local rgba = app.pixelColor.rgba
|
||||
local r = rgba(255, 0, 0)
|
||||
@ -71,4 +72,175 @@ do
|
||||
|
||||
brush:setBgColor(b)
|
||||
expect_img(brush.image, { b, g, g, b })
|
||||
|
||||
-- Test copy image brushes
|
||||
local brush2 = Brush(brush)
|
||||
expect_img(brush2.image, { b, g, g, b })
|
||||
brush2:setFgColor(r)
|
||||
expect_img(brush2.image, { b, r, r, b })
|
||||
brush2:setBgColor(r)
|
||||
expect_img(brush2.image, { r, r, r, r })
|
||||
expect_img(brush.image, { b, g, g, b }) -- First brush wasn't modified
|
||||
end
|
||||
|
||||
-- Tests with Image Brushes
|
||||
-- Brush in a certain pixel format used on different sprites of
|
||||
-- all available pixel formats.
|
||||
do
|
||||
-- RGB sprite
|
||||
local sprRGB = Sprite(2, 2, ColorMode.RGB)
|
||||
local cel = sprRGB.cels[1]
|
||||
expect_img(cel.image, { 0, 0,
|
||||
0, 0})
|
||||
local pal = Palette(4)
|
||||
pal:setColor(1, Color{ r=255, g=0, b=0, a=128 })
|
||||
pal:setColor(2, Color{ r=0, g=255, b=0, a=128 })
|
||||
pal:setColor(3, Color{ r=0, g=0, b=255, a=128 })
|
||||
sprRGB:setPalette(pal)
|
||||
|
||||
-- Test Sprite RGB with RGB brush
|
||||
local brushImg = Image(2, 2, ColorMode.RGB)
|
||||
array_to_pixels({ pal:getColor(1), pal:getColor(2),
|
||||
pal:getColor(3), pal:getColor(0) }, brushImg)
|
||||
local bruRGB = Brush { image=brushImg }
|
||||
|
||||
app.useTool{ tool=pencil, brush=bruRGB, points={ Point(1, 1) } }
|
||||
expect_img(cel.image,
|
||||
{ pal:getColor(1).rgbaPixel, pal:getColor(2).rgbaPixel,
|
||||
pal:getColor(3).rgbaPixel, pal:getColor(0).rgbaPixel })
|
||||
app.undo()
|
||||
|
||||
-- Test Sprite RGB with INDEXED brush
|
||||
local brushImg = Image(2, 2, ColorMode.INDEXED)
|
||||
array_to_pixels({ 1, 2,
|
||||
3, 0 }, brushImg)
|
||||
local bruINDEXED = Brush { image=brushImg }
|
||||
|
||||
app.useTool{ tool=pencil, brush=bruINDEXED, points={ Point(1, 1) } }
|
||||
expect_img(cel.image,
|
||||
{ pal:getColor(1).rgbaPixel, pal:getColor(2).rgbaPixel,
|
||||
pal:getColor(3).rgbaPixel, 0 })
|
||||
app.undo()
|
||||
|
||||
-- Test Sprite RGB with GRAYSCALE brush
|
||||
local brushImg = Image(2, 2, ColorMode.GRAYSCALE)
|
||||
array_to_pixels({ Color{ gray=255, alpha=128 }, Color{ gray=128, alpha=128 },
|
||||
Color{ gray=64, alpha=255 }, Color{ gray=0, alpha=255 } }, brushImg)
|
||||
local bruGRAYSCALE = Brush { image=brushImg }
|
||||
|
||||
app.useTool{ tool=pencil, brush=bruGRAYSCALE, points={ Point(1, 1) } }
|
||||
expect_img(cel.image,
|
||||
{ Color{ gray=255, alpha=128 }.rgbaPixel, Color{ gray=128, alpha=128 }.rgbaPixel,
|
||||
Color{ gray=64, alpha=255 }.rgbaPixel, Color{ gray=0, alpha=255 }.rgbaPixel })
|
||||
|
||||
-- -- -- -- -- -- --
|
||||
-- INDEXED sprite
|
||||
local sprINDEXED = Sprite(2, 2, ColorMode.INDEXED)
|
||||
local cel = sprINDEXED.cels[1]
|
||||
expect_img(cel.image, { 0, 0,
|
||||
0, 0 })
|
||||
local pal = Palette(4)
|
||||
pal:setColor(1, Color{ r=255, g=0, b=0, a=128 })
|
||||
pal:setColor(2, Color{ r=0, g=255, b=0, a=128 })
|
||||
pal:setColor(3, Color{ r=0, g=0, b=255, a=128 })
|
||||
sprINDEXED:setPalette(pal)
|
||||
|
||||
-- Test Sprite INDEXED with RGB brush
|
||||
local brushImg = Image(2, 2, ColorMode.RGB)
|
||||
array_to_pixels({ pal:getColor(1), pal:getColor(2),
|
||||
pal:getColor(3), app.pixelColor.rgba(0, 0, 0, 0) }, brushImg)
|
||||
local bruRGB = Brush { image=brushImg }
|
||||
|
||||
app.useTool{ tool=pencil, brush=bruRGB, points={ Point(1, 1) } }
|
||||
expect_img(cel.image,
|
||||
{ 1, 2,
|
||||
3, 3 })
|
||||
app.undo()
|
||||
|
||||
-- Test Sprite INDEXED with INDEXED brush
|
||||
local brushImg = Image(2, 2, ColorMode.INDEXED)
|
||||
array_to_pixels({ 1, 2,
|
||||
3, 0 }, brushImg)
|
||||
local bruINDEXED = Brush { image=brushImg }
|
||||
|
||||
app.useTool{ tool=pencil, brush=bruINDEXED, points={ Point(1, 1) } }
|
||||
expect_img(cel.image,
|
||||
{ 1, 2,
|
||||
3, 0 })
|
||||
app.undo()
|
||||
|
||||
-- Test Sprite INDEXED with INDEXED brush
|
||||
-- (INDEXED brush with one out of bounds index)
|
||||
local brushImg = Image(2, 2, ColorMode.INDEXED)
|
||||
array_to_pixels({ 1, 5,
|
||||
3, 0 }, brushImg)
|
||||
local bruINDEXED = Brush { image=brushImg }
|
||||
|
||||
app.useTool{ tool=pencil, brush=bruINDEXED, points={ Point(1, 1) } }
|
||||
expect_img(cel.image,
|
||||
{ 1, 3,
|
||||
3, 0 })
|
||||
app.undo()
|
||||
|
||||
-- Test Sprite INDEXED with GRAYSCALE brush
|
||||
local brushImg = Image(2, 2, ColorMode.GRAYSCALE)
|
||||
array_to_pixels({ Color{ gray=255, alpha=128 }, Color{ gray=128, alpha=128 },
|
||||
Color{ gray=64, alpha=255 }, Color{ gray=0, alpha=255 } }, brushImg)
|
||||
local bruGRAYSCALE = Brush { image=brushImg }
|
||||
|
||||
app.useTool{ tool=pencil, brush=bruGRAYSCALE, points={ Point(1, 1) } }
|
||||
expect_img(cel.image,
|
||||
{ 2, 3,
|
||||
3, 3 })
|
||||
|
||||
-- -- -- -- -- -- --
|
||||
-- GRAYSCALE sprite
|
||||
local sprGRAYSCALE = Sprite(2, 2, ColorMode.GRAYSCALE)
|
||||
local cel = sprGRAYSCALE.cels[1]
|
||||
expect_img(cel.image, { 0, 0,
|
||||
0, 0 })
|
||||
local pal = Palette(4)
|
||||
pal:setColor(1, Color{ gray=128, alpha=128 }.grayPixel)
|
||||
pal:setColor(2, Color{ gray=64, alpha=128 }.grayPixel)
|
||||
pal:setColor(3, Color{ gray=32, alpha=255 }.grayPixel)
|
||||
print(pal:getColor(1).grayPixel)
|
||||
print(pal:getColor(2).grayPixel)
|
||||
print(pal:getColor(3).grayPixel)
|
||||
sprGRAYSCALE:setPalette(pal)
|
||||
|
||||
-- Test Sprite GRAYSCALE with RGB brush
|
||||
local brushImg = Image(2, 2, ColorMode.RGB)
|
||||
array_to_pixels({ Color{ r=255, g=0, b=0, a=128 }, Color{ r=0, g=255, b=0, a=128 },
|
||||
Color{ r=0, g=0, b=255, a=128 }, app.pixelColor.rgba(0, 0, 0, 0) }, brushImg)
|
||||
local bruRGB = Brush { image=brushImg }
|
||||
|
||||
app.useTool{ tool=pencil, brush=bruRGB, points={ Point(1, 1) } }
|
||||
expect_img(cel.image,
|
||||
{ Color{ gray=54, alpha=128 }.grayPixel, Color{ gray=182, alpha=128 }.grayPixel,
|
||||
Color{ gray=18, alpha=128 }.grayPixel, 0 })
|
||||
app.undo()
|
||||
|
||||
-- Test Sprite GRAYSCALE with INDEXED brush
|
||||
-- (INDEXED brush with out of bound index)
|
||||
local brushImg = Image(2, 2, ColorMode.INDEXED)
|
||||
array_to_pixels({ 1, 5,
|
||||
3, 0 }, brushImg)
|
||||
local bruINDEXED = Brush { image=brushImg }
|
||||
|
||||
app.useTool{ tool=pencil, brush=bruINDEXED, points={ Point(1, 1) } }
|
||||
expect_img(cel.image,
|
||||
{ Color{ gray=128, alpha=128 }.grayPixel,
|
||||
Color{ gray=32, alpha=255 }.grayPixel })
|
||||
app.undo()
|
||||
|
||||
-- Test Sprite GRAYSCALE with GRAYSCALE brush
|
||||
local brushImg = Image(2, 2, ColorMode.GRAYSCALE)
|
||||
array_to_pixels({ Color{ gray=128, alpha=128 }, Color{ gray=222, alpha=222 },
|
||||
Color{ gray=32, alpha=255 }, Color{ gray=0, alpha=255 } }, brushImg)
|
||||
local bruGRAYSCALE = Brush { image=brushImg }
|
||||
|
||||
app.useTool{ tool=pencil, brush=bruGRAYSCALE, points={ Point(1, 1) } }
|
||||
expect_img(cel.image,
|
||||
{ pal:getColor(1).grayPixel, Color{ gray=222, alpha=222 }.grayPixel,
|
||||
pal:getColor(3).grayPixel, Color{ gray=0, alpha=255 }.grayPixel })
|
||||
end
|
||||
|
30
third_party/CMakeLists.txt
vendored
30
third_party/CMakeLists.txt
vendored
@ -1,5 +1,5 @@
|
||||
# Aseprite
|
||||
# Copyright (C) 2021-2023 Igara Studio S.A.
|
||||
# Copyright (C) 2021-2024 Igara Studio S.A.
|
||||
# Copyright (C) 2001-2018 David Capello
|
||||
|
||||
include_directories(.)
|
||||
@ -54,7 +54,8 @@ if(ENABLE_WEBP AND NOT LAF_BACKEND STREQUAL "skia")
|
||||
endif()
|
||||
|
||||
if(NOT USE_SHARED_TINYXML)
|
||||
add_subdirectory(tinyxml)
|
||||
set(tinyxml2_BUILD_TESTING OFF CACHE BOOL "Build tests for tinyxml2")
|
||||
add_subdirectory(tinyxml2)
|
||||
endif()
|
||||
|
||||
if(REQUIRE_CURL AND NOT USE_SHARED_CURL)
|
||||
@ -195,12 +196,35 @@ if(ENABLE_SCRIPTING)
|
||||
lua/lstrlib.c
|
||||
lua/ltablib.c
|
||||
lua/lutf8lib.c)
|
||||
target_compile_definitions(lua PUBLIC LUA_FLOORN2I=1)
|
||||
|
||||
if(WIN32)
|
||||
# LUA_USE_WINDOWS is defined in luaconf.h when we compile with _WIN32
|
||||
#target_compile_definitions(lua PUBLIC LUA_USE_WINDOWS=1)
|
||||
elseif(APPLE)
|
||||
target_compile_definitions(lua PUBLIC LUA_USE_MACOSX=1)
|
||||
elseif(UNIX)
|
||||
target_compile_definitions(lua PUBLIC LUA_USE_LINUX=1)
|
||||
endif()
|
||||
|
||||
# Compile Lua as C++ to control errors with exceptions and have
|
||||
# stack unwinding (i.e. calling destructors correctly).
|
||||
if(MSVC)
|
||||
target_compile_options(lua PRIVATE -TP)
|
||||
target_compile_options(lualib PRIVATE -TP)
|
||||
target_compile_options(lauxlib PRIVATE -TP)
|
||||
else()
|
||||
target_compile_options(lua PRIVATE -xc++)
|
||||
target_compile_options(lualib PRIVATE -xc++)
|
||||
target_compile_options(lauxlib PRIVATE -xc++)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(lua PUBLIC LUA_FLOORN2I=F2Ifloor)
|
||||
target_compile_definitions(lualib PRIVATE HAVE_SYSTEM)
|
||||
target_include_directories(lua PUBLIC lua)
|
||||
target_include_directories(lauxlib PUBLIC lua)
|
||||
target_include_directories(lualib PUBLIC lua)
|
||||
target_link_libraries(lauxlib lua)
|
||||
target_link_libraries(lualib lua)
|
||||
|
||||
# ixwebsocket
|
||||
if(ENABLE_WEBSOCKET)
|
||||
|
2
third_party/cmark
vendored
2
third_party/cmark
vendored
@ -1 +1 @@
|
||||
Subproject commit 728c68465062223295076d8cb365ca911a55a218
|
||||
Subproject commit 186592f7ff021cd20c5e758239934a3b7848d51f
|
12
third_party/tinyxml/CMakeLists.txt
vendored
12
third_party/tinyxml/CMakeLists.txt
vendored
@ -1,12 +0,0 @@
|
||||
# ASEPRITE
|
||||
# Copyright (C) 2020 Igara Studio S.A.
|
||||
# Copyright (C) 2001-2013 David Capello
|
||||
|
||||
add_library(tinyxml
|
||||
tinyxml.cpp
|
||||
tinyxmlerror.cpp
|
||||
tinyxmlparser.cpp)
|
||||
|
||||
# Use std::string instead of TiXmlString (we've found some threading
|
||||
# issues related to TiXmlString::nullrep_)
|
||||
target_compile_definitions(tinyxml PUBLIC TIXML_USE_STL)
|
298
third_party/tinyxml/changes.txt
vendored
298
third_party/tinyxml/changes.txt
vendored
@ -1,298 +0,0 @@
|
||||
Changes in version 1.0.1:
|
||||
- Fixed comment tags which were outputing as '<?--' instead of
|
||||
the correct '<!--'.
|
||||
- Implemented the Next and Prev methods of the TiXmlAttribute class.
|
||||
- Renamed 'LastAttribtute' to 'LastAttribute'
|
||||
- Fixed bad pointer to 'isspace' that could occur while parsing text.
|
||||
- Errors finding beginning and end of tags no longer throw it into an
|
||||
infinite loop. (Hopefully.)
|
||||
|
||||
Changes in version 1.0.2
|
||||
- Minor documentation fixes.
|
||||
|
||||
Changes in version 1.0.3
|
||||
- After nodes are added to a document, they return a pointer
|
||||
to the new node instead of a bool for success.
|
||||
- Elements can be constructed with a value, which is the
|
||||
element name. Every element must have a value or it will be
|
||||
invalid, but the code changes to enforce this are not fully
|
||||
in place.
|
||||
|
||||
Changes in version 1.1.0
|
||||
- Added the TiXmlAttributeSet class to pull the attributes into
|
||||
a seperate container.
|
||||
- Moved the doubly liked list out of XmlBase. Now XmlBase only
|
||||
requires the Print() function and defines some utility functions.
|
||||
- Moved errors into a seperate file. (With the idea of internationalization
|
||||
to the other latin-1 languages.)
|
||||
- Added the "NodeType"
|
||||
- Fixed white space parsing in text to conform with the standard.
|
||||
Basically, all white space becomes just one space.
|
||||
- Added the TiXmlDeclaration class to read xml declarations.
|
||||
|
||||
Changes in version 1.2.0
|
||||
- Removed the factory. The factory was not really in the spirit
|
||||
of small and simple, confused the code, and was of limited value.
|
||||
- Added FirstChildElement and NextSiblingElement, because they
|
||||
are such common functions.
|
||||
- Re-wrote the example to test and demonstrate more functionality.
|
||||
|
||||
Changes in version 1.2.1
|
||||
- Fixed a bug where comments couldn't be inside elements.
|
||||
- Loading now clears out existing XML rather than appending.
|
||||
- Added the "Clear" method on a node to delete all its children.
|
||||
|
||||
Changes in version 1.2.2
|
||||
- Fixed TiXmlAttribute::Previous actually returning "next." Thanks
|
||||
to Rickard Troedsson for the bug fix.
|
||||
|
||||
Changes in version 1.2.3
|
||||
- Added the TIXML prefix to the error strings to resolve conflicts
|
||||
with #defines in OS headers. Thanks to Steve Lhomme.
|
||||
- Fixed a delete buf that should be a delete [] buf.
|
||||
Thanks to Ephi Sinowitz.
|
||||
|
||||
Changes in version 1.2.4
|
||||
- ReplaceChild() was almost guarenteed to fail. Should be fixed,
|
||||
thanks to Joe Smith. Joe also pointed out that the Print() functions
|
||||
should take stream references: I agree, and would like to overload
|
||||
the Print() method to take either format, but I don't want to do
|
||||
this in a dot release.
|
||||
- Some compilers seem to need an extra <ctype.h> include. Thanks
|
||||
to Steve Lhomme for that.
|
||||
|
||||
Changes in version 2.0.0 BETA
|
||||
- Made the ToXXX() casts safe if 'this' is null.
|
||||
When "LoadFile" is called with a filename, the value will correctly get set.
|
||||
Thanks to Brian Yoder.
|
||||
- Fixed bug where isalpha() and isalnum() would get called with a negative value for
|
||||
high ascii numbers. Thanks to Alesky Aksenov.
|
||||
- Fixed some errors codes that were not getting set.
|
||||
- Made methods "const" that were not.
|
||||
- Added a switch to enable or disable the ignoring of white space. ( TiXmlDocument::SetIgnoreWhiteSpace() )
|
||||
- Greater standardization and code re-use in the parser.
|
||||
- Added a stream out operator.
|
||||
- Added a stream in operator.
|
||||
- Entity support, of predefined entites. &#x entities are untouched by input or output.
|
||||
- Improved text out formatting.
|
||||
- Fixed ReplaceChild bug, thanks to Tao Chen.
|
||||
|
||||
Changes in version 2.0.1
|
||||
- Fixed hanging on loading a 0 length file. Thanks to Jeff Scozzafava.
|
||||
- Fixed crashing on InsertBeforeChild and InsertAfterChild. Also possibility of bad links being
|
||||
created by same function. Thanks to Frank De prins.
|
||||
- Added missing licence text. Thanks to Lars Willemsens.
|
||||
- Added <ctype.h> include, at the suggestion of Steve Walters.
|
||||
|
||||
Changes in version 2.1.0
|
||||
- Yves Berquin brings us the STL switch. The forum on SourceForge, and various emails to
|
||||
me, have long debated all out STL vs. no STL at all. And now you can have it both ways.
|
||||
TinyXml will compile either way.
|
||||
|
||||
Changes in version 2.1.1
|
||||
- Compilation warnings.
|
||||
|
||||
Changes in version 2.1.2
|
||||
- Uneeded code is not compiled in the STL case.
|
||||
- Changed headers so that STL can be turned on or off in tinyxml.h
|
||||
|
||||
Changes in version 2.1.3
|
||||
- Fixed non-const reference in API; now uses a pointer.
|
||||
- Copy constructor of TiXmlString not checking for assignment to self.
|
||||
- Nimrod Cohen found a truly evil bug in the STL implementation that occurs
|
||||
when a string is converted to a c_str and then assigned to self. Search for
|
||||
STL_STRING_BUG for a full description. I'm asserting this is a Microsoft STL
|
||||
bug, since &string and string.c_str() should never be the same. Nevertheless,
|
||||
the code works around it.
|
||||
- Urivan Saaib pointed out a compiler conflict, where the C headers define
|
||||
the isblank macro, which was wiping out the TiXmlString::isblank() method.
|
||||
The method was unused and has been removed.
|
||||
|
||||
Changes in version 2.1.4
|
||||
- Reworked the entity code. Entities were not correctly surving round trip input and output.
|
||||
Will now automatically create entities for high ascii in output.
|
||||
|
||||
Changes in version 2.1.5
|
||||
- Bug fix by kylotan : infinite loop on some input (tinyxmlparser.cpp rev 1.27)
|
||||
- Contributed by Ivica Aracic (bytelord) : 1 new VC++ project to compile versions as static libraries (tinyxml_lib.dsp),
|
||||
and an example usage in xmltest.dsp
|
||||
(Patch request ID 678605)
|
||||
- A suggestion by Ronald Fenner Jr (dormlock) to add #include <istream> and <ostream> for Apple's Project Builder
|
||||
(Patch request ID 697642)
|
||||
- A patch from ohommes that allows to parse correctly dots in element names and attribute names
|
||||
(Patch request 602600 and kylotan 701728)
|
||||
- A patch from hermitgeek ( James ) and wasteland for improper error reporting
|
||||
- Reviewed by Lee, with the following changes:
|
||||
- Got sick of fighting the STL/non-STL thing in the windows build. Broke
|
||||
them out as seperate projects.
|
||||
- I have too long not included the dsw. Added.
|
||||
- TinyXmlText had a protected Print. Odd.
|
||||
- Made LinkEndChild public, with docs and appropriate warnings.
|
||||
- Updated the docs.
|
||||
|
||||
2.2.0
|
||||
- Fixed an uninitialized pointer in the TiXmlAttributes
|
||||
- Fixed STL compilation problem in MinGW (and gcc 3?) - thanks Brian Yoder for finding this one
|
||||
- Fixed a syntax error in TiXmlDeclaration - thanks Brian Yoder
|
||||
- Fletcher Dunn proposed and submitted new error handling that tracked the row and column. Lee
|
||||
modified it to not have performance impact.
|
||||
- General cleanup suggestions from Fletcher Dunn.
|
||||
- In error handling, general errors will no longer clear the error state of specific ones.
|
||||
- Fix error in documentation : comments starting with "<?--" instead of "<!--" (thanks ion_pulse)
|
||||
- Added the TiXmlHandle. An easy, safe way to browse XML DOMs with less code.
|
||||
- Added QueryAttribute calls which have better error messaging. (Proposed by Fletcher Dunn)
|
||||
- Nodes and attributes can now print themselves to strings. (Yves suggestion)
|
||||
- Fixed bug where entities with one character would confuse parser. (Thanks Roman)
|
||||
|
||||
2.2.1
|
||||
- Additional testing (no more bugs found to be fixed in this release)
|
||||
- Significant performance improvement to the cursor code.
|
||||
|
||||
2.3.0
|
||||
- User Data are now defined in TiXmlBase instead of TiXmlNode
|
||||
- Character Entities are now UCS-2
|
||||
- Character Entities can be decimal or hexadecimal
|
||||
- UTF-8 conversion.
|
||||
- Fixed many, many bugs.
|
||||
|
||||
2.3.1
|
||||
- Fixed bug in handling nulls embedded in the input.
|
||||
- Make UTF-8 parser tolerant of bad text encoding.
|
||||
- Added encoding detection.
|
||||
- Many fixes and input from John-Philip Leonard Johansson (JP) and Ellers,
|
||||
including UTF-8 feedback, bug reports, and patches. Thanks!
|
||||
- Added version # constants - a suggestion from JP and Ellers.
|
||||
- [ 979180 ] Missing ; in entity reference, fix from Rob Laveaux.
|
||||
- Copy constructors and assignment have been a long time coming. Thanks to
|
||||
Fokke and JP.
|
||||
|
||||
2.3.2
|
||||
- Made the IsAlpha and IsAlphaNum much more tolerant of non-UTF-8 encodings. Thanks
|
||||
Volker Boerchers for finding the issue.
|
||||
- Ran the program though the magnificent Valgrind - http://valgrind.kde.org - to check
|
||||
for memory errors. Fixed some minor issues.
|
||||
|
||||
2.3.3
|
||||
- Fixed crash when test program was run from incorrect directory.
|
||||
- Fixed bug 1070717 - empty document not returned correctly - thanks Katsuhisa Yuasa.
|
||||
- Bug 1079301 resolved - deprecated stdlib calls. Thanks Adrian Boeing.
|
||||
- Bug 1035218 fixed - documentation errors. Xunji Luo
|
||||
- Other bug fixes have accumulated and been fixed on the way as well; my apologies to
|
||||
authors not credited!
|
||||
- Big fix / addition is to correctly return const values. TinyXml could basically
|
||||
remove const in a method like this: TiXmlElement* Foo() const, where the returned element
|
||||
was a pointer to internal data. That is now: const TiXmlElement* Foo() const and
|
||||
TiXmlElement* Foo().
|
||||
|
||||
2.3.4
|
||||
- Fixed additional const errors, thanks Kent Gibson.
|
||||
- Correctly re-enable warnings after tinyxml header. Thanks Cory Nelson.
|
||||
- Variety of type cleanup and warning fixes. Thanks Warren Stevens.
|
||||
- Cleaned up unneeded constructor calls in TinyString - thanks to Geoff Carlton and
|
||||
the discussion group on sourceforge.
|
||||
|
||||
2.4.0
|
||||
- Improved string class, thanks Tyge Lovset (whose name gets mangled in English - sorry)
|
||||
- Type cast compiler warning, thanks Rob van den Bogaard
|
||||
- Added GetText() convenience function. Thanks Ilya Parniuk & Andrew Ellers for input.
|
||||
- Many thanks to marlonism for finding an infinite loop in bad xml.
|
||||
- A patch to cleanup warnings from Robert Gebis.
|
||||
- Added ValueStr() to get the value of a node as a string.
|
||||
- TiXmlText can now parse and output as CDATA
|
||||
- Additional string improvement from James (z2895)
|
||||
- Removed extraneous 'const', thanks David Aldrich
|
||||
- First pass at switching to the "safe" stdlib functions. Many people have suggested and
|
||||
pushed on this, but Warren Stevens put together the first proposal.
|
||||
- TinyXml now will do EOL normalization before parsing, consistent with the W3C XML spec.
|
||||
- Documents loaded with the UTF-8 BOM will now save with the UTF-8 BOM. Good suggestion
|
||||
from 'instructor_'
|
||||
- Ellers submitted his very popular tutorials, which have been added to the distribution.
|
||||
|
||||
2.4.1
|
||||
- Fixed CDATA output formatting
|
||||
- Fixed memory allocators in TinyString to work with overloaded new/delete
|
||||
|
||||
2.4.2
|
||||
- solosnake pointed out that TIXML_LOG causes problems on an XBOX. The definition in the header
|
||||
was superflous and was moved inside of DEBUG_PARSING
|
||||
|
||||
2.4.3
|
||||
- Fixed a test bug that caused a crash in 'xmltest'. TinyXML was fine, but it isn't good
|
||||
to ship with a broken test suite.
|
||||
- Started converting some functions to not cast between std::string and const char*
|
||||
quite as often.
|
||||
- Added FILE* versions of the document loads - good suggestion from Wade Brainerd
|
||||
- Empty documents might not always return the errors they should. [1398915] Thanks to igor v.
|
||||
- Added some asserts for multiply adding a node, regardng bug [1391937] suggested by Paco Arjonilla.
|
||||
|
||||
2.4.4
|
||||
- Bug find thanks to andre-gross found a memory leak that occured when a document failed to load.
|
||||
- Bug find (and good analysis) by VirtualJim who found a case where attribute parsing
|
||||
should be throwing an error and wasn't.
|
||||
- Steve Hyatt suggested the QueryValueAttribute method, which is now implemented.
|
||||
- DavidA identified a chunk of dead code.
|
||||
- Andrew Baxter sent in some compiler warnings that were good clean up points.
|
||||
|
||||
2.5
|
||||
- Added the Visit() API. Many thanks to both Andrew Ellerton and John-Philip for all their
|
||||
work, code, suggestion, and just general pushing that it should be done.
|
||||
- Removed existing streaming code and use TiXmlPrinter instead.
|
||||
- [ tinyxml-Bugs-1527079 ] Compile error in tinystr.cpp fixed, thanks to Paul Suggs
|
||||
- [ tinyxml-Bugs-1522890 ] SaveFile has no error checks fixed, thanks to Ivan Dobrokotov
|
||||
- Ivan Dobrokotov also reported redundant memory allocation in the Attribute() method, which
|
||||
upon investigation was a mess. The attribute should now be fixed for both const char* and
|
||||
std::string, and the return types match the input parameters.
|
||||
- Feature [ 1511105 ] Make TiXmlComment constructor accept a string / char*, implemented.
|
||||
Thanks to Karl Itschen for the feedback.
|
||||
- [ 1480108 ] Stream parsing fails when CDATA contains tags was found by Tobias Grimm, who also
|
||||
submitted a test case and patch. A significant bug in CDATA streaming (operator>>) has now
|
||||
been fixed.
|
||||
|
||||
2.5.2
|
||||
- Lieven, and others, pointed out a missing const-cast that upset the Open Watcom compiler.
|
||||
Should now be fixed.
|
||||
- ErrorRow and ErrorCol should have been const, and weren't. Fixed thanks to Dmitry Polutov.
|
||||
|
||||
2.5.3
|
||||
- zloe_zlo identified a missing string specialization for QueryValueAttribute() [ 1695429 ]. Worked
|
||||
on this bug, but not sure how to fix it in a safe, cross-compiler way.
|
||||
- increased warning level to 4 and turned on detect 64 bit portability issues for VC2005.
|
||||
May address [ 1677737 ] VS2005: /Wp64 warnings
|
||||
- grosheck identified several problems with the Document copy. Many thanks for [ 1660367 ]
|
||||
- Nice catch, and suggested fix, be Gilad Novik on the Printer dropping entities.
|
||||
"[ 1600650 ] Bug when printing xml text" is now fixed.
|
||||
- A subtle fix from Nicos Gollan in the tinystring initializer:
|
||||
[ 1581449 ] Fix initialiser of TiXmlString::nullrep_
|
||||
- Great catch, although there isn't a submitter for the bug. [ 1475201 ] TinyXML parses entities in comments.
|
||||
Comments should not, in fact, parse entities. Fixed the code path and added tests.
|
||||
- We were not catching all the returns from ftell. Thanks to Bernard for catching that.
|
||||
|
||||
2.5.4
|
||||
- A TiXMLDocument can't be a sub-node. Block this from happening in the 'replace'. Thanks Noam.
|
||||
- [ 1714831 ] TiXmlBase::location is not copied by copy-ctors, fix reported and suggested by Nicola Civran.
|
||||
- Fixed possible memory overrun in the comment reading code - thanks gcarlton77
|
||||
|
||||
2.5.5
|
||||
- Alex van der Wal spotted incorrect types (lf) being used in print and scan. robertnestor pointed out some problems with the simple solution. Types updated.
|
||||
- Johannes Hillert pointed out some bug typos.
|
||||
- Christian Mueller identified inconsistent error handling with Attributes.
|
||||
- olivier barthelemy also reported a problem with double truncation, also related to the %lf issue.
|
||||
- zaelsius came up with a great (and simple) suggestion to fix QueryValueAttribute truncating strings.
|
||||
- added some null pointer checks suggested by hansenk
|
||||
- Sami Väisänen found a (rare) buffer overrun that could occur in parsing.
|
||||
- vi tri filed a bug that led to a refactoring of the attribute setting mess (as well as adding a missing SetDoubleAttribute() )
|
||||
- removed TIXML_ERROR_OUT_OF_MEMORY. TinyXML does not systematically address OOO, and the notion it does is misleading.
|
||||
- vanneto, keithmarshall, others all reported the warning from IsWhiteSpace() usage. Cleaned this up - many thanks to everyone who reported this one.
|
||||
- tibur found a bug in end tag parsing
|
||||
|
||||
|
||||
2.6.2
|
||||
- Switched over to VC 2010
|
||||
- Fixed up all the build issues arising from that. (Lots of latent build problems.)
|
||||
- Removed the old, now unmaintained and likely not working, build files.
|
||||
- Fixed some static analysis issues reported by orbitcowboy from cppcheck.
|
||||
- Bayard 95 sent in analysis from a different analyzer - fixes applied from that as well.
|
||||
- Tim Kosse sent a patch fixing an infinite loop.
|
||||
- Ma Anguo identified a doc issue.
|
||||
- Eddie Cohen identified a missing qualifier resulting in a compilation error on some systems.
|
||||
- Fixed a line ending bug. (What year is this? Can we all agree on a format for text files? Please? ...oh well.)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user