mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-01 01:13:40 +00:00
Merge branch 'main' into beta
This commit is contained in:
commit
e700cce987
@ -256,16 +256,15 @@ elseif(NOT LAF_BACKEND STREQUAL "skia")
|
||||
set(FREETYPE_LIBRARIES ${FREETYPE_LIBRARY})
|
||||
set(FREETYPE_INCLUDE_DIRS ${FREETYPE_DIR}/include)
|
||||
endif()
|
||||
include_directories(${FREETYPE_INCLUDE_DIRS})
|
||||
|
||||
# harfbuzz
|
||||
if(USE_SHARED_HARFBUZZ)
|
||||
find_package(HarfBuzz)
|
||||
elseif(NOT LAF_BACKEND STREQUAL "skia")
|
||||
set(HARFBUZZ_FOUND ON)
|
||||
set(HARFBUZZ_LIBRARIES harfbuzz)
|
||||
set(HARFBUZZ_INCLUDE_DIRS ${HARFBUZZ_DIR}/src)
|
||||
endif()
|
||||
include_directories(${HARFBUZZ_INCLUDE_DIRS})
|
||||
|
||||
if(USE_SHARED_GIFLIB)
|
||||
find_package(GIF REQUIRED)
|
||||
@ -327,11 +326,16 @@ if(ENABLE_WEBP)
|
||||
NAMES libwebp # required for Windows
|
||||
PATHS "${SKIA_LIBRARY_DIR}" NO_DEFAULT_PATH)
|
||||
set(WEBP_INCLUDE_DIR "${SKIA_DIR}/third_party/externals/libwebp/src")
|
||||
if(WEBP_LIBRARIES)
|
||||
set(WEBP_FOUND ON)
|
||||
else()
|
||||
set(WEBP_FOUND OFF)
|
||||
endif()
|
||||
else()
|
||||
set(WEBP_FOUND ON)
|
||||
set(WEBP_LIBRARIES webp webpdemux libwebpmux)
|
||||
set(WEBP_INCLUDE_DIR ${LIBWEBP_DIR}/src)
|
||||
endif()
|
||||
include_directories(${WEBP_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
# Print paths to used libraries
|
||||
|
@ -106,6 +106,28 @@ We have some rules for the changes and commits that are contributed:
|
||||
You can also take a look at the [src/README.md](https://github.com/aseprite/aseprite/tree/main/src/#aseprite-source-code)
|
||||
guide which contains some information about how the code is structured.
|
||||
|
||||
## Pull Request (PR) Assignee
|
||||
|
||||
In case you are a developer or contributor with write or triage access
|
||||
to the repository:
|
||||
|
||||
1. The PR assignee is the one that is working on the PR right now.
|
||||
2. After a PR is sent, you (can) assign the PR to some other developer
|
||||
that will act as a reviewer.
|
||||
* Or if there is no assignee and the PR is not a draft, some
|
||||
developer will take it for review sooner or later.
|
||||
3. That developer will review the PR (or reassign the PR).
|
||||
4. When the review process is done, the reviewer will merge the PR or
|
||||
reassign the PR to you if it needs some changes.
|
||||
5. When you have applied the requested changes, you can reassign the
|
||||
PR to the last reviewer.
|
||||
6. If a PR is labeled with some "needs *something*" label, it means
|
||||
that the PR will not merged as it is, and *something* is required
|
||||
to continue.
|
||||
|
||||
With this workflow you can find the PRs assigned to you to
|
||||
review/continue working in: https://github.com/pulls/assigned
|
||||
|
||||
# Community
|
||||
|
||||
You can use the [Development category](https://community.aseprite.org/c/development)
|
||||
|
13
data/gui.xml
13
data/gui.xml
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2018-2023 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2018-2024 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2001-2018 David Capello -->
|
||||
<gui>
|
||||
<!-- Keyboard shortcuts -->
|
||||
@ -289,11 +289,9 @@
|
||||
<key command="Zoom" shortcut="3"><param name="percentage" value="400" /></key>
|
||||
<key command="Zoom"><param name="percentage" value="500" /></key>
|
||||
<key command="Zoom"><param name="percentage" value="600" /></key>
|
||||
<key command="Zoom"><param name="percentage" value="700" /></key>
|
||||
<key command="Zoom" shortcut="4"><param name="percentage" value="800" /></key>
|
||||
<key command="Zoom"><param name="percentage" value="1000" /></key>
|
||||
<key command="Zoom" shortcut="5"><param name="percentage" value="1600" /></key>
|
||||
<key command="Zoom"><param name="percentage" value="2000" /></key>
|
||||
<key command="Zoom"><param name="percentage" value="2400" /></key>
|
||||
<key command="Zoom" shortcut="6"><param name="percentage" value="3200" /></key>
|
||||
<key command="Zoom" shortcut="Ctrl++" mac="Cmd++">
|
||||
<param name="action" value="in" />
|
||||
@ -479,9 +477,6 @@
|
||||
<key command="NewSpriteFromSelection" shortcut="Ctrl+Alt+N" mac="Cmd+Alt+N" />
|
||||
|
||||
<!-- Commands not associated to menu items and without shortcuts by default -->
|
||||
<key command="ExportSpriteSheet">
|
||||
<param name="source" value="tileset" />
|
||||
</key>
|
||||
<key command="NewLayer">
|
||||
<param name="tilemap" value="true" />
|
||||
</key>
|
||||
@ -539,7 +534,7 @@
|
||||
</key>
|
||||
<key command="ChangePixelFormat">
|
||||
<param name="format" value="indexed" />
|
||||
<param name="dithering" value="old-ordered" />
|
||||
<param name="dithering" value="old" />
|
||||
</key>
|
||||
<key command="ChangeBrush">
|
||||
<param name="change" value="increment-angle" />
|
||||
@ -1102,7 +1097,7 @@
|
||||
</item>
|
||||
<item command="NewFrame" text="@main_menu.frame_new_frame" />
|
||||
</menu>
|
||||
|
||||
|
||||
<menu id="tab_popup_menu">
|
||||
<item command="CloseFile" text="@.close" group="tab_close" />
|
||||
</menu>
|
||||
|
@ -322,6 +322,7 @@
|
||||
<option id="to_gray" type="ToGrayAlgorithm" default="ToGrayAlgorithm::DEFAULT" />
|
||||
<option id="advanced" type="bool" default="false" />
|
||||
<option id="rgbmap_algorithm" type="doc::RgbMapAlgorithm" default="doc::RgbMapAlgorithm::DEFAULT" />
|
||||
<option id="fit_criteria" type="doc::FitCriteria" default="doc::FitCriteria::DEFAULT" />
|
||||
</section>
|
||||
<section id="eyedropper" text="Editor">
|
||||
<option id="channel" type="EyedropperChannel" default="EyedropperChannel::COLOR_ALPHA" />
|
||||
|
@ -111,6 +111,7 @@ overwrite_existent_file = Warning<<File exists, overwrite it?<<{0}||&Yes||&No||&
|
||||
overwrite_files_on_export_sprite_sheet = Export Sprite Sheet Warning\n<<Do you want to overwrite the following file(s)?\n{0}\n||&Yes||&No
|
||||
overwrite_files_on_export = Export Warning\n<<Do you want to overwrite the following file?\n<<{0}\n||&Yes||&No
|
||||
enter_license_disabled = Information\n<<This copy of Aseprite does not support entering a license key.\n<<Consider getting one from https://aseprite.org/download.\n<<Activating Aseprite will give you access to automatic updates.\n||&OK
|
||||
reset_default_confirm = Resetting Preferences\n<<Are you sure you want to reset the selected preferences to their default values?\n||&Yes||&No
|
||||
|
||||
[brightness_contrast]
|
||||
title = Brightness/Contrast
|
||||
@ -280,6 +281,8 @@ Flip_Canvas = Canvas
|
||||
Flip_Horizontally = Horizontally
|
||||
Flip_Selection = Selection
|
||||
Flip_Vertically = Vertically
|
||||
FrameProperties_All = Frame Properties of all frames
|
||||
FrameProperties_Current = Frame Properties of the current range
|
||||
FrameProperties = Frame Properties
|
||||
FrameTagProperties = Tag Properties
|
||||
FullscreenMode = Toggle Fullscreen Mode
|
||||
@ -313,6 +316,7 @@ LayerVisibility = Layer Visibility
|
||||
LinkCels = Links Cels
|
||||
LoadMask = Load Selection
|
||||
LoadPalette = Load Palette
|
||||
LoadDefaultPalette = Load Default Palette
|
||||
MaskAll = Mask All
|
||||
MaskByColor = Mask By Color
|
||||
MaskContent = Mask Content
|
||||
@ -404,6 +408,8 @@ SaveFileAs = Save File As
|
||||
SaveFileCopyAs = Export
|
||||
SaveMask = Save Selection
|
||||
SavePalette = Save Palette
|
||||
SavePaletteAsDefault = Save Palette as Default
|
||||
SavePaletteAsPreset = Save Palette as Preset
|
||||
Screenshot = Screenshot
|
||||
Screenshot_Open = Take & Open Screenshot
|
||||
Screenshot_Save = Take & Save Screenshot
|
||||
@ -458,7 +464,11 @@ SwitchColors = Switch Colors
|
||||
SwapCheckerboardColors = Swap Checkerboard Background Colors
|
||||
SwitchNonactiveLayersOpacity = Switch Nonactive Layers Opacity
|
||||
SymmetryMode = Symmetry Mode
|
||||
TiledMode = Tiled Mode
|
||||
TiledMode = Tiled Mode: {}
|
||||
TiledMode_None = None
|
||||
TiledMode_Both = Both Axes
|
||||
TiledMode_X = X Axis
|
||||
TiledMode_Y = Y Axis
|
||||
Timeline = Switch Timeline
|
||||
ToggleOtherLayersOpacity = Toggle Other Layers Opacity
|
||||
ToggleOtherLayersOpacity_PreviewEditor = Toggle Other Layers Opacity in Preview
|
||||
@ -1259,6 +1269,14 @@ default = Default (Octree)
|
||||
rgb5a3 = Table RGB 5 bits + Alpha 3 bits
|
||||
octree = Octree
|
||||
|
||||
[best_fit_criteria_selector]
|
||||
label = Color Best Fit Criteria:
|
||||
default = Default (Euclidean)
|
||||
rgb = RGB
|
||||
linearized_rgb = Linearized RGB
|
||||
cie_xyz = CIEXYZ
|
||||
cie_lab = CIELAB
|
||||
|
||||
[open_file]
|
||||
title = Open
|
||||
loading = Loading file
|
||||
@ -1518,6 +1536,14 @@ set_cursor_fix_tooltip = Sets the mouse position to the pen location when\nyou h
|
||||
wintab_more_info = (More Information)
|
||||
flash_selected_layer = Flash layer when it is selected
|
||||
non_active_layer_opacity = Opacity for non-active layers:
|
||||
reset_title = Reset Preferences
|
||||
reset_default = Reset configuration options available in the Preferences window
|
||||
reset_tools = Reset all tool preferences
|
||||
reset_installed = Remove installed themes, extensions, and palettes
|
||||
reset_recents = Clear the recently opened file list (including pinned files)
|
||||
reset_perfile = Remove any per-file settings
|
||||
reset_perfile_tooltip = These are specific to opened files and includes\nthings like grid options, background colors, etc.
|
||||
reset = &Reset
|
||||
ok = &OK
|
||||
apply = &Apply
|
||||
cancel = &Cancel
|
||||
@ -1856,3 +1882,4 @@ toggle_horizontal = Toggle Horizontal Symmetry
|
||||
toggle_vertical = Toggle Vertical Symmetry
|
||||
show_options = Symmetry Options
|
||||
reset_position = Reset Symmetry to Center
|
||||
reset_position_to_view_center = Reset Symmetry to View Center
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2019-2020 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2019-2024 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2017 David Capello -->
|
||||
<gui>
|
||||
<window id="color_mode" text="@.title">
|
||||
@ -22,11 +22,14 @@
|
||||
|
||||
<check text="@.flatten" id="flatten" />
|
||||
|
||||
<check id="advanced_check" text="@general.advanced_options" cell_hspan="2" />
|
||||
<hbox id="advanced" cell_hspan="2">
|
||||
<check id="advanced_check" text="@general.advanced_options" />
|
||||
<grid id="advanced" columns="2">
|
||||
<label text="@rgbmap_algorithm_selector.label" />
|
||||
<hbox id="rgbmap_algorithm_placeholder" />
|
||||
</hbox>
|
||||
<hbox id="rgbmap_algorithm_placeholder" cell_align="horizontal" />
|
||||
|
||||
<label text="@best_fit_criteria_selector.label" />
|
||||
<hbox id="best_fit_criteria_placeholder" cell_align="horizontal" />
|
||||
</grid>
|
||||
|
||||
<separator horizontal="true" />
|
||||
<hbox>
|
||||
|
@ -23,6 +23,8 @@
|
||||
<listitem text="@.section_theme" value="section_theme" />
|
||||
<listitem text="@.section_extensions" value="section_extensions" />
|
||||
<listitem text="@.section_experimental" value="section_experimental" />
|
||||
<separator horizontal="true" style="separator_in_view" />
|
||||
<listitem text="@general.reset" value="section_reset" />
|
||||
</listbox>
|
||||
</view>
|
||||
|
||||
@ -515,14 +517,16 @@
|
||||
pref="color_bar.show_invalid_fg_bg_color_alert" />
|
||||
<check id="run_script_alert" text="@.run_script_alert"
|
||||
pref="scripts.show_run_script_alert" />
|
||||
<hbox>
|
||||
<grid columns="4">
|
||||
<label text="@.image_format_alerts" />
|
||||
<check id="css_options_alert" text="!css" pref="css.show_alert" />
|
||||
<check id="gif_options_alert" text="!gif" pref="gif.show_alert" />
|
||||
<check id="jpeg_options_alert" text="!jpeg" pref="jpeg.show_alert" />
|
||||
<boxfiller />
|
||||
<check id="svg_options_alert" text="!svg" pref="svg.show_alert" />
|
||||
<check id="tga_options_alert" text="!tga" pref="tga.show_alert" />
|
||||
</hbox>
|
||||
<check id="webp_options_alert" text="!webp" pref="webp.show_alert" />
|
||||
</grid>
|
||||
<separator horizontal="true" />
|
||||
<hbox>
|
||||
<hbox expansive="true" />
|
||||
@ -592,10 +596,12 @@
|
||||
<slider id="nonactive_layers_opacity" min="0" max="255" width="128" />
|
||||
</hbox>
|
||||
<separator text="@.color_quantization" horizontal="true" />
|
||||
<hbox>
|
||||
<grid columns="2">
|
||||
<label text="@rgbmap_algorithm_selector.label" />
|
||||
<hbox id="rgbmap_algorithm_placeholder" />
|
||||
</hbox>
|
||||
<label text="@best_fit_criteria_selector.label" />
|
||||
<hbox id="best_fit_criteria_placeholder" />
|
||||
</grid>
|
||||
<separator text="@.performance" horizontal="true" />
|
||||
<hbox>
|
||||
<check id="shaders_for_color_selectors"
|
||||
@ -608,6 +614,19 @@
|
||||
pref="tileset.cache_compressed_tilesets" />
|
||||
</vbox>
|
||||
|
||||
<!-- Reset -->
|
||||
<vbox id="section_reset">
|
||||
<separator text="@.reset_title" horizontal="true" />
|
||||
<check id="default_reset" text="@.reset_default" />
|
||||
<check id="tools_reset" text="@.reset_tools" />
|
||||
<check id="installed_reset" text="@.reset_installed" />
|
||||
<check id="recent_reset" text="@.reset_recents" />
|
||||
<check id="perfile_reset" text="@.reset_perfile" tooltip="@.reset_perfile_tooltip" />
|
||||
<hbox>
|
||||
<hbox expansive="true" />
|
||||
<button id="reset_selected_button" text="@.reset" minwidth="60" />
|
||||
</hbox>
|
||||
</vbox>
|
||||
</panel>
|
||||
</hbox>
|
||||
<separator horizontal="true" />
|
||||
|
@ -48,6 +48,7 @@
|
||||
</vbox>
|
||||
<separator horizontal="true" />
|
||||
<hbox>
|
||||
<check text="@general.dont_show" id="dont_show" tooltip="@general.dont_show_tooltip" />
|
||||
<boxfiller />
|
||||
<hbox homogeneous="true">
|
||||
<button text="@general.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />
|
||||
|
2
laf
2
laf
@ -1 +1 @@
|
||||
Subproject commit af450af5f6fabc24a267ee56d6cea2fb1e69b323
|
||||
Subproject commit 851bdbc2454aa381131cd0972781aa86e149504a
|
@ -143,7 +143,7 @@ target_sources(app-lib PRIVATE
|
||||
file/qoi_format.cpp
|
||||
file/svg_format.cpp
|
||||
file/tga_format.cpp)
|
||||
if(ENABLE_WEBP)
|
||||
if(ENABLE_WEBP AND WEBP_FOUND)
|
||||
target_compile_definitions(app-lib PUBLIC -DENABLE_WEBP)
|
||||
target_sources(app-lib PRIVATE
|
||||
file/webp_format.cpp)
|
||||
@ -220,6 +220,7 @@ if(ENABLE_SCRIPTING)
|
||||
script/range_class.cpp
|
||||
script/rectangle_class.cpp
|
||||
script/require.cpp
|
||||
script/script_input_chain.cpp
|
||||
script/security.cpp
|
||||
script/selection_class.cpp
|
||||
script/site_class.cpp
|
||||
@ -581,6 +582,7 @@ target_sources(app-lib PRIVATE
|
||||
ui/alpha_slider.cpp
|
||||
ui/app_menuitem.cpp
|
||||
ui/backup_indicator.cpp
|
||||
ui/best_fit_criteria_selector.cpp
|
||||
ui/browser_view.cpp
|
||||
ui/brush_popup.cpp
|
||||
ui/button_set.cpp
|
||||
@ -751,10 +753,7 @@ target_link_libraries(app-lib
|
||||
${TINYXML_LIBRARY}
|
||||
${GIF_LIBRARIES}
|
||||
${PNG_LIBRARIES}
|
||||
${WEBP_LIBRARIES}
|
||||
${ZLIB_LIBRARIES}
|
||||
${FREETYPE_LIBRARIES}
|
||||
${HARFBUZZ_LIBRARIES}
|
||||
libjpeg-turbo
|
||||
json11
|
||||
archive_static
|
||||
@ -762,6 +761,21 @@ target_link_libraries(app-lib
|
||||
tinyexpr
|
||||
qoi)
|
||||
|
||||
if(ENABLE_WEBP AND WEBP_FOUND)
|
||||
target_link_libraries(app-lib ${WEBP_LIBRARIES})
|
||||
target_include_directories(app-lib PUBLIC ${WEBP_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
if(FREETYPE_FOUND)
|
||||
target_link_libraries(app-lib ${FREETYPE_LIBRARIES})
|
||||
target_include_directories(app-lib PUBLIC ${FREETYPE_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if(HARFBUZZ_FOUND)
|
||||
target_link_libraries(app-lib ${HARFBUZZ_LIBRARIES})
|
||||
target_include_directories(app-lib PUBLIC ${HARFBUZZ_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
# Directory where generated files by "gen" utility will stay.
|
||||
target_include_directories(app-lib PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
|
@ -861,7 +861,7 @@ int app_get_color_to_clear_layer(Layer* layer)
|
||||
if (auto* colorBar = ColorBar::instance())
|
||||
color = colorBar->getBgColor();
|
||||
else
|
||||
color = app::Color::fromRgb(0, 0, 0); // TODO get background color color from doc::Settings
|
||||
color = Preferences::instance().colorBar.bgColor();
|
||||
}
|
||||
else { // All transparent layers are cleared with the mask color
|
||||
color = app::Color::fromMask();
|
||||
|
@ -391,9 +391,8 @@ void AppMenus::reload()
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
// Load scripts
|
||||
ResourceFinder rf;
|
||||
rf.includeUserDir("scripts/.");
|
||||
rf.includeUserDir("scripts");
|
||||
std::string scriptsDir = rf.getFirstOrCreateDefault();
|
||||
scriptsDir = base::get_file_path(scriptsDir);
|
||||
if (base::is_directory(scriptsDir)) {
|
||||
loadScriptsSubmenu(scriptsMenu->getSubmenu(), scriptsDir, true);
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "app/filename_formatter.h"
|
||||
#include "app/restore_visible_layers.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "app/util/layer_utils.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/split_string.h"
|
||||
@ -43,17 +44,6 @@ namespace app {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string get_layer_path(const Layer* layer)
|
||||
{
|
||||
std::string path;
|
||||
for (; layer != layer->sprite()->root(); layer=layer->parent()) {
|
||||
if (!path.empty())
|
||||
path.insert(0, "/");
|
||||
path.insert(0, layer->name());
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
bool match_path(const std::string& filter,
|
||||
const std::string& layer_path,
|
||||
const bool exclude)
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2016-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -31,6 +31,8 @@
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
#include "app/app.h"
|
||||
#include "app/script/engine.h"
|
||||
#include "app/script/script_input_chain.h"
|
||||
#include "app/ui/input_chain.h"
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
@ -143,6 +145,10 @@ void DefaultCliDelegate::exportFiles(Context* ctx, DocExporter& exporter)
|
||||
int DefaultCliDelegate::execScript(const std::string& filename,
|
||||
const Params& params)
|
||||
{
|
||||
ScriptInputChain scriptInputChain;
|
||||
if (!App::instance()->isGui()) {
|
||||
App::instance()->inputChain().prioritize(&scriptInputChain, nullptr);
|
||||
}
|
||||
auto engine = App::instance()->scriptEngine();
|
||||
if (!engine->evalUserFile(filename, params))
|
||||
throw base::Exception("Error executing script %s", filename.c_str());
|
||||
|
@ -12,12 +12,18 @@
|
||||
#include "app/cmd/flatten_layers.h"
|
||||
|
||||
#include "app/cmd/add_layer.h"
|
||||
#include "app/cmd/add_cel.h"
|
||||
#include "app/cmd/configure_background.h"
|
||||
#include "app/cmd/copy_rect.h"
|
||||
#include "app/cmd/move_layer.h"
|
||||
#include "app/cmd/remove_layer.h"
|
||||
#include "app/cmd/set_layer_flags.h"
|
||||
#include "app/cmd/remove_cel.h"
|
||||
#include "app/cmd/replace_image.h"
|
||||
#include "app/cmd/set_layer_name.h"
|
||||
#include "app/cmd/set_layer_opacity.h"
|
||||
#include "app/cmd/set_layer_blend_mode.h"
|
||||
#include "app/cmd/set_cel_opacity.h"
|
||||
#include "app/cmd/set_cel_zindex.h"
|
||||
#include "app/cmd/set_cel_position.h"
|
||||
#include "app/cmd/unlink_cel.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/i18n/strings.h"
|
||||
@ -34,10 +40,10 @@ namespace cmd {
|
||||
|
||||
FlattenLayers::FlattenLayers(doc::Sprite* sprite,
|
||||
const doc::SelectedLayers& layers0,
|
||||
const bool newBlend)
|
||||
const Options options)
|
||||
: WithSprite(sprite)
|
||||
{
|
||||
m_newBlendMethod = newBlend;
|
||||
m_options = options;
|
||||
doc::SelectedLayers layers(layers0);
|
||||
layers.removeChildrenIfParentIsSelected();
|
||||
|
||||
@ -66,8 +72,27 @@ void FlattenLayers::onExecute()
|
||||
if (list.empty())
|
||||
return; // Do nothing
|
||||
|
||||
// Set the drawable area to a union of all cel bounds
|
||||
// when this option is enabled
|
||||
ImageSpec spec = sprite->spec();
|
||||
gfx::Rect area;
|
||||
if (m_options.dynamicCanvas) {
|
||||
for (frame_t frame(0); frame<sprite->totalFrames(); ++frame) {
|
||||
for (Layer* layer : layers) {
|
||||
Cel* cel = layer->cel(frame);
|
||||
if (cel)
|
||||
area |= cel->bounds();
|
||||
}
|
||||
}
|
||||
spec.setSize(area.size());
|
||||
}
|
||||
// Otherwise use the sprite's bounds
|
||||
else {
|
||||
area.setSize(spec.size());
|
||||
}
|
||||
|
||||
// Create a temporary image.
|
||||
ImageRef image(Image::create(sprite->spec()));
|
||||
ImageRef image(Image::create(spec));
|
||||
|
||||
LayerImage* flatLayer; // The layer onto which everything will be flattened.
|
||||
color_t bgcolor; // The background color to use for flatLayer.
|
||||
@ -78,6 +103,12 @@ void FlattenLayers::onExecute()
|
||||
// There exists a visible background layer, so we will flatten onto that.
|
||||
bgcolor = doc->bgColor(flatLayer);
|
||||
}
|
||||
// Get bottom layer when merging layers in-place, but only if
|
||||
// we are not flattening into the background layer
|
||||
else if (m_options.inplace) {
|
||||
flatLayer = static_cast<LayerImage*>(list.front());
|
||||
bgcolor = sprite->transparentColor();
|
||||
}
|
||||
else {
|
||||
// Create a new transparent layer to flatten everything onto it.
|
||||
flatLayer = new LayerImage(sprite);
|
||||
@ -88,7 +119,7 @@ void FlattenLayers::onExecute()
|
||||
}
|
||||
|
||||
render::Render render;
|
||||
render.setNewBlend(m_newBlendMethod);
|
||||
render.setNewBlend(m_options.newBlendMethod);
|
||||
render.setBgOptions(render::BgOptions::MakeNone());
|
||||
|
||||
{
|
||||
@ -97,44 +128,97 @@ void FlattenLayers::onExecute()
|
||||
RestoreVisibleLayers restore;
|
||||
restore.showSelectedLayers(sprite, layers);
|
||||
|
||||
// Map draw area to image coords
|
||||
const gfx::ClipF area_to_image(0, 0, area);
|
||||
|
||||
// Copy all frames to the background.
|
||||
for (frame_t frame(0); frame<sprite->totalFrames(); ++frame) {
|
||||
// Clear the image and render this frame.
|
||||
clear_image(image.get(), bgcolor);
|
||||
render.renderSprite(image.get(), sprite, frame);
|
||||
render.renderSprite(image.get(), sprite, frame, area_to_image);
|
||||
|
||||
// TODO Keep cel links when possible
|
||||
// Get exact bounds for rendered frame
|
||||
gfx::Rect bounds = image->bounds();
|
||||
const bool shrink = doc::algorithm::shrink_bounds(
|
||||
image.get(), image->maskColor(), nullptr,
|
||||
image->bounds(), bounds);
|
||||
|
||||
ImageRef cel_image;
|
||||
// Skip when fully transparent
|
||||
Cel* cel = flatLayer->cel(frame);
|
||||
if (!shrink) {
|
||||
if (!newFlatLayer && cel)
|
||||
executeAndAdd(new cmd::RemoveCel(cel));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply shrunk bounds to new image
|
||||
const ImageRef new_image(doc::crop_image(
|
||||
image.get(), bounds, image->maskColor()));
|
||||
|
||||
// Replace image on existing cel
|
||||
if (cel) {
|
||||
|
||||
// TODO Keep cel links when possible
|
||||
|
||||
if (cel->links())
|
||||
executeAndAdd(new cmd::UnlinkCel(cel));
|
||||
|
||||
cel_image = cel->imageRef();
|
||||
const ImageRef cel_image = cel->imageRef();
|
||||
ASSERT(cel_image);
|
||||
|
||||
executeAndAdd(
|
||||
new cmd::CopyRect(cel_image.get(), image.get(),
|
||||
gfx::Clip(0, 0, image->bounds())));
|
||||
// Reset cel properties when flattening in-place
|
||||
if (!newFlatLayer) {
|
||||
if (cel->opacity() != 255)
|
||||
executeAndAdd(new cmd::SetCelOpacity(cel, 255));
|
||||
|
||||
if (cel->zIndex() != 0)
|
||||
executeAndAdd(new cmd::SetCelZIndex(cel, 0));
|
||||
|
||||
executeAndAdd(new cmd::SetCelPosition(cel,
|
||||
area.x+bounds.x, area.y+bounds.y));
|
||||
}
|
||||
|
||||
// Modify destination cel
|
||||
executeAndAdd(new cmd::ReplaceImage(sprite, cel_image, new_image));
|
||||
}
|
||||
// Add new cel on null
|
||||
else {
|
||||
gfx::Rect bounds(image->bounds());
|
||||
if (doc::algorithm::shrink_bounds(
|
||||
image.get(), image->maskColor(), nullptr, bounds)) {
|
||||
cel_image.reset(
|
||||
doc::crop_image(image.get(), bounds, image->maskColor()));
|
||||
cel = new Cel(frame, cel_image);
|
||||
cel->setPosition(bounds.origin());
|
||||
cel = new Cel(frame, new_image);
|
||||
cel->setPosition(area.x+bounds.x, area.y+bounds.y);
|
||||
|
||||
// No need to undo adding this cel when flattening onto
|
||||
// a new layer, as the layer itself would be destroyed,
|
||||
// hence the lack of a command
|
||||
if (newFlatLayer) {
|
||||
flatLayer->addCel(cel);
|
||||
}
|
||||
else {
|
||||
executeAndAdd(new cmd::AddCel(flatLayer, cel));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify observers when merging down
|
||||
if (m_options.mergeDown)
|
||||
doc->notifyLayerMergedDown(list.back(), flatLayer);
|
||||
|
||||
// Add new flatten layer
|
||||
if (newFlatLayer)
|
||||
executeAndAdd(new cmd::AddLayer(list.front()->parent(), flatLayer, list.front()));
|
||||
if (newFlatLayer) {
|
||||
executeAndAdd(new cmd::AddLayer(
|
||||
list.front()->parent(), flatLayer, list.front()));
|
||||
|
||||
}
|
||||
// Reset layer properties when flattening in-place
|
||||
else {
|
||||
if (flatLayer->opacity() != 255)
|
||||
executeAndAdd(new cmd::SetLayerOpacity(flatLayer, 255));
|
||||
|
||||
if (flatLayer->blendMode() != doc::BlendMode::NORMAL)
|
||||
executeAndAdd(new cmd::SetLayerBlendMode(
|
||||
flatLayer, doc::BlendMode::NORMAL));
|
||||
}
|
||||
|
||||
// Delete flattened layers.
|
||||
for (Layer* layer : layers) {
|
||||
|
@ -20,16 +20,31 @@ namespace cmd {
|
||||
class FlattenLayers : public CmdSequence
|
||||
, public WithSprite {
|
||||
public:
|
||||
|
||||
struct Options {
|
||||
bool newBlendMethod: 1;
|
||||
bool inplace: 1;
|
||||
bool mergeDown: 1;
|
||||
bool dynamicCanvas: 1;
|
||||
|
||||
Options():
|
||||
newBlendMethod(false),
|
||||
inplace(false),
|
||||
mergeDown(false),
|
||||
dynamicCanvas(false) {
|
||||
}
|
||||
};
|
||||
|
||||
FlattenLayers(doc::Sprite* sprite,
|
||||
const doc::SelectedLayers& layers,
|
||||
const bool newBlendMethod);
|
||||
const Options options);
|
||||
|
||||
protected:
|
||||
void onExecute() override;
|
||||
|
||||
private:
|
||||
doc::ObjectIds m_layerIds;
|
||||
bool m_newBlendMethod;
|
||||
Options m_options;
|
||||
};
|
||||
|
||||
} // namespace cmd
|
||||
|
@ -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
|
||||
@ -15,6 +15,7 @@
|
||||
#include "app/cmd/replace_image.h"
|
||||
#include "app/cmd/set_cel_opacity.h"
|
||||
#include "app/cmd/set_palette.h"
|
||||
#include "app/cmd/set_transparent_color.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/doc_event.h"
|
||||
#include "doc/cel.h"
|
||||
@ -73,7 +74,8 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
|
||||
const render::Dithering& dithering,
|
||||
const doc::RgbMapAlgorithm mapAlgorithm,
|
||||
doc::rgba_to_graya_func toGray,
|
||||
render::TaskDelegate* delegate)
|
||||
render::TaskDelegate* delegate,
|
||||
const FitCriteria fitCriteria)
|
||||
: WithSprite(sprite)
|
||||
, m_oldFormat(sprite->pixelFormat())
|
||||
, m_newFormat(newFormat)
|
||||
@ -108,7 +110,8 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
|
||||
cel->layer()->isBackground(),
|
||||
mapAlgorithm,
|
||||
toGray,
|
||||
&superDel);
|
||||
&superDel,
|
||||
fitCriteria);
|
||||
|
||||
superDel.nextImage();
|
||||
}
|
||||
@ -128,20 +131,45 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
|
||||
false, // TODO is background? it depends of the layer where this tileset is used
|
||||
mapAlgorithm,
|
||||
toGray,
|
||||
&superDel);
|
||||
&superDel,
|
||||
fitCriteria);
|
||||
}
|
||||
superDel.nextImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set all cels opacity to 100% if we are converting to indexed.
|
||||
// TODO remove this
|
||||
// By default, when converting to RGB or grayscale, the mask color
|
||||
// is always 0.
|
||||
int newMaskIndex = 0;
|
||||
if (newFormat == IMAGE_INDEXED) {
|
||||
// Set all cels opacity to 100% if we are converting to indexed.
|
||||
// TODO remove this (?)
|
||||
for (Cel* cel : sprite->uniqueCels()) {
|
||||
if (cel->opacity() < 255)
|
||||
m_seq.add(new cmd::SetCelOpacity(cel, 255));
|
||||
m_pre.add(new cmd::SetCelOpacity(cel, 255));
|
||||
}
|
||||
|
||||
// When converting to indexed mode the mask color depends if the
|
||||
// palette includes a fully transparent entry.
|
||||
newMaskIndex = sprite->palette(0)->findMaskColor();
|
||||
if (newMaskIndex < 0)
|
||||
newMaskIndex = 0;
|
||||
|
||||
// We change the transparent color after (m_post) changing the
|
||||
// color mode (when we are already in indexed mode).
|
||||
if (newMaskIndex != sprite->transparentColor())
|
||||
m_post.add(new cmd::SetTransparentColor(sprite, newMaskIndex));
|
||||
}
|
||||
else if (m_oldFormat == IMAGE_INDEXED) {
|
||||
// We change the transparent color before (m_pre) changing the
|
||||
// color mode (when we are still in indexed mode).
|
||||
if (newMaskIndex != sprite->transparentColor())
|
||||
m_pre.add(new cmd::SetTransparentColor(sprite, newMaskIndex));
|
||||
}
|
||||
else {
|
||||
// RGB <-> Grayscale
|
||||
ASSERT(sprite->transparentColor() == 0);
|
||||
}
|
||||
|
||||
// When we are converting to grayscale color mode, we've to destroy
|
||||
@ -152,30 +180,33 @@ SetPixelFormat::SetPixelFormat(Sprite* sprite,
|
||||
PalettesList palettes = sprite->getPalettes();
|
||||
for (Palette* pal : palettes)
|
||||
if (pal->frame() != 0)
|
||||
m_seq.add(new cmd::RemovePalette(sprite, pal));
|
||||
m_pre.add(new cmd::RemovePalette(sprite, pal));
|
||||
|
||||
std::unique_ptr<Palette> graypal(Palette::createGrayscale());
|
||||
if (*graypal != *sprite->palette(0))
|
||||
m_seq.add(new cmd::SetPalette(sprite, 0, graypal.get()));
|
||||
m_pre.add(new cmd::SetPalette(sprite, 0, graypal.get()));
|
||||
}
|
||||
}
|
||||
|
||||
void SetPixelFormat::onExecute()
|
||||
{
|
||||
m_seq.execute(context());
|
||||
m_pre.execute(context());
|
||||
setFormat(m_newFormat);
|
||||
m_post.execute(context());
|
||||
}
|
||||
|
||||
void SetPixelFormat::onUndo()
|
||||
{
|
||||
m_seq.undo();
|
||||
m_post.undo();
|
||||
setFormat(m_oldFormat);
|
||||
m_pre.undo();
|
||||
}
|
||||
|
||||
void SetPixelFormat::onRedo()
|
||||
{
|
||||
m_seq.redo();
|
||||
m_pre.redo();
|
||||
setFormat(m_newFormat);
|
||||
m_post.redo();
|
||||
}
|
||||
|
||||
void SetPixelFormat::setFormat(PixelFormat format)
|
||||
@ -183,12 +214,6 @@ void SetPixelFormat::setFormat(PixelFormat format)
|
||||
Sprite* sprite = this->sprite();
|
||||
|
||||
sprite->setPixelFormat(format);
|
||||
if (format == IMAGE_INDEXED) {
|
||||
int maskIndex = sprite->palette(0)->findMaskColor();
|
||||
sprite->setTransparentColor(maskIndex == -1 ? 0 : maskIndex);
|
||||
}
|
||||
else
|
||||
sprite->setTransparentColor(0);
|
||||
sprite->incrementVersion();
|
||||
|
||||
// Regenerate extras
|
||||
@ -208,7 +233,8 @@ void SetPixelFormat::convertImage(doc::Sprite* sprite,
|
||||
const bool isBackground,
|
||||
const doc::RgbMapAlgorithm mapAlgorithm,
|
||||
doc::rgba_to_graya_func toGray,
|
||||
render::TaskDelegate* delegate)
|
||||
render::TaskDelegate* delegate,
|
||||
const doc::FitCriteria fitCriteria)
|
||||
{
|
||||
ASSERT(oldImage);
|
||||
ASSERT(oldImage->pixelFormat() != IMAGE_TILEMAP);
|
||||
@ -218,7 +244,10 @@ void SetPixelFormat::convertImage(doc::Sprite* sprite,
|
||||
RgbMap* rgbmap;
|
||||
int newMaskIndex = (isBackground ? -1 : 0);
|
||||
if (m_newFormat == IMAGE_INDEXED) {
|
||||
rgbmap = sprite->rgbMap(frame, sprite->rgbMapForSprite(), mapAlgorithm);
|
||||
rgbmap = sprite->rgbMap(frame,
|
||||
sprite->rgbMapForSprite(),
|
||||
mapAlgorithm,
|
||||
fitCriteria);
|
||||
if (m_oldFormat == IMAGE_INDEXED)
|
||||
newMaskIndex = sprite->transparentColor();
|
||||
else
|
||||
@ -238,7 +267,7 @@ void SetPixelFormat::convertImage(doc::Sprite* sprite,
|
||||
toGray,
|
||||
delegate));
|
||||
|
||||
m_seq.add(new cmd::ReplaceImage(sprite, oldImage, newImage));
|
||||
m_pre.add(new cmd::ReplaceImage(sprite, oldImage, newImage));
|
||||
}
|
||||
|
||||
} // namespace cmd
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2020 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
|
||||
@ -12,6 +12,7 @@
|
||||
#include "app/cmd/with_sprite.h"
|
||||
#include "app/cmd_sequence.h"
|
||||
#include "doc/color.h"
|
||||
#include "doc/fit_criteria.h"
|
||||
#include "doc/frame.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/pixel_format.h"
|
||||
@ -37,14 +38,15 @@ namespace cmd {
|
||||
const render::Dithering& dithering,
|
||||
const doc::RgbMapAlgorithm mapAlgorithm,
|
||||
doc::rgba_to_graya_func toGray,
|
||||
render::TaskDelegate* delegate);
|
||||
render::TaskDelegate* delegate,
|
||||
const doc::FitCriteria fitCriteria);
|
||||
|
||||
protected:
|
||||
void onExecute() override;
|
||||
void onUndo() override;
|
||||
void onRedo() override;
|
||||
size_t onMemSize() const override {
|
||||
return sizeof(*this) + m_seq.memSize();
|
||||
return sizeof(*this) + m_pre.memSize() + m_post.memSize();
|
||||
}
|
||||
|
||||
private:
|
||||
@ -56,11 +58,13 @@ namespace cmd {
|
||||
const bool isBackground,
|
||||
const doc::RgbMapAlgorithm mapAlgorithm,
|
||||
doc::rgba_to_graya_func toGray,
|
||||
render::TaskDelegate* delegate);
|
||||
render::TaskDelegate* delegate,
|
||||
const doc::FitCriteria fitCriteria = doc::FitCriteria::DEFAULT);
|
||||
|
||||
doc::PixelFormat m_oldFormat;
|
||||
doc::PixelFormat m_newFormat;
|
||||
CmdSequence m_seq;
|
||||
CmdSequence m_pre;
|
||||
CmdSequence m_post;
|
||||
};
|
||||
|
||||
} // namespace cmd
|
||||
|
@ -63,7 +63,7 @@ void CancelCommand::onExecute(Context* context)
|
||||
case All:
|
||||
// TODO should the ContextBar be a InputChainElement to intercept onCancel()?
|
||||
// Discard brush
|
||||
{
|
||||
if (context->isUIAvailable()) {
|
||||
Command* discardBrush = Commands::instance()->byId(
|
||||
CommandId::DiscardBrush());
|
||||
context->executeCommand(discardBrush);
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "app/modules/palettes.h"
|
||||
#include "app/sprite_job.h"
|
||||
#include "app/transaction.h"
|
||||
#include "app/ui/best_fit_criteria_selector.h"
|
||||
#include "app/ui/dithering_selector.h"
|
||||
#include "app/ui/editor/editor.h"
|
||||
#include "app/ui/editor/editor_render.h"
|
||||
@ -69,7 +70,6 @@ public:
|
||||
const doc::frame_t frame,
|
||||
const doc::PixelFormat pixelFormat,
|
||||
const render::Dithering& dithering,
|
||||
const doc::RgbMapAlgorithm rgbMapAlgorithm,
|
||||
const gen::ToGrayAlgorithm toGray,
|
||||
const gfx::Point& pos,
|
||||
const bool newBlend)
|
||||
@ -83,13 +83,11 @@ public:
|
||||
sprite, frame,
|
||||
pixelFormat,
|
||||
dithering,
|
||||
rgbMapAlgorithm,
|
||||
toGray,
|
||||
newBlend]() { // Copy the matrix
|
||||
run(sprite, frame,
|
||||
pixelFormat,
|
||||
dithering,
|
||||
rgbMapAlgorithm,
|
||||
toGray,
|
||||
newBlend);
|
||||
})
|
||||
@ -114,7 +112,6 @@ private:
|
||||
const doc::frame_t frame,
|
||||
const doc::PixelFormat pixelFormat,
|
||||
const render::Dithering& dithering,
|
||||
const doc::RgbMapAlgorithm rgbMapAlgorithm,
|
||||
const gen::ToGrayAlgorithm toGray,
|
||||
const bool newBlend) {
|
||||
doc::ImageRef tmp(
|
||||
@ -136,9 +133,7 @@ private:
|
||||
m_image.get(),
|
||||
pixelFormat,
|
||||
dithering,
|
||||
sprite->rgbMap(frame,
|
||||
sprite->rgbMapForSprite(),
|
||||
rgbMapAlgorithm),
|
||||
sprite->rgbMap(frame),
|
||||
sprite->palette(frame),
|
||||
(sprite->backgroundLayer() != nullptr),
|
||||
0,
|
||||
@ -193,6 +188,7 @@ public:
|
||||
, m_selectedItem(nullptr)
|
||||
, m_ditheringSelector(nullptr)
|
||||
, m_mapAlgorithmSelector(nullptr)
|
||||
, m_bestFitCriteriaSelector(nullptr)
|
||||
, m_imageJustCreated(true)
|
||||
{
|
||||
const auto& pref = Preferences::instance();
|
||||
@ -219,6 +215,9 @@ public:
|
||||
m_mapAlgorithmSelector = new RgbMapAlgorithmSelector;
|
||||
m_mapAlgorithmSelector->setExpansive(true);
|
||||
|
||||
m_bestFitCriteriaSelector = new BestFitCriteriaSelector;
|
||||
m_bestFitCriteriaSelector->setExpansive(true);
|
||||
|
||||
// Select default dithering method
|
||||
{
|
||||
int index = m_ditheringSelector->findItemIndex(
|
||||
@ -230,8 +229,12 @@ public:
|
||||
// Select default RgbMap algorithm
|
||||
m_mapAlgorithmSelector->algorithm(pref.quantization.rgbmapAlgorithm());
|
||||
|
||||
// Select default best fit criteria
|
||||
m_bestFitCriteriaSelector->criteria(pref.quantization.fitCriteria());
|
||||
|
||||
ditheringPlaceholder()->addChild(m_ditheringSelector);
|
||||
rgbmapAlgorithmPlaceholder()->addChild(m_mapAlgorithmSelector);
|
||||
bestFitCriteriaPlaceholder()->addChild(m_bestFitCriteriaSelector);
|
||||
|
||||
const bool adv = pref.quantization.advanced();
|
||||
advancedCheck()->setSelected(adv);
|
||||
@ -240,6 +243,7 @@ public:
|
||||
// Signals
|
||||
m_ditheringSelector->Change.connect([this]{ onIndexParamChange(); });
|
||||
m_mapAlgorithmSelector->Change.connect([this]{ onIndexParamChange(); });
|
||||
m_bestFitCriteriaSelector->Change.connect([this]{ onIndexParamChange(); });
|
||||
factor()->Change.connect([this]{ onIndexParamChange(); });
|
||||
|
||||
advancedCheck()->Click.connect(
|
||||
@ -301,6 +305,13 @@ public:
|
||||
return doc::RgbMapAlgorithm::DEFAULT;
|
||||
}
|
||||
|
||||
doc::FitCriteria fitCriteria() const {
|
||||
if (m_bestFitCriteriaSelector)
|
||||
return m_bestFitCriteriaSelector->criteria();
|
||||
else
|
||||
return doc::FitCriteria::DEFAULT;
|
||||
}
|
||||
|
||||
gen::ToGrayAlgorithm toGray() const {
|
||||
static_assert(
|
||||
int(gen::ToGrayAlgorithm::LUMA) == 0 &&
|
||||
@ -331,7 +342,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
if (m_mapAlgorithmSelector)
|
||||
if (m_mapAlgorithmSelector || m_bestFitCriteriaSelector)
|
||||
pref.quantization.advanced(advancedCheck()->isSelected());
|
||||
}
|
||||
|
||||
@ -396,6 +407,12 @@ private:
|
||||
visibleBounds.origin(),
|
||||
doc::BlendMode::SRC);
|
||||
|
||||
m_editor->sprite()->rgbMap(
|
||||
0,
|
||||
m_editor->sprite()->rgbMapForSprite(),
|
||||
rgbMapAlgorithm(),
|
||||
fitCriteria());
|
||||
|
||||
m_editor->invalidate();
|
||||
progress()->setValue(0);
|
||||
progress()->setVisible(false);
|
||||
@ -408,7 +425,6 @@ private:
|
||||
m_editor->frame(),
|
||||
dstPixelFormat,
|
||||
dithering(),
|
||||
rgbMapAlgorithm(),
|
||||
toGray(),
|
||||
visibleBounds.origin(),
|
||||
Preferences::instance().experimental.newBlend()));
|
||||
@ -463,6 +479,7 @@ private:
|
||||
ConversionItem* m_selectedItem;
|
||||
DitheringSelector* m_ditheringSelector;
|
||||
RgbMapAlgorithmSelector* m_mapAlgorithmSelector;
|
||||
BestFitCriteriaSelector* m_bestFitCriteriaSelector;
|
||||
bool m_imageJustCreated;
|
||||
};
|
||||
|
||||
@ -485,6 +502,7 @@ private:
|
||||
doc::PixelFormat m_format;
|
||||
render::Dithering m_dithering;
|
||||
doc::RgbMapAlgorithm m_rgbmap;
|
||||
doc::FitCriteria m_fitCriteria = FitCriteria::DEFAULT;
|
||||
gen::ToGrayAlgorithm m_toGray;
|
||||
};
|
||||
|
||||
@ -624,7 +642,10 @@ void ChangePixelFormatCommand::onExecute(Context* context)
|
||||
{
|
||||
bool flatten = false;
|
||||
|
||||
if (context->isUIAvailable() && m_showDlg) {
|
||||
if (!context->isUIAvailable()) {
|
||||
// do nothing
|
||||
}
|
||||
else if (m_showDlg) {
|
||||
ColorModeWindow window(Editor::activeEditor());
|
||||
|
||||
window.remapWindow();
|
||||
@ -640,11 +661,18 @@ void ChangePixelFormatCommand::onExecute(Context* context)
|
||||
m_format = window.pixelFormat();
|
||||
m_dithering = window.dithering();
|
||||
m_rgbmap = window.rgbMapAlgorithm();
|
||||
m_fitCriteria = window.fitCriteria();
|
||||
m_toGray = window.toGray();
|
||||
flatten = window.flattenEnabled();
|
||||
|
||||
window.saveOptions();
|
||||
}
|
||||
else {
|
||||
if (m_format == IMAGE_INDEXED) {
|
||||
m_rgbmap = Preferences::instance().quantization.rgbmapAlgorithm();
|
||||
m_fitCriteria = Preferences::instance().quantization.fitCriteria();
|
||||
}
|
||||
}
|
||||
|
||||
// No conversion needed
|
||||
Doc* doc = context->activeDocument();
|
||||
@ -665,10 +693,13 @@ void ChangePixelFormatCommand::onExecute(Context* context)
|
||||
if (flatten) {
|
||||
Tx tx(Tx::LockDoc, context, doc);
|
||||
const bool newBlend = Preferences::instance().experimental.newBlend();
|
||||
cmd::FlattenLayers::Options options;
|
||||
options.newBlendMethod = newBlend;
|
||||
|
||||
SelectedLayers selLayers;
|
||||
for (auto layer : sprite->root()->layers())
|
||||
selLayers.insert(layer);
|
||||
tx(new cmd::FlattenLayers(sprite, selLayers, newBlend));
|
||||
tx(new cmd::FlattenLayers(sprite, selLayers, options));
|
||||
}
|
||||
|
||||
job.startJobWithCallback(
|
||||
@ -679,7 +710,8 @@ void ChangePixelFormatCommand::onExecute(Context* context)
|
||||
m_dithering,
|
||||
m_rgbmap,
|
||||
get_gray_func(m_toGray),
|
||||
&job)); // SpriteJob is a render::TaskDelegate
|
||||
&job,
|
||||
m_fitCriteria)); // SpriteJob is a render::TaskDelegate
|
||||
});
|
||||
job.waitJob();
|
||||
}
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "app/ui/optional_alert.h"
|
||||
#include "app/ui/status_bar.h"
|
||||
#include "app/ui/timeline/timeline.h"
|
||||
#include "app/util/layer_utils.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/string.h"
|
||||
@ -148,6 +149,17 @@ void destroy_doc(Context* ctx, Doc* doc)
|
||||
}
|
||||
}
|
||||
|
||||
void insert_layers_to_selected_layers(Layer* layer, SelectedLayers& selectedLayers)
|
||||
{
|
||||
if (layer->isGroup()) {
|
||||
auto children = static_cast<LayerGroup*>(layer)->layers();
|
||||
for (auto child : children)
|
||||
insert_layers_to_selected_layers(child, selectedLayers);
|
||||
}
|
||||
else
|
||||
selectedLayers.insert(layer);
|
||||
}
|
||||
|
||||
Doc* generate_sprite_sheet_from_params(
|
||||
DocExporter& exporter,
|
||||
Context* ctx,
|
||||
@ -206,11 +218,14 @@ Doc* generate_sprite_sheet_from_params(
|
||||
if (layerName != kSelectedLayers) {
|
||||
// TODO add a getLayerByName
|
||||
int i = sprite->allLayersCount();
|
||||
for (const Layer* layer : sprite->allLayers()) {
|
||||
for (Layer* layer : sprite->allLayers()) {
|
||||
i--;
|
||||
if (layer->name() == layerName && (layerIndex == -1 ||
|
||||
layerIndex == i)) {
|
||||
selLayers.insert(const_cast<Layer*>(layer));
|
||||
if (get_layer_path(layer) == layerName &&
|
||||
(layerIndex == -1 || layerIndex == i)) {
|
||||
if (layer->isGroup())
|
||||
insert_layers_to_selected_layers(layer, selLayers);
|
||||
else
|
||||
selLayers.insert(layer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -81,9 +81,11 @@ void FlattenLayersCommand::onExecute(Context* context)
|
||||
}
|
||||
}
|
||||
const bool newBlend = Preferences::instance().experimental.newBlend();
|
||||
cmd::FlattenLayers::Options options;
|
||||
options.newBlendMethod = newBlend;
|
||||
tx(new cmd::FlattenLayers(sprite,
|
||||
range.selectedLayers(),
|
||||
newBlend));
|
||||
options));
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
@ -9,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "doc/algorithm/flip_type.h"
|
||||
|
||||
namespace app {
|
||||
@ -24,6 +26,9 @@ namespace app {
|
||||
bool onEnabled(Context* context) override;
|
||||
void onExecute(Context* context) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
bool isListed(const Params& params) const override {
|
||||
return !params.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_flipMask;
|
||||
|
@ -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
|
||||
@ -14,6 +15,7 @@
|
||||
#include "app/context.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/doc_api.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/tx.h"
|
||||
#include "base/convert_to.h"
|
||||
@ -35,6 +37,7 @@ protected:
|
||||
void onLoadParams(const Params& params) override;
|
||||
bool onEnabled(Context* context) override;
|
||||
void onExecute(Context* context) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
|
||||
private:
|
||||
enum Target {
|
||||
@ -137,6 +140,17 @@ void FramePropertiesCommand::onExecute(Context* context)
|
||||
}
|
||||
}
|
||||
|
||||
std::string FramePropertiesCommand::onGetFriendlyName() const
|
||||
{
|
||||
switch (m_target) {
|
||||
case CURRENT_RANGE:
|
||||
return Strings::commands_FrameProperties_Current() ;
|
||||
case ALL_FRAMES:
|
||||
return Strings::commands_FrameProperties_All();
|
||||
}
|
||||
return Command::onGetFriendlyName();
|
||||
}
|
||||
|
||||
Command* CommandFactory::createFramePropertiesCommand()
|
||||
{
|
||||
return new FramePropertiesCommand;
|
||||
|
@ -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
|
||||
|
@ -605,7 +605,8 @@ private:
|
||||
if (key->type() == KeyType::Tool ||
|
||||
key->type() == KeyType::Quicktool ||
|
||||
key->type() == KeyType::WheelAction ||
|
||||
key->type() == KeyType::DragAction) {
|
||||
key->type() == KeyType::DragAction ||
|
||||
key->isListed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2020 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -24,6 +24,10 @@ public:
|
||||
protected:
|
||||
void onLoadParams(const Params& params) override;
|
||||
void onExecute(Context* context) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
bool isListed(const Params& params) const override {
|
||||
return !params.get("path").empty();
|
||||
}
|
||||
|
||||
private:
|
||||
enum Type { Url };
|
||||
@ -59,6 +63,11 @@ void LaunchCommand::onExecute(Context* context)
|
||||
}
|
||||
}
|
||||
|
||||
std::string LaunchCommand::onGetFriendlyName() const
|
||||
{
|
||||
return Command::onGetFriendlyName() + ": " + m_path;
|
||||
}
|
||||
|
||||
Command* CommandFactory::createLaunchCommand()
|
||||
{
|
||||
return new LaunchCommand;
|
||||
|
@ -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
|
||||
|
@ -32,6 +32,7 @@ public:
|
||||
protected:
|
||||
void onLoadParams(const Params& params) override;
|
||||
void onExecute(Context* context) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
|
||||
private:
|
||||
std::string m_preset;
|
||||
@ -88,6 +89,14 @@ void LoadPaletteCommand::onExecute(Context* context)
|
||||
context->executeCommand(cmd);
|
||||
}
|
||||
|
||||
std::string LoadPaletteCommand::onGetFriendlyName() const
|
||||
{
|
||||
std::string name = Command::onGetFriendlyName();
|
||||
if (m_preset == "default")
|
||||
name = Strings::commands_LoadDefaultPalette();
|
||||
return name;
|
||||
}
|
||||
|
||||
Command* CommandFactory::createLoadPaletteCommand()
|
||||
{
|
||||
return new LoadPaletteCommand;
|
||||
|
@ -10,23 +10,18 @@
|
||||
#endif
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/cmd/add_cel.h"
|
||||
#include "app/cmd/replace_image.h"
|
||||
#include "app/cmd/set_cel_position.h"
|
||||
#include "app/cmd/unlink_cel.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/cmd/flatten_layers.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/doc_api.h"
|
||||
#include "app/doc_range.h"
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/tx.h"
|
||||
#include "doc/blend_internals.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/primitives.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "render/rasterize.h"
|
||||
#include "ui/ui.h"
|
||||
|
||||
namespace app {
|
||||
@ -81,86 +76,18 @@ void MergeDownLayerCommand::onExecute(Context* context)
|
||||
|
||||
Tx tx(writer, friendlyName(), ModifyDocument);
|
||||
|
||||
for (frame_t frpos = 0; frpos<sprite->totalFrames(); ++frpos) {
|
||||
// Get frames
|
||||
Cel* src_cel = src_layer->cel(frpos);
|
||||
Cel* dst_cel = dst_layer->cel(frpos);
|
||||
DocRange range;
|
||||
range.selectLayer(writer.layer());
|
||||
range.selectLayer(dst_layer);
|
||||
|
||||
// Get images
|
||||
Image* src_image;
|
||||
if (src_cel != NULL)
|
||||
src_image = src_cel->image();
|
||||
else
|
||||
src_image = NULL;
|
||||
|
||||
ImageRef dst_image;
|
||||
if (dst_cel)
|
||||
dst_image = dst_cel->imageRef();
|
||||
|
||||
// With source image?
|
||||
if (src_image) {
|
||||
int t;
|
||||
int opacity;
|
||||
opacity = MUL_UN8(src_cel->opacity(), src_layer->opacity(), t);
|
||||
|
||||
// No destination image
|
||||
if (!dst_image) { // Only a transparent layer can have a null cel
|
||||
// Copy this cel to the destination layer...
|
||||
|
||||
// Creating a copy of the image
|
||||
dst_image.reset(
|
||||
render::rasterize_with_cel_bounds(src_cel));
|
||||
|
||||
// Creating a copy of the cell
|
||||
dst_cel = new Cel(frpos, dst_image);
|
||||
dst_cel->setPosition(src_cel->x(), src_cel->y());
|
||||
dst_cel->setOpacity(opacity);
|
||||
|
||||
tx(new cmd::AddCel(dst_layer, dst_cel));
|
||||
}
|
||||
// With destination
|
||||
else {
|
||||
gfx::Rect bounds;
|
||||
|
||||
// Merge down in the background layer
|
||||
if (dst_layer->isBackground()) {
|
||||
bounds = sprite->bounds();
|
||||
}
|
||||
// Merge down in a transparent layer
|
||||
else {
|
||||
bounds = src_cel->bounds().createUnion(dst_cel->bounds());
|
||||
}
|
||||
|
||||
doc::color_t bgcolor = app_get_color_to_clear_layer(dst_layer);
|
||||
|
||||
ImageRef new_image(doc::crop_image(
|
||||
dst_image.get(),
|
||||
bounds.x-dst_cel->x(),
|
||||
bounds.y-dst_cel->y(),
|
||||
bounds.w, bounds.h, bgcolor));
|
||||
|
||||
// Draw src_cel on new_image
|
||||
render::rasterize(
|
||||
new_image.get(), src_cel,
|
||||
-bounds.x, -bounds.y, false);
|
||||
|
||||
// First unlink the dst_cel
|
||||
if (dst_cel->links())
|
||||
tx(new cmd::UnlinkCel(dst_cel));
|
||||
|
||||
// Then modify the dst_cel
|
||||
tx(new cmd::SetCelPosition(dst_cel,
|
||||
bounds.x, bounds.y));
|
||||
|
||||
tx(new cmd::ReplaceImage(sprite,
|
||||
dst_cel->imageRef(), new_image));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document->notifyLayerMergedDown(src_layer, dst_layer);
|
||||
document->getApi(tx).removeLayer(src_layer); // src_layer is deleted inside removeLayer()
|
||||
const bool newBlend = Preferences::instance().experimental.newBlend();
|
||||
cmd::FlattenLayers::Options options;
|
||||
options.newBlendMethod = newBlend;
|
||||
options.inplace = true;
|
||||
options.mergeDown = true;
|
||||
options.dynamicCanvas = true;
|
||||
|
||||
tx(new cmd::FlattenLayers(sprite, range.selectedLayers(), options));
|
||||
tx.commit();
|
||||
|
||||
update_screen_for_document(document);
|
||||
|
@ -45,6 +45,7 @@ protected:
|
||||
bool onEnabled(Context* context) override;
|
||||
void onExecute(Context* context) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
bool isListed(const Params& params) const override { return !params.empty(); }
|
||||
|
||||
private:
|
||||
std::string getActionName() const;
|
||||
|
@ -263,6 +263,11 @@ void NewFileCommand::onExecute(Context* ctx)
|
||||
else
|
||||
layer->setName(fmt::format("{} {}", Strings::commands_NewLayer_Layer(), 1));
|
||||
}
|
||||
if (sprite->pixelFormat() == IMAGE_INDEXED) {
|
||||
sprite->rgbMap(0, Sprite::RgbMapFor(!layer->isBackground()),
|
||||
Preferences::instance().quantization.rgbmapAlgorithm(),
|
||||
Preferences::instance().quantization.fitCriteria());
|
||||
}
|
||||
|
||||
// Show the sprite to the user
|
||||
std::unique_ptr<Doc> doc(new Doc(sprite.get()));
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
// Copyright (C) 2016-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -23,6 +24,8 @@ public:
|
||||
protected:
|
||||
void onLoadParams(const Params& params) override;
|
||||
void onExecute(Context* context) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
bool isListed(const Params& params) const override { return !params.empty(); }
|
||||
|
||||
private:
|
||||
std::string m_filename;
|
||||
@ -43,6 +46,11 @@ void OpenBrowserCommand::onExecute(Context* context)
|
||||
App::instance()->mainWindow()->showBrowser(m_filename);
|
||||
}
|
||||
|
||||
std::string OpenBrowserCommand::onGetFriendlyName() const
|
||||
{
|
||||
return Command::onGetFriendlyName() + ": " + m_filename;
|
||||
}
|
||||
|
||||
Command* CommandFactory::createOpenBrowserCommand()
|
||||
{
|
||||
return new OpenBrowserCommand;
|
||||
|
@ -269,6 +269,21 @@ void OpenFileCommand::onExecute(Context* context)
|
||||
}
|
||||
}
|
||||
|
||||
std::string OpenFileCommand::onGetFriendlyName() const
|
||||
{
|
||||
// TO DO: would be better to show the last part of the path
|
||||
// via text size hint instead of a fixed number of chars.
|
||||
auto uiScale = Preferences::instance().general.uiScale();
|
||||
auto scScale = Preferences::instance().general.screenScale();
|
||||
int pos(68.0 / double(uiScale) / double(scScale));
|
||||
return Command::onGetFriendlyName().append(
|
||||
(m_filename.empty() ?
|
||||
"" :
|
||||
(": " + (m_filename.size() >= pos ?
|
||||
m_filename.substr(m_filename.size() - pos, pos) :
|
||||
m_filename))));
|
||||
}
|
||||
|
||||
Command* CommandFactory::createOpenFileCommand()
|
||||
{
|
||||
return new OpenFileCommand;
|
||||
|
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "base/paths.h"
|
||||
|
||||
@ -32,6 +33,7 @@ namespace app {
|
||||
protected:
|
||||
void onLoadParams(const Params& params) override;
|
||||
void onExecute(Context* context) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
|
||||
private:
|
||||
std::string m_filename;
|
||||
|
@ -25,7 +25,9 @@
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/recent_files.h"
|
||||
#include "app/resource_finder.h"
|
||||
#include "app/tools/tool_box.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/ui/best_fit_criteria_selector.h"
|
||||
#include "app/ui/color_button.h"
|
||||
#include "app/ui/main_window.h"
|
||||
#include "app/ui/pref_widget.h"
|
||||
@ -272,14 +274,7 @@ public:
|
||||
// Theme variants
|
||||
fillThemeVariants();
|
||||
|
||||
// Default extension to save files
|
||||
fillExtensionsCombobox(defaultExtension(), m_pref.saveFile.defaultExtension());
|
||||
fillExtensionsCombobox(exportImageDefaultExtension(), m_pref.exportFile.imageDefaultExtension());
|
||||
fillExtensionsCombobox(exportAnimationDefaultExtension(), m_pref.exportFile.animationDefaultExtension());
|
||||
fillExtensionsCombobox(exportSpriteSheetDefaultExtension(), m_pref.spriteSheet.defaultExtension());
|
||||
|
||||
// Number of recent items
|
||||
recentFiles()->setValue(m_pref.general.recentItems());
|
||||
// Recent files
|
||||
clearRecentFiles()->Click.connect([this]{ onClearRecentFiles(); });
|
||||
|
||||
// Template item for active display color profiles
|
||||
@ -295,31 +290,24 @@ public:
|
||||
if (cs->gfxColorSpace()->type() != gfx::ColorSpace::None)
|
||||
workingRgbCs()->addItem(new ColorSpaceItem(cs));
|
||||
}
|
||||
updateColorProfileControls(m_pref.color.manage(),
|
||||
m_pref.color.windowProfile(),
|
||||
m_pref.color.windowProfileName(),
|
||||
m_pref.color.workingRgbSpace(),
|
||||
m_pref.color.filesWithProfile(),
|
||||
m_pref.color.missingProfile());
|
||||
}
|
||||
|
||||
// Alerts
|
||||
openSequence()->setSelectedItemIndex(int(m_pref.openFile.openSequence()));
|
||||
resetAlerts()->Click.connect([this]{ onResetAlerts(); });
|
||||
|
||||
// Cursor
|
||||
paintingCursorType()->setSelectedItemIndex(int(m_pref.cursor.paintingCursorType()));
|
||||
cursorColor()->setColor(m_pref.cursor.cursorColor());
|
||||
|
||||
if (cursorColor()->getColor().getType() == app::Color::MaskType) {
|
||||
cursorColorType()->setSelectedItemIndex(0);
|
||||
cursorColor()->setVisible(false);
|
||||
}
|
||||
else {
|
||||
cursorColorType()->setSelectedItemIndex(1);
|
||||
cursorColor()->setVisible(true);
|
||||
}
|
||||
cursorColorType()->Change.connect([this]{ onCursorColorType(); });
|
||||
nativeCursor()->Click.connect([this]{ onNativeCursorChange(); });
|
||||
|
||||
// Dialogs
|
||||
showAsepriteFileDialog()->Click.connect([this]{
|
||||
nativeFileDialog()->setSelected(
|
||||
!showAsepriteFileDialog()->isSelected());
|
||||
});
|
||||
nativeFileDialog()->Click.connect([this]{
|
||||
showAsepriteFileDialog()->setSelected(
|
||||
!nativeFileDialog()->isSelected());
|
||||
});
|
||||
|
||||
// Grid
|
||||
gridW()->Leave.connect([this] {
|
||||
@ -333,27 +321,10 @@ public:
|
||||
gridH()->setText("1");
|
||||
});
|
||||
|
||||
// Brush preview
|
||||
brushPreview()->setSelectedItemIndex(
|
||||
(int)m_pref.cursor.brushPreview());
|
||||
|
||||
// Guide colors
|
||||
layerEdgesColor()->setColor(m_pref.guides.layerEdgesColor());
|
||||
autoGuidesColor()->setColor(m_pref.guides.autoGuidesColor());
|
||||
|
||||
// Slices default color
|
||||
defaultSliceColor()->setColor(m_pref.slices.defaultColor());
|
||||
|
||||
// Timeline
|
||||
firstFrame()->setTextf("%d", m_globPref.timeline.firstFrame());
|
||||
resetTimelineSel()->Click.connect([this]{ onResetTimelineSel(); });
|
||||
|
||||
// Others
|
||||
if (m_pref.general.expandMenubarOnMouseover())
|
||||
expandMenubarOnMouseover()->setSelected(true);
|
||||
|
||||
if (m_pref.general.dataRecovery())
|
||||
enableDataRecovery()->setSelected(true);
|
||||
enableDataRecovery()->Click.connect(
|
||||
[this](){
|
||||
const bool state = enableDataRecovery()->isSelected();
|
||||
@ -362,100 +333,10 @@ public:
|
||||
keepEditedSpriteDataFor()->setEnabled(state);
|
||||
});
|
||||
|
||||
if (m_pref.general.dataRecovery() &&
|
||||
m_pref.general.keepEditedSpriteData())
|
||||
keepEditedSpriteData()->setSelected(true);
|
||||
else if (!m_pref.general.dataRecovery()) {
|
||||
keepEditedSpriteData()->setEnabled(false);
|
||||
keepEditedSpriteDataFor()->setEnabled(false);
|
||||
}
|
||||
|
||||
if (m_pref.general.keepClosedSpriteOnMemory())
|
||||
keepClosedSpriteOnMemory()->setSelected(true);
|
||||
|
||||
if (m_pref.general.showFullPath())
|
||||
showFullPath()->setSelected(true);
|
||||
|
||||
dataRecoveryPeriod()->setSelectedItemIndex(
|
||||
dataRecoveryPeriod()->findItemIndexByValue(
|
||||
base::convert_to<std::string>(m_pref.general.dataRecoveryPeriod())));
|
||||
|
||||
keepEditedSpriteDataFor()->setSelectedItemIndex(
|
||||
keepEditedSpriteDataFor()->findItemIndexByValue(
|
||||
base::convert_to<std::string>(m_pref.general.keepEditedSpriteDataFor())));
|
||||
|
||||
keepClosedSpriteOnMemoryFor()->setSelectedItemIndex(
|
||||
keepClosedSpriteOnMemoryFor()->findItemIndexByValue(
|
||||
base::convert_to<std::string>(m_pref.general.keepClosedSpriteOnMemoryFor())));
|
||||
|
||||
if (m_pref.editor.zoomFromCenterWithWheel())
|
||||
zoomFromCenterWithWheel()->setSelected(true);
|
||||
|
||||
if (m_pref.editor.zoomFromCenterWithKeys())
|
||||
zoomFromCenterWithKeys()->setSelected(true);
|
||||
|
||||
if (m_pref.selection.autoOpaque())
|
||||
autoOpaque()->setSelected(true);
|
||||
|
||||
if (m_pref.selection.keepSelectionAfterClear())
|
||||
keepSelectionAfterClear()->setSelected(true);
|
||||
|
||||
if (m_pref.selection.autoShowSelectionEdges())
|
||||
autoShowSelectionEdges()->setSelected(true);
|
||||
|
||||
if (m_pref.selection.moveEdges())
|
||||
moveEdges()->setSelected(true);
|
||||
|
||||
if (m_pref.selection.modifiersDisableHandles())
|
||||
modifiersDisableHandles()->setSelected(true);
|
||||
|
||||
if (m_pref.selection.moveOnAddMode())
|
||||
moveOnAddMode()->setSelected(true);
|
||||
|
||||
// If the platform supports native cursors...
|
||||
if (m_system->hasCapability(os::Capabilities::CustomMouseCursor)) {
|
||||
if (m_pref.cursor.useNativeCursor())
|
||||
nativeCursor()->setSelected(true);
|
||||
nativeCursor()->Click.connect([this]{ onNativeCursorChange(); });
|
||||
|
||||
cursorScale()->setSelectedItemIndex(
|
||||
cursorScale()->findItemIndexByValue(
|
||||
base::convert_to<std::string>(m_pref.cursor.cursorScale())));
|
||||
}
|
||||
else {
|
||||
nativeCursor()->setEnabled(false);
|
||||
}
|
||||
|
||||
onNativeCursorChange();
|
||||
|
||||
// "Show Aseprite file dialog" option is the inverse of the old
|
||||
// experimental "use native file dialog" option
|
||||
showAsepriteFileDialog()->setSelected(
|
||||
!m_pref.experimental.useNativeFileDialog());
|
||||
showAsepriteFileDialog()->Click.connect([this]{
|
||||
nativeFileDialog()->setSelected(
|
||||
!showAsepriteFileDialog()->isSelected());
|
||||
});
|
||||
nativeFileDialog()->Click.connect([this]{
|
||||
showAsepriteFileDialog()->setSelected(
|
||||
!nativeFileDialog()->isSelected());
|
||||
});
|
||||
|
||||
#ifdef LAF_WINDOWS // Show Tablet section on Windows
|
||||
{
|
||||
const os::TabletAPI tabletAPI = m_system->tabletOptions().api;
|
||||
if (tabletAPI == os::TabletAPI::Wintab)
|
||||
tabletApiWintabSystem()->setSelected(true);
|
||||
else if (tabletAPI == os::TabletAPI::WintabPackets)
|
||||
tabletApiWintabDirect()->setSelected(true);
|
||||
else
|
||||
tabletApiWindowsPointer()->setSelected(true);
|
||||
onTabletAPIChange();
|
||||
|
||||
tabletApiWindowsPointer()->Click.connect([this](){ onTabletAPIChange(); });
|
||||
tabletApiWintabSystem()->Click.connect([this](){ onTabletAPIChange(); });
|
||||
tabletApiWintabDirect()->Click.connect([this](){ onTabletAPIChange(); });
|
||||
}
|
||||
tabletApiWindowsPointer()->Click.connect([this](){ onTabletAPIChange(); });
|
||||
tabletApiWintabSystem()->Click.connect([this](){ onTabletAPIChange(); });
|
||||
tabletApiWintabDirect()->Click.connect([this](){ onTabletAPIChange(); });
|
||||
#else // For macOS and Linux
|
||||
{
|
||||
// Hide the "section_tablet" item (which is only for Windows at the moment)
|
||||
@ -469,26 +350,11 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_pref.experimental.flashLayer())
|
||||
flashLayer()->setSelected(true);
|
||||
|
||||
nonactiveLayersOpacity()->setValue(m_pref.experimental.nonactiveLayersOpacity());
|
||||
|
||||
rgbmapAlgorithmPlaceholder()->addChild(&m_rgbmapAlgorithmSelector);
|
||||
m_rgbmapAlgorithmSelector.setExpansive(true);
|
||||
m_rgbmapAlgorithmSelector.algorithm(m_pref.quantization.rgbmapAlgorithm());
|
||||
|
||||
if (m_pref.editor.showScrollbars())
|
||||
showScrollbars()->setSelected(true);
|
||||
|
||||
if (m_pref.editor.autoScroll())
|
||||
autoScroll()->setSelected(true);
|
||||
|
||||
if (m_pref.editor.straightLinePreview())
|
||||
straightLinePreview()->setSelected(true);
|
||||
|
||||
if (m_pref.eyedropper.discardBrush())
|
||||
discardBrush()->setSelected(true);
|
||||
bestFitCriteriaPlaceholder()->addChild(&m_bestFitCriteriaSelector);
|
||||
m_bestFitCriteriaSelector.setExpansive(true);
|
||||
|
||||
// Scope
|
||||
bgScope()->addItem(Strings::options_bg_for_new_docs());
|
||||
@ -503,9 +369,6 @@ public:
|
||||
gridScope()->Change.connect([this]{ onChangeGridScope(); });
|
||||
}
|
||||
|
||||
// Update the one/multiple window buttonset (and keep in on sync
|
||||
// with the old/experimental checkbox)
|
||||
uiWindows()->setSelectedItem(multipleWindows()->isSelected() ? 1: 0);
|
||||
uiWindows()->ItemChange.connect([this]() {
|
||||
multipleWindows()->setSelected(uiWindows()->selectedItem() == 1);
|
||||
});
|
||||
@ -513,29 +376,17 @@ public:
|
||||
uiWindows()->setSelectedItem(multipleWindows()->isSelected() ? 1: 0);
|
||||
});
|
||||
|
||||
// Scaling
|
||||
selectScalingItems();
|
||||
|
||||
#ifdef ENABLE_DEVMODE // TODO enable this on Release when Aseprite supports
|
||||
// GPU-acceleration properly
|
||||
if (m_system->hasCapability(os::Capabilities::GpuAccelerationSwitch)) {
|
||||
gpuAcceleration()->setSelected(m_pref.general.gpuAcceleration());
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
#ifndef ENABLE_DEVMODE // TODO enable this on Release when Aseprite supports
|
||||
// GPU-acceleration properly
|
||||
if (!m_system->hasCapability(os::Capabilities::GpuAccelerationSwitch))
|
||||
gpuAcceleration()->setVisible(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
// If the platform does support native menus, we show the option,
|
||||
// in other case, the option doesn't make sense for this platform.
|
||||
if (m_system->menus())
|
||||
showMenuBar()->setSelected(m_pref.general.showMenuBar());
|
||||
else
|
||||
if (!m_system->menus())
|
||||
showMenuBar()->setVisible(false);
|
||||
|
||||
showHome()->setSelected(m_pref.general.showHome());
|
||||
|
||||
// Editor sampling
|
||||
samplingPlaceholder()->addChild(
|
||||
m_samplingSelector = new SamplingSelector(
|
||||
@ -563,7 +414,6 @@ public:
|
||||
rightClickBehavior()->addItem(Strings::options_right_click_rectangular_marquee());
|
||||
rightClickBehavior()->addItem(Strings::options_right_click_lasso());
|
||||
rightClickBehavior()->addItem(Strings::options_right_click_select_layer_and_move());
|
||||
rightClickBehavior()->setSelectedItemIndex((int)m_pref.editor.rightClickMode());
|
||||
|
||||
#ifndef __APPLE__ // Zoom sliding two fingers option only on macOS
|
||||
slideZoom()->setVisible(false);
|
||||
@ -601,11 +451,6 @@ public:
|
||||
|
||||
// Undo preferences
|
||||
limitUndo()->Click.connect([this]{ onLimitUndoCheck(); });
|
||||
limitUndo()->setSelected(m_pref.undo.sizeLimit() != 0);
|
||||
onLimitUndoCheck();
|
||||
|
||||
undoGotoModified()->setSelected(m_pref.undo.gotoModified());
|
||||
undoAllowNonlinearHistory()->setSelected(m_pref.undo.allowNonlinearHistory());
|
||||
|
||||
// Theme buttons
|
||||
themeList()->Change.connect([this]{ onThemeChange(); });
|
||||
@ -620,13 +465,27 @@ public:
|
||||
uninstallExtension()->Click.connect([this]{ onUninstallExtension(); });
|
||||
openExtensionFolder()->Click.connect([this]{ onOpenExtensionFolder(); });
|
||||
|
||||
// Reset checkboxes
|
||||
|
||||
// Prevent the user from clicking "Reset" if they don't have anything selected.
|
||||
auto validateYesButton = [this] {
|
||||
resetSelectedButton()->setEnabled(
|
||||
defaultReset()->isSelected() || installedReset()->isSelected() ||
|
||||
recentReset()->isSelected() || perfileReset()->isSelected() ||
|
||||
toolsReset()->isSelected());
|
||||
};
|
||||
defaultReset()->Click.connect(validateYesButton);
|
||||
installedReset()->Click.connect(validateYesButton);
|
||||
recentReset()->Click.connect(validateYesButton);
|
||||
perfileReset()->Click.connect(validateYesButton);
|
||||
toolsReset()->Click.connect(validateYesButton);
|
||||
resetSelectedButton()->Click.connect([this] { onResetDefault(); });
|
||||
|
||||
defaultReset()->setSelected(true);
|
||||
|
||||
// Apply button
|
||||
buttonApply()->Click.connect([this]{ onApply(); });
|
||||
|
||||
onChangeBgScope();
|
||||
onChangeGridScope();
|
||||
sectionListbox()->selectIndex(m_curSection);
|
||||
|
||||
// Refill languages combobox when extensions are enabled/disabled
|
||||
m_extLanguagesChanges =
|
||||
App::instance()->extensions().LanguagesChange.connect(
|
||||
@ -636,15 +495,178 @@ public:
|
||||
m_extThemesChanges =
|
||||
App::instance()->extensions().ThemesChange.connect(
|
||||
[this]{ reloadThemes(); });
|
||||
|
||||
loadFromPreferences();
|
||||
}
|
||||
|
||||
void loadFromPreferences() {
|
||||
// Default extension to save files
|
||||
fillExtensionsCombobox(defaultExtension(), m_pref.saveFile.defaultExtension());
|
||||
fillExtensionsCombobox(exportImageDefaultExtension(), m_pref.exportFile.imageDefaultExtension());
|
||||
fillExtensionsCombobox(exportAnimationDefaultExtension(), m_pref.exportFile.animationDefaultExtension());
|
||||
fillExtensionsCombobox(exportSpriteSheetDefaultExtension(), m_pref.spriteSheet.defaultExtension());
|
||||
|
||||
// Number of recent items
|
||||
recentFiles()->setValue(m_pref.general.recentItems());
|
||||
|
||||
// Color profiles
|
||||
updateColorProfileControls(m_pref.color.manage(),
|
||||
m_pref.color.windowProfile(),
|
||||
m_pref.color.windowProfileName(),
|
||||
m_pref.color.workingRgbSpace(),
|
||||
m_pref.color.filesWithProfile(),
|
||||
m_pref.color.missingProfile());
|
||||
|
||||
// Alerts
|
||||
openSequence()->setSelectedItemIndex(int(m_pref.openFile.openSequence()));
|
||||
|
||||
// Cursor
|
||||
paintingCursorType()->setSelectedItemIndex(int(m_pref.cursor.paintingCursorType()));
|
||||
cursorColor()->setColor(m_pref.cursor.cursorColor());
|
||||
|
||||
if (cursorColor()->getColor().getType() == app::Color::MaskType) {
|
||||
cursorColorType()->setSelectedItemIndex(0);
|
||||
cursorColor()->setVisible(false);
|
||||
}
|
||||
else {
|
||||
cursorColorType()->setSelectedItemIndex(1);
|
||||
cursorColor()->setVisible(true);
|
||||
}
|
||||
|
||||
// Brush preview
|
||||
brushPreview()->setSelectedItemIndex(
|
||||
(int)m_pref.cursor.brushPreview());
|
||||
|
||||
// Guide colors
|
||||
layerEdgesColor()->setColor(m_pref.guides.layerEdgesColor());
|
||||
autoGuidesColor()->setColor(m_pref.guides.autoGuidesColor());
|
||||
|
||||
// Slices default color
|
||||
defaultSliceColor()->setColor(m_pref.slices.defaultColor());
|
||||
|
||||
// Timeline
|
||||
firstFrame()->setTextf("%d", m_globPref.timeline.firstFrame());
|
||||
|
||||
// Others
|
||||
expandMenubarOnMouseover()->setSelected(m_pref.general.expandMenubarOnMouseover());
|
||||
|
||||
enableDataRecovery()->setSelected(m_pref.general.dataRecovery());
|
||||
|
||||
if (m_pref.general.dataRecovery() &&
|
||||
m_pref.general.keepEditedSpriteData())
|
||||
keepEditedSpriteData()->setSelected(true);
|
||||
else if (!m_pref.general.dataRecovery()) {
|
||||
keepEditedSpriteData()->setEnabled(false);
|
||||
keepEditedSpriteDataFor()->setEnabled(false);
|
||||
}
|
||||
|
||||
keepClosedSpriteOnMemory()->setSelected(m_pref.general.keepClosedSpriteOnMemory());
|
||||
showFullPath()->setSelected(m_pref.general.showFullPath());
|
||||
|
||||
dataRecoveryPeriod()->setSelectedItemIndex(
|
||||
dataRecoveryPeriod()->findItemIndexByValue(
|
||||
base::convert_to<std::string>(m_pref.general.dataRecoveryPeriod())));
|
||||
|
||||
keepEditedSpriteDataFor()->setSelectedItemIndex(
|
||||
keepEditedSpriteDataFor()->findItemIndexByValue(
|
||||
base::convert_to<std::string>(m_pref.general.keepEditedSpriteDataFor())));
|
||||
|
||||
keepClosedSpriteOnMemoryFor()->setSelectedItemIndex(
|
||||
keepClosedSpriteOnMemoryFor()->findItemIndexByValue(
|
||||
base::convert_to<std::string>(m_pref.general.keepClosedSpriteOnMemoryFor())));
|
||||
|
||||
zoomFromCenterWithWheel()->setSelected(m_pref.editor.zoomFromCenterWithWheel());
|
||||
zoomFromCenterWithKeys()->setSelected(m_pref.editor.zoomFromCenterWithKeys());
|
||||
autoOpaque()->setSelected(m_pref.selection.autoOpaque());
|
||||
keepSelectionAfterClear()->setSelected(m_pref.selection.keepSelectionAfterClear());
|
||||
autoShowSelectionEdges()->setSelected( m_pref.selection.autoShowSelectionEdges());
|
||||
moveEdges()->setSelected(m_pref.selection.moveEdges());
|
||||
modifiersDisableHandles()->setSelected(m_pref.selection.modifiersDisableHandles());
|
||||
moveOnAddMode()->setSelected(m_pref.selection.moveOnAddMode());
|
||||
|
||||
// If the platform supports native cursors...
|
||||
if ((int(m_system->capabilities()) &
|
||||
int(os::Capabilities::CustomMouseCursor)) != 0) {
|
||||
nativeCursor()->setSelected(m_pref.cursor.useNativeCursor());
|
||||
|
||||
cursorScale()->setSelectedItemIndex(
|
||||
cursorScale()->findItemIndexByValue(
|
||||
base::convert_to<std::string>(m_pref.cursor.cursorScale())));
|
||||
}
|
||||
else {
|
||||
nativeCursor()->setEnabled(false);
|
||||
}
|
||||
|
||||
onNativeCursorChange();
|
||||
|
||||
// "Show Aseprite file dialog" option is the inverse of the old
|
||||
// experimental "use native file dialog" option
|
||||
showAsepriteFileDialog()->setSelected(
|
||||
!m_pref.experimental.useNativeFileDialog());
|
||||
|
||||
#ifdef LAF_WINDOWS // Show Tablet section on Windows
|
||||
{
|
||||
const os::TabletAPI tabletAPI = m_system->tabletOptions().api;
|
||||
if (tabletAPI == os::TabletAPI::Wintab)
|
||||
tabletApiWintabSystem()->setSelected(true);
|
||||
else if (tabletAPI == os::TabletAPI::WintabPackets)
|
||||
tabletApiWintabDirect()->setSelected(true);
|
||||
else
|
||||
tabletApiWindowsPointer()->setSelected(true);
|
||||
|
||||
onTabletAPIChange();
|
||||
}
|
||||
#endif
|
||||
|
||||
flashLayer()->setSelected(m_pref.experimental.flashLayer());
|
||||
nonactiveLayersOpacity()->setValue(m_pref.experimental.nonactiveLayersOpacity());
|
||||
|
||||
m_rgbmapAlgorithmSelector.algorithm(m_pref.quantization.rgbmapAlgorithm());
|
||||
m_bestFitCriteriaSelector.criteria(m_pref.quantization.fitCriteria());
|
||||
|
||||
showScrollbars()->setSelected(m_pref.editor.showScrollbars());
|
||||
autoScroll()->setSelected(m_pref.editor.autoScroll());
|
||||
straightLinePreview()->setSelected(m_pref.editor.straightLinePreview());
|
||||
discardBrush()->setSelected(m_pref.eyedropper.discardBrush());
|
||||
|
||||
// Update the one/multiple window buttonset (and keep in on sync
|
||||
// with the old/experimental checkbox)
|
||||
uiWindows()->setSelectedItem(multipleWindows()->isSelected() ? 1 : 0);
|
||||
|
||||
// Scaling
|
||||
selectScalingItems();
|
||||
|
||||
if (m_system->hasCapability(os::Capabilities::GpuAccelerationSwitch)) {
|
||||
gpuAcceleration()->setSelected(m_pref.general.gpuAcceleration());
|
||||
}
|
||||
|
||||
if (m_system->menus())
|
||||
showMenuBar()->setSelected(m_pref.general.showMenuBar());
|
||||
|
||||
showHome()->setSelected(m_pref.general.showHome());
|
||||
|
||||
// Right-click
|
||||
rightClickBehavior()->setSelectedItemIndex((int)m_pref.editor.rightClickMode());
|
||||
|
||||
// Undo preferences
|
||||
limitUndo()->setSelected(m_pref.undo.sizeLimit() != 0);
|
||||
onLimitUndoCheck();
|
||||
|
||||
undoGotoModified()->setSelected(m_pref.undo.gotoModified());
|
||||
undoAllowNonlinearHistory()->setSelected(m_pref.undo.allowNonlinearHistory());
|
||||
|
||||
onChangeBgScope();
|
||||
onChangeGridScope();
|
||||
sectionListbox()->selectIndex(m_curSection);
|
||||
}
|
||||
|
||||
bool ok() {
|
||||
return (closer() == buttonOk());
|
||||
}
|
||||
|
||||
void saveConfig() {
|
||||
void saveConfig(bool propagate = true) {
|
||||
// Save preferences in widgets that are bound to options automatically
|
||||
{
|
||||
if (propagate) {
|
||||
Message msg(kSavePreferencesMessage);
|
||||
msg.setPropagateToChildren(true);
|
||||
sendMessage(&msg);
|
||||
@ -756,7 +778,7 @@ public:
|
||||
int j = 2;
|
||||
for (auto& cs : m_colorSpaces) {
|
||||
// We add ICC profiles only
|
||||
auto gfxCs = cs->gfxColorSpace();
|
||||
auto& gfxCs = cs->gfxColorSpace();
|
||||
if (gfxCs->type() != gfx::ColorSpace::ICC)
|
||||
continue;
|
||||
|
||||
@ -828,6 +850,7 @@ public:
|
||||
m_pref.experimental.flashLayer(flashLayer()->isSelected());
|
||||
m_pref.experimental.nonactiveLayersOpacity(nonactiveLayersOpacity()->getValue());
|
||||
m_pref.quantization.rgbmapAlgorithm(m_rgbmapAlgorithmSelector.algorithm());
|
||||
m_pref.quantization.fitCriteria(m_bestFitCriteriaSelector.criteria());
|
||||
|
||||
#ifdef LAF_WINDOWS
|
||||
{
|
||||
@ -893,9 +916,7 @@ public:
|
||||
m_pref.general.showMenuBar(showMenuBar()->isSelected());
|
||||
}
|
||||
|
||||
bool newShowHome = showHome()->isSelected();
|
||||
if (newShowHome != m_pref.general.showHome())
|
||||
m_pref.general.showHome(newShowHome);
|
||||
m_pref.general.showHome(showHome()->isSelected());
|
||||
|
||||
m_pref.save();
|
||||
|
||||
@ -904,8 +925,7 @@ public:
|
||||
}
|
||||
|
||||
// Probably it's safe to switch this flag in runtime
|
||||
if (m_pref.experimental.multipleWindows() != ui::get_multiple_displays())
|
||||
ui::set_multiple_displays(m_pref.experimental.multipleWindows());
|
||||
ui::set_multiple_displays(m_pref.experimental.multipleWindows());
|
||||
|
||||
if (reset_screen)
|
||||
updateScreenScaling();
|
||||
@ -928,6 +948,15 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void restoreDefaultTheme() {
|
||||
setUITheme(m_pref.theme.selected.defaultValue(), false);
|
||||
m_pref.general.screenScale.setValue(
|
||||
skin::SkinTheme::get(this)->preferredScreenScaling());
|
||||
m_pref.general.uiScale.setValue(
|
||||
skin::SkinTheme::get(this)->preferredUIScaling());
|
||||
updateScreenScaling();
|
||||
}
|
||||
|
||||
bool showDialogToInstallExtension(const std::string& filename) {
|
||||
for (Widget* item : sectionListbox()->children()) {
|
||||
if (auto listItem = dynamic_cast<const ListItem*>(item)) {
|
||||
@ -1037,6 +1066,133 @@ private:
|
||||
m_restoreUIScaling = m_pref.general.uiScale();
|
||||
}
|
||||
|
||||
void onResetDefault() {
|
||||
if (ui::Alert::show(Strings::alerts_reset_default_confirm()) != 1)
|
||||
return;
|
||||
|
||||
if (recentReset()->isSelected()) {
|
||||
auto prevLimit = m_pref.general.recentItems();
|
||||
App::instance()->recentFiles()->setLimit(0);
|
||||
App::instance()->recentFiles()->setLimit(prevLimit);
|
||||
}
|
||||
|
||||
if (installedReset()->isSelected()) {
|
||||
// If we're not on the default theme, restore it, since we're gonna be deleting it.
|
||||
restoreDefaultTheme();
|
||||
|
||||
// Load a list with the extensions we can uninstall first, to avoid iterator issues when deleting in-loop.
|
||||
Extensions::List uninstall;
|
||||
for (auto* e : App::instance()->extensions()) {
|
||||
if (!e->canBeUninstalled())
|
||||
continue;
|
||||
|
||||
uninstall.push_back(e);
|
||||
}
|
||||
|
||||
for (auto* e : uninstall) {
|
||||
try {
|
||||
App::instance()->extensions().uninstallExtension(
|
||||
e, DeletePluginPref::kYes);
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
LOG(ERROR, "Uninstalling extension '%s' failed with error '%s'\n",
|
||||
e->displayName().c_str(),
|
||||
ex.what());
|
||||
Console::showException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
ResourceFinder rf;
|
||||
rf.includeUserDir("palettes");
|
||||
const auto& paletteDir = rf.defaultFilename();
|
||||
for (const auto& item : base::list_files(paletteDir)) {
|
||||
const auto path = base::join_path(paletteDir, item);
|
||||
if (base::is_file(path) &&
|
||||
item != "default.ase" &&
|
||||
base::string_to_lower(base::get_file_extension(path)) == "ase") {
|
||||
|
||||
try {
|
||||
base::delete_file(path);
|
||||
LOG(VERBOSE, "Deleted palette: '%s'\n", item.c_str());
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
LOG(ERROR,
|
||||
"Error deleting palette file: %s - %s",
|
||||
path.c_str(),
|
||||
ex.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (perfileReset()->isSelected()) {
|
||||
ResourceFinder rf;
|
||||
rf.includeUserDir("files");
|
||||
const auto& filesDirectory = rf.defaultFilename();
|
||||
|
||||
for (const auto& item : base::list_files(filesDirectory)) {
|
||||
const auto path = base::join_path(filesDirectory, item);
|
||||
if (base::is_file(path) && base::string_to_lower(base::get_file_extension(path)) == "ini") {
|
||||
try {
|
||||
base::delete_file(path);
|
||||
LOG(VERBOSE, "Deleted per-file setting '%s'\n", item.c_str());
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
LOG(ERROR,
|
||||
"Error deleting ini file: %s - %s",
|
||||
path.c_str(),
|
||||
ex.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toolsReset()->isSelected()) {
|
||||
auto* toolBox = App::instance()->toolBox();
|
||||
for (tools::ToolIterator it = toolBox->begin(); it != toolBox->end(); ++it) {
|
||||
tools::Tool* tool = *it;
|
||||
m_pref.resetToolPreferences(tool);
|
||||
LOG(VERBOSE, "Reset tool preferences for tool '%s'\n", tool->getId().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultReset()->isSelected()) {
|
||||
onResetAlerts();
|
||||
onResetBg();
|
||||
onResetColorManagement();
|
||||
onResetGrid();
|
||||
onResetTimelineSel();
|
||||
|
||||
// If we're not on the default theme, restore it.
|
||||
m_restoreThisTheme = m_pref.theme.selected.defaultValue();
|
||||
restoreTheme();
|
||||
|
||||
// Resetting all things.
|
||||
for (Section* section : m_pref.sectionList()) {
|
||||
for (OptionBase* option : section->optionList()) {
|
||||
option->resetToDefault();
|
||||
}
|
||||
section->save();
|
||||
}
|
||||
|
||||
restoreDefaultTheme();
|
||||
|
||||
m_pref.save();
|
||||
loadFromPreferences();
|
||||
|
||||
// Temporarily set the language preference to an empty string
|
||||
// to avoid setCurrentLanguage ignoring the change.
|
||||
m_pref.general.language("");
|
||||
Strings::instance()->setCurrentLanguage(Strings::kDefLanguage);
|
||||
|
||||
// Language reset
|
||||
refillLanguages();
|
||||
}
|
||||
|
||||
saveConfig(false);
|
||||
closeWindow(nullptr);
|
||||
}
|
||||
|
||||
void onNativeCursorChange() {
|
||||
bool state =
|
||||
// If the platform supports custom cursors...
|
||||
@ -1126,11 +1282,11 @@ private:
|
||||
int j = 2;
|
||||
for (auto& cs : m_colorSpaces) {
|
||||
// We add ICC profiles only
|
||||
auto gfxCs = cs->gfxColorSpace();
|
||||
auto& gfxCs = cs->gfxColorSpace();
|
||||
if (gfxCs->type() != gfx::ColorSpace::ICC)
|
||||
continue;
|
||||
|
||||
auto name = gfxCs->name();
|
||||
auto& name = gfxCs->name();
|
||||
windowCs()->addItem(fmt::format(m_templateTextForDisplayCS, name));
|
||||
if (windowProfile == gen::WindowColorProfile::SPECIFIC &&
|
||||
windowProfileName == name) {
|
||||
@ -1179,6 +1335,7 @@ private:
|
||||
jpegOptionsAlert()->resetWithDefaultValue();
|
||||
svgOptionsAlert()->resetWithDefaultValue();
|
||||
tgaOptionsAlert()->resetWithDefaultValue();
|
||||
webpOptionsAlert()->resetWithDefaultValue();
|
||||
}
|
||||
|
||||
void onChangeBgScope() {
|
||||
@ -1842,6 +1999,7 @@ private:
|
||||
std::vector<os::ColorSpaceRef> m_colorSpaces;
|
||||
std::string m_templateTextForDisplayCS;
|
||||
RgbMapAlgorithmSelector m_rgbmapAlgorithmSelector;
|
||||
BestFitCriteriaSelector m_bestFitCriteriaSelector;
|
||||
ButtonSet* m_themeVars = nullptr;
|
||||
SamplingSelector* m_samplingSelector = nullptr;
|
||||
};
|
||||
|
@ -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
|
||||
@ -10,8 +11,11 @@
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "app/ui/input_chain.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace app {
|
||||
|
||||
class PasteCommand : public Command {
|
||||
@ -19,8 +23,11 @@ public:
|
||||
PasteCommand();
|
||||
|
||||
protected:
|
||||
void onLoadParams(const Params& params) override;
|
||||
bool onEnabled(Context* ctx) override;
|
||||
void onExecute(Context* ctx) override;
|
||||
private:
|
||||
std::unique_ptr<gfx::Point> m_position;
|
||||
};
|
||||
|
||||
PasteCommand::PasteCommand()
|
||||
@ -28,6 +35,16 @@ PasteCommand::PasteCommand()
|
||||
{
|
||||
}
|
||||
|
||||
void PasteCommand::onLoadParams(const Params& params)
|
||||
{
|
||||
m_position.reset();
|
||||
if (params.has_param("x") || params.has_param("y")) {
|
||||
m_position.reset(new gfx::Point);
|
||||
m_position->x = params.get_as<int>("x");
|
||||
m_position->y = params.get_as<int>("y");
|
||||
}
|
||||
}
|
||||
|
||||
bool PasteCommand::onEnabled(Context* ctx)
|
||||
{
|
||||
return App::instance()->inputChain().canPaste(ctx);
|
||||
@ -35,7 +52,7 @@ bool PasteCommand::onEnabled(Context* ctx)
|
||||
|
||||
void PasteCommand::onExecute(Context* ctx)
|
||||
{
|
||||
App::instance()->inputChain().paste(ctx);
|
||||
App::instance()->inputChain().paste(ctx, m_position.get());
|
||||
}
|
||||
|
||||
Command* CommandFactory::createPasteCommand()
|
||||
|
@ -39,6 +39,7 @@ protected:
|
||||
void onLoadParams(const Params& params) override;
|
||||
void onExecute(Context* context) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
bool isListed(const Params& params) const override { return !params.empty(); }
|
||||
|
||||
private:
|
||||
std::string m_filename;
|
||||
|
@ -34,6 +34,7 @@ public:
|
||||
protected:
|
||||
void onLoadParams(const Params& params) override;
|
||||
void onExecute(Context* context) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
|
||||
private:
|
||||
std::string m_preset;
|
||||
@ -101,6 +102,15 @@ void SavePaletteCommand::onExecute(Context* ctx)
|
||||
}
|
||||
}
|
||||
|
||||
std::string SavePaletteCommand::onGetFriendlyName() const
|
||||
{
|
||||
if (m_preset == "default")
|
||||
return Strings::commands_SavePaletteAsDefault();
|
||||
else if (m_saveAsPreset)
|
||||
return Strings::commands_SavePaletteAsPreset();
|
||||
return Command::onGetFriendlyName();
|
||||
}
|
||||
|
||||
Command* CommandFactory::createSavePaletteCommand()
|
||||
{
|
||||
return new SavePaletteCommand;
|
||||
|
@ -45,6 +45,7 @@ protected:
|
||||
void onLoadParams(const Params& params) override;
|
||||
void onExecute(Context* context) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
bool isListed(const Params& params) const override { return !params.empty(); }
|
||||
|
||||
private:
|
||||
void selectTiles(const Layer* layer,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
@ -23,6 +24,8 @@ protected:
|
||||
void onLoadParams(const Params& params) override;
|
||||
bool onChecked(Context* context) override;
|
||||
void onExecute(Context* context) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
bool isListed(const Params& params) const override { return !params.empty(); }
|
||||
|
||||
private:
|
||||
int m_size;
|
||||
@ -50,6 +53,11 @@ void SetPaletteEntrySizeCommand::onExecute(Context* context)
|
||||
ColorBar::instance()->getPaletteView()->setBoxSize(m_size);
|
||||
}
|
||||
|
||||
std::string SetPaletteEntrySizeCommand::onGetFriendlyName() const
|
||||
{
|
||||
return Command::onGetFriendlyName() + " " + std::to_string(m_size);
|
||||
}
|
||||
|
||||
Command* CommandFactory::createSetPaletteEntrySizeCommand()
|
||||
{
|
||||
return new SetPaletteEntrySizeCommand;
|
||||
|
@ -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
|
||||
@ -12,6 +13,7 @@
|
||||
#include "app/commands/command.h"
|
||||
#include "app/commands/params.h"
|
||||
#include "app/context.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "filters/tiled_mode.h"
|
||||
|
||||
@ -26,6 +28,8 @@ protected:
|
||||
bool onEnabled(Context* context) override;
|
||||
bool onChecked(Context* context) override;
|
||||
void onExecute(Context* context) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
bool isListed(const Params& params) const override { return !params.empty(); }
|
||||
|
||||
filters::TiledMode m_mode;
|
||||
};
|
||||
@ -64,6 +68,18 @@ void TiledModeCommand::onExecute(Context* ctx)
|
||||
Preferences::instance().document(doc).tiled.mode(m_mode);
|
||||
}
|
||||
|
||||
std::string TiledModeCommand::onGetFriendlyName() const
|
||||
{
|
||||
std::string mode;
|
||||
switch (m_mode) {
|
||||
case filters::TiledMode::NONE: mode = Strings::commands_TiledMode_None(); break;
|
||||
case filters::TiledMode::BOTH: mode = Strings::commands_TiledMode_Both(); break;
|
||||
case filters::TiledMode::X_AXIS: mode = Strings::commands_TiledMode_X(); break;
|
||||
case filters::TiledMode::Y_AXIS: mode = Strings::commands_TiledMode_Y(); break;
|
||||
}
|
||||
return Strings::commands_TiledMode(mode);
|
||||
}
|
||||
|
||||
Command* CommandFactory::createTiledModeCommand()
|
||||
{
|
||||
return new TiledModeCommand;
|
||||
|
@ -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.
|
||||
@ -30,10 +30,12 @@ protected:
|
||||
|
||||
void onExecute(Context* context) override {
|
||||
auto colorBar = ColorBar::instance();
|
||||
colorBar->setTilemapMode(
|
||||
colorBar->tilemapMode() == TilemapMode::Pixels ?
|
||||
TilemapMode::Tiles:
|
||||
TilemapMode::Pixels);
|
||||
if (!colorBar->isTilemapModeLocked()) {
|
||||
colorBar->setTilemapMode(
|
||||
colorBar->tilemapMode() == TilemapMode::Pixels ?
|
||||
TilemapMode::Tiles:
|
||||
TilemapMode::Pixels);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include "app/commands/command_factory.h"
|
||||
#include "app/commands/command_ids.h"
|
||||
#include "app/ui/key_context.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
@ -37,6 +38,10 @@ namespace app {
|
||||
bool isEnabled(Context* context);
|
||||
bool isChecked(Context* context);
|
||||
|
||||
// Returns true if the command must be displayed in the Keyboard
|
||||
// Shortcuts list.
|
||||
virtual bool isListed(const Params& params) const { return true; }
|
||||
|
||||
protected:
|
||||
virtual bool onNeedsParams() const;
|
||||
virtual void onLoadParams(const Params& params);
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019-2021 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.
|
||||
@ -54,6 +54,7 @@ public:
|
||||
protected:
|
||||
void onExecute(Context* ctx) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
bool isListed(const Params& params) const override { return !params.empty(); }
|
||||
};
|
||||
|
||||
ScreenshotCommand::ScreenshotCommand()
|
||||
|
@ -26,6 +26,7 @@ protected:
|
||||
bool onChecked(Context* ctx) override;
|
||||
void onExecute(Context* ctx) override;
|
||||
std::string onGetFriendlyName() const override;
|
||||
bool isListed(const Params& params) const override { return !params.empty(); }
|
||||
};
|
||||
|
||||
SetPlaybackSpeedCommand::SetPlaybackSpeedCommand()
|
||||
|
@ -54,6 +54,8 @@ protected:
|
||||
return Strings::commands_TilesetMode(mode);
|
||||
}
|
||||
|
||||
bool isListed(const Params& params) const override { return !params.empty(); }
|
||||
|
||||
private:
|
||||
TilesetMode m_mode;
|
||||
};
|
||||
|
@ -143,29 +143,27 @@ void DataRecovery::searchForSessions()
|
||||
|
||||
// Existent sessions
|
||||
RECO_TRACE("RECO: Listing sessions from '%s'\n", m_sessionsDir.c_str());
|
||||
for (auto& itemname : base::list_files(m_sessionsDir)) {
|
||||
std::string itempath = base::join_path(m_sessionsDir, itemname);
|
||||
if (base::is_directory(itempath)) {
|
||||
RECO_TRACE("RECO: Session '%s'\n", itempath.c_str());
|
||||
for (const auto& itemname : base::list_files(m_sessionsDir, base::ItemType::Directories)) {
|
||||
const auto& itempath = base::join_path(m_sessionsDir, itemname);
|
||||
RECO_TRACE("RECO: Session '%s' ", itempath.c_str());
|
||||
|
||||
SessionPtr session(new Session(&m_config, itempath));
|
||||
if (!session->isRunning()) {
|
||||
if ((session->isEmpty()) ||
|
||||
(!session->isCrashedSession() && session->isOldSession())) {
|
||||
RECO_TRACE("RECO: - to be deleted (%s)\n",
|
||||
session->isEmpty() ? "is empty":
|
||||
(session->isOldSession() ? "is old":
|
||||
"unknown reason"));
|
||||
session->removeFromDisk();
|
||||
}
|
||||
else {
|
||||
RECO_TRACE("RECO: - to be loaded\n");
|
||||
sessions.push_back(session);
|
||||
}
|
||||
SessionPtr session(new Session(&m_config, itempath));
|
||||
if (!session->isRunning()) {
|
||||
if ((session->isEmpty()) ||
|
||||
(!session->isCrashedSession() && session->isOldSession())) {
|
||||
RECO_TRACE("to be deleted (%s)\n",
|
||||
session->isEmpty() ? "is empty":
|
||||
(session->isOldSession() ? "is old":
|
||||
"unknown reason"));
|
||||
session->removeFromDisk();
|
||||
}
|
||||
else {
|
||||
RECO_TRACE("to be loaded\n");
|
||||
sessions.push_back(session);
|
||||
}
|
||||
else
|
||||
RECO_TRACE("is running\n");
|
||||
}
|
||||
else
|
||||
RECO_TRACE("is running\n");
|
||||
}
|
||||
|
||||
// Sort sessions from the most recent one to the oldest one
|
||||
|
@ -77,7 +77,7 @@ public:
|
||||
, m_docVersions(nullptr)
|
||||
, m_loadInfo(nullptr)
|
||||
, m_taskToken(t) {
|
||||
for (const auto& fn : base::list_files(dir)) {
|
||||
for (const auto& fn : base::list_files(dir, base::ItemType::Files)) {
|
||||
auto i = fn.find('-');
|
||||
if (i == std::string::npos)
|
||||
continue; // Has no ID
|
||||
@ -91,7 +91,12 @@ public:
|
||||
if (!id || !ver)
|
||||
continue; // Error converting strings to ID/ver
|
||||
|
||||
if (!check_magic_number(base::join_path(m_dir, fn))) {
|
||||
// Checking for the magic number of each file takes a long time,
|
||||
// we can guess that all files are valid when there is no
|
||||
// m_taskToken, i.e. when we have to just show the description
|
||||
// of the doc in the list of backups.
|
||||
if (m_taskToken &&
|
||||
!check_magic_number(base::join_path(m_dir, fn))) {
|
||||
RECO_TRACE("RECO: Ignoring invalid file %s (no magic number)\n", fn.c_str());
|
||||
continue;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "base/thread.h"
|
||||
#include "base/time.h"
|
||||
#include "doc/cancel_io.h"
|
||||
#include "ui/app_state.h"
|
||||
#include "fmt/format.h"
|
||||
#include "ver/info.h"
|
||||
|
||||
@ -43,22 +44,24 @@ static const char* kOpenFilename = "open"; // File that indicates if the documen
|
||||
Session::Backup::Backup(const std::string& dir)
|
||||
: m_dir(dir)
|
||||
{
|
||||
DocumentInfo info;
|
||||
read_document_info(dir, info);
|
||||
|
||||
m_fn = info.filename;
|
||||
m_desc =
|
||||
fmt::format("{} Sprite {}x{}, {} {}",
|
||||
info.mode == ColorMode::RGB ? "RGB":
|
||||
info.mode == ColorMode::GRAYSCALE ? "Grayscale":
|
||||
info.mode == ColorMode::INDEXED ? "Indexed":
|
||||
info.mode == ColorMode::BITMAP ? "Bitmap": "Unknown",
|
||||
info.width, info.height, info.frames,
|
||||
info.frames == 1 ? "frame": "frames");
|
||||
}
|
||||
|
||||
std::string Session::Backup::description(const bool withFullPath) const
|
||||
{
|
||||
// Lazy initialize description and filename.
|
||||
if (m_desc.empty()) {
|
||||
DocumentInfo info;
|
||||
read_document_info(m_dir, info);
|
||||
m_fn = info.filename;
|
||||
m_desc =
|
||||
fmt::format("{} Sprite {}x{}, {} {}",
|
||||
info.mode == ColorMode::RGB ? "RGB":
|
||||
info.mode == ColorMode::GRAYSCALE ? "Grayscale":
|
||||
info.mode == ColorMode::INDEXED ? "Indexed":
|
||||
info.mode == ColorMode::BITMAP ? "Bitmap": "Unknown",
|
||||
info.width, info.height, info.frames,
|
||||
info.frames == 1 ? "frame": "frames");
|
||||
}
|
||||
return fmt::format("{}: {}",
|
||||
m_desc,
|
||||
withFullPath ? m_fn:
|
||||
@ -114,11 +117,12 @@ std::string Session::version()
|
||||
const Session::Backups& Session::backups()
|
||||
{
|
||||
if (m_backups.empty()) {
|
||||
for (auto& item : base::list_files(m_path)) {
|
||||
for (const auto& item : base::list_files(m_path, base::ItemType::Directories)) {
|
||||
if (ui::is_app_state_closing())
|
||||
continue;
|
||||
|
||||
std::string docDir = base::join_path(m_path, item);
|
||||
if (base::is_directory(docDir)) {
|
||||
m_backups.push_back(std::make_shared<Backup>(docDir));
|
||||
}
|
||||
m_backups.push_back(std::make_shared<Backup>(docDir));
|
||||
}
|
||||
}
|
||||
return m_backups;
|
||||
@ -147,18 +151,40 @@ bool Session::isOldSession()
|
||||
return true;
|
||||
|
||||
int lifespanDays = m_config->keepEditedSpriteDataFor;
|
||||
base::Time sessionTime = base::get_modification_time(verfile);
|
||||
base::Time sessionTime;
|
||||
|
||||
// Get the session time from the name if possible, to avoid re-scanning when transferring files
|
||||
std::vector<std::string> parts;
|
||||
base::split_string(base::get_file_title(m_path), parts, "-");
|
||||
|
||||
if (parts.size() == 3 && parts[0].size() == 8 && parts[1].size() == 6) {
|
||||
try {
|
||||
sessionTime = base::Time(filenamePartToInt(parts[0].substr(0, 4)),
|
||||
filenamePartToInt(parts[0].substr(4, 2)),
|
||||
filenamePartToInt(parts[0].substr(6, 2)),
|
||||
filenamePartToInt(parts[1].substr(0, 2)),
|
||||
filenamePartToInt(parts[1].substr(2, 2)),
|
||||
filenamePartToInt(parts[1].substr(4, 2)));
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
LOG(ERROR,
|
||||
"Failed to parse a date from '%s', error: %s",
|
||||
parts[0].c_str(),
|
||||
ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
if (!sessionTime.valid()) {
|
||||
// Get modification time as a fallback
|
||||
sessionTime = base::get_modification_time(verfile);
|
||||
}
|
||||
|
||||
return (sessionTime.addDays(lifespanDays) < base::current_time());
|
||||
}
|
||||
|
||||
bool Session::isEmpty()
|
||||
{
|
||||
for (auto& item : base::list_files(m_path)) {
|
||||
if (base::is_directory(base::join_path(m_path, item)))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return base::list_files(m_path, base::ItemType::Directories).empty();
|
||||
}
|
||||
|
||||
void Session::create(base::pid pid)
|
||||
@ -385,12 +411,13 @@ void Session::deleteDirectory(const std::string& dir)
|
||||
if (dir.empty())
|
||||
return;
|
||||
|
||||
for (auto& item : base::list_files(dir)) {
|
||||
for (const auto& item : base::list_files(dir, base::ItemType::Files)) {
|
||||
if (ui::is_app_state_closing())
|
||||
return;
|
||||
|
||||
std::string objfn = base::join_path(dir, item);
|
||||
if (base::is_file(objfn)) {
|
||||
RECO_TRACE("RECO: Deleting file '%s'\n", objfn.c_str());
|
||||
base::delete_file(objfn);
|
||||
}
|
||||
RECO_TRACE("RECO: Deleting file '%s'\n", objfn.c_str());
|
||||
base::delete_file(objfn);
|
||||
}
|
||||
base::remove_directory(dir);
|
||||
}
|
||||
@ -411,5 +438,20 @@ void Session::fixFilename(Doc* doc)
|
||||
base::get_file_title(fn) + "-Recovered" + ext));
|
||||
}
|
||||
|
||||
int Session::filenamePartToInt(const std::string& part) const
|
||||
{
|
||||
if (part.empty())
|
||||
throw base::Exception("Invalid part");
|
||||
|
||||
int result = std::strtol(part.c_str(), NULL, 10);
|
||||
if (errno == ERANGE)
|
||||
throw base::Exception("Number out of range");
|
||||
|
||||
if (result < 0)
|
||||
throw base::Exception("Negative value");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace crash
|
||||
} // namespace app
|
||||
|
@ -35,8 +35,8 @@ namespace crash {
|
||||
std::string description(const bool withFullPath) const;
|
||||
private:
|
||||
std::string m_dir;
|
||||
std::string m_desc;
|
||||
std::string m_fn;
|
||||
mutable std::string m_desc;
|
||||
mutable std::string m_fn;
|
||||
};
|
||||
using BackupPtr = std::shared_ptr<Backup>;
|
||||
using Backups = std::vector<BackupPtr>;
|
||||
@ -78,6 +78,7 @@ namespace crash {
|
||||
void markDocumentAsCorrectlyClosed(Doc* doc);
|
||||
void deleteDirectory(const std::string& dir);
|
||||
void fixFilename(Doc* doc);
|
||||
int filenamePartToInt(const std::string& part) const;
|
||||
|
||||
base::pid m_pid;
|
||||
std::string m_path;
|
||||
|
@ -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
|
||||
@ -1289,13 +1289,18 @@ void DocExporter::renderTexture(Context* ctx,
|
||||
// Make the sprite compatible with the texture so the render()
|
||||
// works correctly.
|
||||
if (sample.sprite()->pixelFormat() != textureImage->pixelFormat()) {
|
||||
RgbMapAlgorithm rgbmapAlgo =
|
||||
Preferences::instance().quantization.rgbmapAlgorithm();
|
||||
FitCriteria fc =
|
||||
Preferences::instance().quantization.fitCriteria();
|
||||
cmd::SetPixelFormat(
|
||||
sample.sprite(),
|
||||
textureImage->pixelFormat(),
|
||||
render::Dithering(),
|
||||
Sprite::DefaultRgbMapAlgorithm(), // TODO add rgbmap algorithm preference
|
||||
rgbmapAlgo,
|
||||
nullptr, // toGray is not needed because the texture is Indexed or RGB
|
||||
nullptr) // TODO add a delegate to show progress
|
||||
nullptr, // TODO add a delegate to show progress
|
||||
fc)
|
||||
.execute(ctx);
|
||||
}
|
||||
|
||||
|
@ -794,7 +794,7 @@ Extensions::Extensions()
|
||||
// Create and get the user extensions directory
|
||||
{
|
||||
ResourceFinder rf2;
|
||||
rf2.includeUserDir("extensions/.");
|
||||
rf2.includeUserDir("extensions");
|
||||
m_userExtensionsPath = rf2.getFirstOrCreateDefault();
|
||||
m_userExtensionsPath = base::normalize_path(m_userExtensionsPath);
|
||||
if (!m_userExtensionsPath.empty() &&
|
||||
@ -811,33 +811,31 @@ Extensions::Extensions()
|
||||
// Load extensions from data/ directory on all possible locations
|
||||
// (installed folder and user folder)
|
||||
while (rf.next()) {
|
||||
auto extensionsDir = rf.filename();
|
||||
const auto& extensionsDir = rf.filename();
|
||||
|
||||
if (base::is_directory(extensionsDir)) {
|
||||
for (auto fn : base::list_files(extensionsDir)) {
|
||||
const auto dir = base::join_path(extensionsDir, fn);
|
||||
if (!base::is_directory(dir))
|
||||
continue;
|
||||
if (!base::is_directory(extensionsDir))
|
||||
continue;
|
||||
|
||||
const bool isBuiltinExtension =
|
||||
(m_userExtensionsPath != base::get_file_path(dir));
|
||||
for (const auto& fn : base::list_files(extensionsDir, base::ItemType::Directories)) {
|
||||
const auto dir = base::join_path(extensionsDir, fn);
|
||||
const bool isBuiltinExtension =
|
||||
(m_userExtensionsPath != base::get_file_path(dir));
|
||||
|
||||
auto fullFn = base::join_path(dir, kPackageJson);
|
||||
fullFn = base::normalize_path(fullFn);
|
||||
auto fullFn = base::join_path(dir, kPackageJson);
|
||||
fullFn = base::normalize_path(fullFn);
|
||||
|
||||
LOG("EXT: Loading extension '%s'...\n", fullFn.c_str());
|
||||
if (!base::is_file(fullFn)) {
|
||||
LOG("EXT: File '%s' not found\n", fullFn.c_str());
|
||||
continue;
|
||||
}
|
||||
LOG("EXT: Loading extension '%s'...\n", fullFn.c_str());
|
||||
if (!base::is_file(fullFn)) {
|
||||
LOG("EXT: File '%s' not found\n", fullFn.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
loadExtension(dir, fullFn, isBuiltinExtension);
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
LOG("EXT: Error loading JSON file: %s\n",
|
||||
ex.what());
|
||||
}
|
||||
try {
|
||||
loadExtension(dir, fullFn, isBuiltinExtension);
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
LOG("EXT: Error loading JSON file: %s\n",
|
||||
ex.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -295,6 +295,13 @@ bool AseFormat::onLoad(FileOp* fop)
|
||||
return false;
|
||||
|
||||
Sprite* sprite = delegate.sprite();
|
||||
|
||||
// Assign RgbMap
|
||||
if (sprite->pixelFormat() == IMAGE_INDEXED)
|
||||
sprite->rgbMap(0, Sprite::RgbMapFor(sprite->isOpaque()),
|
||||
fop->config().rgbMapAlgorithm,
|
||||
fop->config().fitCriteria);
|
||||
|
||||
fop->createDocument(sprite);
|
||||
|
||||
if (sprite->colorSpace() != nullptr &&
|
||||
|
@ -1356,6 +1356,11 @@ ImageRef FileOp::sequenceImageToLoad(
|
||||
// Add the layer
|
||||
sprite->root()->addLayer(layer);
|
||||
|
||||
// Assign RgbMap
|
||||
if (sprite->pixelFormat() == IMAGE_INDEXED)
|
||||
sprite->rgbMap(0, Sprite::RgbMapFor(sprite->isOpaque()),
|
||||
m_config.rgbMapAlgorithm,
|
||||
m_config.fitCriteria);
|
||||
// Done
|
||||
createDocument(sprite);
|
||||
m_seq.layer = layer;
|
||||
|
@ -25,6 +25,7 @@ void FileOpConfig::fillFromPreferences()
|
||||
defaultSliceColor = pref.slices.defaultColor();
|
||||
workingCS = get_working_rgb_space_from_preferences();
|
||||
rgbMapAlgorithm = pref.quantization.rgbmapAlgorithm();
|
||||
fitCriteria = pref.quantization.fitCriteria();
|
||||
cacheCompressedTilesets = pref.tileset.cacheCompressedTilesets();
|
||||
composeGroups = pref.experimental.composeGroups();
|
||||
}
|
||||
|
@ -33,9 +33,14 @@ namespace app {
|
||||
|
||||
app::Color defaultSliceColor = app::Color::fromRgb(0, 0, 255);
|
||||
|
||||
// Algorithm used to create a palette from RGB files.
|
||||
// Algorithm used to fit any color into the available palette colors in
|
||||
// Indexed Color Mode.
|
||||
doc::RgbMapAlgorithm rgbMapAlgorithm = doc::RgbMapAlgorithm::DEFAULT;
|
||||
|
||||
// Fit criteria used to compare colors during the conversion to
|
||||
// Indexed Color Mode.
|
||||
doc::FitCriteria fitCriteria = doc::FitCriteria::DEFAULT;
|
||||
|
||||
// Cache compressed tilesets. When we load a tileset from a
|
||||
// .aseprite file, the compressed data will be stored on memory to
|
||||
// make the save operation faster (as we can re-use the already
|
||||
|
@ -432,6 +432,7 @@ FormatOptionsPtr WebPFormat::onAskUserForFormatOptions(FileOp* fop)
|
||||
pref.webp.imageHint(base::convert_to<int>(win.imageHint()->getValue()));
|
||||
pref.webp.quality(win.quality()->getValue());
|
||||
pref.webp.imagePreset(base::convert_to<int>(win.imagePreset()->getValue()));
|
||||
pref.webp.showAlert(!win.dontShow()->isSelected());
|
||||
|
||||
opts->setLoop(pref.webp.loop());
|
||||
opts->setType(WebPOptions::Type(pref.webp.type()));
|
||||
|
@ -36,10 +36,9 @@ void get_font_dirs(base::paths& fontDirs)
|
||||
|
||||
fontDirs.push_back(fontDir);
|
||||
|
||||
for (const auto& file : base::list_files(fontDir)) {
|
||||
for (const auto& file : base::list_files(fontDir, base::ItemType::Directories)) {
|
||||
std::string fullpath = base::join_path(fontDir, file);
|
||||
if (base::is_directory(fullpath))
|
||||
q.push(fullpath); // Add subdirectory in the queue
|
||||
q.push(fullpath); // Add subdirectory in the queue
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,11 +64,7 @@ std::set<LangInfo> Strings::availableLanguages() const
|
||||
if (!base::is_directory(stringsPath))
|
||||
continue;
|
||||
|
||||
for (const auto& fn : base::list_files(stringsPath)) {
|
||||
// Ignore README/LICENSE files.
|
||||
if (base::get_file_extension(fn) != "ini")
|
||||
continue;
|
||||
|
||||
for (const auto& fn : base::list_files(stringsPath, base::ItemType::Files, "*.ini")) {
|
||||
const std::string langId = base::get_file_title(fn);
|
||||
std::string path = base::join_path(stringsPath, fn);
|
||||
std::string displayName = langId;
|
||||
|
@ -22,12 +22,15 @@ namespace app {
|
||||
|
||||
class Section {
|
||||
public:
|
||||
Section(const std::string& name) : m_name(name) { }
|
||||
virtual ~Section() { }
|
||||
explicit Section(const std::string& name) : m_name(name) { }
|
||||
virtual ~Section() = default;
|
||||
const char* name() const { return m_name.c_str(); }
|
||||
|
||||
virtual Section* section(const char* id) = 0;
|
||||
virtual OptionBase* option(const char* id) = 0;
|
||||
virtual std::vector<OptionBase*> optionList() const { return {}; }
|
||||
virtual std::vector<Section*> sectionList() const { return {}; }
|
||||
virtual void save() = 0;
|
||||
|
||||
obs::signal<void()> BeforeChange;
|
||||
obs::signal<void()> AfterChange;
|
||||
@ -42,9 +45,10 @@ namespace app {
|
||||
: m_section(section)
|
||||
, m_id(id) {
|
||||
}
|
||||
virtual ~OptionBase() { }
|
||||
virtual ~OptionBase() = default;
|
||||
const char* section() const { return m_section->name(); }
|
||||
const char* id() const { return m_id; }
|
||||
virtual void resetToDefault() = 0;
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
virtual void pushLua(lua_State* L) = 0;
|
||||
@ -127,6 +131,10 @@ namespace app {
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
void resetToDefault() override {
|
||||
setValue(m_default);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
void pushLua(lua_State* L) override {
|
||||
script::push_value_to_lua<T>(L, m_value);
|
||||
|
@ -67,14 +67,6 @@ Preferences::Preferences()
|
||||
|
||||
load();
|
||||
|
||||
// Create a connection with the default RgbMapAlgorithm preferences
|
||||
// to change the default algorithm in the "doc" layer.
|
||||
quantization.rgbmapAlgorithm.AfterChange.connect(
|
||||
[](const doc::RgbMapAlgorithm& newValue){
|
||||
doc::Sprite::SetDefaultRgbMapAlgorithm(newValue);
|
||||
});
|
||||
doc::Sprite::SetDefaultRgbMapAlgorithm(quantization.rgbmapAlgorithm());
|
||||
|
||||
// Create a connection with the default document preferences grid
|
||||
// bounds to sync the default grid bounds for new sprites in the
|
||||
// "doc" layer.
|
||||
|
@ -58,7 +58,7 @@ namespace app {
|
||||
Preferences();
|
||||
~Preferences();
|
||||
|
||||
void save();
|
||||
void save() override;
|
||||
|
||||
// Returns true if the given option was set by the user or false
|
||||
// if it contains the default value.
|
||||
|
@ -45,7 +45,7 @@ void PalettesLoaderDelegate::getResourcesPaths(std::map<std::string, std::string
|
||||
if (base::is_directory(rf.filename())) {
|
||||
path = rf.filename();
|
||||
path = base::fix_path_separators(path);
|
||||
for (const auto& fn : base::list_files(path)) {
|
||||
for (const auto& fn : base::list_files(path, base::ItemType::Files)) {
|
||||
// Ignore the default palette that is inside the palettes/ dir
|
||||
// in the user home dir.
|
||||
if (fn == "default.ase" ||
|
||||
@ -53,8 +53,7 @@ void PalettesLoaderDelegate::getResourcesPaths(std::map<std::string, std::string
|
||||
continue;
|
||||
|
||||
std::string fullFn = base::join_path(path, fn);
|
||||
if (base::is_file(fullFn))
|
||||
idAndPath[base::get_file_title(fn)] = fullFn;
|
||||
idAndPath[base::get_file_title(fn)] = fullFn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ int AppFS_listFiles(lua_State* L)
|
||||
lua_newtable(L);
|
||||
if (path) {
|
||||
int i = 0;
|
||||
for (auto fn : base::list_files(path)) {
|
||||
for (const auto& fn : base::list_files(path)) {
|
||||
lua_pushstring(L, fn.c_str());
|
||||
lua_seti(L, -2, ++i);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018-2023 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2015-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -348,21 +348,37 @@ int App_useTool(lua_State* L)
|
||||
params.inkType = get_value_from_lua<tools::InkType>(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Are we going to modify pixels or tiles?
|
||||
type = lua_getfield(L, 1, "tilemapMode");
|
||||
if (type != LUA_TNIL) {
|
||||
site.tilemapMode(TilemapMode(lua_tointeger(L, -1)));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// How the tileset must be modified depending on this tool usage
|
||||
type = lua_getfield(L, 1, "tilesetMode");
|
||||
if (type != LUA_TNIL) {
|
||||
site.tilesetMode(TilesetMode(lua_tointeger(L, -1)));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Color
|
||||
type = lua_getfield(L, 1, "color");
|
||||
if (type != LUA_TNIL)
|
||||
params.fg = convert_args_into_color(L, -1);
|
||||
else {
|
||||
// Default color is the active fgColor
|
||||
else if (site.tilemapMode() == TilemapMode::Tiles)
|
||||
params.fg = Color::fromTile(Preferences::instance().colorBar.fgTile());
|
||||
else // Default color is the active fgColor
|
||||
params.fg = Preferences::instance().colorBar.fgColor();
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
type = lua_getfield(L, 1, "bgColor");
|
||||
if (type != LUA_TNIL)
|
||||
params.bg = convert_args_into_color(L, -1);
|
||||
else if (site.tilemapMode() == TilemapMode::Tiles)
|
||||
params.bg = Color::fromTile(Preferences::instance().colorBar.bgTile());
|
||||
else
|
||||
params.bg = params.fg;
|
||||
params.bg = Preferences::instance().colorBar.bgColor();
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Adjust ink depending on "inkType" and "color"
|
||||
@ -441,20 +457,6 @@ int App_useTool(lua_State* L)
|
||||
}
|
||||
}
|
||||
|
||||
// Are we going to modify pixels or tiles?
|
||||
type = lua_getfield(L, 1, "tilemapMode");
|
||||
if (type != LUA_TNIL) {
|
||||
site.tilemapMode(TilemapMode(lua_tointeger(L, -1)));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// How the tileset must be modified depending on this tool usage
|
||||
type = lua_getfield(L, 1, "tilesetMode");
|
||||
if (type != LUA_TNIL) {
|
||||
site.tilesetMode(TilesetMode(lua_tointeger(L, -1)));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Do the tool loop
|
||||
type = lua_getfield(L, 1, "points");
|
||||
if (type == LUA_TTABLE) {
|
||||
@ -470,6 +472,13 @@ int App_useTool(lua_State* L)
|
||||
bool first = true;
|
||||
|
||||
lua_pushnil(L);
|
||||
tools::ToolBox* toolbox = App::instance()->toolBox();
|
||||
const bool isSelectionInk =
|
||||
(params.ink == toolbox->getInkById(tools::WellKnownInks::Selection));
|
||||
const tools::Pointer::Button button =
|
||||
(!isSelectionInk ? (buttonIdx == 0 ? tools::Pointer::Button::Left :
|
||||
tools::Pointer::Button::Right) :
|
||||
tools::Pointer::Button::Left);
|
||||
while (lua_next(L, -2) != 0) {
|
||||
gfx::Point pt = convert_args_into_point(L, -1);
|
||||
|
||||
@ -477,7 +486,7 @@ int App_useTool(lua_State* L)
|
||||
pt,
|
||||
// TODO configurable params
|
||||
tools::Vec2(0.0f, 0.0f),
|
||||
tools::Pointer::Button::Left,
|
||||
button,
|
||||
tools::Pointer::Type::Unknown,
|
||||
0.0f);
|
||||
if (first) {
|
||||
@ -635,6 +644,30 @@ int App_set_bgColor(lua_State* L)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int App_get_fgTile(lua_State* L)
|
||||
{
|
||||
lua_pushinteger(L, Preferences::instance().colorBar.fgTile());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int App_set_fgTile(lua_State* L)
|
||||
{
|
||||
Preferences::instance().colorBar.fgTile(lua_tointeger(L, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int App_get_bgTile(lua_State* L)
|
||||
{
|
||||
lua_pushinteger(L, Preferences::instance().colorBar.bgTile());
|
||||
return 1;
|
||||
}
|
||||
|
||||
int App_set_bgTile(lua_State* L)
|
||||
{
|
||||
Preferences::instance().colorBar.bgTile(lua_tointeger(L, 2));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int App_get_site(lua_State* L)
|
||||
{
|
||||
app::Context* ctx = App::instance()->context();
|
||||
@ -819,6 +852,8 @@ const Property App_properties[] = {
|
||||
{ "sprites", App_get_sprites, nullptr },
|
||||
{ "fgColor", App_get_fgColor, App_set_fgColor },
|
||||
{ "bgColor", App_get_bgColor, App_set_bgColor },
|
||||
{ "fgTile", App_get_fgTile, App_set_fgTile },
|
||||
{ "bgTile", App_get_bgTile, App_set_bgTile },
|
||||
{ "version", App_get_version, nullptr },
|
||||
{ "apiVersion", App_get_apiVersion, nullptr },
|
||||
{ "site", App_get_site, nullptr },
|
||||
|
133
src/app/script/script_input_chain.cpp
Normal file
133
src/app/script/script_input_chain.cpp
Normal file
@ -0,0 +1,133 @@
|
||||
// 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/script_input_chain.h"
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/cmd/deselect_mask.h"
|
||||
#include "app/cmd/remap_colors.h"
|
||||
#include "app/commands/commands.h"
|
||||
#include "app/context_access.h"
|
||||
#include "app/site.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/util/clipboard.h"
|
||||
#include "doc/mask.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/primitives.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
namespace app {
|
||||
|
||||
ScriptInputChain::~ScriptInputChain() { }
|
||||
|
||||
void ScriptInputChain::onNewInputPriority(InputChainElement* element,
|
||||
const ui::Message* msg) { }
|
||||
|
||||
bool ScriptInputChain::onCanCut(Context* ctx)
|
||||
{
|
||||
return ctx->activeDocument() &&
|
||||
ctx->activeDocument()->isMaskVisible();
|
||||
}
|
||||
|
||||
bool ScriptInputChain::onCanCopy(Context* ctx)
|
||||
{
|
||||
return onCanCut(ctx);
|
||||
}
|
||||
|
||||
bool ScriptInputChain::onCanPaste(Context* ctx)
|
||||
{
|
||||
const Clipboard* clipboard(ctx->clipboard());
|
||||
if (!clipboard)
|
||||
return false;
|
||||
return clipboard->format() == ClipboardFormat::Image &&
|
||||
ctx->activeSite().layer() &&
|
||||
ctx->activeSite().layer()->type() == ObjectType::LayerImage;
|
||||
}
|
||||
|
||||
bool ScriptInputChain::onCanClear(Context* ctx)
|
||||
{
|
||||
return onCanCut(ctx);
|
||||
}
|
||||
|
||||
bool ScriptInputChain::onCut(Context* ctx)
|
||||
{
|
||||
ContextWriter writer(ctx);
|
||||
Clipboard* clipboard = ctx->clipboard();
|
||||
if (!clipboard)
|
||||
return false;
|
||||
if (writer.document()) {
|
||||
clipboard->cut(writer);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ScriptInputChain::onCopy(Context* ctx)
|
||||
{
|
||||
ContextReader reader(ctx);
|
||||
Clipboard* clipboard = ctx->clipboard();
|
||||
if (!clipboard)
|
||||
return false;
|
||||
if (reader.document()) {
|
||||
clipboard->copy(reader);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ScriptInputChain::onPaste(Context* ctx,
|
||||
const gfx::Point* position)
|
||||
{
|
||||
Clipboard* clipboard = ctx->clipboard();
|
||||
if (!clipboard)
|
||||
return false;
|
||||
if (clipboard->format() == ClipboardFormat::Image) {
|
||||
clipboard->paste(ctx, false, position);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ScriptInputChain::onClear(Context* ctx)
|
||||
{
|
||||
// TODO This code is similar to DocView::onClear() and Clipboard::cut()
|
||||
ContextWriter writer(ctx);
|
||||
Doc* document = ctx->activeDocument();
|
||||
if (writer.document()) {
|
||||
ctx->clipboard()->clearContent();
|
||||
CelList cels;
|
||||
const Site site = ctx->activeSite();
|
||||
cels.push_back(site.cel());
|
||||
if (cels.empty()) // No cels to modify
|
||||
return false;
|
||||
Tx tx(writer, "Clear");
|
||||
ctx->clipboard()->clearMaskFromCels(
|
||||
tx, document, site, cels, true);
|
||||
tx.commit();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ScriptInputChain::onCancel(Context* ctx)
|
||||
{
|
||||
// Deselect mask
|
||||
if (ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
|
||||
ContextFlags::HasVisibleMask)) {
|
||||
Command* deselectMask = Commands::instance()->byId(CommandId::DeselectMask());
|
||||
ctx->executeCommand(deselectMask);
|
||||
ctx->activeDocument()->setMaskVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace app
|
40
src/app/script/script_input_chain.h
Normal file
40
src/app/script/script_input_chain.h
Normal file
@ -0,0 +1,40 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_SCRIPT_SCRIPT_INPUT_CHAIN_H_INCLUDED
|
||||
#define APP_SCRIPT_SCRIPT_INPUT_CHAIN_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#ifndef ENABLE_SCRIPTING
|
||||
#error ENABLE_SCRIPTING must be defined
|
||||
#endif
|
||||
|
||||
#include "app/ui/input_chain_element.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
class ScriptInputChain : public InputChainElement {
|
||||
public:
|
||||
|
||||
// InputChainElement impl
|
||||
~ScriptInputChain() override;
|
||||
void onNewInputPriority(InputChainElement* element,
|
||||
const ui::Message* msg) override;
|
||||
bool onCanCut(Context* ctx) override;
|
||||
bool onCanCopy(Context* ctx) override;
|
||||
bool onCanPaste(Context* ctx) override;
|
||||
bool onCanClear(Context* ctx) override;
|
||||
bool onCut(Context* ctx) override;
|
||||
bool onCopy(Context* ctx) override;
|
||||
bool onPaste(Context* ctx,
|
||||
const gfx::Point* position) override;
|
||||
bool onClear(Context* ctx) override;
|
||||
void onCancel(Context* ctx) override;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 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
|
||||
@ -83,6 +83,26 @@ int Site_get_image(lua_State* L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Site_get_tilemapMode(lua_State* L)
|
||||
{
|
||||
auto site = get_obj<Site>(L, 1);
|
||||
if (site)
|
||||
lua_pushinteger(L, (int)site->tilemapMode());
|
||||
else
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Site_get_tilesetMode(lua_State* L)
|
||||
{
|
||||
auto site = get_obj<Site>(L, 1);
|
||||
if (site)
|
||||
lua_pushinteger(L, (int)site->tilesetMode());
|
||||
else
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const luaL_Reg Site_methods[] = {
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
@ -94,6 +114,8 @@ const Property Site_properties[] = {
|
||||
{ "frame", Site_get_frame, nullptr },
|
||||
{ "frameNumber", Site_get_frameNumber, nullptr },
|
||||
{ "image", Site_get_image, nullptr },
|
||||
{ "tilemapMode", Site_get_tilemapMode, nullptr },
|
||||
{ "tilesetMode", Site_get_tilesetMode, nullptr },
|
||||
{ nullptr, nullptr, nullptr }
|
||||
};
|
||||
|
||||
|
@ -339,7 +339,9 @@ int Sprite_flatten(lua_State* L)
|
||||
range.selectLayer(layer);
|
||||
|
||||
Tx tx(sprite);
|
||||
tx(new cmd::FlattenLayers(sprite, range.selectedLayers(), true));
|
||||
cmd::FlattenLayers::Options options;
|
||||
options.newBlendMethod = true;
|
||||
tx(new cmd::FlattenLayers(sprite, range.selectedLayers(), options));
|
||||
tx.commit();
|
||||
return 0;
|
||||
}
|
||||
|
@ -342,6 +342,7 @@ FOR_ENUM(app::tools::RotationAlgorithm)
|
||||
FOR_ENUM(doc::AniDir)
|
||||
FOR_ENUM(doc::BrushPattern)
|
||||
FOR_ENUM(doc::ColorMode)
|
||||
FOR_ENUM(doc::FitCriteria)
|
||||
FOR_ENUM(doc::RgbMapAlgorithm)
|
||||
FOR_ENUM(filters::HueSaturationFilter::Mode)
|
||||
FOR_ENUM(filters::TiledMode)
|
||||
|
@ -109,18 +109,15 @@ bool Sentry::areThereCrashesToReport()
|
||||
|
||||
// At least one .dmp file in the completed/ directory means that
|
||||
// there was at least one crash in the past (this is for macOS).
|
||||
for (auto f : base::list_files(base::join_path(m_dbdir, "completed"))) {
|
||||
if (base::get_file_extension(f) == "dmp")
|
||||
return true;
|
||||
}
|
||||
if (!base::join_path(m_dbdir, "completed"), base::ItemType::Files, "*.dmp").empty())
|
||||
return true;
|
||||
|
||||
// In case that "last_crash" doesn't exist we can check for some
|
||||
// .dmp file in the reports/ directory (it looks like the completed/
|
||||
// directory is not generated on Windows).
|
||||
for (auto f : base::list_files(base::join_path(m_dbdir, "reports"))) {
|
||||
if (base::get_file_extension(f) == "dmp")
|
||||
return true;
|
||||
}
|
||||
if (!base::list_files(base::join_path(m_dbdir, "reports"), base::ItemType::Files, "*.dmp").empty())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
@ -570,6 +570,12 @@ public:
|
||||
ReplaceInkProcessing(ToolLoop* loop) {
|
||||
m_color1 = loop->getPrimaryColor();
|
||||
m_color2 = loop->getSecondaryColor();
|
||||
if (loop->getLayer()->isBackground()) {
|
||||
switch (loop->sprite()->pixelFormat()) {
|
||||
case IMAGE_RGB: m_color2 |= rgba_a_mask; break;
|
||||
case IMAGE_GRAYSCALE: m_color2 |= graya_a_mask; break;
|
||||
}
|
||||
}
|
||||
m_opacity = loop->getOpacity();
|
||||
}
|
||||
|
||||
|
46
src/app/ui/best_fit_criteria_selector.cpp
Normal file
46
src/app/ui/best_fit_criteria_selector.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
// 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/ui/best_fit_criteria_selector.h"
|
||||
|
||||
#include "app/i18n/strings.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
BestFitCriteriaSelector::BestFitCriteriaSelector()
|
||||
{
|
||||
// addItem() must match the FitCriteria enum
|
||||
static_assert(int(doc::FitCriteria::DEFAULT) == 0 &&
|
||||
int(doc::FitCriteria::RGB) == 1 &&
|
||||
int(doc::FitCriteria::linearizedRGB) == 2 &&
|
||||
int(doc::FitCriteria::CIEXYZ) == 3 &&
|
||||
int(doc::FitCriteria::CIELAB) == 4,
|
||||
"Unexpected doc::FitCriteria values");
|
||||
|
||||
addItem(Strings::best_fit_criteria_selector_default());
|
||||
addItem(Strings::best_fit_criteria_selector_rgb());
|
||||
addItem(Strings::best_fit_criteria_selector_linearized_rgb());
|
||||
addItem(Strings::best_fit_criteria_selector_cie_xyz());
|
||||
addItem(Strings::best_fit_criteria_selector_cie_lab());
|
||||
|
||||
criteria(doc::FitCriteria::DEFAULT);
|
||||
}
|
||||
|
||||
doc::FitCriteria BestFitCriteriaSelector::criteria()
|
||||
{
|
||||
return (doc::FitCriteria)getSelectedItemIndex();
|
||||
}
|
||||
|
||||
void BestFitCriteriaSelector::criteria(const doc::FitCriteria criteria)
|
||||
{
|
||||
setSelectedItemIndex((int)criteria);
|
||||
}
|
||||
|
||||
} // namespace app
|
27
src/app/ui/best_fit_criteria_selector.h
Normal file
27
src/app/ui/best_fit_criteria_selector.h
Normal file
@ -0,0 +1,27 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2024 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_UI_BEST_FIT_CRITERIA_SELECTOR_H_INCLUDED
|
||||
#define APP_UI_BEST_FIT_CRITERIA_SELECTOR_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "doc/fit_criteria.h"
|
||||
#include "doc/palette.h"
|
||||
#include "ui/combobox.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
class BestFitCriteriaSelector : public ui::ComboBox {
|
||||
public:
|
||||
BestFitCriteriaSelector();
|
||||
|
||||
doc::FitCriteria criteria();
|
||||
void criteria(doc::FitCriteria criteria);
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -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
|
||||
@ -1692,7 +1692,8 @@ bool ColorBar::onCopy(Context* ctx)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ColorBar::onPaste(Context* ctx)
|
||||
bool ColorBar::onPaste(Context* ctx,
|
||||
const gfx::Point* position)
|
||||
{
|
||||
if (m_tilemapMode == TilemapMode::Tiles) {
|
||||
showRemapTiles();
|
||||
|
@ -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
|
||||
@ -92,6 +92,9 @@ namespace app {
|
||||
|
||||
TilemapMode tilemapMode() const;
|
||||
void setTilemapMode(TilemapMode mode);
|
||||
void lockTilemapMode() { m_tilesButton.setEnabled(false); };
|
||||
void unlockTilemapMode() { m_tilesButton.setEnabled(true); };
|
||||
bool isTilemapModeLocked() const { return !m_tilesButton.isEnabled(); };
|
||||
|
||||
TilesetMode tilesetMode() const;
|
||||
void setTilesetMode(TilesetMode mode);
|
||||
@ -116,7 +119,8 @@ namespace app {
|
||||
bool onCanClear(Context* ctx) override;
|
||||
bool onCut(Context* ctx) override;
|
||||
bool onCopy(Context* ctx) override;
|
||||
bool onPaste(Context* ctx) override;
|
||||
bool onPaste(Context* ctx,
|
||||
const gfx::Point* position) override;
|
||||
bool onClear(Context* ctx) override;
|
||||
void onCancel(Context* ctx) override;
|
||||
|
||||
|
@ -1576,11 +1576,13 @@ private:
|
||||
item->setSelected(false);
|
||||
|
||||
Menu menu;
|
||||
MenuItem
|
||||
reset(Strings::symmetry_reset_position());
|
||||
menu.addChild(&reset);
|
||||
MenuItem resetToCenter(Strings::symmetry_reset_position());
|
||||
MenuItem resetToViewCenter(Strings::symmetry_reset_position_to_view_center());
|
||||
|
||||
reset.Click.connect(
|
||||
menu.addChild(&resetToCenter);
|
||||
menu.addChild(&resetToViewCenter);
|
||||
|
||||
resetToCenter.Click.connect(
|
||||
[doc, &docPref]{
|
||||
docPref.symmetry.xAxis(doc->sprite()->width()/2.0);
|
||||
docPref.symmetry.yAxis(doc->sprite()->height()/2.0);
|
||||
@ -1588,6 +1590,18 @@ private:
|
||||
doc->notifyGeneralUpdate();
|
||||
});
|
||||
|
||||
resetToViewCenter.Click.connect(
|
||||
[doc, &docPref]{
|
||||
auto* editor = Editor::activeEditor();
|
||||
const gfx::Rect& bounds = editor->getViewportBounds();
|
||||
double xViewPosition = bounds.x + bounds.w/2.0;
|
||||
double yViewPosition = bounds.y + bounds.h/2.0;
|
||||
docPref.symmetry.xAxis(xViewPosition);
|
||||
docPref.symmetry.yAxis(yViewPosition);
|
||||
// Redraw symmetry rules
|
||||
doc->notifyGeneralUpdate();
|
||||
});
|
||||
|
||||
menu.showPopup(gfx::Point(bounds.x, bounds.y2()),
|
||||
display());
|
||||
}
|
||||
|
@ -58,7 +58,6 @@ public:
|
||||
: m_session(session)
|
||||
, m_backup(backup)
|
||||
, m_task(nullptr) {
|
||||
updateText();
|
||||
}
|
||||
|
||||
crash::Session* session() const { return m_session; }
|
||||
@ -150,6 +149,15 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void onPaint(PaintEvent& ev) override {
|
||||
// The text is lazily initialized. So we read the backup data only
|
||||
// when we have to show its information.
|
||||
if (text().empty()) {
|
||||
updateText();
|
||||
}
|
||||
ListItem::onPaint(ev);
|
||||
}
|
||||
|
||||
void onSizeHint(SizeHintEvent& ev) override {
|
||||
ListItem::onSizeHint(ev);
|
||||
gfx::Size sz = ev.sizeHint();
|
||||
|
@ -589,12 +589,13 @@ bool DocView::onCopy(Context* ctx)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DocView::onPaste(Context* ctx)
|
||||
bool DocView::onPaste(Context* ctx,
|
||||
const gfx::Point* position)
|
||||
{
|
||||
auto clipboard = ctx->clipboard();
|
||||
if (clipboard->format() == ClipboardFormat::Image ||
|
||||
clipboard->format() == ClipboardFormat::Tilemap) {
|
||||
clipboard->paste(ctx, true);
|
||||
clipboard->paste(ctx, true, position);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
@ -102,7 +102,8 @@ namespace app {
|
||||
bool onCanClear(Context* ctx) override;
|
||||
bool onCut(Context* ctx) override;
|
||||
bool onCopy(Context* ctx) override;
|
||||
bool onPaste(Context* ctx) override;
|
||||
bool onPaste(Context* ctx,
|
||||
const gfx::Point* position) override;
|
||||
bool onClear(Context* ctx) override;
|
||||
void onCancel(Context* ctx) override;
|
||||
|
||||
|
@ -162,6 +162,8 @@ bool DraggingValueState::onUpdateStatusBar(Editor* editor)
|
||||
|
||||
void DraggingValueState::onBeforeCommandExecution(CommandExecutionEvent& ev)
|
||||
{
|
||||
if (m_editor->hasCapture())
|
||||
m_editor->releaseMouse();
|
||||
m_editor->backToPreviousState();
|
||||
}
|
||||
|
||||
|
@ -2658,7 +2658,9 @@ void Editor::setZoomAndCenterInMouse(const Zoom& zoom,
|
||||
}
|
||||
}
|
||||
|
||||
void Editor::pasteImage(const Image* image, const Mask* mask)
|
||||
void Editor::pasteImage(const Image* image,
|
||||
const Mask* mask,
|
||||
const gfx::Point* position)
|
||||
{
|
||||
ASSERT(image);
|
||||
|
||||
@ -2690,11 +2692,14 @@ void Editor::pasteImage(const Image* image, const Mask* mask)
|
||||
Sprite* sprite = this->sprite();
|
||||
|
||||
// Check bounds where the image will be pasted.
|
||||
int x = mask->bounds().x;
|
||||
int y = mask->bounds().y;
|
||||
int x = (position ? position->x : mask->bounds().x);
|
||||
int y = (position ? position->y : mask->bounds().y);
|
||||
{
|
||||
const Rect visibleBounds = getViewportBounds();
|
||||
const Point maskCenter = mask->bounds().center();
|
||||
const Point maskCenter = mask->bounds().center() +
|
||||
(position ? gfx::Point(position->x - mask->bounds().x,
|
||||
position->y - mask->bounds().y)
|
||||
: gfx::Point());
|
||||
|
||||
// If the pasted image original location center point isn't
|
||||
// visible, we center the image in the editor's visible bounds.
|
||||
@ -2747,7 +2752,8 @@ void Editor::pasteImage(const Image* image, const Mask* mask)
|
||||
m_brushPreview.hide();
|
||||
|
||||
Mask mask2(*mask);
|
||||
mask2.setOrigin(x, y);
|
||||
position ? mask2.setOrigin(position->x, position->y)
|
||||
: mask2.setOrigin(x, y);
|
||||
|
||||
PixelsMovementPtr pixelsMovement(
|
||||
new PixelsMovement(UIContext::instance(), site,
|
||||
|
@ -252,7 +252,9 @@ namespace app {
|
||||
void setZoomAndCenterInMouse(const render::Zoom& zoom,
|
||||
const gfx::Point& mousePos, ZoomBehavior zoomBehavior);
|
||||
|
||||
void pasteImage(const Image* image, const Mask* mask = nullptr);
|
||||
void pasteImage(const Image* image,
|
||||
const Mask* mask = nullptr,
|
||||
const gfx::Point* position = nullptr);
|
||||
|
||||
void startSelectionTransformation(const gfx::Point& move, double angle);
|
||||
void startFlipTransformation(doc::algorithm::FlipType flipType);
|
||||
|
@ -736,6 +736,10 @@ void MovingPixelsState::onBeforeCommandExecution(CommandExecutionEvent& ev)
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (command->id() == CommandId::ToggleTilesMode()) {
|
||||
ev.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_pixelsMovement)
|
||||
dropPixels();
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include "doc/layer.h"
|
||||
#include "doc/mask.h"
|
||||
#include "doc/sprite.h"
|
||||
#include "doc/util.h"
|
||||
#include "gfx/region.h"
|
||||
#include "render/render.h"
|
||||
|
||||
@ -131,6 +132,10 @@ PixelsMovement::PixelsMovement(
|
||||
, m_fastMode(false)
|
||||
, m_needsRotSpriteRedraw(false)
|
||||
{
|
||||
// Save and Lock the TilemapMode.
|
||||
// TODO: enable TilemapMode exchanges during PixelMovement.
|
||||
if (m_site.layer()->isTilemap() && ColorBar::instance())
|
||||
ColorBar::instance()->lockTilemapMode();
|
||||
const float cornerThick = (m_site.tilemapMode() == TilemapMode::Tiles ?
|
||||
CORNER_THICK_FOR_TILEMAP_MODE:
|
||||
CORNER_THICK_FOR_PIXELS_MODE);
|
||||
@ -172,6 +177,12 @@ PixelsMovement::PixelsMovement(
|
||||
}
|
||||
}
|
||||
|
||||
PixelsMovement::~PixelsMovement()
|
||||
{
|
||||
if (ColorBar::instance())
|
||||
ColorBar::instance()->unlockTilemapMode();
|
||||
}
|
||||
|
||||
bool PixelsMovement::editMultipleCels() const
|
||||
{
|
||||
return
|
||||
@ -281,6 +292,11 @@ void PixelsMovement::setTransformationBase(const Transformation& t)
|
||||
fullBounds |= gfx::Rect((int)newCorners[i].x, (int)newCorners[i].y, 1, 1);
|
||||
}
|
||||
|
||||
// This align is done to properly invalidate regions on the editor when
|
||||
// partial tiles are selected in the transform bounds
|
||||
if (m_site.tilemapMode() == TilemapMode::Tiles)
|
||||
fullBounds = m_site.grid().alignBounds(fullBounds);
|
||||
|
||||
// If "fullBounds" is empty is because the cel was not moved
|
||||
if (!fullBounds.isEmpty()) {
|
||||
// Notify the modified region.
|
||||
@ -370,6 +386,7 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
|
||||
gfx::RectF bounds = m_initialData.bounds();
|
||||
gfx::PointF abs_initial_pivot = m_initialData.pivot();
|
||||
gfx::PointF abs_pivot = m_currentData.pivot();
|
||||
const bool tilesModeOn = (m_site.tilemapMode() == TilemapMode::Tiles);
|
||||
|
||||
auto newTransformation = m_currentData;
|
||||
|
||||
@ -377,7 +394,25 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
|
||||
|
||||
case MovePixelsHandle: {
|
||||
double dx, dy;
|
||||
if ((moveModifier & FineControl) == 0) {
|
||||
if (tilesModeOn) {
|
||||
if (m_catchPos.x == 0 && m_catchPos.y == 0) {
|
||||
// Movement through keyboard:
|
||||
dx = (pos.x - m_catchPos.x) * m_site.gridBounds().w;
|
||||
dy = (pos.y - m_catchPos.y) * m_site.gridBounds().h;
|
||||
}
|
||||
else {
|
||||
// Movement through mouse/trackpad:
|
||||
const int gridW = m_site.gridBounds().w;
|
||||
const int gridH = m_site.gridBounds().h;
|
||||
gfx::PointF point(
|
||||
snap_to_grid(gfx::Rect(0, 0, gridW, gridH),
|
||||
(gfx::Point)(pos - m_catchPos),
|
||||
PreferSnapTo::ClosestGridVertex));
|
||||
dx = point.x;
|
||||
dy = point.y;
|
||||
}
|
||||
}
|
||||
else if ((moveModifier & FineControl) == 0) {
|
||||
dx = (std::floor(pos.x) - std::floor(m_catchPos.x));
|
||||
dy = (std::floor(pos.y) - std::floor(m_catchPos.y));
|
||||
}
|
||||
@ -395,13 +430,12 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
|
||||
|
||||
bounds.offset(dx, dy);
|
||||
|
||||
if ((m_site.tilemapMode() == TilemapMode::Tiles) ||
|
||||
if (!tilesModeOn &&
|
||||
(moveModifier & SnapToGridMovement) == SnapToGridMovement) {
|
||||
// Snap the x1,y1 point to the grid.
|
||||
gfx::Rect gridBounds = m_site.gridBounds();
|
||||
gfx::PointF gridOffset(
|
||||
snap_to_grid(
|
||||
gridBounds,
|
||||
m_site.gridBounds(),
|
||||
gfx::Point(bounds.origin()),
|
||||
PreferSnapTo::ClosestGridVertex));
|
||||
|
||||
@ -411,9 +445,7 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
|
||||
}
|
||||
|
||||
newTransformation.bounds(bounds);
|
||||
newTransformation.pivot(abs_initial_pivot +
|
||||
bounds.origin() -
|
||||
m_initialData.bounds().origin());
|
||||
newTransformation.pivot(abs_initial_pivot + gfx::PointF(dx, dy));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -496,10 +528,18 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
|
||||
}
|
||||
|
||||
// Snap to grid when resizing tilemaps
|
||||
if (m_site.tilemapMode() == TilemapMode::Tiles) {
|
||||
if (tilesModeOn) {
|
||||
// 'a' is a point in the top-left corner that is inside bounds
|
||||
// unless the corners are inverted (a > b)
|
||||
a.x = a.x - (a.x > b.x? 1 : 0);
|
||||
a.y = a.y - (a.y > b.y? 1 : 0);
|
||||
// 'b' is a point in the lower-right corner that is out of bounds by 1 unit
|
||||
// unless the corners are inverted (a > b)
|
||||
b.x = b.x - (a.x <= b.x? 1 : 0);
|
||||
b.y = b.y - (a.y <= b.y? 1 : 0);
|
||||
gfx::Rect gridBounds = m_site.gridBounds();
|
||||
a = gfx::PointF(snap_to_grid(gridBounds, gfx::Point(a), PreferSnapTo::BoxOrigin));
|
||||
b = gfx::PointF(snap_to_grid(gridBounds, gfx::Point(b), PreferSnapTo::BoxOrigin));
|
||||
b = gfx::PointF(snap_to_grid(gridBounds, gfx::Point(b), PreferSnapTo::BoxEnd));
|
||||
}
|
||||
|
||||
// Do not use "gfx::Rect(a, b)" here because if a > b we want to
|
||||
@ -521,7 +561,7 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
|
||||
case RotateSEHandle: {
|
||||
// Cannot rotate tiles
|
||||
// TODO add support to rotate tiles in straight angles (changing tile flags)
|
||||
if (m_site.tilemapMode() == TilemapMode::Tiles)
|
||||
if (tilesModeOn)
|
||||
break;
|
||||
|
||||
double da = (std::atan2((double)(-pos.y + abs_pivot.y),
|
||||
@ -564,7 +604,7 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
|
||||
// Cannot skew tiles
|
||||
// TODO could we support to skew tiles if we have the set of tiles (e.g. diagonals)?
|
||||
// maybe too complex to implement in UI terms
|
||||
if (m_site.tilemapMode() == TilemapMode::Tiles)
|
||||
if (tilesModeOn)
|
||||
break;
|
||||
|
||||
// u
|
||||
@ -703,7 +743,7 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
|
||||
|
||||
case PivotHandle: {
|
||||
// Calculate the new position of the pivot
|
||||
gfx::PointF newPivot = m_initialData.pivot() + gfx::Point(pos) - m_catchPos;
|
||||
gfx::PointF newPivot = m_initialData.pivot() + pos - m_catchPos;
|
||||
newTransformation = m_initialData;
|
||||
newTransformation.displacePivotTo(newPivot);
|
||||
break;
|
||||
@ -716,6 +756,8 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
|
||||
void PixelsMovement::getDraggedImageCopy(std::unique_ptr<Image>& outputImage,
|
||||
std::unique_ptr<Mask>& outputMask)
|
||||
{
|
||||
// Absurd situation: tilemapMode == Tiles and current layer isn't a tilemap
|
||||
ASSERT(m_site.tilemapMode() == TilemapMode::Pixels || m_site.layer()->isTilemap());
|
||||
gfx::Rect bounds = m_currentData.transformedBounds();
|
||||
if (bounds.isEmpty())
|
||||
return;
|
||||
@ -763,6 +805,39 @@ void PixelsMovement::getDraggedImageCopy(std::unique_ptr<Image>& outputImage,
|
||||
outputMask.reset(mask.release());
|
||||
}
|
||||
|
||||
void PixelsMovement::alignMasksAndTransformData(
|
||||
const Mask* initialMask0,
|
||||
const Mask* initialMask,
|
||||
const Mask* currentMask,
|
||||
const Transformation* initialData,
|
||||
const Transformation* currentData,
|
||||
const doc::Grid& grid,
|
||||
const gfx::Size& deltaA,
|
||||
const gfx::Size& deltaB)
|
||||
{
|
||||
m_initialMask0->replace(make_aligned_mask(&grid, initialMask0));
|
||||
m_initialMask->replace(make_aligned_mask(&grid, initialMask));
|
||||
m_currentMask->replace(make_aligned_mask(&grid, currentMask));
|
||||
m_initialData = *initialData;
|
||||
m_initialData.bounds(m_initialMask0->bounds());
|
||||
m_currentData = *currentData;
|
||||
// Raw grid alignment of currentData can result in unintentional scaling.
|
||||
// That's why we need to know if the artist's intention was just to move
|
||||
// the selection and/or scaling via 'initialDeltaA' and 'initialDeltaB'.
|
||||
const gfx::Point currentDataAlignedOrigin =
|
||||
grid.alignBounds(gfx::Rect(m_initialData.bounds().x + deltaA.w,
|
||||
m_initialData.bounds().y + deltaA.h,
|
||||
1, 1)).origin();
|
||||
int deltaH = deltaB.w - deltaA.w;
|
||||
int deltaV = deltaB.h - deltaA.h;
|
||||
const gfx::RectF currentDataBounds(
|
||||
currentDataAlignedOrigin.x,
|
||||
currentDataAlignedOrigin.y,
|
||||
m_initialData.bounds().w + deltaH,
|
||||
m_initialData.bounds().h + deltaV);
|
||||
m_currentData.bounds(currentDataBounds);
|
||||
}
|
||||
|
||||
void PixelsMovement::stampImage()
|
||||
{
|
||||
stampImage(false);
|
||||
@ -796,6 +871,42 @@ void PixelsMovement::stampImage(bool finalStamp)
|
||||
stampExtraCelImage();
|
||||
}
|
||||
|
||||
// Saving original values before the 'for' loop and the
|
||||
// 'reproduceAllTransformationsWithInnerCmds' function for restoring later.
|
||||
// All values of m_initialXX, m_currentXX will be recalculated
|
||||
// to align their original selection bounds with each cel's grid.
|
||||
const TilemapMode originalSiteTilemapMode = (
|
||||
m_site.tilemapMode() == TilemapMode::Tiles &&
|
||||
m_site.layer()->isTilemap()? TilemapMode::Tiles : TilemapMode::Pixels);
|
||||
const TilesetMode originalSiteTilesetMode = m_site.tilesetMode();
|
||||
const Mask initialMask0(*m_initialMask0);
|
||||
const Mask initialMask(*m_initialMask);
|
||||
const Mask currentMask(*m_currentMask);
|
||||
auto initialData = m_initialData;
|
||||
auto currentData = m_currentData;
|
||||
|
||||
// We need a way to know if 'a' or 'b' corners has changed
|
||||
// as result of a scaling or moving command to replicate the intention on
|
||||
// the other layers according the original mask (which can be aligned or
|
||||
// not to the tilemap grid)
|
||||
//
|
||||
// a ----
|
||||
// | |
|
||||
// | |
|
||||
// ---- b
|
||||
const gfx::Rect currentAlignedBounds(
|
||||
m_site.grid().alignBounds(currentData.bounds()));
|
||||
const gfx::Rect initialAlignedBounds(
|
||||
m_site.grid().alignBounds(initialMask.bounds()));
|
||||
const gfx::Size deltaA(currentAlignedBounds.origin().x -
|
||||
initialAlignedBounds.origin().x,
|
||||
currentAlignedBounds.origin().y -
|
||||
initialAlignedBounds.origin().y);
|
||||
const gfx::Size deltaB(currentAlignedBounds.x2() -
|
||||
initialAlignedBounds.x2(),
|
||||
currentAlignedBounds.y2() -
|
||||
initialAlignedBounds.y2());
|
||||
|
||||
for (Cel* target : cels) {
|
||||
// We'll re-create the transformation for the other cels
|
||||
if (target != currentCel) {
|
||||
@ -803,7 +914,36 @@ void PixelsMovement::stampImage(bool finalStamp)
|
||||
m_site.layer(target->layer());
|
||||
m_site.frame(target->frame());
|
||||
ASSERT(m_site.cel() == target);
|
||||
|
||||
Grid targetGrid(m_site.grid());
|
||||
// Align masks and transformData before to 'reproduceAllTransformationsWithInnerCmds'
|
||||
// Note: this alignement is needed only when the editor is on 'TilemapMode::Tiles',
|
||||
// on the other hand 'TilemapMode::Pixels' do not require any additional
|
||||
// mask/transformData adjustments.
|
||||
if (originalSiteTilemapMode == TilemapMode::Tiles) {
|
||||
if (target->layer()->isTilemap()) {
|
||||
alignMasksAndTransformData(&initialMask0,
|
||||
&initialMask,
|
||||
¤tMask,
|
||||
&initialData,
|
||||
¤tData,
|
||||
targetGrid,
|
||||
deltaA,
|
||||
deltaB);
|
||||
m_site.tilemapMode(TilemapMode::Tiles);
|
||||
}
|
||||
else {
|
||||
m_initialMask0->replace(initialMask0);
|
||||
m_initialMask->replace(initialMask);
|
||||
m_currentMask->replace(currentMask);
|
||||
m_initialData.bounds(initialData.bounds());
|
||||
m_currentData.bounds(currentData.bounds());
|
||||
m_site.tilemapMode(TilemapMode::Pixels);
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_site.tilemapMode(TilemapMode::Pixels);
|
||||
m_site.tilesetMode(TilesetMode::Auto);
|
||||
}
|
||||
reproduceAllTransformationsWithInnerCmds();
|
||||
}
|
||||
|
||||
@ -811,12 +951,20 @@ void PixelsMovement::stampImage(bool finalStamp)
|
||||
stampExtraCelImage();
|
||||
}
|
||||
|
||||
m_initialMask0->replace(initialMask0);
|
||||
m_initialMask->replace(initialMask);
|
||||
m_currentMask->replace(currentMask);
|
||||
m_initialData.bounds(initialData.bounds());
|
||||
m_currentData.bounds(currentData.bounds());
|
||||
m_site.tilesetMode(originalSiteTilesetMode);
|
||||
currentCel = m_site.cel();
|
||||
if (currentCel &&
|
||||
(m_site.layer() != currentCel->layer() ||
|
||||
m_site.frame() != currentCel->frame())) {
|
||||
m_site.layer(currentCel->layer());
|
||||
m_site.frame(currentCel->frame());
|
||||
m_site.tilemapMode(originalSiteTilemapMode);
|
||||
m_site.tilesetMode(originalSiteTilesetMode);
|
||||
redrawExtraImage();
|
||||
}
|
||||
}
|
||||
@ -1000,6 +1148,7 @@ void PixelsMovement::redrawExtraImage(Transformation* transformation)
|
||||
if (m_site.tilemapMode() == TilemapMode::Tiles) {
|
||||
// Transforming tiles
|
||||
extraCelSize = m_site.grid().canvasToTile(bounds).size();
|
||||
bounds = m_site.grid().alignBounds(bounds);
|
||||
}
|
||||
else {
|
||||
// Transforming pixels
|
||||
@ -1046,7 +1195,8 @@ void PixelsMovement::drawImage(
|
||||
auto corners = transformation.transformedCorners();
|
||||
gfx::Rect bounds = corners.bounds(transformation.cornerThick());
|
||||
|
||||
if (m_site.tilemapMode() == TilemapMode::Tiles) {
|
||||
if (m_site.tilemapMode() == TilemapMode::Tiles &&
|
||||
m_site.layer()->isTilemap()) {
|
||||
dst->setMaskColor(doc::notile);
|
||||
dst->clear(dst->maskColor());
|
||||
|
||||
@ -1408,10 +1558,16 @@ void PixelsMovement::reproduceAllTransformationsWithInnerCmds()
|
||||
|
||||
m_document->setMask(m_initialMask0.get());
|
||||
m_initialMask->copyFrom(m_initialMask0.get());
|
||||
m_originalImage.reset(
|
||||
new_image_from_mask(
|
||||
m_site, m_initialMask.get(),
|
||||
Preferences::instance().experimental.newBlend()));
|
||||
if (m_site.layer()->isTilemap() && m_site.tilemapMode() == TilemapMode::Tiles) {
|
||||
m_originalImage.reset(
|
||||
new_tilemap_from_mask(
|
||||
m_site, m_initialMask.get()));
|
||||
}
|
||||
else
|
||||
m_originalImage.reset(
|
||||
new_image_from_mask(
|
||||
m_site, m_initialMask.get(),
|
||||
Preferences::instance().experimental.newBlend()));
|
||||
|
||||
for (const InnerCmd& c : m_innerCmds) {
|
||||
switch (c.type) {
|
||||
|
@ -74,6 +74,7 @@ namespace app {
|
||||
const Image* moveThis,
|
||||
const Mask* mask,
|
||||
const char* operationName);
|
||||
~PixelsMovement();
|
||||
|
||||
const Site& site() { return m_site; }
|
||||
|
||||
@ -161,6 +162,16 @@ namespace app {
|
||||
const double angle);
|
||||
CelList getEditableCels();
|
||||
void reproduceAllTransformationsWithInnerCmds();
|
||||
|
||||
void alignMasksAndTransformData(const Mask* initialMask0,
|
||||
const Mask* initialMask,
|
||||
const Mask* currentMask,
|
||||
const Transformation* initialData,
|
||||
const Transformation* currentData,
|
||||
const doc::Grid& grid,
|
||||
const gfx::Size& deltaA,
|
||||
const gfx::Size& deltaB);
|
||||
|
||||
#if _DEBUG
|
||||
void dumpInnerCmds();
|
||||
#endif
|
||||
|
@ -256,10 +256,8 @@ FontPopup::FontPopup(const FontInfo& fontInfo)
|
||||
// directories (fontDirs)
|
||||
base::paths files;
|
||||
for (const auto& fontDir : fontDirs) {
|
||||
for (const auto& file : base::list_files(fontDir)) {
|
||||
std::string fullpath = base::join_path(fontDir, file);
|
||||
if (base::is_file(fullpath))
|
||||
files.push_back(fullpath);
|
||||
for (const auto& file : base::list_files(fontDir, base::ItemType::Files)) {
|
||||
files.push_back(base::join_path(fontDir, file));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,7 +230,8 @@ bool HomeView::onCopy(Context* ctx)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HomeView::onPaste(Context* ctx)
|
||||
bool HomeView::onPaste(Context* ctx,
|
||||
const gfx::Point* position)
|
||||
{
|
||||
auto clipboard = ctx->clipboard();
|
||||
if (clipboard->format() == ClipboardFormat::Image) {
|
||||
|
@ -74,7 +74,8 @@ namespace app {
|
||||
bool onCanClear(Context* ctx) override;
|
||||
bool onCut(Context* ctx) override;
|
||||
bool onCopy(Context* ctx) override;
|
||||
bool onPaste(Context* ctx) override;
|
||||
bool onPaste(Context* ctx,
|
||||
const gfx::Point* position) override;
|
||||
bool onClear(Context* ctx) override;
|
||||
void onCancel(Context* ctx) override;
|
||||
|
||||
|
@ -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
|
||||
@ -87,10 +88,11 @@ void InputChain::copy(Context* ctx)
|
||||
}
|
||||
}
|
||||
|
||||
void InputChain::paste(Context* ctx)
|
||||
void InputChain::paste(Context* ctx,
|
||||
const gfx::Point* position)
|
||||
{
|
||||
for (auto e : m_elements) {
|
||||
if (e->onCanPaste(ctx) && e->onPaste(ctx))
|
||||
if (e->onCanPaste(ctx) && e->onPaste(ctx, position))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -8,6 +9,8 @@
|
||||
#define APP_INPUT_CHAIN_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "gfx/point.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace ui {
|
||||
@ -35,7 +38,8 @@ namespace app {
|
||||
|
||||
void cut(Context* ctx);
|
||||
void copy(Context* ctx);
|
||||
void paste(Context* ctx);
|
||||
void paste(Context* ctx,
|
||||
const gfx::Point* position);
|
||||
void clear(Context* ctx);
|
||||
void cancel(Context* ctx);
|
||||
|
||||
|
@ -9,6 +9,8 @@
|
||||
#define APP_INPUT_CHAIN_ELEMENT_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "gfx/point.h"
|
||||
|
||||
namespace ui {
|
||||
class Message;
|
||||
}
|
||||
@ -34,7 +36,8 @@ namespace app {
|
||||
// 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;
|
||||
virtual bool onPaste(Context* ctx,
|
||||
const gfx::Point* position) = 0;
|
||||
virtual bool onClear(Context* ctx) = 0;
|
||||
virtual void onCancel(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
|
||||
@ -138,6 +138,7 @@ namespace app {
|
||||
const KeyboardShortcuts& globalKeys) const;
|
||||
bool isPressed() const;
|
||||
bool isLooselyPressed() const;
|
||||
bool isListed() const;
|
||||
|
||||
bool hasAccel(const ui::Accelerator& accel) const;
|
||||
bool hasUserDefinedAccels() const;
|
||||
|
@ -494,6 +494,11 @@ bool Key::isLooselyPressed() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Key::isListed() const
|
||||
{
|
||||
return type() != KeyType::Command || !command()->isListed(params());
|
||||
}
|
||||
|
||||
bool Key::hasAccel(const ui::Accelerator& accel) const
|
||||
{
|
||||
return accels().has(accel);
|
||||
|
@ -61,8 +61,8 @@ namespace app {
|
||||
~SkinTheme();
|
||||
|
||||
const std::string& path() { return m_path; }
|
||||
int preferredScreenScaling() { return m_preferredScreenScaling; }
|
||||
int preferredUIScaling() { return m_preferredUIScaling; }
|
||||
int preferredScreenScaling() const { return m_preferredScreenScaling; }
|
||||
int preferredUIScaling() const { return m_preferredUIScaling; }
|
||||
|
||||
text::FontMgrRef fontMgr() const override { return m_fontMgr; }
|
||||
text::Font* getDefaultFont() const override { return m_defaultFont.get(); }
|
||||
|
@ -4398,7 +4398,8 @@ bool Timeline::onCopy(Context* ctx)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Timeline::onPaste(Context* ctx)
|
||||
bool Timeline::onPaste(Context* ctx,
|
||||
const gfx::Point* position)
|
||||
{
|
||||
auto clipboard = ctx->clipboard();
|
||||
if (clipboard->format() == ClipboardFormat::DocRange) {
|
||||
|
@ -190,7 +190,8 @@ namespace app {
|
||||
bool onCanClear(Context* ctx) override;
|
||||
bool onCut(Context* ctx) override;
|
||||
bool onCopy(Context* ctx) override;
|
||||
bool onPaste(Context* ctx) override;
|
||||
bool onPaste(Context* ctx,
|
||||
const gfx::Point* position) override;
|
||||
bool onClear(Context* ctx) override;
|
||||
void onCancel(Context* ctx) override;
|
||||
|
||||
|
@ -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
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -381,12 +381,13 @@ bool Workspace::onCopy(Context* ctx)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Workspace::onPaste(Context* ctx)
|
||||
bool Workspace::onPaste(Context* ctx,
|
||||
const gfx::Point* position)
|
||||
{
|
||||
WorkspaceView* view = activeView();
|
||||
InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
|
||||
if (activeElement)
|
||||
return activeElement->onPaste(ctx);
|
||||
return activeElement->onPaste(ctx, position);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user