Compare commits

..

59 Commits

Author SHA1 Message Date
David Capello fceed86a96
Merge 334739524a into 6d89a6bc15 2025-07-29 18:01:00 +00:00
David Capello 334739524a Restore the "Position" section in Timeline configuration
We're going to keep this section for a couple of versions as old users
get used to the new Workspace Layout customization.

The "Position" section will contain the layout icon so we can switch
the Workspace Layout combobox from there too,
2025-07-29 14:58:53 -03:00
David Capello 49da8e1ae1 Close layout combobox after clicking the reset/delete layout button
Without this, the combobox is still visible and clicking the button
again will crash the program.
2025-07-29 14:58:53 -03:00
David Capello 047e331b40 Remove base field from "New Layout" dialog 2025-07-29 14:58:53 -03:00
David Capello 0754a82023 Fix loading modified default layouts at the very beginning 2025-07-29 14:58:53 -03:00
David Capello 8f3b15276f Fix some strings
From https://github.com/aseprite/aseprite/pull/3485#pullrequestreview-2824195211
2025-07-29 14:58:53 -03:00
David Capello 763ebba6a2 Fix crashes resetting the default layout + remove some hacks
With this patch we've also simplified some hacks handling the
populateComboBox() call, deferring the deletion of widgets/items when
we are inside an event generated by those items.
2025-07-29 14:58:53 -03:00
David Capello dcbf444aaf Fix several layout saving/loading issues
* Better support to load legacy timeline information: we have to
  estimate workspace bounds)
* Auto-save layouts after resizing a splitter/dock
* Fix resetting expansive widgets inside docks after switching tabs
* Load mirrored layout correctly if it was the last selected layout
2025-07-29 14:58:53 -03:00
David Capello 47fc3b0391 Fix switch/break style 2025-07-29 14:58:53 -03:00
David Capello d0656ddd98 Move LayoutSelector::setActiveLayoutId() impl to .cpp file 2025-07-29 14:58:53 -03:00
David Capello 4dd20d2b85 Minor changes (format, remove unused/unnecessary vars/keywords) 2025-07-29 14:58:53 -03:00
David Capello cb6b512d25 Change notification flag location inside the layout selector widget
This improves the look of the flag as another icon above the tabs
bottom and at the right of the layout selector icon.
2025-07-29 14:58:53 -03:00
David Capello 5b57abd810 [theme] New layout selector icon + bg color fixes 2025-07-29 14:58:53 -03:00
David Capello f69e94c6e3 Improve layout selector widget location when menu bar is visible/hidden 2025-07-29 14:58:53 -03:00
David Capello f1759b069a Remove unused member variable from ui::Dock 2025-07-29 14:58:53 -03:00
Christian Kaiser 88e3c2a48c Polish layout handling
@dacap's notes: A description of the included changes:

* Improve UX auto-saving layouts when docks are modified, and new 'X'
  icon to delete layouts (or reset the 'Default' layout).
* Remove old timeline position controls (Left/Right/Bottom buttons)
  from the Timeline configuration and from the layout selector
* Add support to drag and drop docks to other sides with real-time
  feedback using a semi-transparent UILayer
* Add a context menu w/right-click to dock the widget at the supported
  sides without drag-and-drop

Some review comments in https://github.com/dacap/aseprite/pull/2
2025-07-29 14:58:53 -03:00
David Capello d9415fdf1b Show handles in dockable areas to drag-and-drop them 2025-07-29 14:58:53 -03:00
David Capello d0457cc1f4 Fix bug where the ToolBar popup wasn't being opened
This was a new issue with combination of
9b90159d1b, probably we should close the
popup when the window is resized (instead of onSizeHint()).
2025-07-29 14:58:53 -03:00
David Capello 84c35a63c2 Update saving/loading dock layouts w/tinyxml2 library 2025-07-29 14:58:52 -03:00
David Capello 00b0ba93d1 Save layout changes immediately when docks are resized
This patch includes:

* The layout is updated immediately when docks are resized (before
  this the layout was like a snapshot of the configuration when the
  layout was created or overwritten with the "New Layout" option)
* Saving the active layout used in
  preferences.general.workspace_layout so we can restore it after
  restarting Aseprite
* Change "UI Layout" to "Workspace Layout"
* Some strings moved to en.ini file for i18n
* Fixed a crash on MainWindow::onActiveViewChange() when the
  application was being closed
2025-07-29 14:58:52 -03:00
David Capello eebd7c2666 Improve borders of context & color bars for both sides (left/right) 2025-07-29 14:58:52 -03:00
David Capello e6c1fbf560 Add View > Workspace Layout option to switch the layout w/keys 2025-07-29 14:58:52 -03:00
David Capello 90fae86c9d Add possibility to overwrite existent layouts 2025-07-29 14:58:52 -03:00
David Capello ec41e9fe66 Include StatusBar in the set of customizable widgets in the layout 2025-07-29 14:58:52 -03:00
David Capello 6b0a1028fb Save/Load user defined layouts in new user.aseprite-layouts file
And now we store the TiXmlElement for each Layout, instead of
converting from/to text back and forth.
2025-07-29 14:58:52 -03:00
David Capello dc51ca25e0 Add option to save/restore user-defined layouts on memory
This happens only in memory at the moment (layouts are not saved in
disk yet), and the customization is quite simple (only size of
splitters, timeline position). But in the future we should be able to
dock elements in any place.
2025-07-29 14:58:52 -03:00
David Capello 381d9e663a Set the initial timeline position in the LayoutSelector correctly 2025-07-29 14:58:52 -03:00
David Capello e2fcbc86df Show the timeline when we set its position from the LayoutSelector 2025-07-29 14:58:52 -03:00
David Capello b0683f7914 Improve the layout selector UI
Changes:
* Now we use the "user data" icon as the button to expand the layouts
  combobox
* Added a tooltip to this icon
* Added buttons to configure the Timeline position in the same
  combobox
