Compare commits

..

151 Commits

Author SHA1 Message Date
Martín Capello dc3395ad8b
Merge f6b23dc4e8 into e39dc90001 2025-09-21 20:15:52 +02:00
David Capello e39dc90001 [lua] Enclose key names for plugin preferences with ["..."] when it's required (#5412) 2025-09-19 15:27:00 -03:00
Martín Capello 8f0dea0988 Avoid syntax errors reading extension preferences
Fix #5412 by using brackets and double quotes to enclose the name of
the preferences' properties and avoid issues when they contain unicode
chars
2025-09-19 14:58:35 -03:00
David Capello e8f18087e5 Update laf module 2025-09-18 20:47:56 -03:00
David Capello d8ac1a7a9e Update laf and add new libxrandr dependency 2025-09-17 09:15:35 -03:00
Aiden J Erickson 5cfa75ab3f [multimon] allow windows to open in non-default workareas (#4560, #5426)
Fix issue below:
In multi-monitor setups, native windows, e.g. the preview window, will,
when re-opened, be moved/clamped to the monitor of the main aseprite
window even if the re-opened window was previously and explicitly moved
to a different monitor.
2025-09-16 20:42:21 -03:00
David Capello c444b566e1 Fix painting UI theme colors as they are specified in sRGB color space
Fixes a regression found after merging #5414:
https://github.com/aseprite/aseprite/pull/5414#issuecomment-3286339563

We always expected a sRGB color in ui::Graphics API and we can specify
a color in another color space using the ui::Paint version of its
member functions.

Several functions related to color spaces are now using a ui::Display
to receive the specific display where we're going to paint, instead of
using os::System::instance()->defaultWindow()->colorSpace().
2025-09-16 09:42:35 -03:00
David Capello 5d5f3ec234 Convert AppShortcut in a ui::Shortcut subclass 2025-09-12 15:48:38 -03:00
David Capello 71e680e05a Refactor key context matching (fix #5390)
* Added AppShortcut::fitsBetterThan() to compare shortcuts and decide
  which one is better depending on the current key context and the ones
  where shortcuts are defined
* Added Key::fitsContext() to known if this key definition makes sense
  for a given current key context, e.g. Normal context can be used in
  SelectionTool context, and SelectionTool can be used in Transformation
* Added KeyboardShortcuts::findBestKeyFromMessage() to centralize the
  keyboard shortcuts matching moving some code from
  CustomizedGuiManager::processKey()
* Added more keyboard shortcuts tests (including some for #5390)
2025-09-12 15:41:08 -03:00
David Capello df5dcdc1d9 Add some KeyboardShortcuts basic tests 2025-09-12 15:07:26 -03:00
David Capello 85793a9e5f Add AppShortcut to include app::KeySource + ui::Shortcut
We need a list of shorcuts (AppShortcuts) similar to ui::Shortcuts but
including the key source to compare later where the shortcut was
defined and give more priority to the user-defined shortcuts. This
logic is not yet added, this patch is only a refactor.
2025-09-12 15:07:26 -03:00
David Capello d7b07a5173 Now Paint::color() can receive a color space w/any laf backend 2025-09-12 12:24:53 -03:00
Thorbjørn Lindeijer fae42dbe12 CMake: Use find_package(PkgConfig) instead of direct include
This gets rid of the following warning:

```
CMake Warning (dev) at /usr/share/cmake/Modules/FindPackageHandleStandardArgs.cmake:441 (message):
  The package name passed to `find_package_handle_standard_args` (PkgConfig)
  does not match the name of the calling package (HarfBuzz).  This can lead
  to problems in calling code that expects `find_package` result variables
  (e.g., `_FOUND`) to follow a certain pattern.
Call Stack (most recent call first):
  /usr/share/cmake/Modules/FindPkgConfig.cmake:114 (find_package_handle_standard_args)
  aseprite/cmake/FindHarfBuzz.cmake:33 (include)
  CMakeLists.txt:173 (find_package)
This warning is for project developers.  Use -Wno-dev to suppress it.
```

By applying the following change:
407fa892d9

The file could use further updates based on the upstream version.
2025-09-12 11:18:52 -03:00
David Capello 689e1ff524 Remove trailing whitespace from feature_request.md
This was the only file failing a pre-commit run --all-files
2025-09-11 17:59:13 -03:00
Gaspar Capello 907138d5dd Fix wrong color space in multiple UI elements
This fix solves color space missmatch in the following UI elements:
- Timeline thumbnails
- Palette entries
- Tileset entries
- Color Spectrum and Wheels
- Color Popup Gradients
- Color Button
2025-09-05 14:50:36 -03:00
Christian Kaiser af349ce7ee [lua] Fix new layers not going to the top when selected (fix #5364) 2025-09-04 12:02:10 -03:00
David Capello 3bc4ac0838 Fix the "No Hinting" label for Font info description 2025-09-04 11:52:55 -03:00
Christian Kaiser 1774d86939 Reset weight button when weight is not available, show weight in popup 2025-09-04 11:49:57 -03:00
Christian Kaiser dce1dfd06b Add a font weight dropdown to the font selector 2025-09-04 11:49:57 -03:00
adityarana14 6283d69707 [Script] Added support for mousecursor and still supporting mouseCursor
Added the support for both "mousecursor" and "mouseCursor"
2025-09-04 10:51:49 -03:00
David Capello 21ad78cdbb Move app::Key to its own key.cpp file 2025-09-02 15:22:47 -03:00
David Capello 20b11a0fd3 Add image_benchmark 2025-09-01 17:09:03 -03:00
David Capello 8d4c4857ee Add new kind of Image iterators line by line: Image::read/writeArea() 2025-09-01 17:09:03 -03:00
David Capello 70c8924719 Add flag to switch IMAGE_BITMAP to 8bpp instead of 1bpp
We can set DOC_USE_BITMAP_AS_1BPP=0 to use 8bpp to simplify iteration
of bitmap pixel format using a 8bpp buffer just like indexed mode.
2025-09-01 17:09:03 -03:00
Martín Capello f6b23dc4e8 Remove unused parameter 2025-08-29 15:59:28 -03:00
Martín Capello 9fd1214947 Add i18n for a couple of hardcoded strings 2025-08-29 15:59:21 -03:00
David Capello 286dd1c755 Update laf module 2025-08-28 11:56:26 -03:00
Martín Capello 3a14ac72a4 Fix DECORATIVE widget drawing outside of window 2025-08-27 15:33:10 -03:00
Gaspar Capello 2db193b8e3 Fix pivot position when Tiled Mode is enabled (fix #2316) 2025-08-27 15:32:50 -03:00
David Capello 002356ce19 [lua] Avoid accessing nullptr parent display at exit (#5384) 2025-08-27 15:26:31 -03:00
David Capello 9a1e92da35 Fix arrow keys to move selection (fix #5385)
Regression introduced in f61c2c3950
2025-08-27 15:22:42 -03:00
David Capello 07803ff361 Add missing [[fallthrough]] attribute 2025-08-27 12:25:19 -03:00
David Capello 8e07617a9d Fix non-void function path without return 2025-08-27 12:22:01 -03:00
Martín Capello 9c5ca6bcc6 Internationalize " Copy" string usage 2025-08-27 12:16:35 -03:00
Martín Capello e193891df3 Use "Copy" suffix to name duplicated slices 2025-08-27 12:16:35 -03:00
Martín Capello bb8547d004 Offset duplicated slices to avoid overlapping 2025-08-27 12:16:35 -03:00
Martín Capello 85997a08cf Add slices copy&paste and duplication (fix #4466) 2025-08-27 12:16:35 -03:00
Gaspar Capello 0995e72a6f Fix bug in Saturation layer blend mode (fix #2661) 2025-08-27 12:16:05 -03:00
David Capello f61c2c3950 Revert Esc key behavior to drop+deselect (fix #5102)
This adds a new button in the context bar so we have the three
available options to handle a transformation/drop pixels:

* Drop pixels and deselect (Esc key)
* Drop pixels but keep the selection (Enter key), new "Apply" command
* Discard changes/undo (Ctrl+Z)

This adds a new key context (Transformation) and also fixes tooltip
shortcuts on context bar buttons to show the current configured
shortcut for each action.

Reverts debab653fa and 194f8424a8
2025-08-27 11:56:57 -03:00
David Capello 0c49f2d7ad Update both .xml theme files through the tinyxml2 serializer 2025-08-27 09:31:46 -03:00
David Capello 5f7cc42333 Remove unnecessary call to regenerateCols() in Timeline::refresh()
Related to:
https://github.com/aseprite/aseprite/pull/5367#issuecomment-3225287017
2025-08-26 15:39:55 -03:00
Christian Kaiser 8e75cfc4c7 [lua] Refresh timeline when changing layer collapsed status (fix #5366) 2025-08-26 15:35:28 -03:00
David Capello 983b07383f [lua] Fix default autofit for a Dialog() when autofit is not specified (#5176 / #5321) 2025-08-26 11:23:48 -03:00
Christian Kaiser c57554646b Escape characters in the console that we can't show properly (fix #5324) 2025-08-26 11:21:08 -03:00
Christian Kaiser 74953174d6 [lua] Added `autofit` and `sizeHint` properties to Dialog (fix #5176) 2025-08-26 11:05:06 -03:00
Christian Kaiser 49fa35237a Activate the native window when asking the user to save sprite changes (fix #3542 / #5318) 2025-08-26 09:57:51 -03:00
Christian Kaiser 0ccf9dcc4f [lua] Add app.tip (#5316 / #5348) 2025-08-26 09:00:40 -03:00
David Capello 194f8424a8 Add right-click to configure the cancel selection button (#5102 / #5145) 2025-08-26 07:55:14 -03:00
Joshua Lopez debab653fa Fix marquee tool escape deselection (fix #5102) 2025-08-25 16:51:03 -03:00
hwabis 6e9024d54d Fix mouse wheel zooming not working with zoom tool (quick) 2025-08-25 16:35:14 -03:00
David Capello 1fa7fd0831 Minor UI fixes for dialogs with user data
This patch unifies the behavior of all dialogs with user data to
expand the window vertically onToggleUserData().
2025-08-25 16:20:37 -03:00
David Capello ab6b040e83 Rename Mask::m_freeze_count -> Mask::m_freezes 2025-08-25 15:30:23 -03:00
Gaspar Capello bc312a37b3 Fix Color Management regression (fix #5333)
Before this fix, the assigned color space was always sRGB (default),
so this color space was used for the rest of the rendering.
Now the color space for the back layer of the display
has been made explicit.
2025-08-25 13:10:25 -03:00
Christian Kaiser 3129fda977 [lua] Add missing "DIAGONAL" FlipType (fix #5359) 2025-08-17 14:58:26 -03:00
David Capello 6cb61fb41e Remove deprecated issue template 2025-08-14 14:17:56 -03:00
David Capello aa817a8d2a
Update issue templates 2025-08-14 14:11:47 -03:00
David Capello 40031f83d8 [lua] Fix typo in Sprite:newCel() error 2025-08-13 13:06:05 -03:00
David Capello 90282dbc40 Fix cast error in HarfBuzz library between function types
This includes the following patch:
60c6b7786d
2025-08-11 15:29:58 -03:00
Martín Capello 1227f9c49c Refactor getLayerIndexFromSprite into LayerGroup 2025-08-11 14:47:56 -03:00
Martín Capello d61ae919ad Fix crash dropping file on timeline (fix #5289) 2025-08-11 14:47:56 -03:00
David Capello b2b2583176 [lua] Update scripting API version to 35 2025-08-07 20:18:10 -03:00
David Capello b535212642 Update laf module 2025-08-07 20:14:43 -03:00
Christian Kaiser 229a3cdf65 [lua] Add onchecked parameter to Commands 2025-08-07 18:57:28 -03:00
Christian Kaiser b3814ec912 Unify ContextBar updates when moving pixels, tooltips (fix #5329) 2025-08-06 20:20:42 -03:00
David Capello e88f3bb413 Show error if curl/unzip tools aren't available (fix #5309) 2025-08-06 14:44:21 -03:00
Christian Kaiser eaa2bdf0af [lua] Process mnemonics consistently for plugins (fix #5250) 2025-08-04 15:58:29 -03:00
Christian Kaiser 57309e5aa5 Allow gif encoding to be stopped (fix #2619) 2025-08-01 20:46:32 -03:00
Christian Kaiser 4bb9239f50 [lua] Add `resizeable` property to Dialog constructor (fix #5177)
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-08-01 18:57:50 -03:00
David Capello cef92c1a38 Add .plist files for macOS
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
We don't have an Aseprite.app target in cmake files yet, but we might
add it in a near future.
2025-07-28 16:18:19 -03:00
Christian Kaiser 22e72ab5cb [win] Fix includeDesktopDir returning the default path
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
Uses SHGFP_TYPE_CURRENT which returns the Desktop that the user has configured instead of the default, fixes Windows 11's OneDrive Desktop folder.
2025-07-28 10:47:53 -03:00
Christian Kaiser 80fa065bd5 [lua] Add sprite.undoHistory
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-07-25 13:58:52 -03:00
David Capello de1ccb24dd [win] Don't drop text when IME dialog composition is accepted w/Enter
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
With #5230, now that we can show the IME dialog on Windows, when we
are selecting a specific word/composition in the IME dialog, if we
press Enter we'll receive that Enter onKeyUp(). It's better if we
process the Enter key onKeyDown() (as the IME enter key is not
received in that case).
2025-07-25 09:19:50 -03:00
David Capello 7d91c4b9d9 [win] Fix dead keys on Windows 2025-07-24 17:45:46 -03:00
Cerallin 6d89a6bc15 fix entry
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-07-24 12:50:32 -03:00
Cerallin d4e97b5a96 Add const method Entry::caretPosOnScreen()
This method is for Entry::setTextInput() and IME positioning.
2025-07-24 12:50:32 -03:00
Cerallin 205b18dc0f Make Entry::getCharBoxBounds() a const method 2025-07-24 12:50:32 -03:00
David Capello 2ba051b59b Fix crash deselecting moved pixels when using certain extensions (fix #5280)
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
Although the issue refers to deselecting MovingPixelsState, the same
crash could happen when canceling/finishing WritingTextState or
MovingSelectionState. This fixes the crash for all these states.
2025-07-23 19:01:37 -03:00
Christian Kaiser 3fcb000eb1 Fix slice transformations not updating editors
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-07-22 02:11:07 -03:00
David Capello af9dc3c817 Fix brush boundaries accumulation switching brush type only (fix #5281)
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
2025-07-21 17:28:11 -03:00
David Capello 250dfdc86a Convert the brush generation counter into an atomic var 2025-07-21 17:27:23 -03:00
David Capello c904c41b39 Don't show stroke/fill option for theme fonts
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-07-18 18:48:35 -03:00
David Capello 9e941e9a8b [win32] Fix listing hidden files on Windows (related to #5269 / #3079) 2025-07-18 18:37:44 -03:00
Liebranca bbab4d5875 Add 'Show hidden' check to file selector 2025-07-18 18:26:35 -03:00
David Capello 5c4daff128 Add options to stroke/fill text (fix #5271)
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-07-14 23:17:25 -03:00
David Capello 11a7b061ff Remove unused var 2025-07-14 20:24:32 -03:00
David Capello 283bedf77e Add pinned/recent folders to export file popup menus
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
Related to https://community.aseprite.org/t/25920
2025-07-10 18:55:28 -03:00
David Capello 2eeb6f04a7 Fix "buttononly" bool attribute for <filename> widget 2025-07-10 18:13:10 -03:00
David Capello 706d0b8a7a Add possibility to select export file names w/"one click"
It's not with one click, it requires dragging the mouse but it's
better than two clicks.

Related to https://community.aseprite.org/t/25920
2025-07-10 18:12:38 -03:00
David Capello 2f3a7f5dec Update to new laf API: setTranslateDeadKeys -> setTextInput
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
2025-07-10 16:06:03 -03:00
David Capello d5de74b715 Update laf module
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
2025-07-10 08:52:01 -03:00
David Capello 2d87a7b184 [ui] Fix drawing cursor at the end of the Entry/Combobox field
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
Regression introduced in 1a6a39700e
2025-07-07 22:21:58 -03:00
Gaspar Capello 2f22804fe8 Fix GIF animations with semi-transparent colors (fix #5226)
Prior to this fix, animations were incorrectly converted to GIF when
the animation contained semi-transparent colors or backgrounds
(specifically, colors with alpha <= 128). Essentially, semi-transparent
pixels didn't repaint the pixel of the processed frame, meaning they
retained the color from the previous frame.
2025-07-07 17:43:19 -03:00
Gaspar Capello bf1b4c6f50 Fix session 'isRunning' detection (fix #5252)
Before this fix, sometimes files available for recovery aren't
displayed due to the coincidence of the pid number of a crashed
session with the current pid number of the current session. This
coincidence caused Aseprite to falsely detect that it was
the current session.
2025-07-07 17:15:36 -03:00
David Capello 250244c777 [ui] Fix possible crash when there is no TextBlob to paint (fix #5255)
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
2025-07-07 12:39:12 -03:00
David Capello b4555fc098 Add "Initial State" string for "Undo History" window to en.ini
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-07-03 11:18:21 -03:00
Gaspar Capello 8783135bf7 Fix convert_image_to_surface function (fix #5257)
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
Before this fix, 'convert_image_to_surface' would generate saturated
colors when converting semi-transparent colors. This happened because
 Skia Surfaces assumed colors with pre-multiplied alpha.
2025-07-02 20:44:33 -03:00
David Capello 1a6a39700e Fix suffix separation in Entry fields w/TTF fonts (fix #5261) 2025-07-02 20:43:17 -03:00
David Capello ce742bcbc1 Fix typo in GitHub profile URL
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
2025-07-01 21:44:51 -03:00
David Capello 3c350c3e67 Update laf module
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-06-25 08:20:50 -03:00
David Capello 220d2d3a2d Update laf module
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-06-24 11:16:06 -03:00
Martín Capello 322040c4fb Recover Windows Vista compatibility (fix #5211) 2025-06-24 10:10:55 -03:00
David Capello ee69a2f2f6 Fix baseline alignment of file list items/names w/multiple fonts/emojis
build-auto / build-auto (Debug, macos-latest) (push) Waiting to run Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Waiting to run Details
build-auto / build-auto (Debug, windows-latest) (push) Waiting to run Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Waiting to run Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Waiting to run Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Waiting to run Details
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
2025-06-23 23:03:16 -03:00
David Capello 91f8410749 Fix bug in news items when multiple fonts are needed (fix #5221) 2025-06-23 22:49:05 -03:00
Gaspar Capello 8d5534d4eb Fix do not move the pivot when dragging a selection with Alt key to snap to grid (fix #5238) 2025-06-23 18:54:32 -03:00
David Capello 2ee8c68d94 Update laf module (fix #5240) 2025-06-23 17:56:38 -03:00
David Capello f7040190cc Fix implicit conversion of nullptr constant to 'bool'
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
2025-06-23 09:12:36 -03:00
David Capello 68bf0839aa Don't use userConfigPath to output automated test files 2025-06-23 09:08:02 -03:00
Necronomison e5917389cb
Export user & extension defined properties (fix #5186, #5187) 2025-06-23 09:06:05 -03:00
LeSnow-Ye 27eecb3bdc Greedy merge pixels on saving to SVG formats
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-06-20 10:15:06 -03:00
David Capello b6cbeefb6e [lua] Update scripting API version to 34 2025-06-20 10:00:33 -03:00
Christian Kaiser 5596c3270d Add blendMode, name, opacity and visibility Sprite layer events 2025-06-20 09:58:09 -03:00
David Capello ab29b84f25 Fix crash accessing null extra cel when rendering interactive text (fix #5227)
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-06-20 09:51:04 -03:00
David Capello 444ef0f6b4 Update laf (fix #5233)
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
2025-06-19 19:38:50 -03:00
David Capello b3b956516b Fix themes loading custom TTF fonts without specifying a size (fix #5229)
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-06-13 18:22:00 -03:00
David Capello 32bf699655 Mitigate Linux/X11 lag when resizing windows (#5028)
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
In relation to these findings:
https://github.com/aseprite/aseprite/pull/5166#issuecomment-2944585171
https://github.com/aseprite/aseprite/pull/5166#issuecomment-2945369914
2025-06-11 17:30:00 -03:00
David Capello 5ceeac2f26 Add possibility to disable hinting for theme ttf/otf fonts (fix #5215) 2025-06-11 17:20:17 -03:00
David Capello 0ddf7d939b [ui] Align text with different fonts correctly to ui::Entry baseline
Regression introduced in af6e8b65c3
2025-06-11 17:06:55 -03:00
David Capello bd13e5d574 Fix bug w/user themes with odd size value for the Aseprite font 2025-06-11 15:45:23 -03:00
David Capello e53aa99080 Possible fix to crashes using the FontEntry widget (fix #5214) 2025-06-11 15:42:03 -03:00
David Capello af6e8b65c3 Fix default font for CJK and other translations (fix #5210)
Now we use the correct fallback font with its size for non-existent
glyph in the sprite sheet font.
2025-06-11 14:40:56 -03:00
Martín Capello 68342bdb66 Call parent's class onOpen (fix #5209)
This makes the window's Open signal to be triggered as usual
2025-06-11 12:57:07 -03:00
David Capello 8ff208d8d5 Fix warnings about unused var
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
2025-06-11 10:58:12 -03:00
David Capello 2bc4f0582d Fix crash (and show error message) when default font is nullptr (fix #5208)
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
Probably an edge-case for users that are creating new themes.
2025-06-10 22:02:12 -03:00
David Capello 2be4dc1474 Revert part of "Startup optimizations (#5090)" (fix #5207)
This reverts part of commit b130601716.
2025-06-10 21:19:37 -03:00
David Capello 2b522e222b Rename ui::Accelerator to ui::Shortcut
This was a pending refactor, where "user.aseprite-keys" files were
already using the "shortcut" attribute (instead of "accelerator").
This will include a refactor in the Weblate projects/all translations
to change [select_accelerator] section to [select_shortcut]. But that
must be coordinated after this commit is merged.
2025-06-09 17:20:16 -03:00
Maplegecko 698d79b049 [lua] Make Dialog:separator more in line with other widgets (fix #4989) 2025-06-09 13:26:39 -03:00
David Capello 03422e7251 [lua] Update scripting API version to 33 2025-06-07 12:08:00 -03:00
David Capello 3f1f131a39 Update laf 2025-06-07 12:05:56 -03:00
Christian Kaiser 54ea61fe58 Re-add os.rename and os.remove support 2025-06-07 12:05:34 -03:00
JoshuaL03 4c31c950c5
Fix file dialog size not being loaded properly (fix #4460) (#5202) 2025-06-07 11:30:27 -03:00
martincapello afbd28b3b4 Improve panel resizing performance
By avoiding calculating bounds for HIDDEN panel's children.
2025-06-05 11:11:20 -03:00
Joshua Lopez 1519589184 Fix user data visibility after window size reset (fix aseprite#4923) 2025-06-04 13:37:38 -03:00
Luke Barcenas 2c917e30c0 Fix: Prevent scrollbar from shifting on click 2025-06-03 16:54:42 -03:00
Gaspar Capello 5d0214a89d Fix wrong export of tileset (fix #5053)
Before this fix, when a sprite's color mode was set to Grayscale and
a tileset was exported, the resulting sheet was full of broken
tile images due to a misinterpretation of the pixel format in
the samples.

An unnecessary conversion of the original sprite was also observed
during the "Export Sprite Sheet" command. Specifically,
in the DocExporter::renderTexture function (fix #5088).
2025-06-03 13:19:56 -03:00
David Capello 4b1d49f5dc [ci][clang-tidy] Update clang-tidy-review version
We require the following fix: https://github.com/ZedThree/clang-tidy-review/issues/144
2025-06-02 22:24:42 -03:00
David Capello 0ccc800819 [clang-tidy] Allow if (intCondition) { } to ask for != 0 2025-06-02 20:44:17 -03:00
David Capello 14c0baa3db Convert standalone functions into snake_case 2025-06-02 19:50:05 -03:00
Martín Capello 30dcac99c6 Update copyright's year 2025-06-02 19:45:37 -03:00
Martín Capello d209971f07 Improve feedback when dropping into a cel
Show indicator to let the user know between which layers will the
dropped stuff be inserted
2025-06-02 19:45:37 -03:00
Martín Capello 0a88b86c99 Improve wording and linkage of some functions 2025-06-02 19:45:37 -03:00
Martín Capello ab449a2978 [win] Fix compilation when not using skia backend 2025-06-02 19:45:37 -03:00
Martín Capello b32cd0ff47 Avoid creating surface to determine color mode 2025-06-02 19:45:37 -03:00
Martín Capello ce0b9a6405 Fix tileset addition into destination sprite
Now the tileset addition is made using a command, which allows it to be
undone/redone properly
2025-06-02 19:45:37 -03:00
Martín Capello 67656c4977 Fix crash dropping elements at edge of bottom cel 2025-06-02 19:45:37 -03:00
Martín Capello 7817e7b37a Fix frame displacement of dropped layers 2025-06-02 19:45:37 -03:00
Martín Capello 45fbeda95b Avoid enqueuing events directly to laf-os 2025-06-02 19:45:37 -03:00
Martín Capello a3236bc1e9 Remove ui::Manager as friend of ui::Widget 2025-06-02 19:45:37 -03:00
Martín Capello d37e0df18f Replace use of reset(new...) by std::make_unique() 2025-06-02 19:45:37 -03:00
Martín Capello 7103829e60 Refactor DropOnTimeline command 2025-06-02 19:45:37 -03:00
Martín Capello eab8df6854 Support duplicating layers from other documents
Update DocApi::duplicateLayerAfter and DocApi::duplicateLayerBefore to
allow duplicating layers from documents that are not the same as the
source layer's document
2025-06-02 19:45:37 -03:00
Martín Capello 99a407f0c5 Move OpenFileJob impl from header to cpp file 2025-06-02 19:45:37 -03:00
218 changed files with 7226 additions and 4284 deletions

View File

@ -60,6 +60,8 @@ Checks: >
-readability-uppercase-literal-suffix
WarningsAsErrors: ''
CheckOptions:
- key: readability-implicit-bool-conversion.AllowIntegerConditions
value: true
- key: readability-implicit-bool-conversion.AllowPointerConditions
value: true
- key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic

View File

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

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

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

View File

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

View File

@ -24,7 +24,8 @@ jobs:
sudo apt-get update -qq
sudo apt-get install -y \
libpixman-1-dev libfreetype6-dev libharfbuzz-dev zlib1g-dev \
libx11-dev libxcursor-dev libxi-dev libgl1-mesa-dev libfontconfig1-dev
libx11-dev libxcursor-dev libxi-dev libxrandr-dev libgl1-mesa-dev \
libfontconfig1-dev
- uses: aseprite/get-ninja@main
- uses: ilammy/msvc-dev-cmd@v1
if: runner.os == 'Windows'

View File

@ -28,7 +28,8 @@ jobs:
sudo apt-get update -qq
sudo apt-get install -y \
libpixman-1-dev libfreetype6-dev libharfbuzz-dev zlib1g-dev \
libx11-dev libxcursor-dev libxi-dev libgl1-mesa-dev libfontconfig1-dev
libx11-dev libxcursor-dev libxi-dev libxrandr-dev libgl1-mesa-dev \
libfontconfig1-dev
- name: Install Skia
if: ${{ matrix.ui == 'gui' }}
shell: bash

View File

@ -25,7 +25,7 @@ jobs:
apt_packages: |
libc++-dev, libc++abi-dev, libpixman-1-dev,
libfreetype6-dev, libharfbuzz-dev, zlib1g-dev, libx11-dev,
libxcursor-dev, libxi-dev, libgl1-mesa-dev
libxcursor-dev, libxi-dev, libxrandr-dev, libgl1-mesa-dev
cmake_command: |
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DLAF_BACKEND=none -DCMAKE_EXPORT_COMPILE_COMMANDS=on
- uses: ZedThree/clang-tidy-review/upload@v0.20.1

View File

@ -13,6 +13,6 @@ jobs:
post-comments:
runs-on: ubuntu-latest
steps:
- uses: ZedThree/clang-tidy-review/post@v0.20.1
- uses: ZedThree/clang-tidy-review/post@v0.21.0
with:
token: ${{ secrets.CLANG_TIDY_TOKEN }}

View File

@ -4,7 +4,7 @@ Aseprite is being developed and maintained currently by [Igara Studio](https://i
The active team of developers is:
* [David Capello](https://github.com/dacap)
* [Gaspar Capello](https://github.com/Gsparoken)
* [Gaspar Capello](https://github.com/Gasparoken)
* [Martín Capello](https://github.com/martincapello)
* [Christian Kaiser](https://github.com/ckaiser)
* [Dante Paola](https://github.com/Liebranca)

View File

@ -74,7 +74,7 @@ On macOS you will need macOS 15.4 SDK and Xcode 16.3 (older versions might work)
You will need the following dependencies on Ubuntu/Debian:
sudo apt-get install -y g++ clang cmake ninja-build libx11-dev libxcursor-dev libxi-dev libgl1-mesa-dev libfontconfig1-dev
sudo apt-get install -y g++ clang cmake ninja-build libx11-dev libxcursor-dev libxi-dev libxrandr-dev libgl1-mesa-dev libfontconfig1-dev
Or use clang-12 packages (or newer) in case that clang in your distribution is older than clang 12.0:
@ -82,15 +82,15 @@ Or use clang-12 packages (or newer) in case that clang in your distribution is o
On Fedora:
sudo dnf install -y gcc-c++ clang libcxx-devel cmake ninja-build libX11-devel libXcursor-devel libXi-devel mesa-libGL-devel fontconfig-devel
sudo dnf install -y gcc-c++ clang libcxx-devel cmake ninja-build libX11-devel libXcursor-devel libXi-devel libXrandr-devel mesa-libGL-devel fontconfig-devel
On Arch:
sudo pacman -S gcc clang cmake ninja libx11 libxcursor mesa-libgl fontconfig libwebp
sudo pacman -S gcc clang cmake ninja libx11 libxcursor libxi libxrandr mesa-libgl fontconfig libwebp
On SUSE:
sudo zypper install gcc-c++ clang cmake ninja libX11-devel libXcursor-devel libXi-devel Mesa-libGL-devel fontconfig-devel
sudo zypper install gcc-c++ clang cmake ninja libX11-devel libXcursor-devel libXi-devel libXrandr-devel Mesa-libGL-devel fontconfig-devel
# Automatic Building

View File

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

View File

@ -30,7 +30,7 @@
# HARFBUZZ_INCLUDE_DIRS - containg the HarfBuzz headers
# HARFBUZZ_LIBRARIES - containg the HarfBuzz library
include(FindPkgConfig)
find_package(PkgConfig QUIET)
pkg_check_modules(PC_HARFBUZZ harfbuzz>=0.9.7)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because it is too large Load Diff

View File

@ -34,14 +34,14 @@
type="spritesheet"
descent="2"
file="aseprite_font.png">
<fallback font="Unicode" size="8" />
<fallback font="Unicode" size="14" />
</font>
<font name="Aseprite Mini"
type="spritesheet"
descent="1"
file="aseprite_mini.png">
<fallback font="Unicode" size="6" />
<fallback font="Unicode" size="10" />
</font>
</fonts>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Aseprite -->
<!-- Copyright (C) 2018-2024 Igara Studio S.A. -->
<!-- Copyright (C) 2018-2025 Igara Studio S.A. -->
<!-- Copyright (C) 2001-2018 David Capello -->
<gui>
<!-- Keyboard shortcuts -->
@ -371,6 +371,12 @@
<param name="quantity" value="1" />
</key>
<!-- Main selection actions (apply transformation / undo) -->
<key command="DeselectMask" shortcut="Esc" context="Transformation" />
<key command="Apply" shortcut="Enter" context="Transformation" />
<key command="Apply" shortcut="Enter Pad" context="Transformation" />
<key command="Undo" shortcut="Ctrl+Z" mac="Cmd+Z" context="Transformation" />
<!-- Move selection with arrows -->
<key command="MoveMask" shortcut="Left" context="Selection">
<param name="target" value="content" />
@ -1203,6 +1209,7 @@
<menu id="slice_popup_menu">
<item command="SliceProperties" text="@.properties" group="slice_popup_properties" />
<item command="DuplicateSlice" text="@.duplicate" group="slice_popup_duplicate" />
<item command="RemoveSlice" text="@.delete" group="slice_popup_delete" />
</menu>

View File

@ -356,6 +356,7 @@
<section id="file_selector">
<option id="current_folder" type="std::string" default="&quot;&lt;empty&gt;&quot;" />
<option id="zoom" type="double" default="1.0" />
<option id="show_hidden" type="bool" default="false" />
</section>
<section id="text_tool">
<option id="font_face" type="std::string" />

View File

@ -206,6 +206,7 @@ AddColor_Background = Background
AddColor_Foreground = Foreground
AddColor_Specific = Specific
AdvancedMode = Advanced Mode
Apply = Apply
AutocropSprite = Trim Sprite
AutocropSprite_ByGrid = Trim Sprite by Grid
BackgroundFromLayer = Background from Layer
@ -265,6 +266,7 @@ Despeckle = Despeckle
DeveloperConsole = Developer Console
DiscardBrush = Discard Brush
DuplicateLayer = Duplicate Layer
DuplicateSlice = Duplicate Slice
DuplicateSprite = Duplicate Sprite
DuplicateView = Duplicate View
Exit = Exit
@ -581,8 +583,9 @@ rotsprite = RotSprite
pixel_perfect = Pixel-perfect
linear_gradient = Linear Gradient
radial_gradient = Radial Gradient
drop_pixel = Drop pixels here (Enter)
cancel_drag = Cancel drag and drop (Esc)
drop_pixel_and_deselect = Apply transformation and deselect
drop_pixel = Apply transformation and keep selection
cancel_drag = Cancel transformation and undo/discard changes
auto_select_layer = Auto Select Layer
all = All
none = None
@ -621,6 +624,14 @@ current_layer = Current Layer
first_ref_layer = First Reference Layer
pick = Pick:
sample = Sample:
position_label = P:
rotation_label = R:
position_x = X Position
position_y = Y Position
size_width = Width
size_height = Height
rotation_angle = Angle
rotation_skew = Skew
[convolution_matrix]
reload_stock = &Reload Stock
@ -765,6 +776,7 @@ pinned_folders = Pinned Folders
recent_folders = Recent Folders
all_formats = All formats
all_files = All files
show_hidden = Show hidden
[filters]
selected_cels = Selected
@ -788,7 +800,22 @@ empty_fonts = No system fonts were found
[font_style]
antialias = Antialias
hinting = Hinting
hinting_none = No Hinting
hinting_slight = Slight Hinting
hinting_full = Full Hinting
ligatures = Ligatures
font_weight = Font Weight
italic = Italic
font_weight_100 = Thin
font_weight_200 = Extra Light
font_weight_300 = Light
font_weight_400 = Normal
font_weight_500 = Medium
font_weight_600 = Semi Bold
font_weight_700 = Bold
font_weight_800 = Extra Bold
font_weight_900 = Black
font_weight_1000 = Extra Black
[frame_combo]
all_frames = All frames
@ -816,6 +843,7 @@ same_in_all_tools = Same in all Tools
opacity = Opacity:
tolerance = Tolerance:
show_more = Show more...
copy_of = {} Copy
[general_text]
copy = &Copy
@ -956,6 +984,7 @@ key_context_move_tool = Move Tool
key_context_freehand_tool = Freehand Tool
key_context_shape_tool = Shape Tool
key_context_frames_selection = Frames Selection
key_context_transformation = Transformation
copy_selection = Copy Selection
snap_to_grid = Snap To Grid
lock_axis = Lock Axis
@ -1663,6 +1692,10 @@ from = From:
to = To:
tolerance = Tolerance:
[duplicate_slice]
x_duplicated = Slice "{}" duplicated
n_slices_duplicated = {} slice(s) duplicated
[remove_slice]
x_removed = Slice "{}" removed
n_slices_removed = {} slice(s) removed
@ -1692,7 +1725,8 @@ title = Save Selection (.msk file)
[script_access]
title = Security
script_label = The following script:
file_label = wants to access to this file:
file_label = wants to access this file:
file_write_label = wants to write to this file:
command_label = wants to execute the following command:
websocket_label = wants to open a WebSocket connection to this URL:
clipboard_label = wants to access the system clipboard
@ -1706,7 +1740,7 @@ allow_load_lib_access = &Allow Load External Library
give_full_access = Give Script Full &Access
stop_script = &Stop Script
[select_accelerator]
[select_shortcut]
title = Keyboard Shortcut
key = Key:
clear = Clear
@ -1746,6 +1780,7 @@ delete_file = Delete file, I've already sent it
[slice_popup_menu]
properties = Slice &Properties...
duplicate = D&uplicate Slice
delete = &Delete Slice
[slice_properties]
@ -1833,6 +1868,18 @@ pixel_scale = Pixel Scale
with_vars = Use CSS3 Variables
generate_html = Generate Sample HTML File
[shape]
fill = Fill
stroke = Stroke
stroke_width = Stroke Width
[text_tool]
font_family = Font Family
font_size = Font Size
bold = Bold
italic = Italic
more_options = More Options
[timeline_conf]
position = Position:
left = &Left
@ -1895,6 +1942,7 @@ timeline_show = Show Timeline
[undo_history]
title = Undo History
initial_state = Initial State
[user_data]
user_data = User Data:

View File

@ -23,6 +23,7 @@
<combobox id="location" expansive="true" />
<button text="" id="refresh_button" style="refresh_button"
tooltip="@.refresh_button_tooltip" tooltip_dir="bottom" />
<check id="show_hidden_check" text="@.show_hidden" />
</box>
<vbox id="file_view_placeholder" expansive="true" />
<grid columns="2">

View File

@ -1,7 +1,8 @@
<!-- Aseprite -->
<!-- Copyright (C) 2025 by Igara Studio S.A. -->
<!-- Copyright (C) 2001-2016 by David Capello -->
<gui>
<window id="select_accelerator" text="@.title">
<window id="select_shortcut" text="@.title">
<vbox expansive="true">
<grid columns="3">
<label text="@.key" />

View File

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

View File

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

2
laf

@ -1 +1 @@
Subproject commit 282764015c0f2279c814ee8f32b91b245ac6659a
Subproject commit de781a5066732d700ac4520ee5fc9034e92875ad

View File

@ -180,8 +180,8 @@ if(ENABLE_ASEPRITE_EXE)
if(WIN32)
set(main_resources
main/resources_win32.rc
main/settings.manifest)
main/win/resources_win32.rc
main/win/settings.manifest)
endif()
add_executable(${main_target}
@ -220,6 +220,7 @@ if(ENABLE_TESTS)
find_tests(ui ui-lib)
find_tests(app/cli app-lib)
find_tests(app/file app-lib)
find_tests(app/ui app-lib)
find_tests(app app-lib)
find_tests(. app-lib)
endif()

View File

@ -72,6 +72,10 @@ In Debug mode (`_DEBUG`):
* [`TRACEARGS`](https://github.com/aseprite/laf/blob/f3222bdee2d21556e9da55343e73803c730ecd97/base/debug.h#L40):
in debug mode, it prints in the terminal/console each given argument
In release mode you can use a similar function:
* `PRINTARGS` prints in the terminal/console each given argument
# Detect Platform
You can check the platform using some `laf` macros:

View File

@ -74,7 +74,7 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${output_fn}.tmp ${output_fn}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
MAIN_DEPENDENCY ${strings_en_ini}
DEPENDS ${GEN_DEP})
DEPENDS ${GEN_DEP} commands/commands_list.h)
list(APPEND generated_files ${output_fn})
# Check translations
@ -108,7 +108,7 @@ endif()
# This defines a specific webp decoding utility function for using
# in Windows when dragging and dropping images that are stored as
# webp files (like Chrome does).
if(WIN32 AND ENABLE_WEBP)
if(WIN32 AND ENABLE_WEBP AND LAF_BACKEND STREQUAL "skia")
target_sources(app-lib PRIVATE util/decode_webp.cpp)
endif()
@ -290,7 +290,6 @@ target_sources(app-lib PRIVATE
cmd/copy_region.cpp
cmd/crop_cel.cpp
cmd/deselect_mask.cpp
cmd/drop_on_timeline.cpp
cmd/flatten_layers.cpp
cmd/flip_image.cpp
cmd/flip_mask.cpp
@ -369,6 +368,7 @@ target_sources(app-lib PRIVATE
color_picker.cpp
color_spaces.cpp
color_utils.cpp
commands/apply.cpp
commands/cmd_about.cpp
commands/cmd_add_color.cpp
commands/cmd_advanced_mode.cpp
@ -394,6 +394,7 @@ target_sources(app-lib PRIVATE
commands/cmd_deselect_mask.cpp
commands/cmd_discard_brush.cpp
commands/cmd_duplicate_layer.cpp
commands/cmd_duplicate_slice.cpp
commands/cmd_duplicate_sprite.cpp
commands/cmd_duplicate_view.cpp
commands/cmd_enter_license.cpp
@ -526,6 +527,7 @@ target_sources(app-lib PRIVATE
context_flags.cpp
doc.cpp
doc_api.cpp
doc_api_dnd_helper.cpp
doc_diff.cpp
doc_exporter.cpp
doc_range_ops.cpp
@ -651,6 +653,7 @@ target_sources(app-lib PRIVATE
ui/icon_button.cpp
ui/incompat_file_window.cpp
ui/input_chain.cpp
ui/key.cpp
ui/keyboard_shortcuts.cpp
ui/layer_frame_comboboxes.cpp
ui/main_menu_bar.cpp
@ -669,7 +672,7 @@ target_sources(app-lib PRIVATE
ui/rgbmap_algorithm_selector.cpp
ui/sampling_selector.cpp
ui/search_entry.cpp
ui/select_accelerator.cpp
ui/select_shortcut.cpp
ui/selection_mode_field.cpp
ui/skin/skin_part.cpp
ui/skin/skin_property.cpp
@ -684,6 +687,7 @@ target_sources(app-lib PRIVATE
ui/tile_button.cpp
ui/tileset_selector.cpp
ui/timeline/ani_controls.cpp
ui/timeline/doc_providers.cpp
ui/timeline/timeline.cpp
ui/toolbar.cpp
ui/user_data_view.cpp
@ -706,6 +710,7 @@ target_sources(app-lib PRIVATE
util/layer_utils.cpp
util/msk_file.cpp
util/new_image_from_mask.cpp
util/open_file_job.cpp
util/pal_ops.cpp
util/pic_file.cpp
util/pixel_ratio.cpp
@ -713,6 +718,7 @@ target_sources(app-lib PRIVATE
util/render_text.cpp
util/resize_image.cpp
util/shader_helpers.cpp
util/slice_utils.cpp
util/tile_flags_utils.cpp
util/tileset_utils.cpp
util/wrap_point.cpp

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -79,7 +79,7 @@
#include "os/x11/system.h"
#endif
#if ENABLE_WEBP && LAF_WINDOWS
#if ENABLE_WEBP && LAF_WINDOWS && LAF_SKIA
#include "app/util/decode_webp.h"
#endif
@ -485,7 +485,7 @@ void App::run(const bool runGuiManager)
// How to interpret one finger on Windows tablets.
manager->display()->nativeWindow()->setInterpretOneFingerGestureAsMouseMovement(
preferences().experimental.oneFingerAsMouseMovement());
#if ENABLE_WEBP
#if ENABLE_WEBP && LAF_SKIA
// In Windows we use a custom webp decoder for drag & drop operations.
os::set_decode_webp(util::decode_webp);
#endif

View File

@ -113,11 +113,11 @@ bool can_call_global_shortcut(const AppMenuItem::Native* native)
(focus == nullptr || focus->type() != ui::kEntryWidget ||
!is_text_entry_shortcut(native->shortcut)) &&
(native->keyContext == KeyContext::Any ||
native->keyContext == KeyboardShortcuts::instance()->getCurrentKeyContext());
native->keyContext == KeyboardShortcuts::getCurrentKeyContext());
}
// TODO this should be on "she" library (or we should use
// os::Shortcut instead of ui::Accelerators)
// TODO this should be on laf-os library (or we should use
// os::Shortcut instead of ui::Shortcuts)
int from_scancode_to_unicode(KeyScancode scancode)
{
static int map[] = {
@ -284,22 +284,21 @@ void destroy_menu_item(ui::Widget* item)
os::Shortcut get_os_shortcut_from_key(const Key* key)
{
if (key && !key->accels().empty()) {
const ui::Accelerator& accel = key->accels().front();
if (key && !key->shortcuts().empty()) {
const ui::Shortcut& shortcut = key->shortcuts().front();
#if LAF_MACOS
// Shortcuts with spacebar as modifier do not work well in macOS
// (they will be called when the space bar is unpressed too).
if ((accel.modifiers() & ui::kKeySpaceModifier) == ui::kKeySpaceModifier)
if ((shortcut.modifiers() & ui::kKeySpaceModifier) == ui::kKeySpaceModifier)
return os::Shortcut();
#endif
return os::Shortcut(
(accel.unicodeChar() ? accel.unicodeChar() : from_scancode_to_unicode(accel.scancode())),
accel.modifiers());
return os::Shortcut((shortcut.unicodeChar() ? shortcut.unicodeChar() :
from_scancode_to_unicode(shortcut.scancode())),
shortcut.modifiers());
}
else
return os::Shortcut();
return {};
}
AppMenus* AppMenus::s_instance = nullptr;

View File

@ -1,397 +0,0 @@
// 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/cmd/drop_on_timeline.h"
#include "app/cmd/add_layer.h"
#include "app/cmd/move_cel.h"
#include "app/cmd/set_pixel_format.h"
#include "app/console.h"
#include "app/context_flags.h"
#include "app/doc.h"
#include "app/doc_event.h"
#include "app/file/file.h"
#include "app/tx.h"
#include "app/util/layer_utils.h"
#include "app/util/open_file_job.h"
#include "base/serialization.h"
#include "doc/layer_io.h"
#include "doc/layer_list.h"
#include "doc/subobjects_io.h"
#include "render/dithering.h"
#include <algorithm>
namespace app { namespace cmd {
using namespace base::serialization::little_endian;
DropOnTimeline::DropOnTimeline(app::Doc* doc,
doc::frame_t frame,
doc::layer_t layerIndex,
InsertionPoint insert,
DroppedOn droppedOn,
const base::paths& paths)
: WithDocument(doc)
, m_size(0)
, m_paths(paths)
, m_frame(frame)
, m_layerIndex(layerIndex)
, m_insert(insert)
, m_droppedOn(droppedOn)
{
ASSERT(m_layerIndex >= 0);
for (const auto& path : m_paths)
m_size += path.size();
// Zero layers stored.
write32(m_stream, 0);
m_size += sizeof(uint32_t);
}
DropOnTimeline::DropOnTimeline(app::Doc* doc,
doc::frame_t frame,
doc::layer_t layerIndex,
InsertionPoint insert,
DroppedOn droppedOn,
const doc::ImageRef& image)
: WithDocument(doc)
, m_size(0)
, m_image(image)
, m_frame(frame)
, m_layerIndex(layerIndex)
, m_insert(insert)
, m_droppedOn(droppedOn)
{
ASSERT(m_layerIndex >= 0);
// Zero layers stored.
write32(m_stream, 0);
m_size += sizeof(uint32_t);
}
void DropOnTimeline::onExecute()
{
Doc* destDoc = document();
m_previousTotalFrames = destDoc->sprite()->totalFrames();
int docsProcessed = 0;
while (hasPendingWork()) {
std::unique_ptr<Doc> srcDoc;
if (!getNextDoc(srcDoc))
return;
if (srcDoc) {
docsProcessed++;
// If source document doesn't match the destination document's color
// mode, change it.
if (srcDoc->colorMode() != destDoc->colorMode()) {
// Execute in a source doc transaction because we don't need undo/redo
// this.
Tx tx(srcDoc.get());
tx(new cmd::SetPixelFormat(srcDoc->sprite(),
destDoc->sprite()->pixelFormat(),
render::Dithering(),
Preferences::instance().quantization.rgbmapAlgorithm(),
nullptr,
nullptr,
FitCriteria::DEFAULT));
tx.commit();
}
// If there is only one source document to process and it has a cel that
// can be moved, then move the cel from the source doc into the
// destination doc's selected frame.
const bool isJustOneDoc = (docsProcessed == 1 && !hasPendingWork());
if (isJustOneDoc && canMoveCelFrom(srcDoc.get())) {
auto* srcLayer = static_cast<LayerImage*>(srcDoc->sprite()->firstLayer());
auto* destLayer = static_cast<LayerImage*>(destDoc->sprite()->allLayers()[m_layerIndex]);
executeAndAdd(new MoveCel(srcLayer, 0, destLayer, m_frame, false));
break;
}
// If there is no room for the source frames, add frames to the
// destination sprite.
if (m_frame + srcDoc->sprite()->totalFrames() > destDoc->sprite()->totalFrames()) {
destDoc->sprite()->setTotalFrames(m_frame + srcDoc->sprite()->totalFrames());
}
// Save dropped layers from source document.
saveDroppedLayers(srcDoc->sprite()->root()->layers(), destDoc->sprite());
}
}
if (m_droppedLayersIds.empty())
return;
destDoc->sprite()->incrementVersion();
destDoc->incrementVersion();
insertDroppedLayers();
}
void DropOnTimeline::onUndo()
{
CmdSequence::onUndo();
if (m_droppedLayersIds.empty()) {
notifyGeneralUpdate();
return;
}
Doc* doc = document();
frame_t currentTotalFrames = doc->sprite()->totalFrames();
for (auto id : m_droppedLayersIds) {
auto* layer = doc::get<Layer>(id);
ASSERT(layer);
if (layer) {
DocEvent ev(doc);
ev.sprite(layer->sprite());
ev.layer(layer);
doc->notify_observers<DocEvent&>(&DocObserver::onBeforeRemoveLayer, ev);
LayerGroup* group = layer->parent();
group->removeLayer(layer);
group->incrementVersion();
group->sprite()->incrementVersion();
doc->notify_observers<DocEvent&>(&DocObserver::onAfterRemoveLayer, ev);
delete layer;
}
}
doc->sprite()->setTotalFrames(m_previousTotalFrames);
doc->sprite()->incrementVersion();
m_previousTotalFrames = currentTotalFrames;
}
void DropOnTimeline::onRedo()
{
CmdSequence::onRedo();
if (m_droppedLayersIds.empty()) {
notifyGeneralUpdate();
return;
}
Doc* doc = document();
frame_t currentTotalFrames = doc->sprite()->totalFrames();
doc->sprite()->setTotalFrames(m_previousTotalFrames);
doc->sprite()->incrementVersion();
m_previousTotalFrames = currentTotalFrames;
insertDroppedLayers();
}
void DropOnTimeline::setupInsertionLayer(Layer*& layer, LayerGroup*& group)
{
const LayerList& allLayers = document()->sprite()->allLayers();
layer = allLayers[m_layerIndex];
if (m_insert == InsertionPoint::BeforeLayer && layer->isGroup()) {
group = static_cast<LayerGroup*>(layer);
// The user is trying to drop layers into an empty group, so there is no after
// nor before layer...
if (group->layersCount() == 0) {
layer = nullptr;
return;
}
layer = group->lastLayer();
m_insert = InsertionPoint::AfterLayer;
}
group = layer->parent();
}
bool DropOnTimeline::hasPendingWork()
{
return m_image || !m_paths.empty();
}
bool DropOnTimeline::getNextDocFromImage(std::unique_ptr<Doc>& srcDoc)
{
if (!m_image)
return true;
Sprite* sprite = new Sprite(m_image->spec(), 256);
LayerImage* layer = new LayerImage(sprite);
sprite->root()->addLayer(layer);
Cel* cel = new Cel(0, m_image);
layer->addCel(cel);
srcDoc = std::make_unique<Doc>(sprite);
m_image = nullptr;
return true;
}
bool DropOnTimeline::getNextDocFromPaths(std::unique_ptr<Doc>& srcDoc)
{
Console console;
Context* context = document()->context();
int flags = FILE_LOAD_DATA_FILE | FILE_LOAD_AVOID_BACKGROUND_LAYER | FILE_LOAD_CREATE_PALETTE |
FILE_LOAD_SEQUENCE_YES;
std::unique_ptr<FileOp> fop(FileOp::createLoadDocumentOperation(context, m_paths.front(), flags));
// Remove the path that is currently being processed
m_paths.erase(m_paths.begin());
// Do nothing (the user cancelled or something like that)
if (!fop)
return false;
if (fop->hasError()) {
console.printf(fop->error().c_str());
return true;
}
base::paths fopFilenames;
fop->getFilenameList(fopFilenames);
// Remove paths that will be loaded by the current file operation.
for (const auto& filename : fopFilenames) {
auto it = std::find(m_paths.begin(), m_paths.end(), filename);
if (it != m_paths.end())
m_paths.erase(it);
}
OpenFileJob task(fop.get(), true);
task.showProgressWindow();
// Post-load processing, it is called from the GUI because may require user intervention.
fop->postLoad();
// Show any error
if (fop->hasError() && !fop->isStop())
console.printf(fop->error().c_str());
srcDoc.reset(fop->releaseDocument());
return true;
}
bool DropOnTimeline::getNextDoc(std::unique_ptr<Doc>& srcDoc)
{
if (m_image == nullptr && !m_paths.empty())
return getNextDocFromPaths(srcDoc);
return getNextDocFromImage(srcDoc);
}
void DropOnTimeline::storeDroppedLayerIds(const Layer* layer)
{
if (layer->isGroup()) {
const auto* group = static_cast<const LayerGroup*>(layer);
for (auto* child : group->layers())
storeDroppedLayerIds(child);
m_droppedLayersIds.push_back(group->id());
}
else {
m_droppedLayersIds.push_back(layer->id());
}
}
void DropOnTimeline::saveDroppedLayers(const LayerList& layers, Sprite* sprite)
{
size_t start = m_stream.tellp();
// Calculate the new number of layers.
m_stream.seekg(0);
uint32_t nLayers = read32(m_stream) + layers.size();
// Flat list of all the dropped layers.
LayerList allDroppedLayers;
// Write number of layers (at the beginning of the stream).
m_stream.seekp(0);
write32(m_stream, nLayers);
// Move to where we must start writing.
m_stream.seekp(start);
for (auto it = layers.cbegin(); it != layers.cend(); ++it) {
auto* layer = *it;
// TODO: If we could "relocate" a layer from the source document to the
// destination document we could avoid making a copy here.
std::unique_ptr<Layer> layerCopy(copy_layer_with_sprite(layer, sprite));
layerCopy->displaceFrames(0, m_frame);
write_layer(m_stream, layerCopy.get());
storeDroppedLayerIds(layerCopy.get());
}
size_t end = m_stream.tellp();
m_size += end - start;
}
void DropOnTimeline::insertDroppedLayers()
{
// Layer used as a reference to determine if the dropped layers will be
// inserted after or before it.
Layer* refLayer = nullptr;
// Parent group of the reference layer layer.
LayerGroup* group = nullptr;
// Keep track of the current insertion point.
InsertionPoint insert = m_insert;
setupInsertionLayer(refLayer, group);
SubObjectsFromSprite io(group->sprite());
m_stream.seekg(0);
auto nLayers = read32(m_stream);
for (int i = 0; i < nLayers; ++i) {
auto* layer = read_layer(m_stream, &io);
if (!refLayer) {
group->addLayer(layer);
refLayer = layer;
insert = InsertionPoint::AfterLayer;
}
else if (insert == InsertionPoint::AfterLayer) {
group->insertLayer(layer, refLayer);
refLayer = layer;
}
else if (insert == InsertionPoint::BeforeLayer) {
group->insertLayerBefore(layer, refLayer);
refLayer = layer;
insert = InsertionPoint::AfterLayer;
}
group->incrementVersion();
group->sprite()->incrementVersion();
Doc* doc = static_cast<Doc*>(group->sprite()->document());
DocEvent ev(doc);
ev.sprite(group->sprite());
ev.layer(layer);
doc->notify_observers<DocEvent&>(&DocObserver::onAddLayer, ev);
}
}
// Returns true if the document srcDoc has a cel that can be moved.
// The cel from the srcDoc can be moved only when all of the following
// conditions are met:
// * Drop took place in a cel.
// * Source doc has only one layer with just one frame.
// * The layer from the source doc and the destination cel's layer are both
// Image layers.
// Otherwise this function returns false.
bool DropOnTimeline::canMoveCelFrom(app::Doc* srcDoc)
{
auto* srcLayer = srcDoc->sprite()->firstLayer();
auto* destLayer = document()->sprite()->allLayers()[m_layerIndex];
return m_droppedOn == DroppedOn::Cel && srcDoc->sprite()->allLayersCount() == 1 &&
srcDoc->sprite()->totalFrames() == 1 && srcLayer->isImage() && destLayer->isImage();
}
void DropOnTimeline::notifyGeneralUpdate()
{
Doc* doc = document();
if (!doc)
return;
doc->notifyGeneralUpdate();
}
}} // namespace app::cmd

View File

@ -1,95 +0,0 @@
// 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_CMD_drop_on_timeline_H_INCLUDED
#define APP_CMD_drop_on_timeline_H_INCLUDED
#pragma once
#include "app/cmd/with_document.h"
#include "app/cmd_sequence.h"
#include "app/doc_observer.h"
#include "base/paths.h"
#include "doc/frame.h"
#include "doc/image_ref.h"
#include "doc/layer.h"
#include "doc/layer_list.h"
namespace app { namespace cmd {
class DropOnTimeline : public CmdSequence,
public WithDocument {
public:
enum class InsertionPoint {
BeforeLayer,
AfterLayer,
};
enum class DroppedOn {
Unspecified,
Frame,
Layer,
Cel,
};
// Inserts the layers and frames of the documents pointed by the specified
// paths, at the specified frame and before or after the specified layer index.
DropOnTimeline(app::Doc* doc,
doc::frame_t frame,
doc::layer_t layerIndex,
InsertionPoint insert,
DroppedOn droppedOn,
const base::paths& paths);
// Inserts the image as if it were a document with just one layer and one
// frame, at the specified frame and before or after the specified layer index.
DropOnTimeline(app::Doc* doc,
doc::frame_t frame,
doc::layer_t layerIndex,
InsertionPoint insert,
DroppedOn droppedOn,
const doc::ImageRef& image);
protected:
void onExecute() override;
void onUndo() override;
void onRedo() override;
size_t onMemSize() const override { return sizeof(*this) + m_size; }
private:
void setupInsertionLayer(doc::Layer*& layer, doc::LayerGroup*& group);
void insertDroppedLayers();
bool canMoveCelFrom(app::Doc* srcDoc);
void notifyGeneralUpdate();
bool hasPendingWork();
// Returns the next document to be processed.
// Returns false when the user cancelled the process, or true when the
// process must go on.
bool getNextDoc(std::unique_ptr<Doc>& srcDoc);
bool getNextDocFromImage(std::unique_ptr<Doc>& srcDoc);
bool getNextDocFromPaths(std::unique_ptr<Doc>& srcDoc);
void storeDroppedLayerIds(const doc::Layer* layer);
void saveDroppedLayers(const doc::LayerList& layers, doc::Sprite* sprite);
size_t m_size;
base::paths m_paths;
doc::ImageRef m_image = nullptr;
doc::frame_t m_frame;
doc::layer_t m_layerIndex;
InsertionPoint m_insert;
DroppedOn m_droppedOn;
// Serialized dropped layers' data. Used for redo operation.
std::stringstream m_stream;
// Holds the Object IDs of the dropped layers. Used when determining which
// layers should be removed in an undo operation.
std::vector<doc::ObjectId> m_droppedLayersIds;
// Number of frames the doc had before dropping.
doc::frame_t m_previousTotalFrames;
};
}} // namespace app::cmd
#endif

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -15,6 +15,7 @@
#include "app/ui/editor/editor.h"
#include "os/system.h"
#include "os/window.h"
#include "ui/display.h"
namespace app {
@ -29,17 +30,15 @@ void initialize_color_spaces(Preferences& pref)
pref.color.manage.AfterChange.connect([](bool manage) { g_manage = manage; });
}
os::ColorSpaceRef get_screen_color_space()
os::ColorSpaceRef get_current_color_space(ui::Display* display, Doc* doc)
{
return os::System::instance()->defaultWindow()->colorSpace();
}
os::ColorSpaceRef get_current_color_space()
{
if (auto* editor = Editor::activeEditor())
return editor->document()->osColorSpace();
else
return get_screen_color_space();
if (!doc) {
if (auto* editor = Editor::activeEditor())
doc = editor->document();
}
if (doc)
return doc->osColorSpace();
return display->colorSpace();
}
gfx::ColorSpaceRef get_working_rgb_space_from_preferences()
@ -62,11 +61,11 @@ gfx::ColorSpaceRef get_working_rgb_space_from_preferences()
//////////////////////////////////////////////////////////////////////
// Color conversion
ConvertCS::ConvertCS()
ConvertCS::ConvertCS(ui::Display* display, Doc* doc)
{
if (g_manage) {
auto srcCS = get_current_color_space();
auto dstCS = get_screen_color_space();
auto srcCS = get_current_color_space(display, doc);
auto dstCS = display->colorSpace();
if (srcCS && dstCS)
m_conversion = os::System::instance()->convertBetweenColorSpace(srcCS, dstCS);
}
@ -95,9 +94,9 @@ gfx::Color ConvertCS::operator()(const gfx::Color c)
}
}
ConvertCS convert_from_current_to_screen_color_space()
ConvertCS convert_from_current_to_display_color_space(ui::Display* display)
{
return ConvertCS();
return ConvertCS(display);
}
ConvertCS convert_from_custom_to_srgb(const os::ColorSpaceRef& from)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (c) 2018-2020 Igara Studio S.A.
// Copyright (c) 2018-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -16,21 +16,25 @@ namespace doc {
class Sprite;
}
namespace ui {
class Display;
}
namespace app {
class Doc;
class Preferences;
void initialize_color_spaces(Preferences& pref);
os::ColorSpaceRef get_screen_color_space();
// Returns the color space of the current document.
os::ColorSpaceRef get_current_color_space();
os::ColorSpaceRef get_current_color_space(ui::Display* display, Doc* doc = nullptr);
gfx::ColorSpaceRef get_working_rgb_space_from_preferences();
class ConvertCS {
public:
ConvertCS();
ConvertCS() = delete;
ConvertCS(ui::Display* display, Doc* doc = nullptr);
ConvertCS(const os::ColorSpaceRef& srcCS, const os::ColorSpaceRef& dstCS);
ConvertCS(ConvertCS&&);
ConvertCS& operator=(const ConvertCS&) = delete;
@ -40,7 +44,7 @@ private:
os::Ref<os::ColorSpaceConversion> m_conversion;
};
ConvertCS convert_from_current_to_screen_color_space();
ConvertCS convert_from_current_to_display_color_space(ui::Display* display);
ConvertCS convert_from_custom_to_srgb(const os::ColorSpaceRef& from);
} // namespace app

View File

@ -0,0 +1,46 @@
// Aseprite
// Copyright (C) 2025 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/commands/command.h"
#include "app/context.h"
#include "app/ui/editor/editor.h"
namespace app {
// Depends on the current context/state, used to apply the current
// transformation (drop pixels).
class ApplyCommand : public Command {
public:
ApplyCommand();
protected:
void onExecute(Context* ctx) override;
};
ApplyCommand::ApplyCommand() : Command(CommandId::Apply(), CmdUIOnlyFlag)
{
}
void ApplyCommand::onExecute(Context* ctx)
{
if (!ctx->isUIAvailable())
return;
auto* editor = Editor::activeEditor();
if (editor && editor->isMovingPixels())
editor->dropMovingPixels();
}
Command* CommandFactory::createApplyCommand()
{
return new ApplyCommand;
}
} // namespace app

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -54,11 +55,11 @@ void AdvancedModeCommand::onExecute(Context* context)
if (oldMode == MainWindow::NormalMode && pref.advancedMode.showAlert()) {
KeyPtr key = KeyboardShortcuts::instance()->command(this->id().c_str());
if (!key->accels().empty()) {
if (!key->shortcuts().empty()) {
app::gen::AdvancedMode window;
window.warningLabel()->setTextf("You can go back pressing \"%s\" key.",
key->accels().front().toString().c_str());
key->shortcuts().front().toString().c_str());
window.openWindowInForeground();

View File

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

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2018 David Capello
// Copyright (C) 2001-2025 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.

View File

@ -0,0 +1,122 @@
// Aseprite
// Copyright (C) 2025 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/cmd/add_slice.h"
#include "app/commands/command.h"
#include "app/context.h"
#include "app/context_access.h"
#include "app/context_flags.h"
#include "app/i18n/strings.h"
#include "app/site.h"
#include "app/tx.h"
#include "app/ui/status_bar.h"
#include "app/util/slice_utils.h"
#include "base/convert_to.h"
#include "doc/object_id.h"
#include "doc/slice.h"
namespace app {
// Moves the given slice by the dx and dy values
void offset(Slice* slice, int dx, int dy)
{
for (auto it = slice->begin(); it != slice->end(); ++it) {
auto* sk = (*it).value();
gfx::Rect bounds = sk->bounds();
bounds.offset(gfx::Point{ dx, dy });
sk->setBounds(bounds);
}
}
class DuplicateSliceCommand : public Command {
public:
DuplicateSliceCommand();
protected:
void onLoadParams(const Params& params) override;
bool onEnabled(Context* context) override;
void onExecute(Context* context) override;
private:
ObjectId m_sliceId;
};
DuplicateSliceCommand::DuplicateSliceCommand()
: Command(CommandId::DuplicateSlice(), CmdRecordableFlag)
{
}
void DuplicateSliceCommand::onLoadParams(const Params& params)
{
std::string id = params.get("id");
if (!id.empty())
m_sliceId = ObjectId(base::convert_to<doc::ObjectId>(id));
else
m_sliceId = NullId;
}
bool DuplicateSliceCommand::onEnabled(Context* context)
{
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::HasActiveSprite | ContextFlags::HasActiveLayer);
}
void DuplicateSliceCommand::onExecute(Context* context)
{
std::vector<Slice*> selectedSlices;
{
const ContextReader reader(context);
if (m_sliceId == NullId) {
selectedSlices = get_selected_slices(reader.site());
if (selectedSlices.empty())
return;
}
else
selectedSlices.push_back(reader.sprite()->slices().getById(m_sliceId));
}
ContextWriter writer(context);
Tx tx(writer, "Duplicate Slice");
Sprite* sprite = writer.site().sprite();
Doc* doc = static_cast<Doc*>(sprite->document());
doc->notifyBeforeSlicesDuplication();
for (auto* s : selectedSlices) {
Slice* slice = new Slice(*s);
slice->setName(Strings::general_copy_of(slice->name()));
// Offset a bit the duplicated slice to avoid overlapping
offset(slice, 2, 2);
tx(new cmd::AddSlice(sprite, slice));
doc->notifySliceDuplicated(slice);
}
tx.commit();
std::string sliceName;
if (selectedSlices.size() == 1)
sliceName = selectedSlices[0]->name();
StatusBar::instance()->invalidate();
if (!sliceName.empty()) {
StatusBar::instance()->showTip(1000, Strings::duplicate_slice_x_duplicated(sliceName));
}
else {
StatusBar::instance()->showTip(
1000,
Strings::duplicate_slice_n_slices_duplicated(selectedSlices.size()));
}
}
Command* CommandFactory::createDuplicateSliceCommand()
{
return new DuplicateSliceCommand;
}
} // namespace app

View File

@ -65,7 +65,7 @@ void DuplicateSpriteCommand::onExecute(Context* context)
std::string duplicateFn = params().filename.isSet() ?
params().filename() :
Strings::general_text_copy_of(base::get_file_title(fn)) +
Strings::general_copy_of(base::get_file_title(fn)) +
(!ext.empty() ? "." + ext : "");
bool flatten = params().flatten.isSet() ? params().flatten() :

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -23,7 +23,7 @@
#include "app/ui/app_menuitem.h"
#include "app/ui/keyboard_shortcuts.h"
#include "app/ui/search_entry.h"
#include "app/ui/select_accelerator.h"
#include "app/ui/select_shortcut.h"
#include "app/ui/separator_in_view.h"
#include "app/ui/skin/skin_theme.h"
#include "base/fs.h"
@ -151,7 +151,7 @@ public:
, m_keyOrig(key ? new Key(*key) : nullptr)
, m_menuitem(menuitem)
, m_level(level)
, m_hotAccel(-1)
, m_hotShortcut(-1)
, m_lockButtons(false)
, m_headerItem(headerItem)
{
@ -204,45 +204,45 @@ public:
}
private:
void onChangeAccel(int index)
void onChangeShortcut(int index)
{
LockButtons lock(this);
Accelerator origAccel = m_key->accels()[index];
SelectAccelerator window(origAccel, m_key->keycontext(), m_keys);
Shortcut origShortcut = m_key->shortcuts()[index];
SelectShortcut window(origShortcut, m_key->keycontext(), m_keys);
window.openWindowInForeground();
if (window.isModified()) {
m_key->disableAccel(origAccel, KeySource::UserDefined);
if (!window.accel().isEmpty())
m_key->add(window.accel(), KeySource::UserDefined, m_keys);
m_key->disableShortcut(origShortcut, KeySource::UserDefined);
if (!window.shortcut().isEmpty())
m_key->add(window.shortcut(), KeySource::UserDefined, m_keys);
}
this->window()->layout();
}
void onDeleteAccel(int index)
void onDeleteShortcut(int index)
{
LockButtons lock(this);
// We need to create a copy of the accelerator because
// Key::disableAccel() will modify the accels() collection itself.
ui::Accelerator accel = m_key->accels()[index];
// We need to create a copy of the shortcut because
// Key::disableShortcut() will modify the shortcuts() collection itself.
Shortcut shortcut = m_key->shortcuts()[index];
if (ui::Alert::show(Strings::alerts_delete_shortcut(accel.toString())) != 1)
if (ui::Alert::show(Strings::alerts_delete_shortcut(shortcut.toString())) != 1)
return;
m_key->disableAccel(accel, KeySource::UserDefined);
m_key->disableShortcut(shortcut, KeySource::UserDefined);
window()->layout();
}
void onAddAccel()
void onAddShortcut()
{
LockButtons lock(this);
ui::Accelerator accel;
SelectAccelerator window(accel, m_key ? m_key->keycontext() : KeyContext::Any, m_keys);
ui::Shortcut shortcut;
SelectShortcut window(shortcut, m_key ? m_key->keycontext() : KeyContext::Any, m_keys);
window.openWindowInForeground();
if ((window.isModified()) ||
// We can assign a "None" accelerator to mouse wheel actions
// We can assign a "None" shortcut to mouse wheel actions
(m_key && m_key->type() == KeyType::WheelAction && window.isOK())) {
if (!m_key) {
ASSERT(m_menuitem);
@ -256,7 +256,7 @@ private:
m_menuKeys[m_menuitem] = m_key;
}
m_key->add(window.accel(), KeySource::UserDefined, m_keys);
m_key->add(window.shortcut(), KeySource::UserDefined, m_keys);
}
this->window()->layout();
@ -269,12 +269,12 @@ private:
if (m_key && m_key->keycontext() != KeyContext::Any) {
int w = m_headerItem->contextXPos() +
font()->textLength(convertKeyContextToUserFriendlyString(m_key->keycontext()));
font()->textLength(convert_keycontext_to_user_friendly_string(m_key->keycontext()));
size.w = std::max(size.w, w);
}
if (m_key && !m_key->accels().empty()) {
size_t combos = m_key->accels().size();
if (m_key && !m_key->shortcuts().empty()) {
size_t combos = m_key->shortcuts().size();
if (combos > 1)
size.h *= combos;
}
@ -315,22 +315,23 @@ private:
}
}
if (m_key && !m_key->accels().empty()) {
if (m_key && !m_key->shortcuts().empty()) {
if (m_key->keycontext() != KeyContext::Any) {
g->drawText(convertKeyContextToUserFriendlyString(m_key->keycontext()),
g->drawText(convert_keycontext_to_user_friendly_string(m_key->keycontext()),
fg,
bg,
gfx::Point(contextXPos, y));
}
const int dh = th + 4 * guiscale();
IntersectClip clip(g,
gfx::Rect(keyXPos, y, contextXPos - keyXPos, dh * m_key->accels().size()));
IntersectClip clip(
g,
gfx::Rect(keyXPos, y, contextXPos - keyXPos, dh * m_key->shortcuts().size()));
if (clip) {
int i = 0;
for (const Accelerator& accel : m_key->accels()) {
if (i != m_hotAccel || !m_changeButton) {
g->drawText(getAccelText(accel), fg, bg, gfx::Point(keyXPos, y));
for (const AppShortcut& shortcut : m_key->shortcuts()) {
if (i != m_hotShortcut || !m_changeButton) {
g->drawText(getShortcutText(shortcut), fg, bg, gfx::Point(keyXPos, y));
}
y += dh;
++i;
@ -361,40 +362,41 @@ private:
gfx::Rect bounds = this->bounds();
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
const Accelerators* accels = (m_key ? &m_key->accels() : NULL);
const AppShortcuts* shortcuts = (m_key ? &m_key->shortcuts() : nullptr);
int y = bounds.y;
int dh = textSize().h + 4 * guiscale();
int maxi = (accels && accels->size() > 1 ? accels->size() : 1);
int maxi = (shortcuts && shortcuts->size() > 1 ? shortcuts->size() : 1);
auto theme = SkinTheme::get(this);
auto* theme = SkinTheme::get(this);
for (int i = 0; i < maxi; ++i, y += dh) {
int w = font()->textLength(
(accels && i < (int)accels->size() ? getAccelText((*accels)[i]) : std::string()));
int w = font()->textLength((shortcuts && i < (int)shortcuts->size() ?
getShortcutText((*shortcuts)[i]) :
std::string()));
gfx::Rect itemBounds(bounds.x + m_headerItem->keyXPos(), y, w, dh);
itemBounds = itemBounds.enlarge(
gfx::Border(4 * guiscale(), 0, 6 * guiscale(), 1 * guiscale()));
if (accels && i < (int)accels->size() && mouseMsg->position().y >= itemBounds.y &&
if (shortcuts && i < (int)shortcuts->size() && mouseMsg->position().y >= itemBounds.y &&
mouseMsg->position().y < itemBounds.y + itemBounds.h) {
if (m_hotAccel != i) {
m_hotAccel = i;
if (m_hotShortcut != i) {
m_hotShortcut = i;
m_changeConn = obs::connection();
m_changeButton.reset(new Button(""));
m_changeConn = m_changeButton->Click.connect([this, i] { onChangeAccel(i); });
m_changeConn = m_changeButton->Click.connect([this, i] { onChangeShortcut(i); });
m_changeButton->setStyle(theme->styles.miniButton());
addChild(m_changeButton.get());
m_deleteConn = obs::connection();
m_deleteButton.reset(new Button(""));
m_deleteConn = m_deleteButton->Click.connect([this, i] { onDeleteAccel(i); });
m_deleteConn = m_deleteButton->Click.connect([this, i] { onDeleteShortcut(i); });
m_deleteButton->setStyle(theme->styles.miniButton());
addChild(m_deleteButton.get());
m_changeButton->setBgColor(gfx::ColorNone);
m_changeButton->setBounds(itemBounds);
m_changeButton->setText(getAccelText((*accels)[i]));
m_changeButton->setText(getShortcutText((*shortcuts)[i]));
const char* label = "x";
m_deleteButton->setBgColor(gfx::ColorNone);
@ -411,7 +413,7 @@ private:
if (i == 0 && !m_addButton && (!m_menuitem || m_menuitem->getCommand())) {
m_addConn = obs::connection();
m_addButton.reset(new Button(""));
m_addConn = m_addButton->Click.connect([this] { onAddAccel(); });
m_addConn = m_addButton->Click.connect([this] { onAddShortcut(); });
m_addButton->setStyle(theme->styles.miniButton());
addChild(m_addButton.get());
@ -452,17 +454,15 @@ private:
m_addButton->setVisible(false);
}
m_hotAccel = -1;
m_hotShortcut = -1;
}
std::string getAccelText(const Accelerator& accel) const
std::string getShortcutText(const AppShortcut& shortcut) const
{
if (m_key && m_key->type() == KeyType::WheelAction && accel.isEmpty()) {
if (m_key && m_key->type() == KeyType::WheelAction && shortcut.isEmpty()) {
return Strings::keyboard_shortcuts_default_action();
}
else {
return accel.toString();
}
return shortcut.toString();
}
KeyboardShortcuts& m_keys;
@ -471,14 +471,14 @@ private:
KeyPtr m_keyOrig;
AppMenuItem* m_menuitem;
int m_level;
ui::Accelerators m_newAccels;
ui::Shortcuts m_newShortcuts;
std::shared_ptr<ui::Button> m_changeButton;
std::shared_ptr<ui::Button> m_deleteButton;
std::shared_ptr<ui::Button> m_addButton;
obs::scoped_connection m_changeConn;
obs::scoped_connection m_deleteConn;
obs::scoped_connection m_addConn;
int m_hotAccel;
int m_hotShortcut;
bool m_lockButtons;
HeaderItem* m_headerItem;
};
@ -595,7 +595,7 @@ private:
case KeyContext::MoveTool:
case KeyContext::FreehandTool:
case KeyContext::ShapeTool:
text = convertKeyContextToUserFriendlyString(key->keycontext()) + ": " + text;
text = convert_keycontext_to_user_friendly_string(key->keycontext()) + ": " + text;
break;
}
KeyItem* keyItem = new KeyItem(m_keys, m_menuKeys, text, key, nullptr, 0, &m_headerItem);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2020-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -135,8 +135,16 @@ public:
remapWindow();
centerWindow();
gfx::Rect originalBounds = bounds();
load_window_pos(this, "LayerProperties");
// Queue a remap for after the user data view is configured
// if the window size has been reset and user data is visible
if (originalBounds == bounds() && Preferences::instance().layers.userDataVisibility())
m_remapAfterConfigure = true;
UIContext::instance()->add_observer(this);
}
@ -158,7 +166,12 @@ public:
m_document->add_observer(this);
if (countLayers() > 0) {
m_userDataView.configureAndSet(m_layer->userData(), g_window->propertiesGrid());
m_userDataView.configureAndSet(m_layer->userData(), propertiesGrid());
if (m_remapAfterConfigure) {
remapWindow();
centerWindow();
m_remapAfterConfigure = false;
}
}
updateFromLayer();
@ -355,8 +368,7 @@ private:
{
if (m_layer) {
m_userDataView.toggleVisibility();
g_window->remapWindow();
manager()->invalidate();
expandWindow(gfx::Size(bounds().w, sizeHint().h));
}
}
@ -484,6 +496,7 @@ private:
view::RealRange m_range;
bool m_selfUpdate = false;
UserDataView m_userDataView;
bool m_remapAfterConfigure = false;
};
LayerPropertiesCommand::LayerPropertiesCommand()

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -164,10 +164,10 @@ void NewBrushCommand::createBrush(const Site& site, const Mask* mask)
params.set("change", "custom");
params.set("slot", base::convert_to<std::string>(slot).c_str());
KeyPtr key = KeyboardShortcuts::instance()->command(CommandId::ChangeBrush(), params);
if (key && !key->accels().empty()) {
if (key && !key->shortcuts().empty()) {
std::string tooltip;
tooltip += Strings::new_brush_shortcut() + " ";
tooltip += key->accels().front().toString();
tooltip += key->shortcuts().front().toString();
StatusBar::instance()->showTip(2000, tooltip);
}
}

View File

@ -267,16 +267,8 @@ void NewLayerCommand::onExecute(Context* context)
bool afterBackground = false;
switch (m_type) {
case Type::Layer:
if (m_place == Place::BeforeActiveLayer) {
layer = api.newLayer(parent, name);
api.restackLayerBefore(layer, parent, activeLayer);
}
else
layer = api.newLayerAfter(parent, name, activeLayer);
break;
case Type::Group: layer = api.newGroupAfter(parent, name, activeLayer); break;
case Type::Layer: layer = api.newLayer(parent, name); break;
case Type::Group: layer = api.newGroup(parent, name); break;
case Type::ReferenceLayer:
layer = api.newLayer(parent, name);
if (layer)
@ -311,6 +303,15 @@ void NewLayerCommand::onExecute(Context* context)
ASSERT(layer->parent());
// Reorder the resulting layer.
switch (m_place) {
case Place::AfterActiveLayer: api.restackLayerAfter(layer, parent, activeLayer); break;
case Place::BeforeActiveLayer: api.restackLayerBefore(layer, parent, activeLayer); break;
case Place::Top:
api.restackLayerAfter(layer, sprite->root(), sprite->root()->lastLayer());
break;
}
// Put new layer as an overlay of the background or in the first
// layer in case the sprite is transparent.
if (afterBackground) {

View File

@ -696,7 +696,6 @@ public:
onChangeBgScope();
onChangeGridScope();
sectionListbox()->selectIndex(m_curSection);
// Aseprite format preferences
celFormat()->setSelectedItemIndex(int(m_pref.asepriteFormat.celFormat()));
@ -1046,6 +1045,13 @@ public:
return true;
}
protected:
void onOpen(Event& evt) override
{
sectionListbox()->selectIndex(m_curSection);
app::gen::Options::onOpen(evt);
}
private:
void onInitTheme(InitThemeEvent& ev) override
{
@ -1366,8 +1372,8 @@ private:
if (!item)
return;
const std::string lang = item->langId();
const bool state = (lang == "ar" || lang == "ja" || lang == "ko" || lang == "yue_Hant" ||
lang == "zh_Hans" || lang == "zh_Hant");
const bool state = (lang == "ar" || lang == "ja" || lang == "ko" || lang == "th" ||
lang == "yue_Hant" || lang == "zh_Hans" || lang == "zh_Hant");
fontWarningFiller()->setVisible(state);
fontWarning()->setVisible(state);
layout();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -10,6 +10,7 @@
#endif
#include "app/app.h"
#include "app/color_utils.h"
#include "app/commands/command.h"
#include "app/console.h"
#include "app/context.h"
@ -88,10 +89,10 @@ void PasteTextCommand::onExecute(Context* ctx)
std::string text = window.userText()->text();
app::Color color = window.fontColor()->getColor();
doc::ImageRef image = render_text(
fontInfo,
text,
gfx::rgba(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()));
ui::Paint paint = window.fontFace()->paint();
paint.color(color_utils::color_for_ui(color));
doc::ImageRef image = render_text(fontInfo, text, paint);
if (image) {
Sprite* sprite = editor->sprite();
if (image->pixelFormat() != sprite->pixelFormat()) {

View File

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

View File

@ -21,6 +21,7 @@
#include "app/doc_undo.h"
#include "app/doc_undo_observer.h"
#include "app/docs_observer.h"
#include "app/i18n/strings.h"
#include "app/modules/gui.h"
#include "app/modules/palettes.h"
#include "app/site.h"
@ -292,7 +293,7 @@ public:
base::get_pretty_memory_size(static_cast<Cmd*>(state->cmd())->memSize())
#endif
:
std::string("Initial State"));
Strings::undo_history_initial_state());
if ((g->getClipBounds() & itemBounds).isEmpty())
return;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -8,6 +8,7 @@
FOR_EACH_COMMAND(About)
FOR_EACH_COMMAND(AddColor)
FOR_EACH_COMMAND(AdvancedMode)
FOR_EACH_COMMAND(Apply)
FOR_EACH_COMMAND(AutocropSprite)
FOR_EACH_COMMAND(BackgroundFromLayer)
FOR_EACH_COMMAND(BrightnessContrast)
@ -40,6 +41,7 @@ FOR_EACH_COMMAND(DeselectMask)
FOR_EACH_COMMAND(Despeckle)
FOR_EACH_COMMAND(DiscardBrush)
FOR_EACH_COMMAND(DuplicateLayer)
FOR_EACH_COMMAND(DuplicateSlice)
FOR_EACH_COMMAND(DuplicateSprite)
FOR_EACH_COMMAND(DuplicateView)
FOR_EACH_COMMAND(Exit)

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -128,7 +128,7 @@ private:
case ui::kKeyDownMessage: {
KeyboardShortcuts* keys = KeyboardShortcuts::instance();
const KeyPtr key = keys->command(CommandId::SwitchColors());
if (key && key->isPressed(msg, *keys)) {
if (key && key->isPressed(msg)) {
// Switch colors
app::Color from = m_fromButton->getColor();
app::Color to = m_toButton->getColor();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -48,9 +48,12 @@ public:
// When the main window is closed, we should close the console (in
// other case the main message loop will continue running for the
// console too).
m_mainWindowClosedConn = App::instance()->mainWindow()->Close.connect(
[this] { closeWindow(nullptr); });
// console too). The main window can be nullptr if the console is
// used to show an error when loading the default theme or font at
// the initialization.
if (auto* mainWin = App::instance()->mainWindow()) {
m_mainWindowClosedConn = mainWin->Close.connect([this] { closeWindow(nullptr); });
}
// When the window is closed, we clear the text
Close.connect([this] {
@ -78,7 +81,7 @@ public:
~ConsoleWindow() { TRACE_CON("CON: ~ConsoleWindow this=", this); }
void addMessage(const std::string& msg)
void addMessage(std::string msg)
{
if (!m_hasText) {
m_hasText = true;
@ -90,6 +93,17 @@ public:
gfx::Point pt = m_view.viewScroll();
const bool autoScroll = (pt.y >= maxSize.h - visible.h);
// Escape characters we can't show properly
for (size_t i = 0; i < msg.size(); i++) {
switch (msg[i]) {
case '\a':
case '\b':
case '\r':
case '\t':
case '\v': msg[i] = ' ';
}
}
m_textbox.setText(m_textbox.text() + msg);
if (autoScroll) {

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2023 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -139,6 +139,13 @@ DataRecovery::Sessions DataRecovery::sessions()
return copy;
}
bool DataRecovery::isRunningSession(const SessionPtr& session) const
{
ASSERT(session);
ASSERT(m_inProgress);
return session->path() == m_inProgress->path();
}
void DataRecovery::searchForSessions()
{
Sessions sessions;
@ -150,7 +157,7 @@ void DataRecovery::searchForSessions()
RECO_TRACE("RECO: Session '%s' ", itempath.c_str());
SessionPtr session(new Session(&m_config, itempath));
if (!session->isRunning()) {
if (!isRunningSession(session)) {
if ((session->isEmpty()) || (!session->isCrashedSession() && session->isOldSession())) {
RECO_TRACE("to be deleted (%s)\n",
session->isEmpty() ? "is empty" :

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -45,6 +45,8 @@ public:
// Returns a copy of the list of sessions that can be recovered.
Sessions sessions();
bool isRunningSession(const SessionPtr& session) const;
// Triggered in the UI-thread from the m_thread using an
// ui::execute_from_ui_thread() when the list of sessions is ready
// to be used.

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -19,12 +19,10 @@
#include "app/crash/write_document.h"
#include "app/doc.h"
#include "app/doc_access.h"
#include "app/file/file.h"
#include "app/ui_context.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "base/fstream_path.h"
#include "base/process.h"
#include "base/split_string.h"
#include "base/string.h"
#include "base/thread.h"
@ -128,12 +126,6 @@ const Session::Backups& Session::backups()
return m_backups;
}
bool Session::isRunning()
{
loadPid();
return base::get_process_name(m_pid) == base::get_process_name(base::get_current_process_id());
}
bool Session::isCrashedSession()
{
loadPid();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -47,9 +47,9 @@ public:
std::string name() const;
std::string version();
std::string& path() { return m_path; }
const Backups& backups();
bool isRunning();
bool isCrashedSession();
bool isOldSession();
bool isEmpty();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -338,6 +338,19 @@ void Doc::notifyAfterAddTile(LayerTilemap* layer, frame_t frame, tile_index ti)
notify_observers<DocEvent&>(&DocObserver::onAfterAddTile, ev);
}
void Doc::notifyBeforeSlicesDuplication()
{
DocEvent ev(this);
notify_observers<DocEvent&>(&DocObserver::onBeforeSlicesDuplication, ev);
}
void Doc::notifySliceDuplicated(Slice* slice)
{
DocEvent ev(this);
ev.slice(slice);
notify_observers<DocEvent&>(&DocObserver::onSliceDuplicated, ev);
}
bool Doc::isModified() const
{
return !m_undo->isInSavedStateOrSimilar();

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -141,6 +141,8 @@ public:
void notifyTilesetChanged(Tileset* tileset);
void notifyLayerGroupCollapseChange(Layer* layer);
void notifyAfterAddTile(LayerTilemap* layer, frame_t frame, tile_index ti);
void notifyBeforeSlicesDuplication();
void notifySliceDuplicated(Slice* slice);
//////////////////////////////////////////////////////////////////////
// File related properties

View File

@ -14,6 +14,7 @@
#include "app/cmd/add_cel.h"
#include "app/cmd/add_frame.h"
#include "app/cmd/add_layer.h"
#include "app/cmd/add_tileset.h"
#include "app/cmd/clear_cel.h"
#include "app/cmd/clear_image.h"
#include "app/cmd/copy_cel.h"
@ -693,12 +694,44 @@ void DocApi::restackLayerBefore(Layer* layer, LayerGroup* parent, Layer* beforeT
restackLayerAfter(layer, parent, afterThis);
}
Layer* DocApi::copyLayerWithSprite(doc::Layer* layer, doc::Sprite* sprite)
{
std::unique_ptr<doc::Layer> clone;
if (layer->isTilemap()) {
auto* srcTilemap = static_cast<LayerTilemap*>(layer);
tileset_index tilesetIndex = srcTilemap->tilesetIndex();
// If the caller is trying to make a copy of a tilemap layer specifying a
// different sprite as its owner, then we must copy the tilesets of the
// given tilemap layer into the new owner.
if (sprite != srcTilemap->sprite()) {
auto* srcTilesetCopy = Tileset::MakeCopyCopyingImagesForSprite(srcTilemap->tileset(), sprite);
auto* addTileset = new cmd::AddTileset(sprite, srcTilesetCopy);
m_transaction.execute(addTileset);
tilesetIndex = addTileset->tilesetIndex();
}
clone = std::make_unique<LayerTilemap>(sprite, tilesetIndex);
}
else if (layer->isImage())
clone = std::make_unique<LayerImage>(sprite);
else if (layer->isGroup())
clone = std::make_unique<LayerGroup>(sprite);
else
throw std::runtime_error("Invalid layer type");
if (auto* doc = dynamic_cast<app::Doc*>(sprite->document())) {
doc->copyLayerContent(layer, doc, clone.get());
}
return clone.release();
}
Layer* DocApi::duplicateLayerAfter(Layer* sourceLayer, LayerGroup* parent, Layer* afterLayer)
{
ASSERT(parent);
Layer* newLayerPtr = copy_layer(sourceLayer);
Layer* newLayerPtr = copyLayerWithSprite(sourceLayer, parent->sprite());
newLayerPtr->setName(Strings::general_text_copy_of(newLayerPtr->name()));
newLayerPtr->setName(Strings::general_copy_of(newLayerPtr->name()));
addLayer(parent, newLayerPtr, afterLayer);

View File

@ -9,6 +9,7 @@
#define APP_DOC_API_H_INCLUDED
#pragma once
#include "app/doc_api_dnd_helper.h"
#include "app/drop_frame_place.h"
#include "app/tags_handling.h"
#include "doc/algorithm/flip_type.h"
@ -38,6 +39,7 @@ class Doc;
class Transaction;
using namespace doc;
using namespace docapi;
// High-level API to modify a document adding undo information, i.e.
// adding new "Cmd"s in the given transaction.
@ -134,6 +136,14 @@ public:
// Palette API
void setPalette(Sprite* sprite, frame_t frame, const Palette* newPalette);
// Drag and Drop helper API
void dropDocumentsOnTimeline(app::Doc* doc,
doc::frame_t frame,
doc::layer_t layerIndex,
InsertionPoint insert,
DroppedOn droppedOn,
DocProvider& provider);
private:
void cropImageLayer(LayerImage* layer, const gfx::Rect& bounds, const bool trimOutside);
bool cropCel(LayerImage* layer, Cel* cel, const gfx::Rect& bounds, const bool trimOutside);
@ -145,6 +155,8 @@ private:
const DropFramePlace dropFramePlace,
const TagsHandling tagsHandling);
Layer* copyLayerWithSprite(doc::Layer* layer, doc::Sprite* sprite);
class HandleLinkedCels {
public:
HandleLinkedCels(DocApi& api,

View File

@ -0,0 +1,142 @@
// Aseprite
// Copyright (C) 2025 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/doc_api.h"
#include "app/cmd/copy_cel.h"
#include "app/cmd/set_pixel_format.h"
#include "app/cmd/set_total_frames.h"
#include "app/file/file.h"
#include "app/transaction.h"
#include "doc/layer.h"
#include "render/dithering.h"
namespace {
using namespace app;
// Returns true if the document srcDoc has a cel that can be copied into a
// destDoc's cel.
// The cel from srcDoc can be copied only when all of the following conditions
// are met:
// * Drop took place in a cel.
// * Source doc has only one layer with just one frame.
// * The layer from the source doc and the destination cel's layer are both
// Image layers.
// Otherwise this function returns false.
bool can_copy_cel(Doc* srcDoc, Doc* destDoc, DroppedOn droppedOn, doc::layer_t layerIndex)
{
auto* srcLayer = srcDoc->sprite()->firstLayer();
auto* destLayer = destDoc->sprite()->allLayers()[layerIndex];
return droppedOn == DroppedOn::Cel && srcDoc->sprite()->allLayersCount() == 1 &&
srcDoc->sprite()->totalFrames() == 1 && srcLayer->isImage() && destLayer->isImage();
}
void setup_insertion_layer(Doc* destDoc,
doc::layer_t layerIndex,
InsertionPoint& insert,
Layer*& layer,
LayerGroup*& group)
{
const LayerList& allLayers = destDoc->sprite()->allLayers();
layer = allLayers[layerIndex];
if (insert == InsertionPoint::BeforeLayer && layer->isGroup()) {
group = static_cast<LayerGroup*>(layer);
// The user is trying to drop layers into an empty group, so there is no after
// nor before layer...
if (group->layersCount() == 0) {
layer = nullptr;
return;
}
layer = group->lastLayer();
insert = InsertionPoint::AfterLayer;
}
group = layer->parent();
}
} // namespace
namespace app {
using namespace docapi;
void DocApi::dropDocumentsOnTimeline(app::Doc* destDoc,
doc::frame_t frame,
doc::layer_t layerIndex,
InsertionPoint insert,
DroppedOn droppedOn,
DocProvider& docProvider)
{
// Layer used as a reference to determine if the dropped layers will be
// inserted after or before it.
Layer* refLayer = nullptr;
// Parent group of the reference layer layer.
LayerGroup* group = nullptr;
// Keep track of the current insertion point.
setup_insertion_layer(destDoc, layerIndex, insert, refLayer, group);
int docsProcessed = 0;
while (docProvider.pendingDocs() > 0) {
std::unique_ptr<Doc> srcDoc = docProvider.nextDoc();
docsProcessed++;
// If the provider returned a null document then there was some problem with
// that specific doc but it can continue providing more documents
if (!srcDoc)
continue;
// If source document doesn't match the destination document's color
// mode, change it.
if (srcDoc->colorMode() != destDoc->colorMode()) {
// We can change the source doc pixel format out of any transaction because
// we don't need undo/redo it.
cmd::SetPixelFormat cmd(srcDoc->sprite(),
destDoc->sprite()->pixelFormat(),
render::Dithering(),
Preferences::instance().quantization.rgbmapAlgorithm(),
nullptr,
nullptr,
FitCriteria::DEFAULT);
cmd.execute(destDoc->context());
}
// If there is only one source document to process and it has a single cel
// that can be copied, then copy the cel from the source doc into the
// destination doc's selected frame.
const bool isJustOneDoc = (docsProcessed == 1 && docProvider.pendingDocs() == 0);
if (isJustOneDoc && can_copy_cel(srcDoc.get(), destDoc, droppedOn, layerIndex)) {
auto* srcLayer = static_cast<LayerImage*>(srcDoc->sprite()->firstLayer());
auto* destLayer = static_cast<LayerImage*>(destDoc->sprite()->allLayers()[layerIndex]);
m_transaction.execute(new cmd::CopyCel(srcLayer, 0, destLayer, frame, false));
break;
}
// If there is no room for the source frames, add frames to the
// destination sprite.
if (frame + srcDoc->sprite()->totalFrames() > destDoc->sprite()->totalFrames()) {
m_transaction.execute(
new cmd::SetTotalFrames(destDoc->sprite(), frame + srcDoc->sprite()->totalFrames()));
}
for (auto* srcLayer : srcDoc->sprite()->root()->layers()) {
srcLayer->displaceFrames(0, frame);
if (insert == InsertionPoint::AfterLayer) {
refLayer = duplicateLayerAfter(srcLayer, group, refLayer);
}
else {
refLayer = duplicateLayerBefore(srcLayer, group, refLayer);
insert = InsertionPoint::AfterLayer;
}
}
}
}
} // namespace app

View File

@ -0,0 +1,41 @@
// Aseprite
// Copyright (C) 2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_DOC_API_DND_HELPER_H_INCLUDED
#define APP_DOC_API_DND_HELPER_H_INCLUDED
#pragma once
#include "app/doc.h"
namespace app { namespace docapi {
enum class InsertionPoint {
BeforeLayer,
AfterLayer,
};
enum class DroppedOn {
Unspecified,
Frame,
Layer,
Cel,
};
// Used by the drag & drop helper API as a provider of the documents that
// were dragged and then dropped in some Drag&Drop aware widget.
class DocProvider {
public:
virtual ~DocProvider() = default;
// Returns the next document from the underlying provider's implementation,
// returns null if there is no more documents to provide.
virtual std::unique_ptr<Doc> nextDoc() = 0;
// Returns the number of docs that were not provided yet.
virtual int pendingDocs() = 0;
};
}} // namespace app::docapi
#endif

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -43,6 +43,7 @@
#include "gfx/size.h"
#include "render/dithering.h"
#include "render/ordered_dither.h"
#include "render/quantization.h"
#include "render/render.h"
#include "ver/info.h"
@ -69,6 +70,104 @@ std::string escape_for_json(const std::string& path)
return res;
}
// Forward declaration
void serialize_properties(const doc::UserData::Properties& props, std::ostream& os);
// Helper for a single value
void serialize_variant(const doc::UserData::Variant& value, std::ostream& os)
{
using Properties = doc::UserData::Properties;
switch (value.index()) {
case USER_DATA_PROPERTY_TYPE_BOOL: os << (get_value<bool>(value) ? "true" : "false"); break;
case USER_DATA_PROPERTY_TYPE_INT8: os << static_cast<int64_t>(get_value<int8_t>(value)); break;
case USER_DATA_PROPERTY_TYPE_UINT8:
os << static_cast<int64_t>(get_value<uint8_t>(value));
break;
case USER_DATA_PROPERTY_TYPE_INT16:
os << static_cast<int64_t>(get_value<int16_t>(value));
break;
case USER_DATA_PROPERTY_TYPE_UINT16:
os << static_cast<int64_t>(get_value<uint16_t>(value));
break;
case USER_DATA_PROPERTY_TYPE_INT32:
os << static_cast<int64_t>(get_value<int32_t>(value));
break;
case USER_DATA_PROPERTY_TYPE_UINT32:
os << static_cast<int64_t>(get_value<uint32_t>(value));
break;
case USER_DATA_PROPERTY_TYPE_INT64:
os << static_cast<int64_t>(get_value<int64_t>(value));
break;
case USER_DATA_PROPERTY_TYPE_UINT64:
os << static_cast<int64_t>(get_value<uint64_t>(value));
break;
case USER_DATA_PROPERTY_TYPE_FLOAT: os << get_value<float>(value); break;
case USER_DATA_PROPERTY_TYPE_DOUBLE: os << get_value<double>(value); break;
case USER_DATA_PROPERTY_TYPE_STRING:
os << "\"" << escape_for_json(get_value<std::string>(value)) << "\"";
break;
case USER_DATA_PROPERTY_TYPE_PROPERTIES:
serialize_properties(get_value<Properties>(value), os);
break;
default: os << "\"[unsupported type]\""; break;
}
}
// Serializes a map of properties
void serialize_properties(const doc::UserData::Properties& props, std::ostream& os)
{
os << "{";
bool first = true;
for (const auto& [key, value] : props) {
if (!first)
os << ", ";
first = false;
os << "\"" << escape_for_json(key) << "\": ";
serialize_variant(value, os);
}
os << "}";
}
void serialize_userdata_properties(const doc::UserData& data, std::ostream& os)
{
const auto& propsMaps = data.propertiesMaps();
bool hasAnyProps = false;
for (const auto& [group, props] : propsMaps) {
if (!props.empty()) {
hasAnyProps = true;
break;
}
}
if (hasAnyProps) {
os << ", \"properties\": {";
bool firstProp = true;
for (const auto& [group, props] : propsMaps) {
if (!props.empty()) {
if (!firstProp)
os << ", ";
firstProp = false;
if (group.empty()) {
// Default group: flatten its keys at the top level
bool firstKey = true;
for (const auto& [key, value] : props) {
if (!firstKey)
os << ", ";
firstKey = false;
os << "\"" << escape_for_json(key) << "\": ";
serialize_variant(value, os);
}
}
else {
// Named group: nest under its group name
os << "\"" << escape_for_json(group) << "\": ";
serialize_properties(props, os);
}
}
}
os << "}";
}
}
std::ostream& operator<<(std::ostream& os, const doc::UserData& data)
{
doc::color_t color = data.color();
@ -80,6 +179,9 @@ std::ostream& operator<<(std::ostream& os, const doc::UserData& data)
}
if (!data.text().empty())
os << ", \"data\": \"" << escape_for_json(data.text()) << "\"";
serialize_userdata_properties(data, os);
return os;
}
@ -300,6 +402,35 @@ public:
}
}
void setPixelFormat(const doc::PixelFormat newPixelFormat)
{
if (!m_image || m_image->pixelFormat() == newPixelFormat)
return;
ImageSpec spec(ColorMode(newPixelFormat),
m_image->width(),
m_image->height(),
m_image->maskColor());
ImageRef convertedImg(Image::create(spec));
if (!convertedImg)
return;
clear_image(convertedImg.get(), 0);
render::Dithering dithering;
Sprite* sprite = this->sprite();
render::convert_pixel_format(m_image.get(),
convertedImg.get(),
newPixelFormat,
dithering,
sprite ? sprite->rgbMap(0) : nullptr,
sprite ? sprite->palette(0) : nullptr,
(sprite && sprite->backgroundLayer()),
0,
0,
nullptr);
m_image = convertedImg;
}
private:
Doc* m_document;
Sprite* m_sprite;
@ -679,6 +810,9 @@ Doc* DocExporter::exportSheet(Context* ctx, base::task_token& token)
Sprite* texture = textureDocument->sprite();
Image* textureImage = texture->root()->firstLayer()->cel(frame_t(0))->image();
for (auto& sample : samples)
sample.setPixelFormat(texture->pixelFormat());
renderTexture(ctx, samples, textureImage, token);
if (token.canceled())
return nullptr;
@ -1245,22 +1379,6 @@ void DocExporter::renderTexture(Context* ctx,
++i;
continue;
}
// 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(),
rgbmapAlgo,
nullptr, // toGray is not needed because the texture is Indexed or RGB
nullptr, // TODO add a delegate to show progress
fc)
.execute(ctx);
}
sample.renderSample(textureImage,
sample.inTextureBounds().x + m_innerPadding,
sample.inTextureBounds().y + m_innerPadding,

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -90,6 +90,8 @@ public:
// Slices
virtual void onSliceNameChange(DocEvent& ev) {}
virtual void onBeforeSlicesDuplication(DocEvent& ev) {}
virtual void onSliceDuplicated(DocEvent& ev) {}
// The tileset has changed.
virtual void onTilesetChanged(DocEvent& ev) {}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020-2024 Igara Studio S.A.
// Copyright (C) 2020-2025 Igara Studio S.A.
// Copyright (C) 2017-2018 David Capello
//
// This program is distributed under the terms of
@ -42,6 +42,7 @@
#include "archive_entry.h"
#include "json11.hpp"
#include <cctype>
#include <fstream>
#include <queue>
#include <sstream>
@ -535,6 +536,19 @@ void Extension::updateCategory(const Category newCategory)
#ifdef ENABLE_SCRIPTING
static bool is_simple_id(const char* k)
{
if (!*k)
return false;
if (!std::isalpha(*k) && *k != '_')
return false;
for (const char* p = k + 1; *p; ++p) {
if (!std::isalnum(*p) && *p != '_')
return false;
}
return true;
}
// TODO move this to app/script/tableutils.h
static void serialize_table(lua_State* L, int idx, std::string& result)
{
@ -555,7 +569,21 @@ static void serialize_table(lua_State* L, int idx, std::string& result)
// Save key
if (lua_type(L, -2) == LUA_TSTRING) {
if (const char* k = lua_tostring(L, -2)) {
result += k;
// If this is a simple identifier we can avoid using ["..."] to enclose
// the key name.
if (is_simple_id(k)) {
result += k;
}
// In other cases, we enclose the key name in brackets and
// double quotes ["..."] to avoid syntax errors when the name
// has unicode characters.
else {
result.push_back('[');
result.push_back('"');
result += k;
result.push_back('"');
result.push_back(']');
}
result.push_back('=');
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2023 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -1095,6 +1095,9 @@ public:
gifframe_t nframes = totalFrames();
for (gifframe_t gifFrame = 0; gifFrame < nframes; ++gifFrame) {
ASSERT(frame_it != frame_end);
if (m_fop->isStop())
break;
frame_t frame = *frame_it;
++frame_it;
@ -1401,7 +1404,7 @@ private:
color_t color = *srcIt;
int i;
if (rgba_geta(color) >= 128) {
if (rgba_geta(color) > 0) {
i = framePalette.findExactMatch(rgba_getr(color),
rgba_getg(color),
rgba_getb(color),

View File

@ -75,20 +75,86 @@ bool SvgFormat::onSave(FileOp* fop)
const int pixelScaleValue = std::clamp(svg_options->pixelScale, 0, 10000);
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
FILE* f = handle.get();
auto printcol = [f](int x, int y, int r, int g, int b, int a, int pxScale) {
fprintf(f,
"<rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" fill=\"#%02X%02X%02X\" ",
x * pxScale,
y * pxScale,
pxScale,
pxScale,
r,
g,
b);
if (a != 255)
fprintf(f, "opacity=\"%f\" ", (float)a / 255.0);
fprintf(f, "/>\n");
auto printRect =
[f](int x, int y, int width, int height, int r, int g, int b, int a, int pxScale) {
fprintf(f,
"<rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" fill=\"#%02X%02X%02X\" ",
x * pxScale,
y * pxScale,
width * pxScale,
height * pxScale,
r,
g,
b);
if (a != 255)
fprintf(f, "opacity=\"%f\" ", (float)a / 255.0);
fprintf(f, "/>\n");
};
// Helper function for greedy merge of same-colored pixels
auto mergePixels = [&](auto getPixelColor) {
std::vector<std::vector<bool>> processed(image->height(),
std::vector<bool>(image->width(), false));
for (y = 0; y < image->height(); y++) {
for (x = 0; x < image->width(); x++) {
if (processed[y][x])
continue;
auto [pixelR, pixelG, pixelB, pixelA, shouldSkip] = getPixelColor(x, y);
if (shouldSkip) {
processed[y][x] = true;
continue;
}
// Find the largest rectangle starting from this pixel
int maxWidth = 0;
int maxHeight = 0;
// Find maximum width for current row
for (int w = x; w < image->width(); w++) {
if (processed[y][w])
break;
auto [checkR, checkG, checkB, checkA, skip] = getPixelColor(w, y);
if (skip || checkR != pixelR || checkG != pixelG || checkB != pixelB || checkA != pixelA)
break;
maxWidth = w - x + 1;
}
// Find maximum height with current width
for (int h = y; h < image->height(); h++) {
bool canExtend = true;
for (int w = x; w < x + maxWidth; w++) {
if (processed[h][w]) {
canExtend = false;
break;
}
auto [checkR, checkG, checkB, checkA, skip] = getPixelColor(w, h);
if (skip || checkR != pixelR || checkG != pixelG || checkB != pixelB ||
checkA != pixelA) {
canExtend = false;
break;
}
}
if (!canExtend)
break;
maxHeight = h - y + 1;
}
// Mark rectangle as processed and output it
for (int h = y; h < y + maxHeight; h++) {
for (int w = x; w < x + maxWidth; w++) {
processed[h][w] = true;
}
}
printRect(x, y, maxWidth, maxHeight, pixelR, pixelG, pixelB, pixelA, pixelScaleValue);
}
fop->setProgress((float)y / (float)(image->height()));
}
};
fprintf(f, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
fprintf(
f,
@ -98,59 +164,55 @@ bool SvgFormat::onSave(FileOp* fop)
switch (image->pixelFormat()) {
case IMAGE_RGB: {
for (y = 0; y < image->height(); y++) {
for (x = 0; x < image->width(); x++) {
c = get_pixel_fast<RgbTraits>(image.get(), x, y);
alpha = rgba_geta(c);
if (alpha != 0x00)
printcol(x, y, rgba_getr(c), rgba_getg(c), rgba_getb(c), alpha, pixelScaleValue);
}
fop->setProgress((float)y / (float)(image->height()));
}
auto getPixelColor = [&](int px, int py) -> std::tuple<int, int, int, int, bool> {
c = get_pixel_fast<RgbTraits>(image.get(), px, py);
alpha = rgba_geta(c);
if (alpha == 0x00)
return { 0, 0, 0, 0, true };
return { rgba_getr(c), rgba_getg(c), rgba_getb(c), alpha, false };
};
mergePixels(getPixelColor);
break;
}
case IMAGE_GRAYSCALE: {
for (y = 0; y < image->height(); y++) {
for (x = 0; x < image->width(); x++) {
c = get_pixel_fast<GrayscaleTraits>(image.get(), x, y);
auto v = graya_getv(c);
alpha = graya_geta(c);
if (alpha != 0x00)
printcol(x, y, v, v, v, alpha, pixelScaleValue);
}
fop->setProgress((float)y / (float)(image->height()));
}
auto getPixelColor = [&](int px, int py) -> std::tuple<int, int, int, int, bool> {
c = get_pixel_fast<GrayscaleTraits>(image.get(), px, py);
auto v = graya_getv(c);
alpha = graya_geta(c);
if (alpha == 0x00)
return { 0, 0, 0, 0, true };
return { v, v, v, alpha, false };
};
mergePixels(getPixelColor);
break;
}
case IMAGE_INDEXED: {
unsigned char image_palette[256][4];
for (y = 0; y < 256; y++) {
fop->sequenceGetColor(y, &r, &g, &b);
image_palette[y][0] = r;
image_palette[y][1] = g;
image_palette[y][2] = b;
fop->sequenceGetAlpha(y, &a);
image_palette[y][3] = a;
for (int i = 0; i < 256; i++) {
fop->sequenceGetColor(i, &r, &g, &b);
image_palette[i][0] = r;
image_palette[i][1] = g;
image_palette[i][2] = b;
fop->sequenceGetAlpha(i, &a);
image_palette[i][3] = a;
}
color_t mask_color = -1;
if (fop->document()->sprite()->backgroundLayer() == NULL ||
!fop->document()->sprite()->backgroundLayer()->isVisible()) {
mask_color = fop->document()->sprite()->transparentColor();
}
for (y = 0; y < image->height(); y++) {
for (x = 0; x < image->width(); x++) {
c = get_pixel_fast<IndexedTraits>(image.get(), x, y);
if (c != mask_color)
printcol(x,
y,
image_palette[c][0] & 0xff,
image_palette[c][1] & 0xff,
image_palette[c][2] & 0xff,
image_palette[c][3] & 0xff,
pixelScaleValue);
}
fop->setProgress((float)y / (float)(image->height()));
}
auto getPixelColor = [&](int px, int py) -> std::tuple<int, int, int, int, bool> {
c = get_pixel_fast<IndexedTraits>(image.get(), px, py);
if (c == mask_color)
return { 0, 0, 0, 0, true };
return { image_palette[c][0] & 0xff,
image_palette[c][1] & 0xff,
image_palette[c][2] & 0xff,
image_palette[c][3] & 0xff,
false };
};
mergePixels(getPixelColor);
break;
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -82,6 +82,9 @@ public:
unsigned int m_version;
bool m_removed;
mutable bool m_is_folder;
#ifdef _WIN32
bool m_isHidden = false;
#endif
std::atomic<double> m_thumbnailProgress;
std::atomic<os::Surface*> m_thumbnail;
#ifdef _WIN32
@ -266,7 +269,7 @@ IFileItem* FileSystemModule::getRootFileItem()
fileitem->m_pidl = pidl;
fileitem->m_fullpidl = pidl;
SFGAOF attrib = SFGAO_FOLDER;
SFGAOF attrib = SFGAO_FOLDER | SFGAO_HIDDEN;
shl_idesktop->GetAttributesOf(1, (LPCITEMIDLIST*)&pidl, &attrib);
update_by_pidl(fileitem, attrib);
@ -357,7 +360,7 @@ bool FileItem::isHidden() const
ASSERT(m_displayname != NOTINITIALIZED);
#ifdef _WIN32
return false;
return m_isHidden;
#else
return m_displayname[0] == '.';
#endif
@ -462,7 +465,7 @@ const FileItemList& FileItem::children()
// Get the interface to enumerate subitems
hr = pFolder->EnumObjects(
reinterpret_cast<HWND>(os::System::instance()->defaultWindow()->nativeHandle()),
SHCONTF_FOLDERS | SHCONTF_NONFOLDERS,
SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN,
&pEnum);
if (hr == S_OK && pEnum) {
@ -473,10 +476,9 @@ const FileItemList& FileItem::children()
while (pEnum->Next(256, itempidl, &fetched) == S_OK && fetched > 0) {
// Request the SFGAO_FOLDER attribute to know what of the
// item is file or a folder
for (c = 0; c < fetched; ++c) {
attribs[c] = SFGAO_FOLDER;
pFolder->GetAttributesOf(1, (LPCITEMIDLIST*)itempidl, attribs + c);
}
for (c = 0; c < fetched; ++c)
attribs[c] = SFGAO_FOLDER | SFGAO_HIDDEN;
pFolder->GetAttributesOf(fetched, (LPCITEMIDLIST*)itempidl, attribs);
// Generate the FileItems
for (c = 0; c < fetched; ++c) {
@ -755,6 +757,9 @@ static void update_by_pidl(FileItem* fileitem, SFGAOF attrib)
// Is it a folder?
fileitem->m_is_folder = calc_is_folder(fileitem->m_filename, attrib);
#if _WIN32
fileitem->m_isHidden = (attrib & SFGAO_HIDDEN ? true : false);
#endif
// Get the name to display

View File

@ -14,7 +14,9 @@
#include "text/font.h"
#include "text/font_mgr.h"
#include "text/sprite_sheet_font.h"
#include "text/typeface.h"
#include "ui/manager.h"
#include "ui/scale.h"
#include <set>
@ -87,9 +89,9 @@ text::FontRef FontData::getFont(text::FontMgrRef& fontMgr, float size)
// Load fallback
if (m_fallback) {
text::FontRef fallback = m_fallback->getFont(fontMgr, m_fallbackSize);
text::FontRef fallback = m_fallback->getFont(fontMgr, m_fallbackSize * ui::guiscale());
if (font)
font->setFallback(fallback.get());
font->setFallback(fallback);
else
return fallback; // Don't double-cache the fallback font
}

View File

@ -34,7 +34,8 @@ public:
void setName(const std::string& name) { m_name = name; }
void setFilename(const std::string& filename) { m_filename = filename; }
void setDefaultSize(const float size) { m_size = size; }
void setAntialias(bool antialias) { m_antialias = antialias; }
void setAntialias(const bool antialias) { m_antialias = antialias; }
void setHinting(const text::FontHinting hinting) { m_hinting = hinting; }
void setFallback(FontData* fallback, float fallbackSize)
{
m_fallback = fallback;

View File

@ -11,6 +11,7 @@
#include "app/fonts/font_info.h"
#include "app/fonts/font_data.h"
#include "app/i18n/strings.h"
#include "app/pref/preferences.h"
#include "base/fs.h"
#include "base/split_string.h"
@ -142,19 +143,22 @@ std::string FontInfo::humanString() const
}
result += fmt::format(" {}pt", size());
if (!result.empty()) {
if (style().weight() >= text::FontStyle::Weight::SemiBold)
result += " Bold";
if (style().weight() != text::FontStyle::Weight::Normal)
result +=
" " +
Strings::Translate(
fmt::format("font_style.font_weight_{}", static_cast<int>(style().weight())).c_str());
if (style().slant() != text::FontStyle::Slant::Upright)
result += " Italic";
result += " " + Strings::font_style_italic();
if (antialias())
result += " Antialias";
result += " " + Strings::font_style_antialias();
if (ligatures())
result += " Ligatures";
result += " " + Strings::font_style_ligatures();
switch (hinting()) {
case text::FontHinting::None: result += " No Hinting"; break;
case text::FontHinting::Slight: result += " Slight Hinting"; break;
case text::FontHinting::None: result += " " + Strings::font_style_hinting_none(); break;
case text::FontHinting::Slight: result += " " + Strings::font_style_hinting_slight(); break;
case text::FontHinting::Normal: break;
case text::FontHinting::Full: result += " Full Hinting"; break;
case text::FontHinting::Full: result += " " + Strings::font_style_hinting_full(); break;
}
}
return result;
@ -177,6 +181,7 @@ app::FontInfo convert_to(const std::string& from)
bool italic = false;
app::FontInfo::Flags flags = app::FontInfo::Flags::None;
text::FontHinting hinting = text::FontHinting::Normal;
text::FontStyle::Weight weight = text::FontStyle::Weight::Normal;
if (!parts.empty()) {
if (parts[0].compare(0, 5, "file=") == 0) {
@ -214,16 +219,17 @@ app::FontInfo convert_to(const std::string& from)
else if (hintingStr == "full")
hinting = text::FontHinting::Full;
}
else if (parts[i].compare(0, 7, "weight=") == 0) {
std::string weightStr = parts[i].substr(7);
weight = static_cast<text::FontStyle::Weight>(std::atoi(weightStr.c_str()));
}
}
}
text::FontStyle style;
if (bold && italic)
style = text::FontStyle::BoldItalic();
else if (bold)
style = text::FontStyle::Bold();
else if (italic)
style = text::FontStyle::Italic();
const text::FontStyle style(
bold ? text::FontStyle::Weight::Bold : weight,
text::FontStyle::Width::Normal,
italic ? text::FontStyle::Slant::Italic : text::FontStyle::Slant::Upright);
return app::FontInfo(type, name, size, style, flags, hinting);
}
@ -243,7 +249,7 @@ std::string convert_to(const app::FontInfo& from)
if (!result.empty()) {
if (from.size() > 0.0f)
result += fmt::format(",size={}", from.size());
if (from.style().weight() >= text::FontStyle::Weight::SemiBold)
if (from.style().weight() == text::FontStyle::Weight::Bold)
result += ",bold";
if (from.style().slant() != text::FontStyle::Slant::Upright)
result += ",italic";
@ -262,6 +268,8 @@ std::string convert_to(const app::FontInfo& from)
case text::FontHinting::Full: result += "full"; break;
}
}
if (from.style().weight() != text::FontStyle::Weight::Bold)
result += ",weight=" + std::to_string(static_cast<int>(from.style().weight()));
}
return result;
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -30,6 +30,7 @@
#include "os/surface.h"
#include "os/system.h"
#include "ui/intern.h"
#include "ui/paint.h"
#include "ui/system.h"
#include "ui/theme.h"
@ -118,9 +119,6 @@ void draw_color(ui::Graphics* g,
app::Color color = _color;
const int alpha = color.getAlpha();
// Color space conversion
auto convertColor = convert_from_current_to_screen_color_space();
if (alpha < 255) {
if (rc.w == rc.h)
draw_checkered_grid(g, rc, gfx::Size(rc.w / 2, rc.h / 2));
@ -133,11 +131,15 @@ void draw_color(ui::Graphics* g,
color = app::Color::fromGray(color.getGray(), color.getAlpha());
}
// The color is in the current sprite color space.
ui::Paint paint;
paint.color(color_utils::color_for_ui(color), get_current_color_space(g->display()).get());
if (color.getType() == app::Color::IndexType) {
int index = color.getIndex();
if (index >= 0 && index < get_current_palette()->size()) {
g->fillRect(convertColor(color_utils::color_for_ui(color)), rc);
g->drawRect(rc, paint);
}
else {
g->fillRect(gfx::rgba(0, 0, 0), rc);
@ -147,7 +149,7 @@ void draw_color(ui::Graphics* g,
}
}
else {
g->fillRect(convertColor(color_utils::color_for_ui(color)), rc);
g->drawRect(rc, paint);
}
}
}
@ -219,7 +221,8 @@ void draw_tile(ui::Graphics* g, const Rect& rc, const Site& site, doc::tile_t ti
int w = tileImage->width();
int h = tileImage->height();
os::SurfaceRef surface = os::System::instance()->makeRgbaSurface(w, h);
os::SurfaceRef surface =
os::System::instance()->makeRgbaSurface(w, h, get_current_color_space(g->display()));
convert_image_to_surface(tileImage.get(), get_current_palette(), surface.get(), 0, 0, 0, 0, w, h);
ui::Paint paint;

View File

@ -406,7 +406,7 @@ void load_window_pos(Window* window, const char* section, const bool limitMinSiz
if (get_multiple_displays()) {
Rect frame = get_config_rect(section, "WindowFrame", gfx::Rect());
if (!frame.isEmpty()) {
limit_with_workarea(parentDisplay, frame);
limit_least(frame);
window->loadNativeFrame(frame);
}
}
@ -680,82 +680,77 @@ void CustomizedGuiManager::onNewDisplayConfiguration(Display* display)
bool CustomizedGuiManager::processKey(Message* msg)
{
App* app = App::instance();
const KeyboardShortcuts* keys = KeyboardShortcuts::instance();
const KeyContext contexts[] = { keys->getCurrentKeyContext(), KeyContext::Normal };
int n = (contexts[0] != contexts[1] ? 2 : 1);
for (int i = 0; i < n; ++i) {
for (const KeyPtr& key : *keys) {
if (key->isPressed(msg, *keys, contexts[i])) {
// Cancel menu-bar loops (to close any popup menu)
app->mainWindow()->getMenuBar()->cancelMenuLoop();
const KeyPtr key = keys->findBestKeyFromMessage(msg);
if (!key)
return false;
switch (key->type()) {
case KeyType::Tool: {
tools::Tool* current_tool = app->activeTool();
tools::Tool* select_this_tool = key->tool();
tools::ToolBox* toolbox = app->toolBox();
std::vector<tools::Tool*> possibles;
// Cancel menu-bar loops (to close any popup menu)
App* app = App::instance();
app->mainWindow()->getMenuBar()->cancelMenuLoop();
// Collect all tools with the pressed keyboard-shortcut
for (tools::Tool* tool : *toolbox) {
const KeyPtr key = keys->tool(tool);
if (key && key->isPressed(msg, *keys))
possibles.push_back(tool);
}
switch (key->type()) {
case KeyType::Tool: {
tools::Tool* current_tool = app->activeTool();
tools::Tool* select_this_tool = key->tool();
tools::ToolBox* toolbox = app->toolBox();
std::vector<tools::Tool*> possibles;
if (possibles.size() >= 2) {
bool done = false;
// Collect all tools with the pressed keyboard-shortcut
for (tools::Tool* tool : *toolbox) {
const KeyPtr key = keys->tool(tool);
if (key && key->isPressed(msg))
possibles.push_back(tool);
}
for (size_t i = 0; i < possibles.size(); ++i) {
if (possibles[i] != current_tool &&
ToolBar::instance()->isToolVisible(possibles[i])) {
select_this_tool = possibles[i];
done = true;
break;
}
}
if (possibles.size() >= 2) {
bool done = false;
if (!done) {
for (size_t i = 0; i < possibles.size(); ++i) {
// If one of the possibilities is the current tool
if (possibles[i] == current_tool) {
// We select the next tool in the possibilities
select_this_tool = possibles[(i + 1) % possibles.size()];
break;
}
}
}
}
ToolBar::instance()->selectTool(select_this_tool);
return true;
}
case KeyType::Command: {
Command* command = key->command();
// Commands are executed only when the main window is
// the current window running.
if (getForegroundWindow() == app->mainWindow()) {
// OK, so we can execute the command represented
// by the pressed-key in the message...
UIContext::instance()->executeCommandFromMenuOrShortcut(command, key->params());
return true;
}
break;
}
case KeyType::Quicktool: {
// Do nothing, it is used in the editor through the
// KeyboardShortcuts::getCurrentQuicktool() function.
for (size_t i = 0; i < possibles.size(); ++i) {
if (possibles[i] != current_tool && ToolBar::instance()->isToolVisible(possibles[i])) {
select_this_tool = possibles[i];
done = true;
break;
}
}
break;
if (!done) {
for (size_t i = 0; i < possibles.size(); ++i) {
// If one of the possibilities is the current tool
if (possibles[i] == current_tool) {
// We select the next tool in the possibilities
select_this_tool = possibles[(i + 1) % possibles.size()];
break;
}
}
}
}
ToolBar::instance()->selectTool(select_this_tool);
return true;
}
case KeyType::Command: {
Command* command = key->command();
// Commands are executed only when the main window is
// the current window running.
if (getForegroundWindow() == app->mainWindow()) {
// OK, so we can execute the command represented
// by the pressed-key in the message...
UIContext::instance()->executeCommandFromMenuOrShortcut(command, key->params());
return true;
}
break;
}
case KeyType::Quicktool: {
// Do nothing, it is used in the editor through the
// KeyboardShortcuts::getCurrentQuicktool() function.
break;
}
}
return false;
}

View File

@ -213,7 +213,7 @@ void ResourceFinder::includeDesktopDir(const char* filename)
#ifdef _WIN32
std::vector<wchar_t> buf(MAX_PATH);
HRESULT hr = SHGetFolderPath(NULL, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_DEFAULT, &buf[0]);
HRESULT hr = SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, &buf[0]);
if (hr == S_OK) {
addPath(base::join_path(base::to_utf8(&buf[0]), filename));
}

View File

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

View File

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

View File

@ -1,11 +1,12 @@
// Aseprite
// Copyright (c) 2022-2024 Igara Studio S.A.
// Copyright (c) 2022-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#include "app/script/canvas_widget.h"
#include "app/color_spaces.h"
#include "app/script/graphics_context.h"
#include "app/ui/skin/skin_theme.h"
#include "os/system.h"
@ -45,7 +46,7 @@ void Canvas::callPaint()
return;
os::Paint p;
p.color(bgColor());
p.color(bgColor(), m_surface->colorSpace().get());
m_surface->drawRect(m_surface->bounds(), p);
// Draw only on resize (onPaint we draw the cached m_surface)
@ -189,7 +190,9 @@ void Canvas::onResize(ui::ResizeEvent& ev)
}
if (!m_surface || m_surface->width() != w || m_surface->height() != h) {
m_surface = system->makeSurface(w, h);
ui::Display* display = this->display();
os::ColorSpaceRef cs = (display ? display->colorSpace() : nullptr);
m_surface = system->makeSurface(w, h, cs);
callPaint();
}
}

View File

@ -60,6 +60,8 @@ namespace app { namespace script {
using namespace ui;
static constexpr const int kDefaultAutofit = ui::LEFT | ui::TOP;
namespace {
class DialogWindow : public WindowWithHand {
@ -107,6 +109,7 @@ struct Dialog {
std::map<std::string, ui::Widget*> dataWidgets;
std::map<std::string, ui::Widget*> labelWidgets;
int currentRadioGroup = 0;
int autofit = kDefaultAutofit;
// Member used to hold current state about the creation of a tabs
// widget. After creation it is reset to null to be ready for the
@ -127,12 +130,13 @@ struct Dialog {
int showRef = LUA_REFNIL;
lua_State* L = nullptr;
Dialog(const ui::Window::Type windowType, const std::string& title)
Dialog(const ui::Window::Type windowType, const std::string& title, bool sizeable)
: window(windowType, title)
, grid(2, false)
, currentGrid(&grid)
{
window.addChild(&grid);
window.setSizeable(sizeable);
all_dialogs.push_back(this);
}
@ -192,12 +196,20 @@ struct Dialog {
it->second->setText(text);
}
void setAutofit(int align)
{
// Accept both 0 or a valid subset of align parameters.
if (align == 0 || (align & (ui::LEFT | ui::RIGHT | ui::TOP | ui::BOTTOM)))
autofit = align;
}
Display* parentDisplay() const
{
Display* parentDisplay = window.parentDisplay();
if (!parentDisplay) {
const auto mainWindow = App::instance()->mainWindow();
parentDisplay = mainWindow->display();
const auto* mainWindow = App::instance()->mainWindow();
if (mainWindow)
parentDisplay = mainWindow->display();
}
return parentDisplay;
}
@ -209,6 +221,9 @@ struct Dialog {
// origin/scale (or main window if a parent window wasn't specified).
if (window.ownDisplay()) {
const Display* parentDisplay = this->parentDisplay();
if (!parentDisplay)
return bounds;
const int scale = parentDisplay->scale();
const gfx::Point dialogOrigin = window.display()->nativeWindow()->contentRect().origin();
const gfx::Point mainOrigin = parentDisplay->nativeWindow()->contentRect().origin();
@ -223,6 +238,9 @@ struct Dialog {
window.expandWindow(rc.size());
Display* parentDisplay = this->parentDisplay();
if (!parentDisplay)
return;
const int scale = parentDisplay->scale();
const gfx::Point mainOrigin = parentDisplay->nativeWindow()->contentRect().origin();
gfx::Rect frame = window.display()->nativeWindow()->contentRect();
@ -230,8 +248,10 @@ struct Dialog {
window.display()->nativeWindow()->setFrame(frame);
}
else {
gfx::Rect oldBounds(window.bounds());
window.setBounds(rc);
window.invalidate();
parentDisplay()->invalidateRect(oldBounds);
}
}
@ -365,6 +385,8 @@ int Dialog_new(lua_State* L)
// Get the title and the type of window (with or without title bar)
ui::Window::Type windowType = ui::Window::WithTitleBar;
std::string title = "Script";
bool sizeable = true;
int autofit = kDefaultAutofit;
if (lua_isstring(L, 1)) {
title = lua_tostring(L, 1);
}
@ -378,9 +400,21 @@ int Dialog_new(lua_State* L)
if (type != LUA_TNIL && lua_toboolean(L, -1))
windowType = ui::Window::WithoutTitleBar;
lua_pop(L, 1);
type = lua_getfield(L, 1, "resizeable");
if (type != LUA_TNIL && !lua_toboolean(L, -1))
sizeable = false;
lua_pop(L, 1);
type = lua_getfield(L, 1, "autofit");
if (type != LUA_TNIL) {
autofit = lua_tointeger(L, -1);
}
lua_pop(L, 1);
}
auto dlg = push_new<Dialog>(L, windowType, title);
auto dlg = push_new<Dialog>(L, windowType, title, sizeable);
dlg->setAutofit(autofit);
// The uservalue of the dialog userdata will contain a table that
// stores all the callbacks to handle events. As these callbacks can
@ -575,8 +609,9 @@ int Dialog_add_widget(lua_State* L, Widget* widget)
bool vexpand = (widget->type() == Canvas::Type());
// This is to separate different kind of widgets without label in
// different rows.
if (dlg->lastWidgetType != widget->type() || dlg->autoNewRow) {
// different rows. Separator widgets will always create a new row.
if (dlg->lastWidgetType != widget->type() || dlg->autoNewRow ||
widget->type() == ui::kSeparatorWidget) {
dlg->lastWidgetType = widget->type();
dlg->hbox = nullptr;
}
@ -631,8 +666,8 @@ int Dialog_add_widget(lua_State* L, Widget* widget)
dlg->labelWidgets[id] = labelWidget;
}
else {
// For tabs we don't want the empty space of an unspecified label.
if (widget->type() != Tabs::Type()) {
// For tabs and separators, we don't want the empty space of an unspecified label.
if (widget->type() != Tabs::Type() && widget->type() != ui::kSeparatorWidget) {
dlg->currentGrid->addChildInCell(new ui::HBox, 1, 1, ui::LEFT | ui::TOP);
}
}
@ -641,14 +676,15 @@ int Dialog_add_widget(lua_State* L, Widget* widget)
if (widget->type() == ui::kButtonWidget)
hbox->enableFlags(ui::HOMOGENEOUS);
// For tabs we don't want the empty space of an unspecified label, so
// For tabs and unlabeled separators, we don't want the empty space of an unspecified label, so
// span 2 columns.
const int hspan = (widget->type() == Tabs::Type() ? 2 : 1);
const int hspan =
((widget->type() == Tabs::Type()) || (widget->type() == ui::kSeparatorWidget && !label) ? 2 :
1);
dlg->currentGrid->addChildInCell(hbox,
hspan,
1,
ui::HORIZONTAL | (vexpand ? ui::VERTICAL : ui::TOP));
dlg->hbox = hbox;
}
@ -709,12 +745,7 @@ int Dialog_separator(lua_State* L)
dlg->dataWidgets[id] = widget;
}
dlg->mainWidgets.push_back(widget);
dlg->currentGrid->addChildInCell(widget, 2, 1, ui::HORIZONTAL | ui::TOP);
dlg->hbox = nullptr;
lua_pushvalue(L, 1);
return 1;
return Dialog_add_widget(L, widget);
}
int Dialog_label(lua_State* L)
@ -1512,6 +1543,10 @@ int Dialog_modify(lua_State* L)
type = lua_getfield(L, 2, "text");
if (const char* s = lua_tostring(L, -1)) {
widget->setText(s);
// Re-process mnemonics for buttons
if (widget->type() == WidgetType::kButtonWidget)
widget->processMnemonicFromText();
relayout = true;
}
lua_pop(L, 1);
@ -1629,6 +1664,10 @@ int Dialog_modify(lua_State* L)
lua_pop(L, 1);
type = lua_getfield(L, 2, "mouseCursor");
if (type == LUA_TNIL) {
lua_pop(L, 1);
type = lua_getfield(L, 2, "mousecursor");
}
if (type != LUA_TNIL) {
if (auto canvas = dynamic_cast<Canvas*>(widget)) {
auto cursor = (ui::CursorType)lua_tointeger(L, -1);
@ -1646,8 +1685,26 @@ int Dialog_modify(lua_State* L)
if (relayout && !dlg->window.isResizing()) {
dlg->window.layout();
gfx::Rect bounds(dlg->window.bounds().w, dlg->window.sizeHint().h);
dlg->window.expandWindow(bounds.size());
if (dlg->autofit > 0) {
gfx::Rect oldBounds = dlg->window.bounds();
gfx::Size resize(oldBounds.size());
if (dlg->autofit & ui::TOP || dlg->autofit & ui::BOTTOM)
resize.h = dlg->window.sizeHint().h;
if (dlg->autofit & ui::LEFT || dlg->autofit & ui::RIGHT)
resize.w = dlg->window.sizeHint().w;
gfx::Size difference = resize - oldBounds.size();
const auto& bounds = dlg->getWindowBounds();
gfx::Rect newBounds(bounds.x, bounds.y, resize.w, resize.h);
if (dlg->autofit & ui::BOTTOM)
newBounds.y = bounds.y - difference.h;
if (dlg->autofit & ui::RIGHT)
newBounds.x = bounds.x - difference.w;
dlg->setWindowBounds(newBounds);
}
}
}
lua_pushvalue(L, 1);
@ -1869,6 +1926,27 @@ int Dialog_get_bounds(lua_State* L)
return 1;
}
int Dialog_get_sizeHint(lua_State* L)
{
auto dlg = get_obj<Dialog>(L, 1);
push_new<gfx::Size>(L, dlg->window.sizeHint());
return 1;
}
int Dialog_get_autofit(lua_State* L)
{
auto dlg = get_obj<Dialog>(L, 1);
lua_pushinteger(L, dlg->autofit);
return 1;
}
int Dialog_set_autofit(lua_State* L)
{
auto dlg = get_obj<Dialog>(L, 1);
dlg->setAutofit(lua_tointeger(L, 2));
return 0;
}
int Dialog_set_bounds(lua_State* L)
{
auto dlg = get_obj<Dialog>(L, 1);
@ -1910,9 +1988,11 @@ const luaL_Reg Dialog_methods[] = {
};
const Property Dialog_properties[] = {
{ "data", Dialog_get_data, Dialog_set_data },
{ "bounds", Dialog_get_bounds, Dialog_set_bounds },
{ nullptr, nullptr, nullptr }
{ "data", Dialog_get_data, Dialog_set_data },
{ "bounds", Dialog_get_bounds, Dialog_set_bounds },
{ "autofit", Dialog_get_autofit, Dialog_set_autofit },
{ "sizeHint", Dialog_get_sizeHint, nullptr },
{ nullptr, nullptr, nullptr }
};
} // anonymous namespace

View File

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

View File

@ -370,6 +370,10 @@ public:
#if ENABLE_REMAP_TILESET_EVENT
RemapTileset,
#endif
LayerBlendMode,
LayerName,
LayerOpacity,
LayerVisibility,
};
SpriteEvents(const Sprite* sprite) : m_spriteId(sprite->id()) { doc()->add_observer(this); }
@ -401,6 +405,14 @@ public:
else if (std::strcmp(eventName, "remaptileset") == 0)
return RemapTileset;
#endif
else if (std::strcmp(eventName, "layerblendmode") == 0)
return LayerBlendMode;
else if (std::strcmp(eventName, "layername") == 0)
return LayerName;
else if (std::strcmp(eventName, "layeropacity") == 0)
return LayerOpacity;
else if (std::strcmp(eventName, "layervisibility") == 0)
return LayerVisibility;
else
return Unknown;
}
@ -460,6 +472,38 @@ public:
});
}
void onLayerBlendModeChange(DocEvent& ev) override
{
call(LayerBlendMode,
{
{ "layer", ev.layer() }
});
}
void onLayerNameChange(DocEvent& ev) override
{
call(LayerName,
{
{ "layer", ev.layer() }
});
}
void onLayerOpacityChange(DocEvent& ev) override
{
call(LayerOpacity,
{
{ "layer", ev.layer() }
});
}
void onAfterLayerVisibilityChange(DocEvent& ev) override
{
call(LayerVisibility,
{
{ "layer", ev.layer() }
});
}
private:
void onAddFirstListener(EventType eventType) override
{

View File

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

View File

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

View File

@ -40,6 +40,8 @@ int secure_io_lines(lua_State* L);
int secure_io_input(lua_State* L);
int secure_io_output(lua_State* L);
int secure_os_execute(lua_State* L);
int secure_os_remove(lua_State* L);
int secure_os_rename(lua_State* L);
int secure_package_loadlib(lua_State* L);
enum {
@ -49,6 +51,8 @@ enum {
io_input,
io_output,
os_execute,
os_remove,
os_rename,
package_loadlib,
};
@ -64,6 +68,8 @@ static struct {
{ "io", "input", secure_io_input },
{ "io", "output", secure_io_output },
{ "os", "execute", secure_os_execute },
{ "os", "remove", secure_os_remove },
{ "os", "rename", secure_os_rename },
{ "package", "loadlib", secure_package_loadlib },
};
@ -185,6 +191,81 @@ int secure_os_execute(lua_State* L)
return replaced_functions[os_execute].origfunc(L);
}
int file_result(lua_State* L, bool result, int errorNo = 0, const std::string& fileName = "")
{
if (result) {
lua_pushboolean(L, 1);
return 1;
}
luaL_pushfail(L);
if (fileName.empty())
lua_pushstring(L, strerror(errorNo));
else
lua_pushfstring(L, "%s: %s", fileName.c_str(), strerror(errorNo));
lua_pushinteger(L, errorNo);
return 3;
}
int secure_os_remove(lua_State* L)
{
const std::string absFilename = base::get_canonical_path(luaL_checkstring(L, 1));
if (absFilename.empty())
return file_result(L, false, ENOENT, absFilename);
if (!ask_access(L, absFilename.data(), FileAccessMode::Write, ResourceType::File))
return file_result(L, false, EACCES, absFilename);
if (base::is_directory(absFilename)) {
try {
base::remove_directory(absFilename);
return file_result(L, true);
}
catch (const std::exception&) {
return file_result(L, false, EIO, absFilename);
}
}
try {
base::delete_file(absFilename);
}
catch (const std::exception&) {
return file_result(L, false, EIO, absFilename);
}
return file_result(L, true);
}
int secure_os_rename(lua_State* L)
{
const std::string absSourceFilename = base::get_canonical_path(luaL_checkstring(L, 1));
const std::string absDestFilename = base::get_absolute_path(luaL_checkstring(L, 2));
lua_pop(L, 2);
if (absSourceFilename.empty())
return file_result(L, false, ENOENT, absSourceFilename);
if (absDestFilename.empty())
return file_result(L, false, EINVAL, absDestFilename);
if (!ask_access(L, absSourceFilename.data(), FileAccessMode::Write, ResourceType::File))
return file_result(L, false, EACCES, absSourceFilename);
try {
// If the destination file already exists, we should ask for permission to overwrite it.
if (!base::get_canonical_path(absDestFilename).empty() &&
!ask_access(L, absDestFilename.data(), FileAccessMode::Write, ResourceType::File)) {
return file_result(L, false, EACCES, absDestFilename);
}
base::move_file(absSourceFilename, absDestFilename);
return file_result(L, true);
}
catch (const std::exception&) {
return file_result(L, false, EIO, absSourceFilename);
}
}
int secure_package_loadlib(lua_State* L)
{
const char* cmd = luaL_checkstring(L, 1);
@ -201,7 +282,7 @@ void overwrite_unsecure_functions(lua_State* L)
{
// Remove unsupported functions
lua_getglobal(L, "os");
for (const char* name : { "remove", "rename", "exit", "tmpname" }) {
for (const char* name : { "exit", "tmpname" }) {
lua_pushcfunction(L, unsupported);
lua_setfield(L, -2, name);
}
@ -280,7 +361,15 @@ bool ask_access(lua_State* L,
{
std::string label;
switch (resourceType) {
case ResourceType::File: label = Strings::script_access_file_label(); break;
case ResourceType::File: {
if (mode == FileAccessMode::Write) {
label = Strings::script_access_file_write_label();
}
else {
label = Strings::script_access_file_label();
}
break;
}
case ResourceType::Command: label = Strings::script_access_command_label(); break;
case ResourceType::WebSocket: label = Strings::script_access_websocket_label(); break;
case ResourceType::Clipboard: label = Strings::script_access_clipboard_label(); break;

View File

@ -13,6 +13,8 @@
#include "app/console.h"
#include "app/context.h"
#include "app/context_observer.h"
#include "app/doc.h"
#include "app/doc_undo.h"
#include "app/script/docobj.h"
#include "app/script/engine.h"
#include "app/script/luacpp.h"

View File

@ -64,6 +64,7 @@
#include "doc/tag.h"
#include "doc/tileset.h"
#include "doc/tilesets.h"
#include "undo/undo_state.h"
#include <algorithm>
@ -456,7 +457,7 @@ int Sprite_newCel(lua_State* L)
auto sprite = get_docobj<Sprite>(L, 1);
auto layerBase = get_docobj<Layer>(L, 2);
if (!layerBase->isImage())
return luaL_error(L, "unexpected kinf of layer in Sprite:newCel()");
return luaL_error(L, "unexpected kind of layer in Sprite:newCel()");
frame_t frame = get_frame_number_from_arg(L, 3);
if (frame < 0 || frame > sprite->lastFrame())
@ -1029,6 +1030,42 @@ int Sprite_set_useLayerUuids(lua_State* L)
return 0;
}
int Sprite_get_undoHistory(lua_State* L)
{
const auto* sprite = get_docobj<Sprite>(L, 1);
const auto* doc = static_cast<Doc*>(sprite->document());
const auto* history = doc->undoHistory();
if (!history) {
lua_pushnil(L);
return 1;
}
const undo::UndoState* currentState = history->currentState();
const undo::UndoState* s = history->firstState();
const bool canRedo = history->canRedo();
bool pastCurrent = !currentState && canRedo;
int undoSteps = 0;
int redoSteps = 0;
while (s) {
if (pastCurrent && canRedo)
redoSteps++;
else if (currentState || !canRedo)
undoSteps++;
if (s == currentState || !currentState)
pastCurrent = true;
s = s->next();
}
lua_newtable(L);
setfield_integer(L, "undoSteps", undoSteps);
setfield_integer(L, "redoSteps", redoSteps);
return 1;
}
const luaL_Reg Sprite_methods[] = {
{ "__eq", Sprite_eq },
{ "resize", Sprite_resize },
@ -1094,6 +1131,7 @@ const Property Sprite_properties[] = {
{ "events", Sprite_get_events, nullptr },
{ "tileManagementPlugin", Sprite_get_tileManagementPlugin, Sprite_set_tileManagementPlugin },
{ "useLayerUuids", Sprite_get_useLayerUuids, Sprite_set_useLayerUuids },
{ "undoHistory", Sprite_get_undoHistory, nullptr },
{ nullptr, nullptr, nullptr }
};

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.

View File

@ -10,6 +10,7 @@
#include "config.h"
#endif
#include "app/color_spaces.h"
#include "app/util/conversion_to_surface.h"
#include "doc/blend_mode.h"
#include "doc/cel.h"
@ -21,7 +22,8 @@
namespace app { namespace thumb {
os::SurfaceRef get_cel_thumbnail(const doc::Cel* cel,
os::SurfaceRef get_cel_thumbnail(ui::Display* display,
const doc::Cel* cel,
const bool scaleUpToFit,
const gfx::Size& fitInSize)
{
@ -55,7 +57,8 @@ os::SurfaceRef get_cel_thumbnail(const doc::Cel* cel,
if (os::SurfaceRef thumbnail = os::System::instance()->makeRgbaSurface(
thumbnailImage->width(),
thumbnailImage->height())) {
thumbnailImage->height(),
get_current_color_space(display))) {
convert_image_to_surface(thumbnailImage.get(),
palette,
thumbnail.get(),

View File

@ -11,6 +11,7 @@
#include "gfx/size.h"
#include "os/surface.h"
#include "ui/display.h"
namespace doc {
class Cel;
@ -22,7 +23,8 @@ class Surface;
namespace app { namespace thumb {
os::SurfaceRef get_cel_thumbnail(const doc::Cel* cel,
os::SurfaceRef get_cel_thumbnail(ui::Display* display,
const doc::Cel* cel,
const bool scaleUpToFit,
const gfx::Size& fitInSize);

View File

@ -19,10 +19,10 @@
#include "app/ui/keyboard_shortcuts.h"
#include "app/ui_context.h"
#include "os/menus.h"
#include "ui/accelerator.h"
#include "ui/menu.h"
#include "ui/message.h"
#include "ui/scale.h"
#include "ui/shortcut.h"
#include "ui/size_hint_event.h"
#include "ui/widget.h"
@ -139,8 +139,8 @@ void AppMenuItem::onSizeHint(SizeHintEvent& ev)
border().width();
size.h = textHeight() + border().height();
if (m_key && !m_key->accels().empty()) {
size.w += font()->textLength(m_key->accels().front().toString());
if (m_key && !m_key->shortcuts().empty()) {
size.w += font()->textLength(m_key->shortcuts().front().toString());
}
}

View File

@ -431,8 +431,8 @@ void BrushPopup::regenerate(ui::Display* display, const gfx::Point& pos)
params.set("change", "custom");
params.set("slot", base::convert_to<std::string>(slot).c_str());
KeyPtr key = KeyboardShortcuts::instance()->command(CommandId::ChangeBrush(), params);
if (key && !key->accels().empty())
shortcut = key->accels().front().toString();
if (key && !key->shortcuts().empty())
shortcut = key->shortcuts().front().toString();
}
m_customBrushes->addItem(new SelectBrushItem(brush, slot));
m_customBrushes->addItem(new BrushShortcutItem(shortcut, slot));

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2016-2018 David Capello
//
// This program is distributed under the terms of
@ -107,11 +107,11 @@ public:
}
}
os::Surface* getCanvas(int w, int h, gfx::Color bgColor)
os::Surface* getCanvas(Display* display, int w, int h, gfx::Color bgColor)
{
assert_ui_thread();
auto activeCS = get_current_color_space();
auto activeCS = get_current_color_space(display);
if (!m_canvas || m_canvas->width() != w || m_canvas->height() != h ||
m_canvas->colorSpace() != activeCS) {
@ -432,9 +432,9 @@ void ColorSelector::onPaint(ui::PaintEvent& ev)
SkCanvas* canvas;
bool isSRGB;
// TODO compare both color spaces
if ((!get_current_color_space() || get_current_color_space()->isSRGB()) &&
(!g->getInternalSurface()->colorSpace() ||
g->getInternalSurface()->colorSpace()->isSRGB())) {
auto displayCs = get_current_color_space(display());
auto gCs = g->getInternalSurface()->colorSpace();
if ((!displayCs || displayCs->isSRGB()) && (!gCs || gCs->isSRGB())) {
// We can render directly in the ui::Graphics surface
canvas = &static_cast<os::SkiaSurface*>(g->getInternalSurface())->canvas();
isSRGB = true;
@ -442,7 +442,7 @@ void ColorSelector::onPaint(ui::PaintEvent& ev)
else {
// We'll paint in the ColorSelector::Painter canvas, and so we
// can convert color spaces.
painterSurface = painter.getCanvas(rc.w, rc.h, theme->colors.workspace());
painterSurface = painter.getCanvas(display(), rc.w, rc.h, theme->colors.workspace());
canvas = &static_cast<os::SkiaSurface*>(painterSurface)->canvas();
isSRGB = false;
}
@ -497,7 +497,7 @@ void ColorSelector::onPaint(ui::PaintEvent& ev)
else
#endif // SK_ENABLE_SKSL
{
painterSurface = painter.getCanvas(rc.w, rc.h, theme->colors.workspace());
painterSurface = painter.getCanvas(display(), rc.w, rc.h, theme->colors.workspace());
}
if (painterSurface)

View File

@ -58,9 +58,7 @@ public:
return;
}
// Color space conversion
auto convertColor = convert_from_current_to_screen_color_space();
Paint paint;
gfx::Color color = gfx::ColorNone;
int w = std::max(rc.w - 1, 1);
@ -110,7 +108,11 @@ public:
color = color_utils::color_for_ui(app::Color::fromGray(255 * x / w));
break;
}
g->drawVLine(convertColor(color), rc.x + x, rc.y, rc.h);
// Color space conversion
paint.color(color, get_current_color_space(slider->display()).get());
g->drawVLine(rc.x + x, rc.y, rc.h, paint);
}
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2020-2022 Igara Studio S.A.
// Copyright (C) 2020-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -11,6 +11,7 @@
#include "app/ui/color_wheel.h"
#include "app/color_spaces.h"
#include "app/color_utils.h"
#include "app/i18n/strings.h"
#include "app/pref/preferences.h"
@ -358,6 +359,9 @@ void ColorWheel::onPaintMainArea(ui::Graphics* g, const gfx::Rect& rc)
int n = getHarmonies();
int boxsize = std::min(rc.w / 10, rc.h / 10);
ui::Paint paint;
auto cs = get_current_color_space(g->display());
for (int i = 0; i < n; ++i) {
app::Color color = getColorInHarmony(i);
double angle = color.getHsvHue() - 30.0;
@ -374,9 +378,10 @@ void ColorWheel::onPaintMainArea(ui::Graphics* g, const gfx::Rect& rc)
paintColorIndicator(g, pos, color.getHsvValue() < 0.5);
g->fillRect(
gfx::rgba(color.getRed(), color.getGreen(), color.getBlue(), 255),
gfx::Rect(rc.x + rc.w - (n - i) * boxsize, rc.y + rc.h - boxsize, boxsize, boxsize));
paint.color(gfx::rgba(color.getRed(), color.getGreen(), color.getBlue(), 255), cs.get());
g->drawRect(
gfx::Rect(rc.x + rc.w - (n - i) * boxsize, rc.y + rc.h - boxsize, boxsize, boxsize),
paint);
}
}
}

View File

@ -1001,12 +1001,12 @@ public:
m_angle.setSuffix("°");
m_skew.setSuffix("°");
addChild(new Label("P:"));
addChild(new Label(Strings::context_bar_position_label()));
addChild(&m_x);
addChild(&m_y);
addChild(&m_w);
addChild(&m_h);
addChild(new Label("R:"));
addChild(new Label(Strings::context_bar_rotation_label()));
addChild(&m_angle);
addChild(&m_skew);
@ -1047,6 +1047,16 @@ public:
m_skew.Change.connect([this] { onChangeSkew(); });
}
void setupTooltips(TooltipManager* tooltipManager)
{
tooltipManager->addTooltipFor(&m_x, Strings::context_bar_position_x(), BOTTOM);
tooltipManager->addTooltipFor(&m_y, Strings::context_bar_position_y(), BOTTOM);
tooltipManager->addTooltipFor(&m_w, Strings::context_bar_size_width(), BOTTOM);
tooltipManager->addTooltipFor(&m_h, Strings::context_bar_size_height(), BOTTOM);
tooltipManager->addTooltipFor(&m_angle, Strings::context_bar_rotation_angle(), BOTTOM);
tooltipManager->addTooltipFor(&m_skew, Strings::context_bar_rotation_skew(), BOTTOM);
}
void update(const Transformation& t)
{
auto rc = t.bounds();
@ -1380,11 +1390,12 @@ public:
class ContextBar::DropPixelsField : public ButtonSet {
public:
DropPixelsField() : ButtonSet(2)
DropPixelsField() : ButtonSet(3)
{
auto* theme = SkinTheme::get(this);
addItem(theme->parts.dropPixelsOk(), theme->styles.contextBarButton());
addItem(theme->parts.dropPixelsDrop(), theme->styles.contextBarButton());
addItem(theme->parts.dropPixelsCancel(), theme->styles.contextBarButton());
setOfferCapture(false);
}
@ -1392,8 +1403,26 @@ public:
void setupTooltips(TooltipManager* tooltipManager)
{
// TODO Enter and Esc should be configurable keys
tooltipManager->addTooltipFor(at(0), Strings::context_bar_drop_pixel(), BOTTOM);
tooltipManager->addTooltipFor(at(1), Strings::context_bar_cancel_drag(), BOTTOM);
tooltipManager->addTooltipFor(
at(0),
key_tooltip(Strings::context_bar_drop_pixel_and_deselect().c_str(),
CommandId::DeselectMask(),
{},
KeyContext::Transformation),
BOTTOM);
tooltipManager->addTooltipFor(at(1),
key_tooltip(Strings::context_bar_drop_pixel().c_str(),
CommandId::Apply(),
{},
KeyContext::Transformation),
BOTTOM);
tooltipManager->addTooltipFor(at(2),
key_tooltip(Strings::context_bar_cancel_drag().c_str(),
CommandId::Undo(),
{},
KeyContext::Transformation),
BOTTOM);
}
obs::signal<void(ContextBarObserver::DropAction)> DropPixels;
@ -1404,8 +1433,9 @@ protected:
ButtonSet::onItemChange(item);
switch (selectedItem()) {
case 0: DropPixels(ContextBarObserver::DropPixels); break;
case 1: DropPixels(ContextBarObserver::CancelDrag); break;
case 0: DropPixels(ContextBarObserver::Deselect); break;
case 1: DropPixels(ContextBarObserver::DropPixels); break;
case 2: DropPixels(ContextBarObserver::CancelDrag); break;
}
}
};
@ -1857,7 +1887,7 @@ private:
class ContextBar::FontSelector : public FontEntry {
public:
FontSelector(ContextBar* contextBar)
FontSelector(ContextBar* contextBar) : FontEntry(true) // With stroke and fill options
{
// Load the font from the preferences
setInfo(FontInfo::getFromPreferences(), FontEntry::From::Init);
@ -2559,6 +2589,11 @@ FontInfo ContextBar::fontInfo() const
return m_fontSelector->info();
}
FontEntry* ContextBar::fontEntry()
{
return m_fontSelector;
}
render::DitheringMatrix ContextBar::ditheringMatrix()
{
return m_ditheringSelector->ditheringMatrix();
@ -2621,6 +2656,7 @@ void ContextBar::setupTooltips(TooltipManager* tooltipManager)
m_dropPixels->setupTooltips(tooltipManager);
m_symmetry->setupTooltips(tooltipManager);
m_sliceFields->setupTooltips(tooltipManager);
m_transformation->setupTooltips(tooltipManager);
}
void ContextBar::registerCommands()

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -90,6 +90,7 @@ public:
// For text tool
FontInfo fontInfo() const;
FontEntry* fontEntry();
// For gradients
render::DitheringMatrix ditheringMatrix();

View File

@ -1,4 +1,5 @@
// Aseprite
// Copyright (C) 2025 Igara Studio S.A.
// Copyright (C) 2001-2015 David Capello
//
// This program is distributed under the terms of
@ -12,7 +13,7 @@ namespace app {
class ContextBarObserver {
public:
enum DropAction { DropPixels, CancelDrag };
enum DropAction { Deselect, DropPixels, CancelDrag };
virtual ~ContextBarObserver() {}
virtual void onDropPixels(DropAction action) {}

View File

@ -180,6 +180,7 @@ DitheringSelector::DitheringSelector(Type type) : m_type(type)
m_extChanges = extensions.DitheringMatricesChange.connect([this] { regenerate(); });
setUseCustomWidget(true);
regenerate();
}
void DitheringSelector::onInitTheme(ui::InitThemeEvent& ev)
@ -189,16 +190,6 @@ void DitheringSelector::onInitTheme(ui::InitThemeEvent& ev)
setSizeHint(calcItemSizeHint(0));
}
void DitheringSelector::onVisible(bool visible)
{
if (visible && !m_initialized) {
// Only do the expensive regeneration when we're set as visible for the first time.
regenerate();
m_initialized = true;
}
ComboBox::onVisible(visible);
}
void DitheringSelector::setSelectedItemByName(const std::string& name)
{
int index = findItemIndex(name);

View File

@ -31,7 +31,6 @@ public:
protected:
void onInitTheme(ui::InitThemeEvent& ev) override;
void onVisible(bool visible) override;
private:
void regenerate(int selectedItemIndex = 0);
@ -39,7 +38,6 @@ private:
Type m_type;
obs::scoped_connection m_extChanges;
bool m_initialized = false;
};
} // namespace app

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2024 Igara Studio S.A.
// Copyright (C) 2018-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -36,15 +36,18 @@
#include "app/ui/workspace.h"
#include "app/ui_context.h"
#include "app/util/clipboard.h"
#include "app/util/slice_utils.h"
#include "base/fs.h"
#include "doc/color.h"
#include "doc/layer.h"
#include "doc/slice.h"
#include "doc/sprite.h"
#include "fmt/format.h"
#include "ui/accelerator.h"
#include "ui/alert.h"
#include "ui/display.h"
#include "ui/menu.h"
#include "ui/message.h"
#include "ui/shortcut.h"
#include "ui/system.h"
#include "ui/view.h"
@ -147,11 +150,11 @@ protected:
KeyPtr rmb = keys->action(KeyAction::RightMouseButton, KeyContext::Any);
// Convert action keys into mouse messages.
if (lmb->isPressed(msg, *keys) || rmb->isPressed(msg, *keys)) {
if (lmb->isPressed(msg) || rmb->isPressed(msg)) {
MouseMessage mouseMsg(
(msg->type() == kKeyDownMessage ? kMouseDownMessage : kMouseUpMessage),
PointerType::Unknown,
(lmb->isPressed(msg, *keys) ? kButtonLeft : kButtonRight),
(lmb->isPressed(msg) ? kButtonLeft : kButtonRight),
msg->modifiers(),
mousePosInDisplay());
@ -306,6 +309,11 @@ bool DocView::onCloseView(Workspace* workspace, bool quitting)
// See if the sprite has changes
while (m_document->isModified()) {
if (quitting) {
// Make sure the window is active so we can see the message when we close the app.
display()->nativeWindow()->activate();
}
// ask what want to do the user with the changes in the sprite
int ret = Alert::show(Strings::alerts_save_sprite_changes(
m_document->name(),
@ -510,6 +518,8 @@ bool DocView::onCanCopy(Context* ctx)
return true;
else if (m_editor->isMovingPixels())
return true;
else if (m_editor->hasSelectedSlices())
return true;
else
return false;
}
@ -528,6 +538,11 @@ bool DocView::onCanPaste(Context* ctx)
return true;
}
}
if (ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable) &&
ctx->clipboard()->format() == ClipboardFormat::Slices) {
return true;
}
return false;
}
@ -560,15 +575,22 @@ bool DocView::onCopy(Context* ctx)
ctx->clipboard()->copy(reader);
return true;
}
else
return false;
std::vector<Slice*> selectedSlices = get_selected_slices(reader.site());
if (!selectedSlices.empty()) {
ctx->clipboard()->copySlices(selectedSlices);
return true;
}
return false;
}
bool DocView::onPaste(Context* ctx, const gfx::Point* position)
{
auto clipboard = ctx->clipboard();
if (clipboard->format() == ClipboardFormat::Image ||
clipboard->format() == ClipboardFormat::Tilemap) {
clipboard->format() == ClipboardFormat::Tilemap ||
clipboard->format() == ClipboardFormat::Slices) {
clipboard->paste(ctx, true, position);
return true;
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -457,6 +457,8 @@ void BrushPreview::show(const gfx::Point& screenPos)
// Here we re-use the cached surface
if (!cached && m_uiLayer->surface()) {
m_uiLayer->surface()->clear();
gfx::Rect layerBounds = m_uiLayer->surface()->bounds();
ui::Graphics g(display, m_uiLayer->surface(), 0, 0);

Some files were not shown because too many files have changed in this diff Show More