* Fixed some bugs in Dock using space for hidden widgets
2025-07-29 14:58:52 -03:00
David Capello 47c8e5eae3 Save/restore color bar splitter position correctly 2025-07-29 14:58:52 -03:00
David Capello bba8b00bd2 Save/restore timeline splitter position correctly 2025-07-29 14:58:52 -03:00
David Capello fc57df4305 Fix popups & tooltips direction when ToolBar is docked at the left side 2025-07-29 14:58:52 -03:00
David Capello 7ca6f53eb6 Fix std::clamp() max bound in TipWindow::pointAt() 2025-07-29 14:58:52 -03:00
David Capello 1f75d5444d Use std::unique_ptr in ToolBar members 2025-07-29 14:58:52 -03:00
David Capello 4858a5103e Fix memory leaks in MainWindow
Temporary/created subdocks must be deleted automatically, and children
that are not part of the window hierarchy must be deleted explicitly
now (using some std::unique_ptrs).
2025-07-29 14:58:52 -03:00
David Capello fa21d87ba8 Add Dock widget, initial & basic version of dockable elements (#518)
Some missing features so far:

1) Restore old layout configuration (color bar split pos, timeline
   pos, etc.) and migrate to new Dock layout
2) Load/saving Dock layout
3) Create & customize current layoout (drag-and-drop widgets, etc.)
2025-07-29 14:58:52 -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
41 changed files with 669 additions and 166 deletions

View File

@ -357,6 +357,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

@ -766,6 +766,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
@ -1858,6 +1859,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:
frame_header = Frame Header:

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">

2
laf

@ -1 +1 @@
Subproject commit 7c01959d7f0f2662a48eaa98e9b94336c4e45fda
Subproject commit 8ec4b553f1618f7a4b47cdcf4cfc2663266111ac

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}

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) 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

@ -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

@ -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>
@ -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

@ -1857,7 +1857,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);
@ -2565,6 +2565,11 @@ FontInfo ContextBar::fontInfo() const
return m_fontSelector->info();
}
FontEntry* ContextBar::fontEntry()
{
return m_fontSelector;
}
render::DitheringMatrix ContextBar::ditheringMatrix()
{
return m_ditheringSelector->ditheringMatrix();

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
@ -92,6 +92,7 @@ public:
// For text tool
FontInfo fontInfo() const;
FontEntry* fontEntry();
// For gradients
render::DitheringMatrix ditheringMatrix();

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);

View File

@ -58,6 +58,7 @@
#include "app/util/tile_flags_utils.h"
#include "base/chrono.h"
#include "base/convert_to.h"
#include "base/scoped_value.h"
#include "doc/doc.h"
#include "doc/mask_boundaries.h"
#include "doc/slice.h"
@ -266,6 +267,23 @@ void Editor::setStateInternal(const EditorStatePtr& newState)
{
m_brushPreview.hide();
// Some onLeaveState impls (like the ones from MovingPixelsState,
// WritingTextState, MovingSelectionState) might generate a
// Tx/Transaction::commit(), which will add a new undo state,
// triggering a sprite change scripting event
// (SpriteEvents::onAddUndoState). This event could be handled by an
// extension and that extension might want to save the current
// sprite (e.g. calling Sprite_saveCopyAs, the kind of extension
// that takes snapshots after each sprite change). That will be a
// new Context::executeCommand() for the save command, generating a
// BeforeCommandExecution signal, getting back to onLeaveState
// again. In that case, we just ignore the reentry as the first
// onLeaveState should handle everything (to avoid an stack
// overflow/infinite recursion).
if (m_leavingState)
return;
base::ScopedValue leaving(m_leavingState, true);
// Fire before change state event, set the state, and fire after
// change state event.
EditorState::LeaveAction leaveAction = m_state->onLeaveState(this, newState.get());

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
@ -458,6 +458,13 @@ private:
DocView* m_docView;
// Special flag to avoid re-entering a new state when we are leaving
// the current one. This avoids an infinite onLeaveState() recursion
// in some special cases when an extension (third-party code)
// creates a new sprite change in the same sprite change scripting
// event.
bool m_leavingState = false;
// Last known mouse position received by this editor when the
// mouse button was pressed. Used for auto-scrolling. To get the
// current mouse position on the editor you can use

View File

@ -428,8 +428,8 @@ bool MovingSliceState::onMouseMove(Editor* editor, MouseMessage* msg)
if (editor->slicesTransforms())
drawExtraCel();
// Redraw the editor.
editor->invalidate();
// Notify changes
m_site.document()->notifyGeneralUpdate();
// Use StandbyState implementation
return StandbyState::onMouseMove(editor, msg);

View File

@ -15,6 +15,7 @@
#include "app/commands/command.h"
#include "app/extra_cel.h"
#include "app/fonts/font_info.h"
#include "app/i18n/strings.h"
#include "app/pref/preferences.h"
#include "app/site.h"
#include "app/tx.h"
@ -43,12 +44,42 @@
#include "os/skia/skia_surface.h"
#endif
#include <cmath>
namespace app {
using namespace ui;
// Get ui::Paint to render text from context bar options / preferences
static ui::Paint get_paint_for_text()
{
ui::Paint paint;
if (auto* app = App::instance()) {
if (auto* ctxBar = app->contextBar())
paint = ctxBar->fontEntry()->paint();
}
paint.color(color_utils::color_for_ui(Preferences::instance().colorBar.fgColor()));
return paint;
}
static gfx::RectF calc_blob_bounds(const text::TextBlobRef& blob)
{
gfx::RectF bounds = get_text_blob_required_bounds(blob);
ui::Paint paint = get_paint_for_text();
if (paint.style() == ui::Paint::Style::Stroke ||
paint.style() == ui::Paint::Style::StrokeAndFill) {
bounds.enlarge(std::ceil(paint.strokeWidth()));
}
return bounds;
}
class WritingTextState::TextEditor : public Entry {
public:
enum TextPreview {
Intermediate, // With selection preview / user interface
Final, // Final to be rendered in the cel
};
TextEditor(Editor* editor, const Site& site, const gfx::Rect& bounds)
: Entry(4096, "")
, m_editor(editor)
@ -61,7 +92,7 @@ public:
setPersistSelection(true);
createExtraCel(site, bounds);
renderExtraCelBase();
renderExtraCel(TextPreview::Intermediate);
FontInfo fontInfo = App::instance()->contextBar()->fontInfo();
if (auto font = Fonts::instance()->fontFromInfo(fontInfo))
@ -76,36 +107,37 @@ public:
// Returns the extra cel with the text rendered (but without the
// selected text highlighted).
ExtraCelRef extraCel()
ExtraCelRef extraCel(const TextPreview textPreview)
{
renderExtraCelBase();
renderExtraCelText(false);
renderExtraCel(textPreview);
return m_extraCel;
}
void setExtraCelBounds(const gfx::Rect& bounds)
void setExtraCelBounds(const gfx::RectF& bounds)
{
doc::Image* extraImg = m_extraCel->image();
if (!extraImg || bounds.w != extraImg->width() || bounds.h != extraImg->height()) {
if (!extraImg || std::ceil(bounds.w) != extraImg->width() ||
std::ceil(bounds.h) != extraImg->height()) {
createExtraCel(m_editor->getSite(), bounds);
}
else {
m_baseBounds = bounds;
m_extraCel->cel()->setBounds(bounds);
}
renderExtraCelBase();
renderExtraCelText(true);
renderExtraCel(TextPreview::Intermediate);
}
obs::signal<void(const gfx::Size&)> NewRequiredBounds;
obs::signal<void(const gfx::RectF&)> NewRequiredBounds;
private:
void createExtraCel(const Site& site, const gfx::Rect& bounds)
{
m_baseBounds = bounds;
m_extraCel->create(ExtraCel::Purpose::TextPreview,
site.tilemapMode(),
site.sprite(),
bounds,
bounds.size(),
gfx::Size(std::ceil(bounds.w), std::ceil(bounds.h)),
site.frame(),
255);
@ -176,7 +208,7 @@ private:
// Notify that we could make the text editor bigger to show this
// text blob.
NewRequiredBounds(get_text_blob_required_size(blob));
NewRequiredBounds(calc_blob_bounds(blob));
}
void onPaint(PaintEvent& ev) override
@ -205,8 +237,7 @@ private:
}
// Render extra cel with text + selected text
renderExtraCelBase();
renderExtraCelText(true);
renderExtraCel(TextPreview::Intermediate);
m_doc->setExtraCel(m_extraCel);
// Paint caret
@ -227,76 +258,80 @@ private:
}
}
void renderExtraCelBase()
void renderExtraCel(const TextPreview textPreview)
{
doc::Image* extraImg = m_extraCel->image();
ASSERT(extraImg);
if (!extraImg)
return;
const doc::Cel* extraCel = m_extraCel->cel();
extraImg->clear(extraImg->maskColor());
text::TextBlobRef blob = textBlob();
doc::ImageRef blobImage;
gfx::RectF bounds;
if (blob) {
const ui::Paint paint = get_paint_for_text();
bounds = calc_blob_bounds(blob);
blobImage = render_text_blob(blob, bounds, get_paint_for_text());
if (!blobImage)
return;
// Invert selected range in the image
if (textPreview == TextPreview::Intermediate) {
Range range;
getEntryThemeInfo(nullptr, nullptr, nullptr, &range);
if (!range.isEmpty()) {
gfx::RectF selectedBounds = getCharBoxBounds(range.from) | getCharBoxBounds(range.to - 1);
if (!selectedBounds.isEmpty()) {
selectedBounds.offset(-bounds.origin());
#ifdef LAF_SKIA
sk_sp<SkSurface> skSurface = wrap_docimage_in_sksurface(blobImage.get());
os::SurfaceRef surface = base::make_ref<os::SkiaSurface>(skSurface);
os::Paint paint2 = paint;
paint2.blendMode(os::BlendMode::Xor);
paint2.style(os::Paint::Style::Fill);
surface->drawRect(selectedBounds, paint2);
#endif // LAF_SKIA
}
}
}
}
doc::Cel* extraCel = m_extraCel->cel();
ASSERT(extraCel);
if (!extraCel)
return;
extraImg->clear(extraImg->maskColor());
extraCel->setPosition(m_baseBounds.x + bounds.x, m_baseBounds.y + bounds.y);
render::Render().renderLayer(extraImg,
m_editor->layer(),
m_editor->frame(),
gfx::Clip(0, 0, extraCel->bounds()),
doc::BlendMode::SRC);
}
void renderExtraCelText(const bool withSelection)
{
const auto textColor = color_utils::color_for_image(Preferences::instance().colorBar.fgColor(),
IMAGE_RGB);
text::TextBlobRef blob = textBlob();
if (!blob)
return;
doc::ImageRef image = render_text_blob(blob, textColor);
if (!image)
return;
// Invert selected range in the image
if (withSelection) {
Range range;
getEntryThemeInfo(nullptr, nullptr, nullptr, &range);
if (!range.isEmpty()) {
gfx::RectF selectedBounds = getCharBoxBounds(range.from) | getCharBoxBounds(range.to - 1);
if (!selectedBounds.isEmpty()) {
#ifdef LAF_SKIA
sk_sp<SkSurface> skSurface = wrap_docimage_in_sksurface(image.get());
os::SurfaceRef surface = base::make_ref<os::SkiaSurface>(skSurface);
os::Paint paint;
paint.blendMode(os::BlendMode::Xor);
paint.color(textColor);
surface->drawRect(selectedBounds, paint);
#endif // LAF_SKIA
}
}
if (blobImage) {
doc::blend_image(extraImg,
blobImage.get(),
gfx::Clip(blobImage->bounds().size()),
m_doc->sprite()->palette(m_editor->frame()),
255,
doc::BlendMode::NORMAL);
}
doc::Image* extraImg = m_extraCel->image();
ASSERT(extraImg);
if (!extraImg)
return;
doc::blend_image(extraImg,
image.get(),
gfx::Clip(image->bounds().size()),
m_doc->sprite()->palette(m_editor->frame()),
255,
doc::BlendMode::NORMAL);
}
Editor* m_editor;
Doc* m_doc;
ExtraCelRef m_extraCel;
// Initial bounds for the entry field. This can be modified later to
// render the text in case some initial letter/glyph needs some
// extra room at the left side.
gfx::Rect m_baseBounds;
};
WritingTextState::WritingTextState(Editor* editor, const gfx::Rect& bounds)
@ -312,10 +347,10 @@ WritingTextState::WritingTextState(Editor* editor, const gfx::Rect& bounds)
m_fontChangeConn =
App::instance()->contextBar()->FontChange.connect(&WritingTextState::onFontChange, this);
m_entry->NewRequiredBounds.connect([this](const gfx::Size& blobSize) {
if (m_bounds.w < blobSize.w || m_bounds.h < blobSize.h) {
m_bounds.w = std::max(m_bounds.w, blobSize.w);
m_bounds.h = std::max(m_bounds.h, blobSize.h);
m_entry->NewRequiredBounds.connect([this](const gfx::RectF& blobBounds) {
if (m_bounds.w < blobBounds.w || m_bounds.h < blobBounds.h) {
m_bounds.w = std::max(m_bounds.w, blobBounds.w);
m_bounds.h = std::max(m_bounds.h, blobBounds.h);
m_entry->setExtraCelBounds(m_bounds);
m_entry->setBounds(calcEntryBounds());
}
@ -388,11 +423,11 @@ void WritingTextState::onCommitMouseMove(Editor* editor, const gfx::PointF& spri
if (!m_movingBounds)
return;
gfx::Point delta(spritePos - m_cursorStart);
gfx::PointF delta(spritePos - m_cursorStart);
if (delta.x == 0 && delta.y == 0)
return;
m_bounds.setOrigin(gfx::Point(delta + m_boundsOrigin));
m_bounds.setOrigin(delta + m_boundsOrigin);
m_entry->setExtraCelBounds(m_bounds);
m_entry->setBounds(calcEntryBounds());
}
@ -408,12 +443,7 @@ bool WritingTextState::onSetCursor(Editor* editor, const gfx::Point& mouseScreen
return true;
}
bool WritingTextState::onKeyDown(Editor*, KeyMessage*)
{
return false;
}
bool WritingTextState::onKeyUp(Editor*, KeyMessage* msg)
bool WritingTextState::onKeyDown(Editor*, KeyMessage* msg)
{
// Cancel loop pressing Esc key
if (msg->scancode() == ui::kKeyEsc) {
@ -422,7 +452,17 @@ bool WritingTextState::onKeyUp(Editor*, KeyMessage* msg)
// Drop text pressing Enter key
else if (msg->scancode() == ui::kKeyEnter) {
drop();
return true;
}
return false;
}
bool WritingTextState::onKeyUp(Editor*, KeyMessage* msg)
{
// Note: We cannot process kKeyEnter key here to drop the text as it
// could be received after the Enter key is pressed in the IME
// dialog to accept the composition (not to accept the text). So we
// process kKeyEnter in onKeyDown().
return true;
}
@ -475,8 +515,8 @@ EditorState::LeaveAction WritingTextState::onLeaveState(Editor* editor, EditorSt
// Paints the text in the active layer/sprite creating an
// undoable transaction.
Site site = m_editor->getSite();
ExtraCelRef extraCel = m_entry->extraCel();
Tx tx(site.document(), "Text Tool");
ExtraCelRef extraCel = m_entry->extraCel(TextEditor::Final);
Tx tx(site.document(), Strings::tools_text());
ExpandCelCanvas expand(site, site.layer(), TiledMode::NONE, tx, ExpandCelCanvas::None);
expand.validateDestCanvas(gfx::Region(extraCel->cel()->bounds()));
@ -527,7 +567,7 @@ void WritingTextState::onFontChange(const FontInfo& fontInfo, FontEntry::From fr
// This is useful to show changes to the anti-alias option
// immediately.
auto dummy = m_entry->extraCel();
auto dummy = m_entry->extraCel(TextEditor::Intermediate);
if (fromField == FontEntry::From::Popup) {
if (m_entry)

View File

@ -1,5 +1,5 @@
// 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.
@ -59,7 +59,7 @@ private:
DelayedMouseMove m_delayedMouseMove;
Editor* m_editor;
gfx::Rect m_bounds;
gfx::RectF m_bounds;
std::unique_ptr<TextEditor> m_entry;
// True if the text was discarded.
@ -71,7 +71,7 @@ private:
bool m_mouseMoveReceived = false;
bool m_movingBounds = false;
gfx::PointF m_cursorStart;
gfx::Point m_boundsOrigin;
gfx::PointF m_boundsOrigin;
obs::scoped_connection m_beforeCmdConn;
obs::scoped_connection m_fontChangeConn;

View File

@ -45,6 +45,7 @@ FileList::FileList()
, m_multiselect(false)
, m_zoom(1.0)
, m_itemsPerRow(0)
, m_showHidden(Preferences::instance().fileSelector.showHidden())
{
setFocusStop(true);
setDoubleBuffered(true);
@ -173,6 +174,14 @@ void FileList::animateToZoom(const double zoom)
startAnimation(ANI_ZOOM, 10);
}
void FileList::setShowHidden(const bool show)
{
m_showHidden = show;
m_req_valid = false;
m_selected = nullptr;
regenerateList();
}
bool FileList::onProcessMessage(Message* msg)
{
switch (msg->type()) {
@ -825,7 +834,7 @@ void FileList::regenerateList()
for (FileItemList::iterator it = m_list.begin(); it != m_list.end();) {
IFileItem* fileitem = *it;
if (fileitem->isHidden())
if (fileitem->isHidden() && !m_showHidden)
it = m_list.erase(it);
else if (!fileitem->isFolder() && !fileitem->hasExtension(m_exts)) {
it = m_list.erase(it);

View File

@ -54,6 +54,7 @@ public:
double zoom() const { return m_zoom; }
void setZoom(const double zoom);
void animateToZoom(const double zoom);
void setShowHidden(const bool show);
obs::signal<void()> FileSelected;
obs::signal<void()> FileAccepted;
@ -137,6 +138,7 @@ private:
double m_toZoom;
int m_itemsPerRow;
bool m_showHidden;
};
} // namespace app

View File

@ -316,6 +316,7 @@ FileSelector::FileSelector(FileSelectorType type) : m_type(type), m_navigationLo
for (auto child : viewType()->children())
child->setFocusStop(false);
showHiddenCheck()->setSelected(Preferences::instance().fileSelector.showHidden());
m_fileList = new FileList();
m_fileList->setId("fileview");
m_fileName->setAssociatedFileList(m_fileList);
@ -334,6 +335,10 @@ FileSelector::FileSelector(FileSelectorType type) : m_type(type), m_navigationLo
viewType()->ItemChange.connect([this] { onChangeViewType(); });
location()->CloseListBox.connect([this] { onLocationCloseListBox(); });
fileType()->Change.connect([this] { onFileTypeChange(); });
showHiddenCheck()->Click.connect([this] {
Preferences::instance().fileSelector.showHidden(showHiddenCheck()->isSelected());
m_fileList->setShowHidden(showHiddenCheck()->isSelected());
});
m_fileList->FileSelected.connect([this] { onFileListFileSelected(); });
m_fileList->FileAccepted.connect([this] { onFileListFileAccepted(); });
m_fileList->CurrentFolderChanged.connect([this] { onFileListCurrentFolderChanged(); });

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2022 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.
@ -10,8 +10,10 @@
#include "app/ui/filename_field.h"
#include "app/app.h"
#include "app/i18n/strings.h"
#include "app/pref/preferences.h"
#include "app/recent_files.h"
#include "app/ui/skin/skin_theme.h"
#include "base/fs.h"
#include "ui/box.h"
@ -24,6 +26,11 @@ namespace app {
using namespace ui;
FilenameField::FilenameButton::FilenameButton(const std::string& text) : ButtonSet(1)
{
addItem(text);
}
FilenameField::FilenameField(const Type type, const std::string& pathAndFilename)
: m_entry(type == EntryAndButton ? new ui::Entry(1024, "") : nullptr)
, m_button(type == EntryAndButton ? Strings::select_file_browse() : Strings::select_file_text())
@ -46,7 +53,10 @@ FilenameField::FilenameField(const Type type, const std::string& pathAndFilename
if (m_entry)
m_entry->Change.connect([this] { setFilename(updatedFilename()); });
m_button.Click.connect([this] { onBrowse(); });
m_button.ItemChange.connect([this](ButtonSet::Item* item) {
m_button.setSelectedItem(nullptr);
onBrowse();
});
initTheme();
m_editFullPathChangeConn = Preferences::instance().general.editFullPath.AfterChange.connect(
@ -94,7 +104,6 @@ void FilenameField::onSetEditFullPath()
void FilenameField::onBrowse()
{
const gfx::Rect bounds = m_button.bounds();
m_button.setSelected(false);
ui::Menu menu;
ui::MenuItem choose(Strings::select_file_choose());
@ -107,6 +116,11 @@ void FilenameField::onBrowse()
menu.addChild(&relative);
menu.addChild(&absolute);
if (auto* recent = App::instance()->recentFiles()) {
addFoldersToMenu(&menu, recent->pinnedFolders(), Strings::file_selector_pinned_folders());
addFoldersToMenu(&menu, recent->recentFolders(), Strings::file_selector_recent_folders());
}
choose.Click.connect([this] {
std::string fn = SelectOutputFile();
if (!fn.empty()) {
@ -120,6 +134,21 @@ void FilenameField::onBrowse()
menu.showPopup(gfx::Point(bounds.x, bounds.y2()), display());
}
void FilenameField::addFoldersToMenu(ui::Menu* menu,
const base::paths& folders,
const std::string& separatorTitle)
{
if (folders.empty())
return;
menu->addChild(new ui::Separator(separatorTitle, ui::HORIZONTAL));
for (const std::string& folder : folders) {
MenuItem* folderItem = new MenuItem(folder);
folderItem->Click.connect([this, folder] { setFilename(base::join_path(folder, m_file)); });
menu->addChild(folderItem);
}
}
void FilenameField::setFilename(const std::string& pathAndFilename)
{
const std::string spritePath = base::get_file_path(m_docFilename);
@ -164,11 +193,6 @@ void FilenameField::onInitTheme(ui::InitThemeEvent& ev)
{
HBox::onInitTheme(ev);
setChildSpacing(0);
auto theme = skin::SkinTheme::get(this);
ui::Style* style = theme->styles.miniButton();
if (style)
m_button.setStyle(style);
}
void FilenameField::onUpdateText()
@ -181,9 +205,9 @@ void FilenameField::updateWidgets()
if (m_entry)
m_entry->setText(displayedFilename());
else if (m_file.empty())
m_button.setText(Strings::select_file_text());
m_button.getItem(0)->setText(Strings::select_file_text());
else
m_button.setText(displayedFilename());
m_button.getItem(0)->setText(displayedFilename());
Change();
}

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 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.
@ -8,14 +8,19 @@
#define APP_UI_FILENAME_FIELD_H_INCLUDED
#pragma once
#include "app/ui/button_set.h"
#include "base/paths.h"
#include "obs/connection.h"
#include "obs/signal.h"
#include "ui/box.h"
#include "ui/button.h"
#include "ui/entry.h"
#include <string>
namespace ui {
class Menu;
}
namespace app {
class FilenameField : public ui::HBox {
@ -44,17 +49,25 @@ protected:
void onSetEditFullPath();
private:
class FilenameButton : public ButtonSet {
public:
FilenameButton(const std::string& text);
};
void setEditFullPath(const bool on);
void updateWidgets();
void onBrowse();
std::string updatedFilename() const;
void addFoldersToMenu(ui::Menu* menu,
const base::paths& folders,
const std::string& separatorTitle);
std::string m_path;
std::string m_pathBase;
std::string m_file;
std::string m_docFilename;
ui::Entry* m_entry;
ui::Button m_button;
FilenameButton m_button;
bool m_editFullPath;
bool m_askOverwrite;

View File

@ -12,10 +12,12 @@
#include "app/app.h"
#include "app/console.h"
#include "app/i18n/strings.h"
#include "app/recent_files.h"
#include "app/ui/font_popup.h"
#include "app/ui/skin/skin_theme.h"
#include "base/contains.h"
#include "base/convert_to.h"
#include "base/scoped_value.h"
#include "fmt/format.h"
#include "ui/display.h"
@ -262,22 +264,91 @@ void FontEntry::FontSize::onEntryChange()
Change();
}
FontEntry::FontStyle::FontStyle() : ButtonSet(3, true)
FontEntry::FontStyle::FontStyle(ui::TooltipManager* tooltips) : ButtonSet(3, true)
{
addItem("B");
addItem("I");
addItem("...");
setMultiMode(MultiMode::Set);
tooltips->addTooltipFor(getItem(0), Strings::text_tool_bold(), BOTTOM);
tooltips->addTooltipFor(getItem(1), Strings::text_tool_italic(), BOTTOM);
tooltips->addTooltipFor(getItem(2), Strings::text_tool_more_options(), BOTTOM);
}
FontEntry::FontEntry()
FontEntry::FontStroke::FontStroke(ui::TooltipManager* tooltips) : m_fill(2)
{
auto* theme = skin::SkinTheme::get(this);
m_fill.addItem(theme->parts.toolFilledRectangle(), theme->styles.contextBarButton());
m_fill.addItem(theme->parts.toolRectangle(), theme->styles.contextBarButton());
m_fill.setSelectedItem(0);
m_fill.ItemChange.connect([this] { Change(); });
m_stroke.setText("0");
m_stroke.setSuffix("pt");
m_stroke.ValueChange.connect([this] { Change(); });
addChild(&m_fill);
addChild(&m_stroke);
tooltips->addTooltipFor(m_fill.getItem(0), Strings::shape_fill(), BOTTOM);
tooltips->addTooltipFor(m_fill.getItem(1), Strings::shape_stroke(), BOTTOM);
tooltips->addTooltipFor(&m_stroke, Strings::shape_stroke_width(), BOTTOM);
}
bool FontEntry::FontStroke::fill() const
{
return const_cast<FontStroke*>(this)->m_fill.getItem(0)->isSelected();
}
float FontEntry::FontStroke::stroke() const
{
return m_stroke.textDouble();
}
FontEntry::FontStroke::WidthEntry::WidthEntry() : ui::IntEntry(0, 100, this)
{
}
void FontEntry::FontStroke::WidthEntry::onValueChange()
{
ui::IntEntry::onValueChange();
ValueChange();
}
bool FontEntry::FontStroke::WidthEntry::onAcceptUnicodeChar(int unicodeChar)
{
return (IntEntry::onAcceptUnicodeChar(unicodeChar) || unicodeChar == '.');
}
std::string FontEntry::FontStroke::WidthEntry::onGetTextFromValue(int value)
{
return fmt::format("{:.1f}", value / 10.0);
}
int FontEntry::FontStroke::WidthEntry::onGetValueFromText(const std::string& text)
{
return int(10.0 * base::convert_to<double>(text));
}
FontEntry::FontEntry(const bool withStrokeAndFill)
: m_style(&m_tooltips)
, m_stroke(withStrokeAndFill ? std::make_unique<FontStroke>(&m_tooltips) : nullptr)
{
m_face.setExpansive(true);
m_size.setExpansive(false);
m_style.setExpansive(false);
addChild(&m_tooltips);
addChild(&m_face);
addChild(&m_size);
addChild(&m_style);
if (m_stroke)
addChild(m_stroke.get());
m_tooltips.addTooltipFor(&m_face, Strings::text_tool_font_family(), BOTTOM);
m_tooltips.addTooltipFor(m_size.getEntryWidget(), Strings::text_tool_font_size(), BOTTOM);
m_face.setMinSize(gfx::Size(128 * guiscale(), 0));
@ -299,6 +370,8 @@ FontEntry::FontEntry()
});
m_style.ItemChange.connect(&FontEntry::onStyleItemClick, this);
if (m_stroke)
m_stroke->Change.connect(&FontEntry::onStrokeChange, this);
}
// Defined here as FontPopup type is not fully defined in the header
@ -327,6 +400,29 @@ void FontEntry::setInfo(const FontInfo& info, const From fromField)
FontChange(m_info, fromField);
}
ui::Paint FontEntry::paint()
{
ui::Paint paint;
ui::Paint::Style style = ui::Paint::Fill;
if (m_stroke) {
const float stroke = m_stroke->stroke();
if (m_stroke->fill()) {
if (stroke > 0.0f) {
style = ui::Paint::StrokeAndFill;
paint.strokeWidth(stroke);
}
}
else {
style = ui::Paint::Stroke;
paint.strokeWidth(stroke);
}
}
paint.style(style);
return paint;
}
void FontEntry::onStyleItemClick(ButtonSet::Item* item)
{
text::FontStyle style = m_info.style();
@ -404,4 +500,9 @@ void FontEntry::onStyleItemClick(ButtonSet::Item* item)
}
}
void FontEntry::onStrokeChange()
{
FontChange(m_info, From::Paint);
}
} // namespace app

View File

@ -14,7 +14,11 @@
#include "ui/box.h"
#include "ui/button.h"
#include "ui/combobox.h"
#include "ui/int_entry.h"
#include "ui/paint.h"
#include "ui/tooltips.h"
#include <memory>
#include <string>
namespace app {
@ -30,18 +34,22 @@ public:
Flags,
Hinting,
Popup,
Paint,
};
FontEntry();
FontEntry(bool withStrokeAndFill);
~FontEntry();
FontInfo info() { return m_info; }
void setInfo(const FontInfo& info, From from);
ui::Paint paint();
obs::signal<void(const FontInfo&, From)> FontChange;
private:
void onStyleItemClick(ButtonSet::Item* item);
void onStrokeChange();
class FontFace : public SearchEntry {
public:
@ -73,13 +81,40 @@ private:
class FontStyle : public ButtonSet {
public:
FontStyle();
FontStyle(ui::TooltipManager* tooltips);
};
class FontStroke : public HBox {
public:
FontStroke(ui::TooltipManager* tooltips);
bool fill() const;
float stroke() const;
obs::signal<void()> Change;
private:
class WidthEntry : public ui::IntEntry,
public ui::SliderDelegate {
public:
WidthEntry();
obs::signal<void()> ValueChange;
private:
void onValueChange() override;
bool onAcceptUnicodeChar(int unicodeChar) override;
// SliderDelegate impl
std::string onGetTextFromValue(int value) override;
int onGetValueFromText(const std::string& text) override;
};
ButtonSet m_fill;
WidthEntry m_stroke;
};
ui::TooltipManager m_tooltips;
FontInfo m_info;
FontFace m_face;
FontSize m_size;
FontStyle m_style;
std::unique_ptr<FontStroke> m_stroke;
bool m_lockFace = false;
};

View File

@ -194,7 +194,11 @@ private:
if (!blob)
return;
doc::ImageRef image = render_text_blob(blob, gfx::rgba(0, 0, 0));
ui::Paint paint;
paint.color(gfx::rgba(0, 0, 0));
paint.style(ui::Paint::Fill);
const gfx::RectF textBounds = get_text_blob_required_bounds(blob);
doc::ImageRef image = render_text_blob(blob, textBounds, paint);
if (!image)
return;

View File

@ -1483,6 +1483,8 @@ void SkinTheme::drawEntryText(ui::Graphics* g, ui::Entry* widget)
// Draw caret at the end of the text
if (!delegate.caretDrawn()) {
bounds.x += delegate.textBounds().w;
gfx::Rect charBounds(bounds.x + widget->bounds().x,
bounds.y + widget->bounds().y,
0,

View File

@ -60,7 +60,7 @@ private:
} // anonymous namespace
gfx::Size get_text_blob_required_size(const text::TextBlobRef& blob)
gfx::RectF get_text_blob_required_bounds(const text::TextBlobRef& blob)
{
ASSERT(blob != nullptr);
@ -74,32 +74,28 @@ gfx::Size get_text_blob_required_size(const text::TextBlobRef& blob)
bounds.w = 1;
if (bounds.h < 1)
bounds.h = 1;
return gfx::Size(std::ceil(bounds.w), std::ceil(bounds.h));
return bounds;
}
doc::ImageRef render_text_blob(const text::TextBlobRef& blob, gfx::Color color)
doc::ImageRef render_text_blob(const text::TextBlobRef& blob,
const gfx::RectF& textBounds,
const ui::Paint& paint)
{
ASSERT(blob != nullptr);
os::Paint paint;
// TODO offer Stroke, StrokeAndFill, and Fill styles
paint.style(os::Paint::Fill);
paint.color(color);
gfx::Size blobSize = get_text_blob_required_size(blob);
doc::ImageRef image(doc::Image::create(doc::IMAGE_RGB, blobSize.w, blobSize.h));
doc::ImageRef image(
doc::Image::create(doc::IMAGE_RGB, std::ceil(textBounds.w), std::ceil(textBounds.h)));
#ifdef LAF_SKIA
sk_sp<SkSurface> skSurface = wrap_docimage_in_sksurface(image.get());
os::SurfaceRef surface = base::make_ref<os::SkiaSurface>(skSurface);
text::draw_text(surface.get(), blob, gfx::PointF(0, 0), &paint);
text::draw_text(surface.get(), blob, -textBounds.origin(), &paint);
#endif // LAF_SKIA
return image;
}
doc::ImageRef render_text(const FontInfo& fontInfo, const std::string& text, gfx::Color color)
doc::ImageRef render_text(const FontInfo& fontInfo, const std::string& text, const ui::Paint& paint)
{
Fonts* fonts = Fonts::instance();
ASSERT(fonts);
@ -113,10 +109,6 @@ doc::ImageRef render_text(const FontInfo& fontInfo, const std::string& text, gfx
const text::FontMgrRef fontMgr = fonts->fontMgr();
ASSERT(fontMgr);
os::Paint paint;
paint.style(os::Paint::StrokeAndFill);
paint.color(color);
// We have to measure all text runs which might use different
// fonts (e.g. if the given font is not enough to shape other code
// points/languages).

View File

@ -11,25 +11,28 @@
#include "doc/image_ref.h"
#include "gfx/color.h"
#include "gfx/rect.h"
#include "text/text_blob.h"
#include "ui/paint.h"
#include <string>
namespace app {
class Color;
class FontInfo;
namespace skin {
class SkinTheme;
}
// Returns the exact bounds that are required to draw this TextBlob,
// i.e. the image size that will be required in render_text_blob().
gfx::Size get_text_blob_required_size(const text::TextBlobRef& blob);
// Returns the exact bounds that are required to draw this TextBlob in
// the origin point (0, 0), i.e. the image size that will be required
// in render_text_blob().
gfx::RectF get_text_blob_required_bounds(const text::TextBlobRef& blob);
doc::ImageRef render_text_blob(const text::TextBlobRef& blob, gfx::Color color);
doc::ImageRef render_text_blob(const text::TextBlobRef& blob,
const gfx::RectF& textBounds,
const ui::Paint& paint);
doc::ImageRef render_text(const FontInfo& fontInfo, const std::string& text, gfx::Color color);
doc::ImageRef render_text(const FontInfo& fontInfo,
const std::string& text,
const ui::Paint& paint);
} // namespace app

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
@ -257,11 +257,9 @@ Widget* WidgetLoader::convertXmlElementToWidget(const XMLElement* elem,
((ExprEntry*)widget)->setDecimals(strtol(decimals, nullptr, 10));
}
if (elem_name == "filename") {
const char* button_only = elem->Attribute("button_only");
const app::FilenameField::Type type = ((button_only != nullptr &&
strtol(button_only, nullptr, 10) == 1) ?
app::FilenameField::Type::ButtonOnly :
app::FilenameField::Type::EntryAndButton);
const bool buttononly = bool_attr(elem, "buttononly", false);
const app::FilenameField::Type type = (buttononly ? app::FilenameField::Type::ButtonOnly :
app::FilenameField::Type::EntryAndButton);
widget = new app::FilenameField(type, "");
}
@ -534,7 +532,7 @@ Widget* WidgetLoader::convertXmlElementToWidget(const XMLElement* elem,
}
else if (elem_name == "font") {
if (!widget)
widget = new FontEntry;
widget = new FontEntry(false);
}
// Was the widget created?

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
@ -20,11 +20,12 @@
#include "doc/primitives.h"
#include <algorithm>
#include <atomic>
#include <cmath>
namespace doc {
static int generation = 0;
static std::atomic<int> g_generation = 0;
Brush::Brush()
{
@ -300,7 +301,7 @@ void Brush::setCenter(const gfx::Point& center)
// Cleans the brush's data (image and region).
void Brush::clean()
{
m_gen = ++generation;
m_gen = ++g_generation;
m_image.reset();
m_maskBitmap.reset();
m_backupImage.reset();

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>

86
src/main/osx/Info.plist Normal file
View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>aseprite</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Document.icns</string>
<key>CFBundleTypeName</key>
<string>Aseprite Sprite</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>ase</string>
<string>bmp</string>
<string>flc</string>
<string>fli</string>
<string>gif</string>
<string>ico</string>
<string>jpeg</string>
<string>jpg</string>
<string>pcx</string>
<string>png</string>
<string>tga</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Document.icns</string>
<key>CFBundleTypeName</key>
<string>Aseprite Sprite</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>aseprite-extension</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>Extension.icns</string>
<key>CFBundleTypeName</key>
<string>Aseprite Extension</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
</dict>
</array>
<key>CFBundleDisplayName</key>
<string>Aseprite</string>
<key>CFBundleExecutable</key>
<string>aseprite</string>
<key>CFBundleIdentifier</key>
<string>org.aseprite.Aseprite</string>
<key>CFBundleName</key>
<string>Aseprite</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>1.3</string>
<key>CFBundleVersion</key>
<string>1.3</string>
<key>CFBundleIconFile</key>
<string>Aseprite.icns</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.graphics-design</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2001-2025, Igara Studio S.A.
All rights reserved.</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSRequiresAquaSystemAppearance</key>
<false/>
</dict>
</plist>

View File

@ -106,7 +106,6 @@ void Box::onResize(ResizeEvent& ev)
continue; \
\
int size = 0; \
int sizeDiff = 0; \
\
if (align() & HOMOGENEOUS) { \
if (i < visibleChildren - 1) \

View File

@ -128,6 +128,15 @@ int Entry::lastCaretPos() const
return int(m_boxes.size() - 1);
}
gfx::Point Entry::caretPosOnScreen() const
{
const gfx::Point caretPos = getCharBoxBounds(m_caret).point2();
const os::Window* nativeWindow = display()->nativeWindow();
const gfx::Point pos = nativeWindow->pointToScreen(caretPos + bounds().origin());
return pos;
}
void Entry::setCaretPos(const int pos)
{
gfx::Size caretSize = theme()->getEntryCaretSize(this);
@ -160,6 +169,8 @@ void Entry::setCaretPos(const int pos)
startTimer();
m_state = true;
os::System::instance()->setTextInput(true, caretPosOnScreen());
invalidate();
}
@ -251,7 +262,7 @@ gfx::Rect Entry::getEntryTextBounds() const
return onGetEntryTextBounds();
}
gfx::Rect Entry::getCharBoxBounds(const int i)
gfx::Rect Entry::getCharBoxBounds(const int i) const
{
ASSERT(i >= 0 && i < int(m_boxes.size()));
if (i >= 0 && i < int(m_boxes.size()))
@ -288,8 +299,9 @@ bool Entry::onProcessMessage(Message* msg)
}
// Start processing dead keys
if (m_translate_dead_keys)
os::System::instance()->setTranslateDeadKeys(true);
if (m_translate_dead_keys) {
os::System::instance()->setTextInput(true, caretPosOnScreen());
}
break;
case kFocusLeaveMessage:
@ -304,7 +316,7 @@ bool Entry::onProcessMessage(Message* msg)
// Stop processing dead keys
if (m_translate_dead_keys)
os::System::instance()->setTranslateDeadKeys(false);
os::System::instance()->setTextInput(false);
break;
case kKeyDownMessage:

View File

@ -43,6 +43,7 @@ public:
int caretPos() const { return m_caret; }
int lastCaretPos() const;
gfx::Point caretPosOnScreen() const;
void setCaretPos(int pos);
void setCaretToEnd();
@ -76,7 +77,7 @@ public:
obs::signal<void()> Change;
protected:
gfx::Rect getCharBoxBounds(int i);
gfx::Rect getCharBoxBounds(int i) const;
// Events
bool onProcessMessage(Message* msg) override;

View File

@ -115,8 +115,8 @@ bool IntEntry::onProcessMessage(Message* msg)
case kKeyDownMessage:
if (hasFocus() && !isReadOnly()) {
KeyMessage* keymsg = static_cast<KeyMessage*>(msg);
int chr = keymsg->unicodeChar();
if (chr >= 32 && (chr < '0' || chr > '9')) {
const int chr = keymsg->unicodeChar();
if (chr >= 32 && !onAcceptUnicodeChar(chr)) {
// "Eat" all keys that aren't number
return true;
}
@ -166,6 +166,11 @@ void IntEntry::onValueChange()
// Do nothing
}
bool IntEntry::onAcceptUnicodeChar(const int unicodeChar)
{
return (unicodeChar >= '0' && unicodeChar <= '9');
}
void IntEntry::openPopup()
{
m_slider->setValue(getValue());

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2022 Igara Studio S.A.
// Copyright (C) 2022-2025 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
@ -36,6 +36,7 @@ protected:
// New events
virtual void onValueChange();
virtual bool onAcceptUnicodeChar(int unicodeChar);
int m_min;
int m_max;

View File

@ -228,3 +228,69 @@ do
c = app.open(fn)
assert(c.tileManagementPlugin == nil)
end
-- Undo History
function test_undo_history()
local sprite = Sprite(1, 1)
assert(sprite.undoHistory.undoSteps == 0)
assert(sprite.undoHistory.redoSteps == 0)
sprite:resize(10, 10)
assert(sprite.undoHistory.undoSteps == 1)
assert(sprite.undoHistory.redoSteps == 0)
sprite:resize(10, 15)
assert(sprite.undoHistory.undoSteps == 2)
assert(sprite.undoHistory.redoSteps == 0)
sprite:resize(10, 30)
assert(sprite.undoHistory.undoSteps == 3)
assert(sprite.undoHistory.redoSteps == 0)
app.undo()
assert(sprite.undoHistory.undoSteps == 2)
assert(sprite.undoHistory.redoSteps == 1)
app.undo()
assert(sprite.undoHistory.undoSteps == 1)
assert(sprite.undoHistory.redoSteps == 2)
app.redo()
assert(sprite.undoHistory.undoSteps == 2)
assert(sprite.undoHistory.redoSteps == 1)
app.undo()
app.undo()
assert(sprite.undoHistory.undoSteps == 0)
assert(sprite.undoHistory.redoSteps == 3)
sprite:resize(10, 30)
if (app.preferences.undo.allow_nonlinear_history) then
assert(sprite.undoHistory.undoSteps == 4)
assert(sprite.undoHistory.redoSteps == 0)
else
assert(sprite.undoHistory.undoSteps == 1)
assert(sprite.undoHistory.redoSteps == 0)
end
end
do
local prevSetting = app.preferences.undo.allow_nonlinear_history
app.preferences.undo.allow_nonlinear_history = true
test_undo_history()
app.preferences.undo.allow_nonlinear_history = prevSetting
end
do
local prevSetting = app.preferences.undo.allow_nonlinear_history
app.preferences.undo.allow_nonlinear_history = false
test_undo_history()
app.preferences.undo.allow_nonlinear_history = prevSetting
end