mirror of https://github.com/aseprite/aseprite.git
Compare commits
89 Commits
4a70697360
...
935283e78c
Author | SHA1 | Date |
---|---|---|
|
935283e78c | |
|
357c054432 | |
|
e0f33a0413 | |
|
50ede4f062 | |
|
5f7cc42333 | |
|
8e75cfc4c7 | |
|
983b07383f | |
|
c57554646b | |
|
74953174d6 | |
|
49fa35237a | |
|
0ccf9dcc4f | |
|
194f8424a8 | |
|
debab653fa | |
|
6e9024d54d | |
|
1fa7fd0831 | |
|
ab6b040e83 | |
|
bc312a37b3 | |
|
3129fda977 | |
|
6cb61fb41e | |
|
aa817a8d2a | |
|
40031f83d8 | |
|
90282dbc40 | |
|
1227f9c49c | |
|
d61ae919ad | |
|
b2b2583176 | |
|
b535212642 | |
|
229a3cdf65 | |
|
b3814ec912 | |
|
e88f3bb413 | |
|
eaa2bdf0af | |
|
57309e5aa5 | |
|
4bb9239f50 | |
|
cef92c1a38 | |
|
22e72ab5cb | |
|
80fa065bd5 | |
|
de1ccb24dd | |
|
7d91c4b9d9 | |
|
6d89a6bc15 | |
|
d4e97b5a96 | |
|
205b18dc0f | |
|
2ba051b59b | |
|
3fcb000eb1 | |
|
af9dc3c817 | |
|
250dfdc86a | |
|
c904c41b39 | |
|
9e941e9a8b | |
|
bbab4d5875 | |
|
5c4daff128 | |
|
11a7b061ff | |
|
283bedf77e | |
|
2eeb6f04a7 | |
|
706d0b8a7a | |
|
2f3a7f5dec | |
|
d5de74b715 | |
|
2d87a7b184 | |
|
2f22804fe8 | |
|
bf1b4c6f50 | |
|
250244c777 | |
|
b4555fc098 | |
|
8783135bf7 | |
|
1a6a39700e | |
|
ce742bcbc1 | |
|
3c350c3e67 | |
|
220d2d3a2d | |
|
322040c4fb | |
|
ee69a2f2f6 | |
|
91f8410749 | |
|
8d5534d4eb | |
|
2ee8c68d94 | |
|
f7040190cc | |
|
68bf0839aa | |
|
e5917389cb | |
|
27eecb3bdc | |
|
b6cbeefb6e | |
|
5596c3270d | |
|
ab29b84f25 | |
|
444ef0f6b4 | |
|
b3b956516b | |
|
32bf699655 | |
|
5ceeac2f26 | |
|
0ddf7d939b | |
|
bd13e5d574 | |
|
e53aa99080 | |
|
af6e8b65c3 | |
|
68342bdb66 | |
|
8ff208d8d5 | |
|
2bc4f0582d | |
|
2be4dc1474 | |
|
2b522e222b |
|
@ -1,9 +0,0 @@
|
|||
Describe your bug report or feature request here
|
||||
...
|
||||
...
|
||||
...
|
||||
|
||||
### Aseprite and System version
|
||||
|
||||
* Aseprite version: version number, installer/portable/Steam/beta/dev/commit-hash
|
||||
* System: Windows/macOS/Linux, version, distribution
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug, triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots or a screen recording to help explain your problem.
|
||||
|
||||
**Aseprite & System (please complete the following information):**
|
||||
- Aseprite: [version number, installer/portable/Steam/beta/dev/commit-hash]
|
||||
- System: [Windows/macOS/Linux, version, distribution]
|
||||
- Extensions: [List the extensions you have installed]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for Aseprite
|
||||
title: ''
|
||||
labels: feature, triage
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Did other user suggested a similar idea?**
|
||||
- [ ] No
|
||||
- [ ] Yes/Links to similar ideas
|
||||
> You can try to find a similar feature requests before in:
|
||||
> - GitHub issues: https://github.com/aseprite/aseprite/issues?q=label%3Afeature
|
||||
> - Community site: https://community.aseprite.org/c/features/7
|
||||
> - Steam community: https://steamcommunity.com/app/431730/discussions/1/
|
||||
> In case you find a similar feature request, making a comment there will be useful to give some traction and show interest in the feature.
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
|
@ -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)
|
||||
|
|
8
build.sh
8
build.sh
|
@ -428,9 +428,17 @@ if [ ! -d "$skia_library_dir" ] ; then
|
|||
skia_url=$(bash laf/misc/skia-url.sh $skia_build)
|
||||
skia_file=$(basename $skia_url)
|
||||
if [ ! -f "$skia_dir/$skia_file" ] ; then
|
||||
if ! command -v curl >/dev/null 2>&1 ; then
|
||||
echo "Error: 'curl' command line tool is not available in PATH"
|
||||
exit 1
|
||||
fi
|
||||
curl --ssl-revoke-best-effort -L -o "$skia_dir/$skia_file" "$skia_url"
|
||||
fi
|
||||
if [ ! -d "$skia_library_dir" ] ; then
|
||||
if ! command -v unzip >/dev/null 2>&1 ; then
|
||||
echo "Error: 'unzip' command line tool is not available in PATH"
|
||||
exit 1
|
||||
fi
|
||||
unzip -n -d "$skia_dir" "$skia_dir/$skia_file"
|
||||
fi
|
||||
else
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1203,6 +1203,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>
|
||||
|
||||
|
|
|
@ -146,6 +146,10 @@
|
|||
<value id="KEEP_AS_IS" value="1" />
|
||||
<value id="RAW_IMAGE" value="2" />
|
||||
</enum>
|
||||
<enum id="CancelSelection">
|
||||
<value id="DISCARD" value="0" />
|
||||
<value id="DESELECT" value="1" />
|
||||
</enum>
|
||||
</types>
|
||||
|
||||
<global>
|
||||
|
@ -325,6 +329,7 @@
|
|||
<option id="force_rotsprite" type="bool" default="false" />
|
||||
<option id="multicel_when_layers_or_frames" type="bool" default="true" />
|
||||
<option id="snap_to_grid" type="bool" default="true" />
|
||||
<option id="cancel_selection" type="CancelSelection" default="CancelSelection::DISCARD" />
|
||||
</section>
|
||||
<section id="quantization">
|
||||
<option id="with_alpha" type="bool" default="true" />
|
||||
|
@ -356,6 +361,7 @@
|
|||
<section id="file_selector">
|
||||
<option id="current_folder" type="std::string" default=""<empty>"" />
|
||||
<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" />
|
||||
|
|
|
@ -265,6 +265,7 @@ Despeckle = Despeckle
|
|||
DeveloperConsole = Developer Console
|
||||
DiscardBrush = Discard Brush
|
||||
DuplicateLayer = Duplicate Layer
|
||||
DuplicateSlice = Duplicate Slice
|
||||
DuplicateSprite = Duplicate Sprite
|
||||
DuplicateView = Duplicate View
|
||||
Exit = Exit
|
||||
|
@ -554,6 +555,8 @@ amount = Amount:
|
|||
flatten = Merge layers
|
||||
|
||||
[context_bar]
|
||||
discard_changes = Discard Changes
|
||||
deselect = Deselect
|
||||
center = Center
|
||||
fit_screen = Fit Screen
|
||||
back = Back
|
||||
|
@ -582,7 +585,7 @@ 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)
|
||||
cancel_drag = Cancel drag and drop (Esc)\nRight-click: Configure action
|
||||
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
|
||||
|
@ -816,6 +828,7 @@ same_in_all_tools = Same in all Tools
|
|||
opacity = Opacity:
|
||||
tolerance = Tolerance:
|
||||
show_more = Show more...
|
||||
copy_of = {} Copy
|
||||
|
||||
[general_text]
|
||||
copy = &Copy
|
||||
|
@ -1663,6 +1676,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
|
||||
|
@ -1707,7 +1724,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
|
||||
|
@ -1747,6 +1764,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]
|
||||
|
@ -1834,6 +1852,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
|
||||
|
@ -1896,6 +1926,7 @@ timeline_show = Show Timeline
|
|||
|
||||
[undo_history]
|
||||
title = Undo History
|
||||
initial_state = Initial State
|
||||
|
||||
[user_data]
|
||||
user_data = User Data:
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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" />
|
|
@ -1,17 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2019-2024 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2019-2025 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2017-2018 David Capello -->
|
||||
<gui>
|
||||
<window id="slice_properties" text="@.title" help="slices#slice-properties">
|
||||
<vbox>
|
||||
<vbox expansive="true">
|
||||
<grid id="properties_grid" columns="3">
|
||||
<label id="label1" text="@.name" />
|
||||
<entry id="name" maxsize="256" magnet="true" cell_align="horizontal" expansive="true" />
|
||||
<button id="user_data" icon="icon_user_data" maxsize="32" tooltip="@.user_data_tooltip" />
|
||||
</grid>
|
||||
<grid columns="2">
|
||||
<separator horizontal="true" cell_hspan="2" />
|
||||
<grid columns="3" expansive="true">
|
||||
<separator horizontal="true" cell_hspan="3" />
|
||||
|
||||
<box />
|
||||
<hbox homogeneous="true">
|
||||
|
@ -20,6 +20,7 @@
|
|||
<label text="@.width" />
|
||||
<label text="@.height" />
|
||||
</hbox>
|
||||
<boxfiller cell_align="horizontal" />
|
||||
|
||||
<label text="@.bounds" />
|
||||
<hbox homogeneous="true">
|
||||
|
@ -28,6 +29,7 @@
|
|||
<expr id="bounds_w" />
|
||||
<expr id="bounds_h" />
|
||||
</hbox>
|
||||
<boxfiller />
|
||||
|
||||
<check text="@.center" id="center" />
|
||||
<hbox homogeneous="true">
|
||||
|
@ -36,16 +38,18 @@
|
|||
<expr id="center_w" />
|
||||
<expr id="center_h" />
|
||||
</hbox>
|
||||
<boxfiller />
|
||||
|
||||
<check text="@.pivot" id="pivot" />
|
||||
<hbox>
|
||||
<expr id="pivot_x" />
|
||||
<expr id="pivot_y" />
|
||||
</hbox>
|
||||
<boxfiller />
|
||||
|
||||
<separator horizontal="true" cell_hspan="2" />
|
||||
|
||||
<hbox cell_hspan="2">
|
||||
<boxfiller cell_align="vertical" cell_hspan="3" />
|
||||
<separator horizontal="true" cell_hspan="3" cell_align="horizontal" />
|
||||
<hbox cell_hspan="3">
|
||||
<boxfiller />
|
||||
<hbox homogeneous="true">
|
||||
<button text="@general.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2019-2021 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2019-2025 Igara Studio S.A. -->
|
||||
<!-- Copyright (C) 2015-2018 David Capello -->
|
||||
<gui>
|
||||
<window id="tag_properties" text="@.title">
|
||||
|
@ -23,13 +23,14 @@
|
|||
<check text="@.repeat" id="limit_repeat" />
|
||||
<vbox id="repeat_placeholder" cell_hspan="2" />
|
||||
</grid>
|
||||
<boxfiller />
|
||||
<grid columns="2">
|
||||
<separator horizontal="true" cell_hspan="2" minwidth="180" />
|
||||
<separator horizontal="true" cell_align="horizontal" cell_hspan="2" minwidth="180" />
|
||||
|
||||
<box horizontal="true" homogeneous="true" cell_hspan="2" cell_align="right">
|
||||
<hbox homogeneous="true" cell_hspan="2" cell_align="right">
|
||||
<button text="@general.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />
|
||||
<button text="@general.cancel" closewindow="true" />
|
||||
</box>
|
||||
</hbox>
|
||||
</grid>
|
||||
</vbox>
|
||||
</window>
|
||||
|
|
2
laf
2
laf
|
@ -1 +1 @@
|
|||
Subproject commit 7d30a582e5c8655fab368c4d61f3125855a7b30d
|
||||
Subproject commit 39f6dca9c623e2d005a5c5609afa97ac749b4e33
|
|
@ -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}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -393,6 +393,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
|
||||
|
@ -669,7 +670,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
|
||||
|
@ -715,6 +716,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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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.
|
||||
ui::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();
|
||||
|
@ -273,8 +273,8 @@ private:
|
|||
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,7 +315,7 @@ 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()),
|
||||
fg,
|
||||
|
@ -324,13 +324,14 @@ private:
|
|||
}
|
||||
|
||||
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 Shortcut& 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 Shortcuts* shortcuts = (m_key ? &m_key->shortcuts() : NULL);
|
||||
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 Shortcut& 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;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2020-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2020-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
|
@ -166,7 +166,7 @@ public:
|
|||
m_document->add_observer(this);
|
||||
|
||||
if (countLayers() > 0) {
|
||||
m_userDataView.configureAndSet(m_layer->userData(), g_window->propertiesGrid());
|
||||
m_userDataView.configureAndSet(m_layer->userData(), propertiesGrid());
|
||||
if (m_remapAfterConfigure) {
|
||||
remapWindow();
|
||||
centerWindow();
|
||||
|
@ -368,8 +368,7 @@ private:
|
|||
{
|
||||
if (m_layer) {
|
||||
m_userDataView.toggleVisibility();
|
||||
g_window->remapWindow();
|
||||
manager()->invalidate();
|
||||
expandWindow(gfx::Size(bounds().w, sizeHint().h));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1046,7 +1046,11 @@ public:
|
|||
}
|
||||
|
||||
protected:
|
||||
void onOpen(Event& evt) override { sectionListbox()->selectIndex(m_curSection); }
|
||||
void onOpen(Event& evt) override
|
||||
{
|
||||
sectionListbox()->selectIndex(m_curSection);
|
||||
app::gen::Options::onOpen(evt);
|
||||
}
|
||||
|
||||
private:
|
||||
void onInitTheme(InitThemeEvent& ev) override
|
||||
|
@ -1368,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();
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
@ -40,6 +40,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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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" :
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -70,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();
|
||||
|
@ -81,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;
|
||||
}
|
||||
|
||||
|
@ -323,7 +424,7 @@ public:
|
|||
dithering,
|
||||
sprite ? sprite->rgbMap(0) : nullptr,
|
||||
sprite ? sprite->palette(0) : nullptr,
|
||||
sprite ? sprite->backgroundLayer() : nullptr,
|
||||
(sprite && sprite->backgroundLayer()),
|
||||
0,
|
||||
0,
|
||||
nullptr);
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -682,11 +682,11 @@ bool CustomizedGuiManager::processKey(Message* msg)
|
|||
{
|
||||
App* app = App::instance();
|
||||
const KeyboardShortcuts* keys = KeyboardShortcuts::instance();
|
||||
const KeyContext contexts[] = { keys->getCurrentKeyContext(), KeyContext::Normal };
|
||||
const KeyContext contexts[] = { KeyboardShortcuts::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])) {
|
||||
if (key->isPressed(msg, contexts[i])) {
|
||||
// Cancel menu-bar loops (to close any popup menu)
|
||||
app->mainWindow()->getMenuBar()->cancelMenuLoop();
|
||||
|
||||
|
@ -700,7 +700,7 @@ bool CustomizedGuiManager::processKey(Message* msg)
|
|||
// 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))
|
||||
if (key && key->isPressed(msg))
|
||||
possibles.push_back(tool);
|
||||
}
|
||||
|
||||
|
|
|
@ -213,7 +213,7 @@ void ResourceFinder::includeDesktopDir(const char* filename)
|
|||
#ifdef _WIN32
|
||||
|
||||
std::vector<wchar_t> buf(MAX_PATH);
|
||||
HRESULT hr = SHGetFolderPath(NULL, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_DEFAULT, &buf[0]);
|
||||
HRESULT hr = SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, &buf[0]);
|
||||
if (hr == S_OK) {
|
||||
addPath(base::join_path(base::to_utf8(&buf[0]), filename));
|
||||
}
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
|
||||
// Increment this value if the scripting API is modified between two
|
||||
// released Aseprite versions.
|
||||
#define API_VERSION 33
|
||||
#define API_VERSION 35
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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 }
|
||||
};
|
||||
|
||||
|
|
|
@ -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,6 +196,13 @@ 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();
|
||||
|
@ -230,8 +241,10 @@ struct Dialog {
|
|||
window.display()->nativeWindow()->setFrame(frame);
|
||||
}
|
||||
else {
|
||||
gfx::Rect oldBounds(window.bounds());
|
||||
window.setBounds(rc);
|
||||
window.invalidate();
|
||||
parentDisplay()->invalidateRect(oldBounds);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -365,6 +378,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 +393,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
|
||||
|
@ -1509,6 +1536,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);
|
||||
|
@ -1643,8 +1674,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);
|
||||
|
@ -1866,6 +1915,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);
|
||||
|
@ -1907,9 +1977,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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,11 +29,16 @@ struct Plugin {
|
|||
|
||||
class PluginCommand : public Command {
|
||||
public:
|
||||
PluginCommand(const std::string& id, const std::string& title, int onclickRef, int onenabledRef)
|
||||
PluginCommand(const std::string& id,
|
||||
const std::string& title,
|
||||
int onclickRef,
|
||||
int onenabledRef,
|
||||
int oncheckedRef)
|
||||
: Command(id.c_str(), CmdUIOnlyFlag)
|
||||
, m_title(title)
|
||||
, m_onclickRef(onclickRef)
|
||||
, m_onenabledRef(onenabledRef)
|
||||
, m_oncheckedRef(oncheckedRef)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -72,28 +77,44 @@ protected:
|
|||
bool onEnabled(Context* context) override
|
||||
{
|
||||
if (m_onenabledRef) {
|
||||
script::Engine* engine = App::instance()->scriptEngine();
|
||||
lua_State* L = engine->luaState();
|
||||
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, m_onenabledRef);
|
||||
if (lua_pcall(L, 0, 1, 0)) {
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
Console().printf("Error: %s", s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
bool ret = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return ret;
|
||||
}
|
||||
return callScriptRef(m_onenabledRef);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onChecked(Context* context) override
|
||||
{
|
||||
if (m_oncheckedRef) {
|
||||
return callScriptRef(m_oncheckedRef);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool callScriptRef(int ref)
|
||||
{
|
||||
ASSERT(ref);
|
||||
script::Engine* engine = App::instance()->scriptEngine();
|
||||
lua_State* L = engine->luaState();
|
||||
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
|
||||
if (lua_pcall(L, 0, 1, 0)) {
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
Console().printf("Error: %s", s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
bool ret = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
std::string m_title;
|
||||
int m_onclickRef;
|
||||
int m_onenabledRef;
|
||||
int m_oncheckedRef;
|
||||
};
|
||||
|
||||
void deleteCommandIfExistent(Extension* ext, const std::string& id)
|
||||
|
@ -126,6 +147,7 @@ int Plugin_newCommand(lua_State* L)
|
|||
if (lua_istable(L, 2)) {
|
||||
std::string id, title, group;
|
||||
int onenabledRef = 0;
|
||||
int oncheckedRef = 0;
|
||||
|
||||
lua_getfield(L, 2, "id");
|
||||
if (const char* s = lua_tostring(L, -1)) {
|
||||
|
@ -156,6 +178,14 @@ int Plugin_newCommand(lua_State* L)
|
|||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
type = lua_getfield(L, 2, "onchecked");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
oncheckedRef = luaL_ref(L, LUA_REGISTRYINDEX); // does a pop
|
||||
}
|
||||
else {
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
type = lua_getfield(L, 2, "onclick");
|
||||
if (type == LUA_TFUNCTION) {
|
||||
int onclickRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
|
@ -164,7 +194,7 @@ int Plugin_newCommand(lua_State* L)
|
|||
// overwriting a previous registered command)
|
||||
deleteCommandIfExistent(plugin->ext, id);
|
||||
|
||||
auto cmd = new PluginCommand(id, title, onclickRef, onenabledRef);
|
||||
auto cmd = new PluginCommand(id, title, onclickRef, onenabledRef, oncheckedRef);
|
||||
Commands::instance()->add(cmd);
|
||||
plugin->ext->addCommand(id);
|
||||
|
||||
|
@ -172,6 +202,7 @@ int Plugin_newCommand(lua_State* L)
|
|||
if (!group.empty() && App::instance()->isGui()) { // On CLI menus do not make sense
|
||||
if (auto appMenus = AppMenus::instance()) {
|
||||
auto menuItem = std::make_unique<AppMenuItem>(title, id);
|
||||
menuItem->processMnemonicFromText();
|
||||
appMenus->addMenuItemIntoGroup(group, std::move(menuItem));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -221,7 +221,7 @@ int secure_os_remove(lua_State* L)
|
|||
base::remove_directory(absFilename);
|
||||
return file_result(L, true);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
catch (const std::exception&) {
|
||||
return file_result(L, false, EIO, absFilename);
|
||||
}
|
||||
}
|
||||
|
@ -229,7 +229,7 @@ int secure_os_remove(lua_State* L)
|
|||
try {
|
||||
base::delete_file(absFilename);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
catch (const std::exception&) {
|
||||
return file_result(L, false, EIO, absFilename);
|
||||
}
|
||||
|
||||
|
@ -261,7 +261,7 @@ int secure_os_rename(lua_State* L)
|
|||
base::move_file(absSourceFilename, absDestFilename);
|
||||
return file_result(L, true);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
catch (const std::exception&) {
|
||||
return file_result(L, false, EIO, absSourceFilename);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include "app/console.h"
|
||||
#include "app/context.h"
|
||||
#include "app/context_observer.h"
|
||||
#include "app/doc.h"
|
||||
#include "app/doc_undo.h"
|
||||
#include "app/script/docobj.h"
|
||||
#include "app/script/engine.h"
|
||||
#include "app/script/luacpp.h"
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
#include "doc/tag.h"
|
||||
#include "doc/tileset.h"
|
||||
#include "doc/tilesets.h"
|
||||
#include "undo/undo_state.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
|
@ -456,7 +457,7 @@ int Sprite_newCel(lua_State* L)
|
|||
auto sprite = get_docobj<Sprite>(L, 1);
|
||||
auto layerBase = get_docobj<Layer>(L, 2);
|
||||
if (!layerBase->isImage())
|
||||
return luaL_error(L, "unexpected kinf of layer in Sprite:newCel()");
|
||||
return luaL_error(L, "unexpected kind of layer in Sprite:newCel()");
|
||||
|
||||
frame_t frame = get_frame_number_from_arg(L, 3);
|
||||
if (frame < 0 || frame > sprite->lastFrame())
|
||||
|
@ -1029,6 +1030,42 @@ int Sprite_set_useLayerUuids(lua_State* L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int Sprite_get_undoHistory(lua_State* L)
|
||||
{
|
||||
const auto* sprite = get_docobj<Sprite>(L, 1);
|
||||
const auto* doc = static_cast<Doc*>(sprite->document());
|
||||
const auto* history = doc->undoHistory();
|
||||
|
||||
if (!history) {
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const undo::UndoState* currentState = history->currentState();
|
||||
const undo::UndoState* s = history->firstState();
|
||||
const bool canRedo = history->canRedo();
|
||||
bool pastCurrent = !currentState && canRedo;
|
||||
|
||||
int undoSteps = 0;
|
||||
int redoSteps = 0;
|
||||
while (s) {
|
||||
if (pastCurrent && canRedo)
|
||||
redoSteps++;
|
||||
else if (currentState || !canRedo)
|
||||
undoSteps++;
|
||||
|
||||
if (s == currentState || !currentState)
|
||||
pastCurrent = true;
|
||||
|
||||
s = s->next();
|
||||
}
|
||||
|
||||
lua_newtable(L);
|
||||
setfield_integer(L, "undoSteps", undoSteps);
|
||||
setfield_integer(L, "redoSteps", redoSteps);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const luaL_Reg Sprite_methods[] = {
|
||||
{ "__eq", Sprite_eq },
|
||||
{ "resize", Sprite_resize },
|
||||
|
@ -1094,6 +1131,7 @@ const Property Sprite_properties[] = {
|
|||
{ "events", Sprite_get_events, nullptr },
|
||||
{ "tileManagementPlugin", Sprite_get_tileManagementPlugin, Sprite_set_tileManagementPlugin },
|
||||
{ "useLayerUuids", Sprite_get_useLayerUuids, Sprite_set_useLayerUuids },
|
||||
{ "undoHistory", Sprite_get_undoHistory, nullptr },
|
||||
{ nullptr, nullptr, nullptr }
|
||||
};
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -388,6 +388,7 @@ FOR_ENUM(app::gen::TimelinePosition)
|
|||
FOR_ENUM(app::gen::ToGrayAlgorithm)
|
||||
FOR_ENUM(app::gen::WindowColorProfile)
|
||||
FOR_ENUM(app::gen::AlphaRange)
|
||||
FOR_ENUM(app::gen::CancelSelection)
|
||||
FOR_ENUM(app::tools::ColorFromTo)
|
||||
FOR_ENUM(app::tools::DynamicSensor)
|
||||
FOR_ENUM(app::tools::FreehandAlgorithm)
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
|
@ -1397,6 +1407,7 @@ public:
|
|||
}
|
||||
|
||||
obs::signal<void(ContextBarObserver::DropAction)> DropPixels;
|
||||
obs::signal<void(ContextBarObserver::DropAction, const gfx::Point&)> ConfigureDropPixels;
|
||||
|
||||
protected:
|
||||
void onItemChange(Item* item) override
|
||||
|
@ -1408,6 +1419,21 @@ protected:
|
|||
case 1: DropPixels(ContextBarObserver::CancelDrag); break;
|
||||
}
|
||||
}
|
||||
|
||||
void onRightClick(Item* item) override
|
||||
{
|
||||
ButtonSet::onRightClick(item);
|
||||
|
||||
const gfx::Rect rc = item->bounds();
|
||||
const gfx::Point pt(rc.x, rc.y2());
|
||||
|
||||
auto action = ContextBarObserver::DropPixels;
|
||||
switch (selectedItem()) {
|
||||
case 0: action = ContextBarObserver::DropPixels; break;
|
||||
case 1: action = ContextBarObserver::CancelDrag; break;
|
||||
}
|
||||
ConfigureDropPixels(action, pt);
|
||||
}
|
||||
};
|
||||
|
||||
class ContextBar::EyedropperField : public HBox {
|
||||
|
@ -1857,7 +1883,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);
|
||||
|
@ -1942,6 +1968,8 @@ ContextBar::ContextBar(TooltipManager* tooltipManager, ColorBar* colorBar)
|
|||
m_keysConn = KeyboardShortcuts::instance()->UserChange.connect(
|
||||
[this, tooltipManager] { setupTooltips(tooltipManager); });
|
||||
m_dropPixelsConn = m_dropPixels->DropPixels.connect(&ContextBar::onDropPixels, this);
|
||||
m_configureDropPixelsConn =
|
||||
m_dropPixels->ConfigureDropPixels.connect(&ContextBar::onConfigureDropPixels, this);
|
||||
|
||||
setActiveBrush(createBrushFromPreferences());
|
||||
|
||||
|
@ -2086,6 +2114,14 @@ void ContextBar::onDropPixels(ContextBarObserver::DropAction action)
|
|||
notify_observers(&ContextBarObserver::onDropPixels, action);
|
||||
}
|
||||
|
||||
void ContextBar::onConfigureDropPixels(ContextBarObserver::DropAction action, const gfx::Point& pt)
|
||||
{
|
||||
notify_observers<ContextBarObserver::DropAction, const gfx::Point&>(
|
||||
&ContextBarObserver::onConfigureDropPixels,
|
||||
action,
|
||||
pt);
|
||||
}
|
||||
|
||||
void ContextBar::updateSliceFields(const Site& site)
|
||||
{
|
||||
if (site.sprite())
|
||||
|
@ -2559,6 +2595,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 +2662,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()
|
||||
|
|
|
@ -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();
|
||||
|
@ -128,6 +129,7 @@ private:
|
|||
void onFgOrBgColorChange(doc::Brush::ImageColor imageColor);
|
||||
void onOpacityRangeChange();
|
||||
void onDropPixels(ContextBarObserver::DropAction action);
|
||||
void onConfigureDropPixels(ContextBarObserver::DropAction action, const gfx::Point& pt);
|
||||
void updateSliceFields(const Site& site);
|
||||
|
||||
// ActiveToolObserver impl
|
||||
|
@ -212,6 +214,7 @@ private:
|
|||
obs::scoped_connection m_alphaRangeConn;
|
||||
obs::scoped_connection m_keysConn;
|
||||
obs::scoped_connection m_dropPixelsConn;
|
||||
obs::scoped_connection m_configureDropPixelsConn;
|
||||
obs::scoped_connection m_sizeConn;
|
||||
obs::scoped_connection m_angleConn;
|
||||
obs::scoped_connection m_opacityConn;
|
||||
|
|
|
@ -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
|
||||
|
@ -8,6 +9,8 @@
|
|||
#define APP_CONTEXT_BAR_OBSERVER_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "gfx/fwd.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
class ContextBarObserver {
|
||||
|
@ -16,6 +19,7 @@ public:
|
|||
|
||||
virtual ~ContextBarObserver() {}
|
||||
virtual void onDropPixels(DropAction action) {}
|
||||
virtual void onConfigureDropPixels(DropAction action, const gfx::Point& pt) {}
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
@ -2537,6 +2555,16 @@ void Editor::onBeforeLayerEditableChange(DocEvent& ev, bool newState)
|
|||
m_state->onBeforeLayerEditableChange(this, ev.layer(), newState);
|
||||
}
|
||||
|
||||
void Editor::onBeforeSlicesDuplication(DocEvent& ev)
|
||||
{
|
||||
clearSlicesSelection();
|
||||
}
|
||||
|
||||
void Editor::onSliceDuplicated(DocEvent& ev)
|
||||
{
|
||||
selectSlice(ev.slice());
|
||||
}
|
||||
|
||||
void Editor::setCursor(const gfx::Point& mouseDisplayPos)
|
||||
{
|
||||
bool used = false;
|
||||
|
|
|
@ -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
|
||||
|
@ -343,6 +343,8 @@ protected:
|
|||
void onRemoveSlice(DocEvent& ev) override;
|
||||
void onBeforeLayerVisibilityChange(DocEvent& ev, bool newState) override;
|
||||
void onBeforeLayerEditableChange(DocEvent& ev, bool newState) override;
|
||||
void onBeforeSlicesDuplication(DocEvent& ev) override;
|
||||
void onSliceDuplicated(DocEvent& ev) override;
|
||||
|
||||
// ActiveToolObserver impl
|
||||
void onActiveToolChange(tools::Tool* tool) override;
|
||||
|
@ -458,6 +460,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
|
||||
|
|
|
@ -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
|
||||
|
@ -22,6 +22,7 @@
|
|||
#include "app/commands/commands.h"
|
||||
#include "app/commands/move_thing.h"
|
||||
#include "app/console.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/tools/ink.h"
|
||||
|
@ -48,6 +49,7 @@
|
|||
#include "fmt/format.h"
|
||||
#include "gfx/rect.h"
|
||||
#include "ui/manager.h"
|
||||
#include "ui/menu.h"
|
||||
#include "ui/message.h"
|
||||
#include "ui/system.h"
|
||||
#include "ui/view.h"
|
||||
|
@ -286,9 +288,6 @@ bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
|
|||
UIContext* ctx = UIContext::instance();
|
||||
ctx->setActiveView(editor->getDocView());
|
||||
|
||||
ContextBar* contextBar = App::instance()->contextBar();
|
||||
contextBar->updateForMovingPixels(getTransformation(editor));
|
||||
|
||||
// Start scroll loop
|
||||
if (editor->checkForScroll(msg) || editor->checkForZoom(msg))
|
||||
return true;
|
||||
|
@ -442,10 +441,6 @@ void MovingPixelsState::onCommitMouseMove(Editor* editor, const gfx::PointF& spr
|
|||
// Drag the image to that position
|
||||
m_pixelsMovement->moveImage(spritePos, moveModifier);
|
||||
|
||||
// Update context bar and status bar
|
||||
ContextBar* contextBar = App::instance()->contextBar();
|
||||
contextBar->updateForMovingPixels(transformation);
|
||||
|
||||
m_editor->updateStatusBar();
|
||||
}
|
||||
|
||||
|
@ -475,16 +470,13 @@ bool MovingPixelsState::onKeyDown(Editor* editor, KeyMessage* msg)
|
|||
// FineControl now (e.g. if we pressed another modifier key).
|
||||
m_lockedKeyAction = KeyAction::None;
|
||||
|
||||
if (msg->scancode() == kKeyEnter || // TODO make this key customizable
|
||||
msg->scancode() == kKeyEnterPad || msg->scancode() == kKeyEsc) {
|
||||
// TODO make these keys customizable
|
||||
if (msg->scancode() == kKeyEsc) {
|
||||
cancelDrag();
|
||||
return true;
|
||||
}
|
||||
if (msg->scancode() == kKeyEnter || msg->scancode() == kKeyEnterPad) {
|
||||
dropPixels();
|
||||
|
||||
// The escape key drop pixels and deselect the mask.
|
||||
if (msg->scancode() == kKeyEsc) { // TODO make this key customizable
|
||||
Command* cmd = Commands::instance()->byId(CommandId::DeselectMask());
|
||||
UIContext::instance()->executeCommandFromMenuOrShortcut(cmd);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -529,6 +521,10 @@ bool MovingPixelsState::onUpdateStatusBar(Editor* editor)
|
|||
const Transformation& transform(getTransformation(editor));
|
||||
gfx::Size imageSize = m_pixelsMovement->getInitialImageSize();
|
||||
|
||||
// Update the context bar along with the status bar
|
||||
ContextBar* contextBar = App::instance()->contextBar();
|
||||
contextBar->updateForMovingPixels(transform);
|
||||
|
||||
int w = int(transform.bounds().w);
|
||||
int h = int(transform.bounds().h);
|
||||
int gcd = base::gcd(w, h);
|
||||
|
@ -796,14 +792,64 @@ void MovingPixelsState::onDropPixels(ContextBarObserver::DropAction action)
|
|||
|
||||
switch (action) {
|
||||
case ContextBarObserver::DropPixels: dropPixels(); break;
|
||||
case ContextBarObserver::CancelDrag: cancelDrag(); break;
|
||||
}
|
||||
}
|
||||
|
||||
case ContextBarObserver::CancelDrag:
|
||||
void MovingPixelsState::cancelDrag()
|
||||
{
|
||||
if (!m_pixelsMovement || m_discarded)
|
||||
return;
|
||||
|
||||
switch (Preferences::instance().selection.cancelSelection()) {
|
||||
case gen::CancelSelection::DISCARD:
|
||||
m_pixelsMovement->discardImage(PixelsMovement::DontCommitChanges);
|
||||
m_discarded = true;
|
||||
|
||||
// Quit from MovingPixelsState, back to standby.
|
||||
m_editor->backToPreviousState();
|
||||
dropPixels();
|
||||
break;
|
||||
|
||||
case gen::CancelSelection::DESELECT: {
|
||||
dropPixels();
|
||||
|
||||
Command* cmd = Commands::instance()->byId(CommandId::DeselectMask());
|
||||
UIContext::instance()->executeCommandFromMenuOrShortcut(cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MovingPixelsState::onConfigureDropPixels(ContextBarObserver::DropAction action,
|
||||
const gfx::Point& pt)
|
||||
{
|
||||
if (!isActiveEditor())
|
||||
return;
|
||||
|
||||
switch (action) {
|
||||
case ContextBarObserver::DropPixels:
|
||||
// Do nothing
|
||||
break;
|
||||
|
||||
case ContextBarObserver::CancelDrag: {
|
||||
Menu menu;
|
||||
|
||||
MenuItem discardChanges(Strings::context_bar_discard_changes());
|
||||
MenuItem deselect(Strings::context_bar_deselect());
|
||||
menu.addChild(&discardChanges);
|
||||
menu.addChild(&deselect);
|
||||
|
||||
auto& opt = Preferences::instance().selection.cancelSelection;
|
||||
discardChanges.setSelected(opt() == gen::CancelSelection::DISCARD);
|
||||
deselect.setSelected(opt() == gen::CancelSelection::DESELECT);
|
||||
|
||||
discardChanges.Click.connect([&opt] { opt(gen::CancelSelection::DISCARD); });
|
||||
deselect.Click.connect([&opt] { opt(gen::CancelSelection::DESELECT); });
|
||||
|
||||
ContextBar* contextBar = App::instance()->contextBar();
|
||||
menu.showPopup(pt, contextBar->display());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
|
@ -77,6 +77,7 @@ public:
|
|||
|
||||
// ContextBarObserver
|
||||
void onDropPixels(ContextBarObserver::DropAction action) override;
|
||||
void onConfigureDropPixels(ContextBarObserver::DropAction action, const gfx::Point& pt) override;
|
||||
|
||||
// PixelsMovementDelegate
|
||||
void onPivotChange() override;
|
||||
|
@ -95,6 +96,7 @@ private:
|
|||
|
||||
void setTransparentColor(bool opaque, const app::Color& color);
|
||||
void dropPixels();
|
||||
void cancelDrag();
|
||||
|
||||
bool isActiveDocument() const;
|
||||
bool isActiveEditor() const;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
@ -420,10 +420,12 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
|
|||
// Now we calculate the difference from x1,y1 point and we can
|
||||
// use it to adjust all coordinates (x1, y1, x2, y2).
|
||||
bounds.setOrigin(gridOffset);
|
||||
newTransformation.pivot(abs_initial_pivot - m_initialData.bounds().origin() + gridOffset);
|
||||
}
|
||||
else
|
||||
newTransformation.pivot(abs_initial_pivot + gfx::PointF(dx, dy));
|
||||
|
||||
newTransformation.bounds(bounds);
|
||||
newTransformation.pivot(abs_initial_pivot + gfx::PointF(dx, dy));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -464,8 +464,7 @@ bool StandbyState::onKeyDown(Editor* editor, KeyMessage* msg)
|
|||
checkStartDrawingStraightLine(editor, nullptr, nullptr))
|
||||
return false;
|
||||
|
||||
Keys keys = KeyboardShortcuts::instance()->getDragActionsFromKeyMessage(KeyContext::MouseWheel,
|
||||
msg);
|
||||
Keys keys = KeyboardShortcuts::instance()->getDragActionsFromKeyMessage(msg);
|
||||
if (editor->hasMouse() && !keys.empty()) {
|
||||
// Don't enter DraggingValueState to change brush size if we are
|
||||
// in a selection-like tool
|
||||
|
|
|
@ -74,7 +74,11 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
|
|||
double dz = delta.x + delta.y;
|
||||
WheelAction wheelAction = WheelAction::None;
|
||||
|
||||
if (KeyboardShortcuts::instance()->hasMouseWheelCustomization()) {
|
||||
if (tools::Tool* quickTool = App::instance()->activeToolManager()->quickTool();
|
||||
quickTool && quickTool->getId() == tools::WellKnownInks::Zoom) {
|
||||
wheelAction = WheelAction::Zoom;
|
||||
}
|
||||
else if (KeyboardShortcuts::instance()->hasMouseWheelCustomization()) {
|
||||
if (!Preferences::instance().editor.zoomWithSlide() && msg->preciseWheel())
|
||||
wheelAction = WheelAction::VScroll;
|
||||
else
|
||||
|
|
|
@ -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
|
||||
|
@ -45,6 +45,7 @@
|
|||
#include "app/ui_context.h"
|
||||
#include "app/util/expand_cel_canvas.h"
|
||||
#include "app/util/layer_utils.h"
|
||||
#include "app/util/slice_utils.h"
|
||||
#include "doc/brush.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/image.h"
|
||||
|
@ -693,7 +694,7 @@ public:
|
|||
// popup menu to create a new one.
|
||||
if (!m_editor->selectSliceBox(bounds) && (bounds.w > 1 || bounds.h > 1)) {
|
||||
Slice* slice = new Slice;
|
||||
slice->setName(getUniqueSliceName());
|
||||
slice->setName(get_unique_slice_name(m_sprite));
|
||||
|
||||
SliceKey key(bounds);
|
||||
slice->insert(getFrame(), key);
|
||||
|
@ -716,18 +717,6 @@ private:
|
|||
// EditorObserver impl
|
||||
void onScrollChanged(Editor* editor) override { updateAllVisibleRegion(); }
|
||||
void onZoomChanged(Editor* editor) override { updateAllVisibleRegion(); }
|
||||
|
||||
std::string getUniqueSliceName() const
|
||||
{
|
||||
std::string prefix = "Slice";
|
||||
int max = 0;
|
||||
|
||||
for (Slice* slice : m_sprite->slices())
|
||||
if (std::strncmp(slice->name().c_str(), prefix.c_str(), prefix.size()) == 0)
|
||||
max = std::max(max, (int)std::strtol(slice->name().c_str() + prefix.size(), nullptr, 10));
|
||||
|
||||
return fmt::format("{} {}", prefix, max + 1);
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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,35 +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)
|
||||
{
|
||||
if (bounds.w != m_extraCel->image()->width() || bounds.h != m_extraCel->image()->height()) {
|
||||
doc::Image* extraImg = m_extraCel->image();
|
||||
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);
|
||||
|
||||
|
@ -175,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
|
||||
|
@ -204,8 +237,7 @@ private:
|
|||
}
|
||||
|
||||
// Render extra cel with text + selected text
|
||||
renderExtraCelBase();
|
||||
renderExtraCelText(true);
|
||||
renderExtraCel(TextPreview::Intermediate);
|
||||
m_doc->setExtraCel(m_extraCel);
|
||||
|
||||
// Paint caret
|
||||
|
@ -226,62 +258,80 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
void renderExtraCelBase()
|
||||
void renderExtraCel(const TextPreview textPreview)
|
||||
{
|
||||
auto extraImg = m_extraCel->image();
|
||||
extraImg->clear(extraImg->maskColor());
|
||||
render::Render().renderLayer(extraImg,
|
||||
m_editor->layer(),
|
||||
m_editor->frame(),
|
||||
gfx::Clip(0, 0, m_extraCel->cel()->bounds()),
|
||||
doc::BlendMode::SRC);
|
||||
}
|
||||
doc::Image* extraImg = m_extraCel->image();
|
||||
ASSERT(extraImg);
|
||||
if (!extraImg)
|
||||
return;
|
||||
|
||||
void renderExtraCelText(const bool withSelection)
|
||||
{
|
||||
const auto textColor = color_utils::color_for_image(Preferences::instance().colorBar.fgColor(),
|
||||
IMAGE_RGB);
|
||||
extraImg->clear(extraImg->maskColor());
|
||||
|
||||
text::TextBlobRef blob = textBlob();
|
||||
if (!blob)
|
||||
return;
|
||||
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;
|
||||
|
||||
doc::ImageRef image = render_text_blob(blob, textColor);
|
||||
if (!image)
|
||||
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);
|
||||
|
||||
// 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()) {
|
||||
selectedBounds.offset(-bounds.origin());
|
||||
|
||||
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);
|
||||
sk_sp<SkSurface> skSurface = wrap_docimage_in_sksurface(blobImage.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);
|
||||
os::Paint paint2 = paint;
|
||||
paint2.blendMode(os::BlendMode::Xor);
|
||||
paint2.style(os::Paint::Style::Fill);
|
||||
surface->drawRect(selectedBounds, paint2);
|
||||
#endif // LAF_SKIA
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doc::blend_image(m_extraCel->image(),
|
||||
image.get(),
|
||||
gfx::Clip(image->bounds().size()),
|
||||
m_doc->sprite()->palette(m_editor->frame()),
|
||||
255,
|
||||
doc::BlendMode::NORMAL);
|
||||
doc::Cel* extraCel = m_extraCel->cel();
|
||||
ASSERT(extraCel);
|
||||
if (!extraCel)
|
||||
return;
|
||||
|
||||
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);
|
||||
|
||||
if (blobImage) {
|
||||
doc::blend_image(extraImg,
|
||||
blobImage.get(),
|
||||
gfx::Clip(blobImage->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)
|
||||
|
@ -297,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());
|
||||
}
|
||||
|
@ -373,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());
|
||||
}
|
||||
|
@ -393,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) {
|
||||
|
@ -407,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;
|
||||
}
|
||||
|
||||
|
@ -460,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()));
|
||||
|
@ -512,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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
@ -18,6 +18,7 @@
|
|||
#include "base/time.h"
|
||||
#include "os/surface.h"
|
||||
#include "text/font.h"
|
||||
#include "text/font_metrics.h"
|
||||
#include "ui/ui.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -44,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);
|
||||
|
@ -172,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()) {
|
||||
|
@ -401,7 +411,6 @@ void FileList::onPaint(ui::PaintEvent& ev)
|
|||
gfx::Rect bounds = clientBounds();
|
||||
|
||||
g->fillRect(theme->colors.background(), bounds);
|
||||
// g->fillRect(bgcolor, gfx::Rect(bounds.x, y, bounds.w, itemSize.h));
|
||||
|
||||
int i = 0, selectedIndex = -1;
|
||||
for (IFileItem* fi : m_list) {
|
||||
|
@ -506,17 +515,28 @@ void FileList::paintItem(ui::Graphics* g, IFileItem* fi, const int i)
|
|||
|
||||
// item name
|
||||
if (isIconView() && textBounds.w > info.bounds.w) {
|
||||
g->drawAlignedUIText(fi->displayName().c_str(),
|
||||
g->drawAlignedUIText(fi->displayName(),
|
||||
fgcolor,
|
||||
bgcolor,
|
||||
(textBounds & gfx::Rect(info.bounds).shrink(2 * guiscale())),
|
||||
ui::CENTER | ui::TOP | ui::CHARWRAP);
|
||||
}
|
||||
else {
|
||||
g->drawText(fi->displayName().c_str(),
|
||||
fgcolor,
|
||||
bgcolor,
|
||||
gfx::Point(textBounds.x + 2 * guiscale(), textBounds.y + 2 * guiscale()));
|
||||
auto blob = text::TextBlob::MakeWithShaper(theme->fontMgr(), font(), fi->displayName());
|
||||
if (blob) {
|
||||
Paint paint;
|
||||
paint.color(fgcolor);
|
||||
paint.style(os::Paint::Fill);
|
||||
|
||||
text::FontMetrics metrics;
|
||||
font()->metrics(&metrics);
|
||||
const float baselineDelta = -metrics.ascent - blob->baseline();
|
||||
|
||||
g->drawTextBlob(
|
||||
blob,
|
||||
gfx::PointF(textBounds.x + 2 * guiscale(), textBounds.y + 2 * guiscale() + baselineDelta),
|
||||
paint);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw thumbnail progress bar
|
||||
|
@ -814,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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(); });
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
@ -166,6 +168,12 @@ void FontEntry::FontFace::onChange()
|
|||
base::ScopedValue lock(m_fromEntryChange, true);
|
||||
SearchEntry::onChange();
|
||||
|
||||
// This shouldn't happen, but we received crash reports where the
|
||||
// m_popup is nullptr here.
|
||||
ASSERT(m_popup);
|
||||
if (!m_popup)
|
||||
return;
|
||||
|
||||
m_popup->setSearchText(text());
|
||||
|
||||
// Changing the search text doesn't generate a FontChange
|
||||
|
@ -207,8 +215,13 @@ void FontEntry::FontFace::onCloseIconPressed()
|
|||
std::sort(pinnedFonts.begin(), pinnedFonts.end());
|
||||
}
|
||||
|
||||
// Refill the list with the new pinned/unpinned item
|
||||
m_popup->recreatePinnedItems();
|
||||
// This shouldn't happen, but we received crash reports where the
|
||||
// m_popup is nullptr here.
|
||||
ASSERT(m_popup);
|
||||
if (m_popup) {
|
||||
// Refill the list with the new pinned/unpinned item
|
||||
m_popup->recreatePinnedItems();
|
||||
}
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
@ -251,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));
|
||||
|
||||
|
@ -288,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
|
||||
|
@ -316,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();
|
||||
|
@ -393,4 +500,9 @@ void FontEntry::onStyleItemClick(ButtonSet::Item* item)
|
|||
}
|
||||
}
|
||||
|
||||
void FontEntry::onStrokeChange()
|
||||
{
|
||||
FontChange(m_info, From::Paint);
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -13,7 +13,7 @@
|
|||
#include "app/ui/key_context.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/vector2d.h"
|
||||
#include "ui/accelerator.h"
|
||||
#include "ui/shortcut.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
@ -106,7 +106,7 @@ inline KeyAction operator&(KeyAction a, KeyAction b)
|
|||
class Key;
|
||||
using KeyPtr = std::shared_ptr<Key>;
|
||||
using Keys = std::vector<KeyPtr>;
|
||||
using KeySourceAccelList = std::vector<std::pair<KeySource, ui::Accelerator>>;
|
||||
using KeySourceShortcutList = std::vector<std::pair<KeySource, ui::Shortcut>>;
|
||||
using DragVector = base::Vector2d<double>;
|
||||
|
||||
class Key {
|
||||
|
@ -119,29 +119,26 @@ public:
|
|||
static KeyPtr MakeDragAction(WheelAction dragAction);
|
||||
|
||||
KeyType type() const { return m_type; }
|
||||
const ui::Accelerators& accels() const;
|
||||
const KeySourceAccelList addsKeys() const { return m_adds; }
|
||||
const KeySourceAccelList delsKeys() const { return m_dels; }
|
||||
const ui::Shortcuts& shortcuts() const;
|
||||
const KeySourceShortcutList& addsKeys() const { return m_adds; }
|
||||
const KeySourceShortcutList& delsKeys() const { return m_dels; }
|
||||
|
||||
void add(const ui::Accelerator& accel, const KeySource source, KeyboardShortcuts& globalKeys);
|
||||
const ui::Accelerator* isPressed(const ui::Message* msg,
|
||||
const KeyboardShortcuts& globalKeys,
|
||||
const KeyContext keyContext) const;
|
||||
const ui::Accelerator* isPressed(const ui::Message* msg,
|
||||
const KeyboardShortcuts& globalKeys) const;
|
||||
void add(const ui::Shortcut& shortcut, KeySource source, KeyboardShortcuts& globalKeys);
|
||||
const ui::Shortcut* isPressed(const ui::Message* msg, KeyContext keyContext) const;
|
||||
const ui::Shortcut* isPressed(const ui::Message* msg) const;
|
||||
bool isPressed() const;
|
||||
bool isLooselyPressed() const;
|
||||
bool isCommandListed() const;
|
||||
|
||||
bool hasAccel(const ui::Accelerator& accel) const;
|
||||
bool hasUserDefinedAccels() const;
|
||||
bool hasShortcut(const ui::Shortcut& shortcut) const;
|
||||
bool hasUserDefinedShortcuts() const;
|
||||
|
||||
// The KeySource indicates from where the key was disabled
|
||||
// (e.g. if it was removed from an extension-defined file, or from
|
||||
// user-defined).
|
||||
void disableAccel(const ui::Accelerator& accel, const KeySource source);
|
||||
void disableShortcut(const ui::Shortcut& shortcut, KeySource source);
|
||||
|
||||
// Resets user accelerators to the original & extension-defined ones.
|
||||
// Resets user shortcuts to the original & extension-defined ones.
|
||||
void reset();
|
||||
|
||||
void copyOriginalToUser();
|
||||
|
@ -164,11 +161,11 @@ public:
|
|||
|
||||
private:
|
||||
KeyType m_type;
|
||||
KeySourceAccelList m_adds;
|
||||
KeySourceAccelList m_dels;
|
||||
// Final list of accelerators after processing the
|
||||
KeySourceShortcutList m_adds;
|
||||
KeySourceShortcutList m_dels;
|
||||
// Final list of shortcuts after processing the
|
||||
// addition/deletion of extension-defined & user-defined keys.
|
||||
mutable std::unique_ptr<ui::Accelerators> m_accels;
|
||||
mutable std::unique_ptr<ui::Shortcuts> m_shortcuts;
|
||||
KeyContext m_keycontext;
|
||||
|
||||
// for KeyType::Command
|
||||
|
|
|
@ -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
|
||||
|
@ -28,8 +28,8 @@
|
|||
#include "app/xml_document.h"
|
||||
#include "app/xml_exception.h"
|
||||
#include "fmt/format.h"
|
||||
#include "ui/accelerator.h"
|
||||
#include "ui/message.h"
|
||||
#include "ui/shortcut.h"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
|
@ -244,13 +244,13 @@ std::string get_user_friendly_string_for_wheelaction(app::WheelAction wheelActio
|
|||
return std::string();
|
||||
}
|
||||
|
||||
void erase_accel(app::KeySourceAccelList& kvs,
|
||||
const app::KeySource source,
|
||||
const ui::Accelerator& accel)
|
||||
void erase_shortcut(app::KeySourceShortcutList& kvs,
|
||||
const app::KeySource source,
|
||||
const ui::Shortcut& shortcut)
|
||||
{
|
||||
for (auto it = kvs.begin(); it != kvs.end();) {
|
||||
auto& kv = *it;
|
||||
if (kv.first == source && kv.second == accel) {
|
||||
if (kv.first == source && kv.second == shortcut) {
|
||||
it = kvs.erase(it);
|
||||
}
|
||||
else
|
||||
|
@ -258,7 +258,7 @@ void erase_accel(app::KeySourceAccelList& kvs,
|
|||
}
|
||||
}
|
||||
|
||||
void erase_accels(app::KeySourceAccelList& kvs, const app::KeySource source)
|
||||
void erase_shortcuts(app::KeySourceShortcutList& kvs, const app::KeySource source)
|
||||
{
|
||||
for (auto it = kvs.begin(); it != kvs.end();) {
|
||||
auto& kv = *it;
|
||||
|
@ -436,104 +436,100 @@ KeyPtr Key::MakeDragAction(WheelAction dragAction)
|
|||
return k;
|
||||
}
|
||||
|
||||
const ui::Accelerators& Key::accels() const
|
||||
const ui::Shortcuts& Key::shortcuts() const
|
||||
{
|
||||
if (!m_accels) {
|
||||
m_accels = std::make_unique<ui::Accelerators>();
|
||||
if (!m_shortcuts) {
|
||||
m_shortcuts = std::make_unique<ui::Shortcuts>();
|
||||
|
||||
// Add default keys
|
||||
for (const auto& kv : m_adds) {
|
||||
if (kv.first == KeySource::Original)
|
||||
m_accels->add(kv.second);
|
||||
m_shortcuts->add(kv.second);
|
||||
}
|
||||
|
||||
// Delete/add extension-defined keys
|
||||
for (const auto& kv : m_dels) {
|
||||
if (kv.first == KeySource::ExtensionDefined)
|
||||
m_accels->remove(kv.second);
|
||||
m_shortcuts->remove(kv.second);
|
||||
else {
|
||||
ASSERT(kv.first != KeySource::Original);
|
||||
}
|
||||
}
|
||||
for (const auto& kv : m_adds) {
|
||||
if (kv.first == KeySource::ExtensionDefined)
|
||||
m_accels->add(kv.second);
|
||||
m_shortcuts->add(kv.second);
|
||||
}
|
||||
|
||||
// Delete/add user-defined keys
|
||||
for (const auto& kv : m_dels) {
|
||||
if (kv.first == KeySource::UserDefined)
|
||||
m_accels->remove(kv.second);
|
||||
m_shortcuts->remove(kv.second);
|
||||
}
|
||||
for (const auto& kv : m_adds) {
|
||||
if (kv.first == KeySource::UserDefined)
|
||||
m_accels->add(kv.second);
|
||||
m_shortcuts->add(kv.second);
|
||||
}
|
||||
}
|
||||
return *m_accels;
|
||||
return *m_shortcuts;
|
||||
}
|
||||
|
||||
void Key::add(const ui::Accelerator& accel, const KeySource source, KeyboardShortcuts& globalKeys)
|
||||
void Key::add(const ui::Shortcut& shortcut, const KeySource source, KeyboardShortcuts& globalKeys)
|
||||
{
|
||||
m_adds.emplace_back(source, accel);
|
||||
m_accels.reset();
|
||||
m_adds.emplace_back(source, shortcut);
|
||||
m_shortcuts.reset();
|
||||
|
||||
// Remove the accelerator from other commands
|
||||
// Remove the shortcut from other commands
|
||||
if (source == KeySource::ExtensionDefined || source == KeySource::UserDefined) {
|
||||
erase_accel(m_dels, source, accel);
|
||||
erase_shortcut(m_dels, source, shortcut);
|
||||
|
||||
globalKeys.disableAccel(accel, source, m_keycontext, this);
|
||||
globalKeys.disableShortcut(shortcut, source, m_keycontext, this);
|
||||
}
|
||||
}
|
||||
|
||||
const ui::Accelerator* Key::isPressed(const Message* msg,
|
||||
const KeyboardShortcuts& globalKeys,
|
||||
const KeyContext keyContext) const
|
||||
const ui::Shortcut* Key::isPressed(const Message* msg, const KeyContext keyContext) const
|
||||
{
|
||||
if (auto keyMsg = dynamic_cast<const KeyMessage*>(msg)) {
|
||||
for (const Accelerator& accel : accels()) {
|
||||
if (accel.isPressed(keyMsg->modifiers(), keyMsg->scancode(), keyMsg->unicodeChar()) &&
|
||||
if (const auto* keyMsg = dynamic_cast<const KeyMessage*>(msg)) {
|
||||
for (const Shortcut& shortcut : shortcuts()) {
|
||||
if (shortcut.isPressed(keyMsg->modifiers(), keyMsg->scancode(), keyMsg->unicodeChar()) &&
|
||||
(m_keycontext == KeyContext::Any || m_keycontext == keyContext)) {
|
||||
return &accel;
|
||||
return &shortcut;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto mouseMsg = dynamic_cast<const MouseMessage*>(msg)) {
|
||||
for (const Accelerator& accel : accels()) {
|
||||
if ((accel.modifiers() == mouseMsg->modifiers()) &&
|
||||
else if (const auto* mouseMsg = dynamic_cast<const MouseMessage*>(msg)) {
|
||||
for (const Shortcut& shortcut : shortcuts()) {
|
||||
if ((shortcut.modifiers() == mouseMsg->modifiers()) &&
|
||||
(m_keycontext == KeyContext::Any ||
|
||||
// TODO we could have multiple mouse wheel key-context,
|
||||
// like "sprite editor" context, or "timeline" context,
|
||||
// etc.
|
||||
m_keycontext == KeyContext::MouseWheel)) {
|
||||
return &accel;
|
||||
return &shortcut;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ui::Accelerator* Key::isPressed(const Message* msg, const KeyboardShortcuts& globalKeys) const
|
||||
const ui::Shortcut* Key::isPressed(const Message* msg) const
|
||||
{
|
||||
return isPressed(msg, globalKeys, globalKeys.getCurrentKeyContext());
|
||||
return isPressed(msg, KeyboardShortcuts::getCurrentKeyContext());
|
||||
}
|
||||
|
||||
bool Key::isPressed() const
|
||||
{
|
||||
for (const Accelerator& accel : this->accels()) {
|
||||
if (accel.isPressed())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
const auto& ss = this->shortcuts();
|
||||
return std::any_of(ss.begin(), ss.end(), [](const Shortcut& shortcut) {
|
||||
return shortcut.isPressed();
|
||||
});
|
||||
}
|
||||
|
||||
bool Key::isLooselyPressed() const
|
||||
{
|
||||
for (const Accelerator& accel : this->accels()) {
|
||||
if (accel.isLooselyPressed())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
const auto& ss = this->shortcuts();
|
||||
return std::any_of(ss.begin(), ss.end(), [](const Shortcut& shortcut) {
|
||||
return shortcut.isLooselyPressed();
|
||||
});
|
||||
}
|
||||
|
||||
bool Key::isCommandListed() const
|
||||
|
@ -541,51 +537,49 @@ bool Key::isCommandListed() const
|
|||
return type() == KeyType::Command && command()->isListed(params());
|
||||
}
|
||||
|
||||
bool Key::hasAccel(const ui::Accelerator& accel) const
|
||||
bool Key::hasShortcut(const ui::Shortcut& shortcut) const
|
||||
{
|
||||
return accels().has(accel);
|
||||
return shortcuts().has(shortcut);
|
||||
}
|
||||
|
||||
bool Key::hasUserDefinedAccels() const
|
||||
bool Key::hasUserDefinedShortcuts() const
|
||||
{
|
||||
for (const auto& kv : m_adds) {
|
||||
if (kv.first == KeySource::UserDefined)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return std::any_of(m_adds.begin(), m_adds.end(), [](const auto& kv) {
|
||||
return (kv.first == KeySource::UserDefined);
|
||||
});
|
||||
}
|
||||
|
||||
void Key::disableAccel(const ui::Accelerator& accel, const KeySource source)
|
||||
void Key::disableShortcut(const ui::Shortcut& shortcut, const KeySource source)
|
||||
{
|
||||
// It doesn't make sense that the default keyboard shortcuts file
|
||||
// (gui.xml) removes some accelerator.
|
||||
// (gui.xml) removes some shortcut.
|
||||
ASSERT(source != KeySource::Original);
|
||||
|
||||
erase_accel(m_adds, source, accel);
|
||||
erase_accel(m_dels, source, accel);
|
||||
erase_shortcut(m_adds, source, shortcut);
|
||||
erase_shortcut(m_dels, source, shortcut);
|
||||
|
||||
m_dels.emplace_back(source, accel);
|
||||
m_accels.reset();
|
||||
m_dels.emplace_back(source, shortcut);
|
||||
m_shortcuts.reset();
|
||||
}
|
||||
|
||||
void Key::reset()
|
||||
{
|
||||
erase_accels(m_adds, KeySource::UserDefined);
|
||||
erase_accels(m_dels, KeySource::UserDefined);
|
||||
m_accels.reset();
|
||||
erase_shortcuts(m_adds, KeySource::UserDefined);
|
||||
erase_shortcuts(m_dels, KeySource::UserDefined);
|
||||
m_shortcuts.reset();
|
||||
}
|
||||
|
||||
void Key::copyOriginalToUser()
|
||||
{
|
||||
// Erase all user-defined keys
|
||||
erase_accels(m_adds, KeySource::UserDefined);
|
||||
erase_accels(m_dels, KeySource::UserDefined);
|
||||
erase_shortcuts(m_adds, KeySource::UserDefined);
|
||||
erase_shortcuts(m_dels, KeySource::UserDefined);
|
||||
|
||||
// Then copy all original & extension-defined keys as user-defined
|
||||
auto copy = m_adds;
|
||||
for (const auto& kv : copy)
|
||||
m_adds.emplace_back(KeySource::UserDefined, kv.second);
|
||||
m_accels.reset();
|
||||
m_shortcuts.reset();
|
||||
}
|
||||
|
||||
std::string Key::triggerString() const
|
||||
|
@ -693,21 +687,21 @@ void KeyboardShortcuts::importFile(XMLElement* rootElement, KeySource source)
|
|||
// add the keyboard shortcut to the command
|
||||
KeyPtr key = this->command(command_name, params, keycontext);
|
||||
if (key && command_key) {
|
||||
Accelerator accel(command_key);
|
||||
Shortcut shortcut(command_key);
|
||||
|
||||
if (!removed) {
|
||||
key->add(accel, source, *this);
|
||||
key->add(shortcut, source, *this);
|
||||
|
||||
// Add the shortcut to the menuitems with this command
|
||||
// (this is only visual, the
|
||||
// "CustomizedGuiManager::onProcessMessage" is the only
|
||||
// one that process keyboard shortcuts)
|
||||
if (key->accels().size() == 1) {
|
||||
if (key->shortcuts().size() == 1) {
|
||||
AppMenus::instance()->applyShortcutToMenuitemsWithCommand(command, params, key);
|
||||
}
|
||||
}
|
||||
else
|
||||
key->disableAccel(accel, source);
|
||||
key->disableShortcut(shortcut, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -729,12 +723,12 @@ void KeyboardShortcuts::importFile(XMLElement* rootElement, KeySource source)
|
|||
KeyPtr key = this->tool(tool);
|
||||
if (key && tool_key) {
|
||||
LOG(VERBOSE, "KEYS: Shortcut for tool %s: %s\n", tool_id, tool_key);
|
||||
Accelerator accel(tool_key);
|
||||
Shortcut shortcut(tool_key);
|
||||
|
||||
if (!removed)
|
||||
key->add(accel, source, *this);
|
||||
key->add(shortcut, source, *this);
|
||||
else
|
||||
key->disableAccel(accel, source);
|
||||
key->disableShortcut(shortcut, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -755,12 +749,12 @@ void KeyboardShortcuts::importFile(XMLElement* rootElement, KeySource source)
|
|||
KeyPtr key = this->quicktool(tool);
|
||||
if (key && tool_key) {
|
||||
LOG(VERBOSE, "KEYS: Shortcut for quicktool %s: %s\n", tool_id, tool_key);
|
||||
Accelerator accel(tool_key);
|
||||
Shortcut shortcut(tool_key);
|
||||
|
||||
if (!removed)
|
||||
key->add(accel, source, *this);
|
||||
key->add(shortcut, source, *this);
|
||||
else
|
||||
key->disableAccel(accel, source);
|
||||
key->disableShortcut(shortcut, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -791,12 +785,12 @@ void KeyboardShortcuts::importFile(XMLElement* rootElement, KeySource source)
|
|||
action_id,
|
||||
(keycontextstr ? keycontextstr : "Any"),
|
||||
action_key);
|
||||
Accelerator accel(action_key);
|
||||
Shortcut shortcut(action_key);
|
||||
|
||||
if (!removed)
|
||||
key->add(accel, source, *this);
|
||||
key->add(shortcut, source, *this);
|
||||
else
|
||||
key->disableAccel(accel, source);
|
||||
key->disableShortcut(shortcut, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -817,12 +811,12 @@ void KeyboardShortcuts::importFile(XMLElement* rootElement, KeySource source)
|
|||
KeyPtr key = this->wheelAction(action);
|
||||
if (key && action_key) {
|
||||
LOG(VERBOSE, "KEYS: Shortcut for wheel action %s: %s\n", action_id, action_key);
|
||||
Accelerator accel(action_key);
|
||||
Shortcut shortcut(action_key);
|
||||
|
||||
if (!removed)
|
||||
key->add(accel, source, *this);
|
||||
key->add(shortcut, source, *this);
|
||||
else
|
||||
key->disableAccel(accel, source);
|
||||
key->disableShortcut(shortcut, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -854,12 +848,12 @@ void KeyboardShortcuts::importFile(XMLElement* rootElement, KeySource source)
|
|||
}
|
||||
|
||||
LOG(VERBOSE, "KEYS: Shortcut for drag action %s: %s\n", action_id, action_key);
|
||||
Accelerator accel(action_key);
|
||||
Shortcut shortcut(action_key);
|
||||
|
||||
if (!removed)
|
||||
key->add(accel, source, *this);
|
||||
key->add(shortcut, source, *this);
|
||||
else
|
||||
key->disableAccel(accel, source);
|
||||
key->disableShortcut(shortcut, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -904,24 +898,25 @@ void KeyboardShortcuts::exportFile(const std::string& filename)
|
|||
void KeyboardShortcuts::exportKeys(XMLElement* parent, KeyType type)
|
||||
{
|
||||
for (KeyPtr& key : m_keys) {
|
||||
// Save only user defined accelerators.
|
||||
// Save only user defined shortcuts.
|
||||
if (key->type() != type)
|
||||
continue;
|
||||
|
||||
for (const auto& kv : key->delsKeys())
|
||||
if (kv.first == KeySource::UserDefined)
|
||||
exportAccel(parent, key.get(), kv.second, true);
|
||||
exportShortcut(parent, key.get(), kv.second, true);
|
||||
|
||||
for (const auto& kv : key->addsKeys())
|
||||
if (kv.first == KeySource::UserDefined)
|
||||
exportAccel(parent, key.get(), kv.second, false);
|
||||
exportShortcut(parent, key.get(), kv.second, false);
|
||||
}
|
||||
}
|
||||
|
||||
void KeyboardShortcuts::exportAccel(XMLElement* parent,
|
||||
const Key* key,
|
||||
const ui::Accelerator& accel,
|
||||
bool removed)
|
||||
// static
|
||||
void KeyboardShortcuts::exportShortcut(XMLElement* parent,
|
||||
const Key* key,
|
||||
const ui::Shortcut& shortcut,
|
||||
bool removed)
|
||||
{
|
||||
XMLElement* elem = parent->InsertNewChildElement("key");
|
||||
|
||||
|
@ -964,7 +959,7 @@ void KeyboardShortcuts::exportAccel(XMLElement* parent,
|
|||
break;
|
||||
}
|
||||
|
||||
elem->SetAttribute("shortcut", accel.toString().c_str());
|
||||
elem->SetAttribute("shortcut", shortcut.toString().c_str());
|
||||
|
||||
if (removed)
|
||||
elem->SetAttribute("removed", "true");
|
||||
|
@ -1062,27 +1057,28 @@ KeyPtr KeyboardShortcuts::dragAction(const WheelAction dragAction) const
|
|||
return key;
|
||||
}
|
||||
|
||||
void KeyboardShortcuts::disableAccel(const ui::Accelerator& accel,
|
||||
const KeySource source,
|
||||
const KeyContext keyContext,
|
||||
const Key* newKey)
|
||||
void KeyboardShortcuts::disableShortcut(const ui::Shortcut& shortcut,
|
||||
const KeySource source,
|
||||
const KeyContext keyContext,
|
||||
const Key* newKey)
|
||||
{
|
||||
for (KeyPtr& key : m_keys) {
|
||||
if (key.get() != newKey && key->keycontext() == keyContext && key->hasAccel(accel) &&
|
||||
if (key.get() != newKey && key->keycontext() == keyContext && key->hasShortcut(shortcut) &&
|
||||
// Tools can contain the same keyboard shortcut
|
||||
(key->type() != KeyType::Tool || newKey == nullptr || newKey->type() != KeyType::Tool) &&
|
||||
// DragActions can share the same keyboard shortcut (e.g. to
|
||||
// change different values using different DragVectors)
|
||||
(key->type() != KeyType::DragAction || newKey == nullptr ||
|
||||
newKey->type() != KeyType::DragAction)) {
|
||||
key->disableAccel(accel, source);
|
||||
key->disableShortcut(shortcut, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KeyContext KeyboardShortcuts::getCurrentKeyContext() const
|
||||
// static
|
||||
KeyContext KeyboardShortcuts::getCurrentKeyContext()
|
||||
{
|
||||
auto ctx = UIContext::instance();
|
||||
auto* ctx = UIContext::instance();
|
||||
Doc* doc = ctx->activeDocument();
|
||||
if (doc && doc->isMaskVisible() &&
|
||||
// The active key context will be the selectedTool() (in the
|
||||
|
@ -1116,7 +1112,7 @@ bool KeyboardShortcuts::getCommandFromKeyMessage(const Message* msg,
|
|||
int n = (contexts[0] != contexts[1] ? 2 : 1);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
for (KeyPtr& key : m_keys) {
|
||||
if (key->type() == KeyType::Command && key->isPressed(msg, *this, contexts[i])) {
|
||||
if (key->type() == KeyType::Command && key->isPressed(msg, contexts[i])) {
|
||||
if (command)
|
||||
*command = key->command();
|
||||
if (params)
|
||||
|
@ -1168,12 +1164,12 @@ WheelAction KeyboardShortcuts::getWheelActionFromMouseMessage(const KeyContext c
|
|||
const ui::Message* msg)
|
||||
{
|
||||
WheelAction wheelAction = WheelAction::None;
|
||||
const ui::Accelerator* bestAccel = nullptr;
|
||||
const ui::Shortcut* bestShortcut = nullptr;
|
||||
for (const KeyPtr& key : m_keys) {
|
||||
if (key->type() == KeyType::WheelAction && key->keycontext() == context) {
|
||||
const ui::Accelerator* accel = key->isPressed(msg, *this);
|
||||
if ((accel) && (!bestAccel || bestAccel->modifiers() < accel->modifiers())) {
|
||||
bestAccel = accel;
|
||||
const ui::Shortcut* shortcut = key->isPressed(msg);
|
||||
if ((shortcut) && (!bestShortcut || bestShortcut->modifiers() < shortcut->modifiers())) {
|
||||
bestShortcut = shortcut;
|
||||
wheelAction = key->wheelAction();
|
||||
}
|
||||
}
|
||||
|
@ -1181,15 +1177,14 @@ WheelAction KeyboardShortcuts::getWheelActionFromMouseMessage(const KeyContext c
|
|||
return wheelAction;
|
||||
}
|
||||
|
||||
Keys KeyboardShortcuts::getDragActionsFromKeyMessage(const KeyContext context,
|
||||
const ui::Message* msg)
|
||||
Keys KeyboardShortcuts::getDragActionsFromKeyMessage(const ui::Message* msg)
|
||||
{
|
||||
KeyPtr bestKey = nullptr;
|
||||
Keys keys;
|
||||
for (const KeyPtr& key : m_keys) {
|
||||
if (key->type() == KeyType::DragAction) {
|
||||
const ui::Accelerator* accel = key->isPressed(msg, *this);
|
||||
if (accel) {
|
||||
const ui::Shortcut* shortcut = key->isPressed(msg);
|
||||
if (shortcut) {
|
||||
keys.push_back(key);
|
||||
}
|
||||
}
|
||||
|
@ -1199,11 +1194,9 @@ Keys KeyboardShortcuts::getDragActionsFromKeyMessage(const KeyContext context,
|
|||
|
||||
bool KeyboardShortcuts::hasMouseWheelCustomization() const
|
||||
{
|
||||
for (const KeyPtr& key : m_keys) {
|
||||
if (key->type() == KeyType::WheelAction && key->hasUserDefinedAccels())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return std::any_of(m_keys.begin(), m_keys.end(), [](const KeyPtr& key) {
|
||||
return (key->type() == KeyType::WheelAction && key->hasUserDefinedShortcuts());
|
||||
});
|
||||
}
|
||||
|
||||
void KeyboardShortcuts::clearMouseWheelKeys()
|
||||
|
@ -1245,38 +1238,38 @@ void KeyboardShortcuts::setDefaultMouseWheelKeys(const bool zoomWithWheel)
|
|||
|
||||
KeyPtr key;
|
||||
key = std::make_shared<Key>(WheelAction::Zoom);
|
||||
key->add(Accelerator(zoomWithWheel ? kKeyNoneModifier : kKeyCtrlModifier, kKeyNil, 0),
|
||||
key->add(Shortcut(zoomWithWheel ? kKeyNoneModifier : kKeyCtrlModifier, kKeyNil, 0),
|
||||
KeySource::Original,
|
||||
*this);
|
||||
m_keys.push_back(key);
|
||||
|
||||
if (!zoomWithWheel) {
|
||||
key = std::make_shared<Key>(WheelAction::VScroll);
|
||||
key->add(Accelerator(kKeyNoneModifier, kKeyNil, 0), KeySource::Original, *this);
|
||||
key->add(Shortcut(kKeyNoneModifier, kKeyNil, 0), KeySource::Original, *this);
|
||||
m_keys.push_back(key);
|
||||
}
|
||||
|
||||
key = std::make_shared<Key>(WheelAction::HScroll);
|
||||
key->add(Accelerator(kKeyShiftModifier, kKeyNil, 0), KeySource::Original, *this);
|
||||
key->add(Shortcut(kKeyShiftModifier, kKeyNil, 0), KeySource::Original, *this);
|
||||
m_keys.push_back(key);
|
||||
|
||||
key = std::make_shared<Key>(WheelAction::FgColor);
|
||||
key->add(Accelerator(kKeyAltModifier, kKeyNil, 0), KeySource::Original, *this);
|
||||
key->add(Shortcut(kKeyAltModifier, kKeyNil, 0), KeySource::Original, *this);
|
||||
m_keys.push_back(key);
|
||||
|
||||
key = std::make_shared<Key>(WheelAction::BgColor);
|
||||
key->add(Accelerator((KeyModifiers)(kKeyAltModifier | kKeyShiftModifier), kKeyNil, 0),
|
||||
key->add(Shortcut((KeyModifiers)(kKeyAltModifier | kKeyShiftModifier), kKeyNil, 0),
|
||||
KeySource::Original,
|
||||
*this);
|
||||
m_keys.push_back(key);
|
||||
|
||||
if (zoomWithWheel) {
|
||||
key = std::make_shared<Key>(WheelAction::BrushSize);
|
||||
key->add(Accelerator(kKeyCtrlModifier, kKeyNil, 0), KeySource::Original, *this);
|
||||
key->add(Shortcut(kKeyCtrlModifier, kKeyNil, 0), KeySource::Original, *this);
|
||||
m_keys.push_back(key);
|
||||
|
||||
key = std::make_shared<Key>(WheelAction::Frame);
|
||||
key->add(Accelerator((KeyModifiers)(kKeyCtrlModifier | kKeyShiftModifier), kKeyNil, 0),
|
||||
key->add(Shortcut((KeyModifiers)(kKeyCtrlModifier | kKeyShiftModifier), kKeyNil, 0),
|
||||
KeySource::Original,
|
||||
*this);
|
||||
m_keys.push_back(key);
|
||||
|
@ -1321,9 +1314,9 @@ std::string key_tooltip(const char* str, const app::Key* key)
|
|||
std::string res;
|
||||
if (str)
|
||||
res += str;
|
||||
if (key && !key->accels().empty()) {
|
||||
if (key && !key->shortcuts().empty()) {
|
||||
res += " (";
|
||||
res += key->accels().front().toString();
|
||||
res += key->shortcuts().front().toString();
|
||||
res += ")";
|
||||
}
|
||||
return res;
|
||||
|
|
|
@ -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
|
||||
|
@ -50,20 +50,20 @@ public:
|
|||
KeyPtr tool(tools::Tool* tool) const;
|
||||
KeyPtr quicktool(tools::Tool* tool) const;
|
||||
KeyPtr action(const KeyAction action, const KeyContext keyContext = KeyContext::Any) const;
|
||||
KeyPtr wheelAction(const WheelAction action) const;
|
||||
KeyPtr dragAction(const WheelAction action) const;
|
||||
KeyPtr wheelAction(WheelAction action) const;
|
||||
KeyPtr dragAction(WheelAction action) const;
|
||||
|
||||
void disableAccel(const ui::Accelerator& accel,
|
||||
const KeySource source,
|
||||
const KeyContext keyContext,
|
||||
const Key* newKey);
|
||||
void disableShortcut(const ui::Shortcut& shortcut,
|
||||
KeySource source,
|
||||
KeyContext keyContext,
|
||||
const Key* newKey);
|
||||
|
||||
KeyContext getCurrentKeyContext() const;
|
||||
static KeyContext getCurrentKeyContext();
|
||||
bool getCommandFromKeyMessage(const ui::Message* msg, Command** command, Params* params);
|
||||
tools::Tool* getCurrentQuicktool(tools::Tool* currentTool);
|
||||
KeyAction getCurrentActionModifiers(KeyContext context);
|
||||
WheelAction getWheelActionFromMouseMessage(const KeyContext context, const ui::Message* msg);
|
||||
Keys getDragActionsFromKeyMessage(const KeyContext context, const ui::Message* msg);
|
||||
WheelAction getWheelActionFromMouseMessage(KeyContext context, const ui::Message* msg);
|
||||
Keys getDragActionsFromKeyMessage(const ui::Message* msg);
|
||||
bool hasMouseWheelCustomization() const;
|
||||
void clearMouseWheelKeys();
|
||||
void addMissingMouseWheelKeys();
|
||||
|
@ -77,10 +77,10 @@ public:
|
|||
|
||||
private:
|
||||
void exportKeys(tinyxml2::XMLElement* parent, KeyType type);
|
||||
void exportAccel(tinyxml2::XMLElement* parent,
|
||||
const Key* key,
|
||||
const ui::Accelerator& accel,
|
||||
bool removed);
|
||||
static void exportShortcut(tinyxml2::XMLElement* parent,
|
||||
const Key* key,
|
||||
const ui::Shortcut& shortcut,
|
||||
bool removed);
|
||||
|
||||
mutable Keys m_keys;
|
||||
};
|
||||
|
|
|
@ -177,9 +177,6 @@ protected:
|
|||
ui::Style* style = theme->styles.newsItem();
|
||||
ui::Style* styleDetail = theme->styles.newsItemDetail();
|
||||
|
||||
text::FontMetrics metrics;
|
||||
font()->metrics(&metrics);
|
||||
|
||||
gfx::Size textSize = theme->calcSizeHint(this, style);
|
||||
gfx::Rect textBounds(bounds.x, bounds.y, bounds.w, textSize.h);
|
||||
gfx::Rect detailsBounds(bounds.x, bounds.y + textSize.h, bounds.w, bounds.h - textSize.h);
|
||||
|
@ -187,19 +184,28 @@ protected:
|
|||
gfx::Border border = theme->calcBorder(this, style);
|
||||
gfx::Border borderDetail = theme->calcBorder(this, styleDetail);
|
||||
|
||||
PaintWidgetPartInfo info(this);
|
||||
info.text = &m_title;
|
||||
info.baseline = border.top() - metrics.ascent;
|
||||
theme->paintWidgetPart(g, style, bounds, info);
|
||||
if (!m_titleBlob)
|
||||
m_titleBlob = text::TextBlob::MakeWithShaper(theme->fontMgr(), font(), m_title);
|
||||
|
||||
info.text = &m_desc;
|
||||
info.baseline += border.bottom() + borderDetail.top() + metrics.descent - metrics.ascent;
|
||||
theme->paintWidgetPart(g, styleDetail, detailsBounds, info);
|
||||
PaintWidgetPartInfo info(this);
|
||||
info.baseline = border.top();
|
||||
if (m_titleBlob) {
|
||||
info.textBlob = m_titleBlob;
|
||||
info.baseline += m_titleBlob->baseline();
|
||||
theme->paintWidgetPart(g, style, bounds, info);
|
||||
info.baseline += border.bottom();
|
||||
|
||||
info.text = &m_desc;
|
||||
info.textBlob = nullptr;
|
||||
info.baseline += borderDetail.top() + m_titleBlob->baseline();
|
||||
theme->paintWidgetPart(g, styleDetail, detailsBounds, info);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_title;
|
||||
std::string m_desc;
|
||||
text::TextBlobRef m_titleBlob;
|
||||
};
|
||||
|
||||
class ProblemsItem : public NewsItem {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2020 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
|
||||
|
@ -9,7 +9,7 @@
|
|||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/ui/select_accelerator.h"
|
||||
#include "app/ui/select_shortcut.h"
|
||||
|
||||
#include "app/ui/key.h"
|
||||
#include "app/ui/keyboard_shortcuts.h"
|
||||
|
@ -23,23 +23,23 @@ namespace app {
|
|||
|
||||
using namespace ui;
|
||||
|
||||
class SelectAccelerator::KeyField : public ui::Entry {
|
||||
class SelectShortcut::KeyField : public ui::Entry {
|
||||
public:
|
||||
KeyField(const Accelerator& accel) : ui::Entry(256, "")
|
||||
KeyField(const Shortcut& shortcut) : ui::Entry(256, "")
|
||||
{
|
||||
setTranslateDeadKeys(false);
|
||||
setExpansive(true);
|
||||
setFocusMagnet(true);
|
||||
setAccel(accel);
|
||||
setShortcut(shortcut);
|
||||
}
|
||||
|
||||
void setAccel(const Accelerator& accel)
|
||||
void setShortcut(const Shortcut& shortcut)
|
||||
{
|
||||
m_accel = accel;
|
||||
m_shortcut = shortcut;
|
||||
updateText();
|
||||
}
|
||||
|
||||
obs::signal<void(const ui::Accelerator*)> AccelChange;
|
||||
obs::signal<void(const ui::Shortcut*)> ShortcutChange;
|
||||
|
||||
protected:
|
||||
bool onProcessMessage(Message* msg) override
|
||||
|
@ -56,19 +56,19 @@ protected:
|
|||
if (keymsg->scancode() == kKeySpace)
|
||||
modifiers = (KeyModifiers)(modifiers & ~kKeySpaceModifier);
|
||||
|
||||
m_accel = Accelerator(
|
||||
m_shortcut = Shortcut(
|
||||
modifiers,
|
||||
keymsg->scancode(),
|
||||
keymsg->unicodeChar() > 32 ? std::tolower(keymsg->unicodeChar()) : 0);
|
||||
|
||||
// Convert the accelerator to a string, and parse it
|
||||
// again. Just to obtain the exact accelerator we'll read
|
||||
// Convert the shortcut to a string, and parse it
|
||||
// again. Just to obtain the exact shortcut we'll read
|
||||
// when we import the gui.xml file or an .aseprite-keys file.
|
||||
m_accel = Accelerator(m_accel.toString());
|
||||
m_shortcut = Shortcut(m_shortcut.toString());
|
||||
|
||||
updateText();
|
||||
|
||||
AccelChange(&m_accel);
|
||||
ShortcutChange(&m_shortcut);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
@ -78,20 +78,20 @@ protected:
|
|||
|
||||
void updateText()
|
||||
{
|
||||
setText(
|
||||
Accelerator(kKeyNoneModifier, m_accel.scancode(), m_accel.unicodeChar()).toString().c_str());
|
||||
setText(Shortcut(kKeyNoneModifier, m_shortcut.scancode(), m_shortcut.unicodeChar()).toString());
|
||||
}
|
||||
|
||||
Accelerator m_accel;
|
||||
private:
|
||||
Shortcut m_shortcut;
|
||||
};
|
||||
|
||||
SelectAccelerator::SelectAccelerator(const ui::Accelerator& accel,
|
||||
const KeyContext keyContext,
|
||||
const KeyboardShortcuts& currentKeys)
|
||||
: m_keyField(new KeyField(accel))
|
||||
SelectShortcut::SelectShortcut(const ui::Shortcut& shortcut,
|
||||
const KeyContext keyContext,
|
||||
const KeyboardShortcuts& currentKeys)
|
||||
: m_keyField(new KeyField(shortcut))
|
||||
, m_keyContext(keyContext)
|
||||
, m_currentKeys(currentKeys)
|
||||
, m_accel(accel)
|
||||
, m_shortcut(shortcut)
|
||||
, m_ok(false)
|
||||
, m_modified(false)
|
||||
{
|
||||
|
@ -107,7 +107,7 @@ SelectAccelerator::SelectAccelerator(const ui::Accelerator& accel,
|
|||
space()->Click.connect([this] { onModifierChange(kKeySpaceModifier, space()); });
|
||||
win()->Click.connect([this] { onModifierChange(kKeyWinModifier, win()); });
|
||||
|
||||
m_keyField->AccelChange.connect(&SelectAccelerator::onAccelChange, this);
|
||||
m_keyField->ShortcutChange.connect(&SelectShortcut::onShortcutChange, this);
|
||||
clearButton()->Click.connect([this] { onClear(); });
|
||||
okButton()->Click.connect([this] { onOK(); });
|
||||
cancelButton()->Click.connect([this] { onCancel(); });
|
||||
|
@ -115,63 +115,63 @@ SelectAccelerator::SelectAccelerator(const ui::Accelerator& accel,
|
|||
addChild(&m_tooltipManager);
|
||||
}
|
||||
|
||||
void SelectAccelerator::onModifierChange(KeyModifiers modifier, CheckBox* checkbox)
|
||||
void SelectShortcut::onModifierChange(KeyModifiers modifier, CheckBox* checkbox)
|
||||
{
|
||||
bool state = (checkbox->isSelected());
|
||||
KeyModifiers modifiers = m_accel.modifiers();
|
||||
KeyScancode scancode = m_accel.scancode();
|
||||
int unicodeChar = m_accel.unicodeChar();
|
||||
KeyModifiers modifiers = m_shortcut.modifiers();
|
||||
KeyScancode scancode = m_shortcut.scancode();
|
||||
int unicodeChar = m_shortcut.unicodeChar();
|
||||
|
||||
modifiers = (KeyModifiers)((modifiers & ~modifier) | (state ? modifier : 0));
|
||||
if (modifiers == kKeySpaceModifier && scancode == kKeySpace)
|
||||
modifiers = kKeyNoneModifier;
|
||||
|
||||
m_accel = Accelerator(modifiers, scancode, unicodeChar);
|
||||
m_shortcut = Shortcut(modifiers, scancode, unicodeChar);
|
||||
|
||||
m_keyField->setAccel(m_accel);
|
||||
m_keyField->setShortcut(m_shortcut);
|
||||
m_keyField->requestFocus();
|
||||
updateAssignedTo();
|
||||
}
|
||||
|
||||
void SelectAccelerator::onAccelChange(const ui::Accelerator* accel)
|
||||
void SelectShortcut::onShortcutChange(const ui::Shortcut* shortcut)
|
||||
{
|
||||
m_accel = *accel;
|
||||
m_shortcut = *shortcut;
|
||||
updateModifiers();
|
||||
updateAssignedTo();
|
||||
}
|
||||
|
||||
void SelectAccelerator::onClear()
|
||||
void SelectShortcut::onClear()
|
||||
{
|
||||
m_accel = Accelerator(kKeyNoneModifier, kKeyNil, 0);
|
||||
m_shortcut = Shortcut(kKeyNoneModifier, kKeyNil, 0);
|
||||
|
||||
m_keyField->setAccel(m_accel);
|
||||
m_keyField->setShortcut(m_shortcut);
|
||||
updateModifiers();
|
||||
updateAssignedTo();
|
||||
|
||||
m_keyField->requestFocus();
|
||||
}
|
||||
|
||||
void SelectAccelerator::onOK()
|
||||
void SelectShortcut::onOK()
|
||||
{
|
||||
m_ok = true;
|
||||
m_modified = (m_origAccel != m_accel);
|
||||
m_modified = (m_origShortcut != m_shortcut);
|
||||
closeWindow(NULL);
|
||||
}
|
||||
|
||||
void SelectAccelerator::onCancel()
|
||||
void SelectShortcut::onCancel()
|
||||
{
|
||||
closeWindow(NULL);
|
||||
}
|
||||
|
||||
void SelectAccelerator::updateModifiers()
|
||||
void SelectShortcut::updateModifiers()
|
||||
{
|
||||
alt()->setSelected(m_accel.modifiers() & kKeyAltModifier ? true : false);
|
||||
ctrl()->setSelected(m_accel.modifiers() & kKeyCtrlModifier ? true : false);
|
||||
shift()->setSelected(m_accel.modifiers() & kKeyShiftModifier ? true : false);
|
||||
space()->setSelected(m_accel.modifiers() & kKeySpaceModifier ? true : false);
|
||||
alt()->setSelected((m_shortcut.modifiers() & kKeyAltModifier) == kKeyAltModifier);
|
||||
ctrl()->setSelected((m_shortcut.modifiers() & kKeyCtrlModifier) == kKeyCtrlModifier);
|
||||
shift()->setSelected((m_shortcut.modifiers() & kKeyShiftModifier) == kKeyShiftModifier);
|
||||
space()->setSelected((m_shortcut.modifiers() & kKeySpaceModifier) == kKeySpaceModifier);
|
||||
#if __APPLE__
|
||||
win()->setVisible(false);
|
||||
cmd()->setSelected(m_accel.modifiers() & kKeyCmdModifier ? true : false);
|
||||
cmd()->setSelected((m_shortcut.modifiers() & kKeyCmdModifier) == kKeyCmdModifier);
|
||||
#else
|
||||
#if __linux__
|
||||
win()->setText(kWinKeyName);
|
||||
|
@ -180,17 +180,17 @@ void SelectAccelerator::updateModifiers()
|
|||
"Also known as Windows key, logo key,\ncommand key, or system key.",
|
||||
TOP);
|
||||
#endif
|
||||
win()->setSelected(m_accel.modifiers() & kKeyWinModifier ? true : false);
|
||||
win()->setSelected((m_shortcut.modifiers() & kKeyWinModifier) == kKeyWinModifier);
|
||||
cmd()->setVisible(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SelectAccelerator::updateAssignedTo()
|
||||
void SelectShortcut::updateAssignedTo()
|
||||
{
|
||||
std::string res = "None";
|
||||
|
||||
for (const KeyPtr& key : m_currentKeys) {
|
||||
if (key->keycontext() == m_keyContext && key->hasAccel(m_accel)) {
|
||||
if (key->keycontext() == m_keyContext && key->hasShortcut(m_shortcut)) {
|
||||
res = key->triggerString();
|
||||
break;
|
||||
}
|
|
@ -1,35 +1,36 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_UI_SELECT_ACCELERATOR_H_INCLUDED
|
||||
#define APP_UI_SELECT_ACCELERATOR_H_INCLUDED
|
||||
#ifndef APP_UI_SELECT_SHORTCUT_H_INCLUDED
|
||||
#define APP_UI_SELECT_SHORTCUT_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/ui/key_context.h"
|
||||
#include "ui/accelerator.h"
|
||||
#include "ui/shortcut.h"
|
||||
#include "ui/tooltips.h"
|
||||
|
||||
#include "select_accelerator.xml.h"
|
||||
#include "select_shortcut.xml.h"
|
||||
|
||||
namespace app {
|
||||
class KeyboardShortcuts;
|
||||
|
||||
class SelectAccelerator : public app::gen::SelectAccelerator {
|
||||
class SelectShortcut : public app::gen::SelectShortcut {
|
||||
public:
|
||||
SelectAccelerator(const ui::Accelerator& accelerator,
|
||||
const KeyContext keyContext,
|
||||
const KeyboardShortcuts& currentKeys);
|
||||
SelectShortcut(const ui::Shortcut& shortcut,
|
||||
KeyContext keyContext,
|
||||
const KeyboardShortcuts& currentKeys);
|
||||
|
||||
bool isOK() const { return m_ok; }
|
||||
bool isModified() const { return m_modified; }
|
||||
const ui::Accelerator& accel() const { return m_accel; }
|
||||
const ui::Shortcut& shortcut() const { return m_shortcut; }
|
||||
|
||||
private:
|
||||
void onModifierChange(ui::KeyModifiers modifier, ui::CheckBox* checkbox);
|
||||
void onAccelChange(const ui::Accelerator* accel);
|
||||
void onShortcutChange(const ui::Shortcut* shortcut);
|
||||
void onClear();
|
||||
void onOK();
|
||||
void onCancel();
|
||||
|
@ -42,8 +43,8 @@ private:
|
|||
KeyField* m_keyField;
|
||||
KeyContext m_keyContext;
|
||||
const KeyboardShortcuts& m_currentKeys;
|
||||
ui::Accelerator m_origAccel;
|
||||
ui::Accelerator m_accel;
|
||||
ui::Shortcut m_origShortcut;
|
||||
ui::Shortcut m_shortcut;
|
||||
bool m_ok;
|
||||
bool m_modified;
|
||||
};
|
|
@ -42,6 +42,7 @@
|
|||
#include "text/font.h"
|
||||
#include "text/font_metrics.h"
|
||||
#include "text/font_style_set.h"
|
||||
#include "text/text_blob.h"
|
||||
#include "ui/intern.h"
|
||||
#include "ui/ui.h"
|
||||
|
||||
|
@ -257,6 +258,9 @@ static FontData* load_font(const XMLElement* xmlFont, const std::string& xmlFile
|
|||
if (xmlFont->Attribute("antialias"))
|
||||
antialias = bool_attr(xmlFont, "antialias", false);
|
||||
|
||||
text::FontHinting hinting = (bool_attr(xmlFont, "hinting", true) ? text::FontHinting::Normal :
|
||||
text::FontHinting::None);
|
||||
|
||||
std::string fontFilename;
|
||||
if (platformFileStr)
|
||||
fontFilename = app::find_font(xmlDir, platformFileStr);
|
||||
|
@ -270,6 +274,7 @@ static FontData* load_font(const XMLElement* xmlFont, const std::string& xmlFile
|
|||
font->setName(nameStr ? nameStr : (platformFileStr ? platformFileStr : fileStr));
|
||||
font->setFilename(fontFilename);
|
||||
font->setAntialias(antialias);
|
||||
font->setHinting(hinting);
|
||||
|
||||
if (!fontFilename.empty())
|
||||
LOG(VERBOSE, "THEME: Font file '%s' found\n", fontFilename.c_str());
|
||||
|
@ -311,6 +316,9 @@ static FontData* load_font(const XMLElement* xmlFont, const std::string& xmlFile
|
|||
if (!font && systemStr) {
|
||||
font = try_to_load_system_font(xmlFont);
|
||||
}
|
||||
|
||||
if (font && nameStr)
|
||||
font->setName(nameStr);
|
||||
}
|
||||
else {
|
||||
throw base::Exception(
|
||||
|
@ -536,8 +544,7 @@ void SkinTheme::loadXml(BackwardCompatibility* backward)
|
|||
float size = 0.0f;
|
||||
if (const char* sizeStr = xmlFont->Attribute("size"))
|
||||
size = std::strtof(sizeStr, nullptr);
|
||||
|
||||
if (fontData->defaultSize() != 0.0f)
|
||||
if (size == 0.0f && fontData->defaultSize() != 0.0f)
|
||||
size = fontData->defaultSize();
|
||||
|
||||
const char* mnemonicsStr = xmlFont->Attribute("mnemonics");
|
||||
|
@ -545,9 +552,23 @@ void SkinTheme::loadXml(BackwardCompatibility* backward)
|
|||
|
||||
text::FontRef font = fontData->getFont(m_fontMgr, size * ui::guiscale());
|
||||
|
||||
// SpriteSheetFonts have a default preferred size.
|
||||
if (size == 0.0f && font->defaultSize() > 0.0f) {
|
||||
size = font->defaultSize();
|
||||
// No font?
|
||||
if (font == nullptr) {
|
||||
LOG(ERROR, "THEME: Error loading font for theme %s\n", idStr);
|
||||
xmlFont = xmlFont->NextSiblingElement();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (size == 0.0f) {
|
||||
// SpriteSheetFonts have a default preferred size.
|
||||
if (font->defaultSize() > 0.0f) {
|
||||
size = font->defaultSize();
|
||||
}
|
||||
// For some user extensions, we need to specify at least a
|
||||
// default size of 10 for TTF theme fonts.
|
||||
else {
|
||||
size = 10.0f;
|
||||
}
|
||||
font = fontData->getFont(m_fontMgr, size * ui::guiscale());
|
||||
}
|
||||
|
||||
|
@ -1329,6 +1350,7 @@ public:
|
|||
, m_h(h)
|
||||
{
|
||||
m_widget->getEntryThemeInfo(&m_index, &m_caret, &m_state, &m_range);
|
||||
m_suffixIndex = m_widget->text().size();
|
||||
}
|
||||
|
||||
int index() const { return m_index; }
|
||||
|
@ -1348,6 +1370,11 @@ public:
|
|||
bg = ColorNone;
|
||||
fg = colors.text();
|
||||
|
||||
// Suffix text
|
||||
if (m_index >= m_suffixIndex) {
|
||||
fg = colors.entrySuffix();
|
||||
}
|
||||
|
||||
// Selected
|
||||
if ((m_index >= m_range.from) && (m_index < m_range.to)) {
|
||||
if (m_widget->hasFocus())
|
||||
|
@ -1412,6 +1439,7 @@ private:
|
|||
int m_lastX; // Last position used to fill the background
|
||||
int m_y, m_h;
|
||||
int m_charStartX;
|
||||
int m_suffixIndex;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
@ -1424,8 +1452,10 @@ void SkinTheme::drawEntryText(ui::Graphics* g, ui::Entry* widget)
|
|||
DrawEntryTextDelegate delegate(widget, g, bounds.origin(), widget->textHeight());
|
||||
int scroll = delegate.index();
|
||||
|
||||
if (!widget->text().empty()) {
|
||||
const std::string& textString = widget->text();
|
||||
// Full text to paint: widget text + suffix
|
||||
const std::string textString = widget->text() + widget->getSuffix();
|
||||
|
||||
if (!textString.empty()) {
|
||||
base::utf8_decode dec(textString);
|
||||
auto pos = dec.pos();
|
||||
for (int i = 0; i < scroll && dec.next(); ++i)
|
||||
|
@ -1433,41 +1463,28 @@ void SkinTheme::drawEntryText(ui::Graphics* g, ui::Entry* widget)
|
|||
|
||||
IntersectClip clip(g, bounds);
|
||||
if (clip) {
|
||||
text::FontMetrics metrics;
|
||||
widget->font()->metrics(&metrics);
|
||||
const float baselineShift = -metrics.ascent - widget->textBlob()->baseline();
|
||||
int baselineAdjustment = widget->textBaseline();
|
||||
if (auto blob = widget->textBlob()) {
|
||||
baselineAdjustment -= blob->baseline();
|
||||
}
|
||||
else {
|
||||
text::FontMetrics metrics;
|
||||
widget->font()->metrics(&metrics);
|
||||
baselineAdjustment += metrics.ascent;
|
||||
}
|
||||
|
||||
g->drawTextWithDelegate(std::string(pos, textString.end()), // TODO use a string_view()
|
||||
colors.text(),
|
||||
ColorNone,
|
||||
gfx::Point(bounds.x, bounds.y + baselineShift),
|
||||
gfx::Point(bounds.x, baselineAdjustment),
|
||||
&delegate);
|
||||
}
|
||||
}
|
||||
|
||||
bounds.x += delegate.textBounds().w;
|
||||
|
||||
// Draw suffix if there is enough space
|
||||
if (!widget->getSuffix().empty()) {
|
||||
Rect sufBounds(bounds.x,
|
||||
bounds.y,
|
||||
bounds.x2() - widget->childSpacing() - bounds.x,
|
||||
widget->textHeight());
|
||||
IntersectClip clip(g, sufBounds & widget->clientChildrenBounds());
|
||||
if (clip) {
|
||||
drawText(g,
|
||||
widget->getSuffix().c_str(),
|
||||
colors.entrySuffix(),
|
||||
ColorNone,
|
||||
widget,
|
||||
sufBounds,
|
||||
widget->align(),
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
@ -1577,13 +1594,13 @@ void SkinTheme::paintMenuItem(ui::PaintEvent& ev)
|
|||
}
|
||||
// Draw the keyboard shortcut
|
||||
else if (AppMenuItem* appMenuItem = dynamic_cast<AppMenuItem*>(widget)) {
|
||||
if (appMenuItem->key() && !appMenuItem->key()->accels().empty()) {
|
||||
if (appMenuItem->key() && !appMenuItem->key()->shortcuts().empty()) {
|
||||
int old_align = appMenuItem->align();
|
||||
|
||||
pos = bounds;
|
||||
pos.w -= widget->childSpacing() / 4;
|
||||
|
||||
std::string buf = appMenuItem->key()->accels().front().toString();
|
||||
std::string buf = appMenuItem->key()->shortcuts().front().toString();
|
||||
|
||||
widget->setAlign(RIGHT | MIDDLE);
|
||||
drawText(g, buf.c_str(), fg, ColorNone, widget, pos, widget->align(), 0);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
|
@ -181,8 +181,7 @@ void SliceWindow::onPivotChange()
|
|||
void SliceWindow::onToggleUserData()
|
||||
{
|
||||
m_userDataView.toggleVisibility();
|
||||
remapWindow();
|
||||
manager()->invalidate();
|
||||
expandWindow(gfx::Size(bounds().w, sizeHint().h));
|
||||
}
|
||||
|
||||
void SliceWindow::onModifyField(ui::Entry* entry, const Mods mods)
|
||||
|
|
|
@ -136,7 +136,7 @@ class StatusBar::Indicators : public HBox {
|
|||
g->drawText(text(),
|
||||
textColor,
|
||||
ColorNone,
|
||||
Point(rc.x, guiscaled_center(rc.y, rc.h, font()->lineHeight())));
|
||||
Point(rc.x, guiscaled_center(rc.y, rc.h, textHeight())));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -536,9 +536,9 @@ public:
|
|||
|
||||
// Tool shortcut
|
||||
KeyPtr key = KeyboardShortcuts::instance()->tool(tool);
|
||||
if (key && !key->accels().empty()) {
|
||||
if (key && !key->shortcuts().empty()) {
|
||||
add(theme->parts.iconKey(), true);
|
||||
m_indicators->addTextIndicator(key->accels().front().toString().c_str());
|
||||
m_indicators->addTextIndicator(key->shortcuts().front().toString().c_str());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
||||
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
|
@ -132,8 +132,7 @@ void TagWindow::onRepeatChange()
|
|||
void TagWindow::onToggleUserData()
|
||||
{
|
||||
m_userDataView.toggleVisibility();
|
||||
remapWindow();
|
||||
manager()->invalidate();
|
||||
expandWindow(gfx::Size(bounds().w, sizeHint().h));
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Aseprite
|
||||
// Copyright (C) 2018-2023 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
|
||||
|
@ -110,11 +110,11 @@ std::string AniControls::getTooltipFor(int index) const
|
|||
tooltip = cmd->friendlyName();
|
||||
|
||||
KeyPtr key = KeyboardShortcuts::instance()->command(cmd->id().c_str());
|
||||
if (!key || key->accels().empty())
|
||||
if (!key || key->shortcuts().empty())
|
||||
key = KeyboardShortcuts::instance()->command(cmd->id().c_str(), Params(), KeyContext::Normal);
|
||||
if (key && !key->accels().empty()) {
|
||||
if (key && !key->shortcuts().empty()) {
|
||||
tooltip += "\n\n" + Strings::ani_controls_shortcut() + " ";
|
||||
tooltip += key->accels().front().toString();
|
||||
tooltip += key->shortcuts().front().toString();
|
||||
}
|
||||
|
||||
if (index == ACTION_PLAY) {
|
||||
|
|
|
@ -4321,6 +4321,12 @@ void Timeline::clearAndInvalidateRange()
|
|||
}
|
||||
}
|
||||
|
||||
void Timeline::refresh()
|
||||
{
|
||||
regenerateRows();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
app::gen::GlobalPref::Timeline& Timeline::timelinePref() const
|
||||
{
|
||||
return Preferences::instance().timeline;
|
||||
|
@ -4576,7 +4582,7 @@ void Timeline::onDrop(ui::DragEvent& e)
|
|||
|
||||
// Determine at which frame and layer the content was dropped on.
|
||||
frame_t frame = m_frame;
|
||||
layer_t layerIndex = getLayerIndex(m_layer);
|
||||
layer_t layerIndex = m_sprite->root()->getLayerIndex(m_layer);
|
||||
InsertionPoint insert = InsertionPoint::BeforeLayer;
|
||||
DroppedOn droppedOn = DroppedOn::Unspecified;
|
||||
TRACE("m_dropRange.type() %d\n", m_dropRange.type());
|
||||
|
@ -4602,7 +4608,7 @@ void Timeline::onDrop(ui::DragEvent& e)
|
|||
break;
|
||||
case Range::kLayers:
|
||||
droppedOn = DroppedOn::Layer;
|
||||
if (m_dropTarget.vhit != DropTarget::VeryBottom) {
|
||||
if (m_dropTarget.vhit != DropTarget::VeryBottom && !m_dropRange.selectedLayers().empty()) {
|
||||
auto* selectedLayer = *m_dropRange.selectedLayers().begin();
|
||||
layerIndex = getLayerIndex(selectedLayer);
|
||||
}
|
||||
|
|
|
@ -155,6 +155,8 @@ public:
|
|||
|
||||
void clearAndInvalidateRange();
|
||||
|
||||
void refresh();
|
||||
|
||||
protected:
|
||||
bool onProcessMessage(ui::Message* msg) override;
|
||||
void onInitTheme(ui::InitThemeEvent& ev) override;
|
||||
|
|
|
@ -571,9 +571,9 @@ void ToolBar::openTipWindow(int group_index, Tool* tool)
|
|||
|
||||
// Tool shortcut
|
||||
KeyPtr key = KeyboardShortcuts::instance()->tool(tool);
|
||||
if (key && !key->accels().empty()) {
|
||||
if (key && !key->shortcuts().empty()) {
|
||||
tooltip += "\n\n";
|
||||
tooltip += Strings::tools_shortcut(key->accels().front().toString());
|
||||
tooltip += Strings::tools_shortcut(key->shortcuts().front().toString());
|
||||
}
|
||||
}
|
||||
else if (group_index == PreviewVisibilityIndex) {
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#endif
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/cmd/add_slice.h"
|
||||
#include "app/cmd/clear_mask.h"
|
||||
#include "app/cmd/deselect_mask.h"
|
||||
#include "app/cmd/set_mask.h"
|
||||
|
@ -20,6 +21,7 @@
|
|||
#include "app/doc_api.h"
|
||||
#include "app/doc_range.h"
|
||||
#include "app/doc_range_ops.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/modules/gfx.h"
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/pref/preferences.h"
|
||||
|
@ -32,6 +34,7 @@
|
|||
#include "app/util/cel_ops.h"
|
||||
#include "app/util/clipboard.h"
|
||||
#include "app/util/new_image_from_mask.h"
|
||||
#include "app/util/slice_utils.h"
|
||||
#include "clip/clip.h"
|
||||
#include "doc/algorithm/shrink_bounds.h"
|
||||
#include "doc/blend_image.h"
|
||||
|
@ -114,6 +117,9 @@ struct Clipboard::Data {
|
|||
// Selected set of layers/layers/cels
|
||||
ClipboardRange range;
|
||||
|
||||
// Selected slices
|
||||
std::vector<Slice> slices;
|
||||
|
||||
Data() { range.observeUIContext(); }
|
||||
|
||||
~Data()
|
||||
|
@ -132,6 +138,7 @@ struct Clipboard::Data {
|
|||
picks.clear();
|
||||
mask.reset();
|
||||
range.invalidate();
|
||||
slices.clear();
|
||||
}
|
||||
|
||||
ClipboardFormat format() const
|
||||
|
@ -146,6 +153,8 @@ struct Clipboard::Data {
|
|||
return ClipboardFormat::PaletteEntries;
|
||||
else if (tileset && picks.picks())
|
||||
return ClipboardFormat::Tileset;
|
||||
else if (!slices.empty())
|
||||
return ClipboardFormat::Slices;
|
||||
else
|
||||
return ClipboardFormat::None;
|
||||
}
|
||||
|
@ -212,6 +221,7 @@ void Clipboard::setData(Image* image,
|
|||
Mask* mask,
|
||||
Palette* palette,
|
||||
Tileset* tileset,
|
||||
const std::vector<Slice*>* slices,
|
||||
bool set_native_clipboard,
|
||||
bool image_source_is_transparent)
|
||||
{
|
||||
|
@ -226,6 +236,11 @@ void Clipboard::setData(Image* image,
|
|||
else
|
||||
m_data->image.reset(image);
|
||||
|
||||
if (slices) {
|
||||
for (auto* slice : *slices)
|
||||
m_data->slices.push_back(*slice);
|
||||
}
|
||||
|
||||
if (set_native_clipboard && use_native_clipboard()) {
|
||||
// Copy tilemap to the native clipboard
|
||||
if (isTilemap) {
|
||||
|
@ -262,6 +277,7 @@ bool Clipboard::copyFromDocument(const Site& site, bool merged)
|
|||
(mask ? new Mask(*mask) : nullptr),
|
||||
(pal ? new Palette(*pal) : nullptr),
|
||||
Tileset::MakeCopyCopyingImages(ts),
|
||||
nullptr,
|
||||
true, // set native clipboard
|
||||
site.layer() && !site.layer()->isBackground());
|
||||
|
||||
|
@ -277,6 +293,7 @@ bool Clipboard::copyFromDocument(const Site& site, bool merged)
|
|||
(mask ? new Mask(*mask) : nullptr),
|
||||
(pal ? new Palette(*pal) : nullptr),
|
||||
nullptr,
|
||||
nullptr,
|
||||
true, // set native clipboard
|
||||
site.layer() && !site.layer()->isBackground());
|
||||
|
||||
|
@ -401,6 +418,7 @@ void Clipboard::copyImage(const Image* image, const Mask* mask, const Palette* p
|
|||
(mask ? new Mask(*mask) : nullptr),
|
||||
(pal ? new Palette(*pal) : nullptr),
|
||||
nullptr,
|
||||
nullptr,
|
||||
App::instance()->isGui(),
|
||||
false);
|
||||
}
|
||||
|
@ -415,6 +433,7 @@ void Clipboard::copyTilemap(const Image* image,
|
|||
(mask ? new Mask(*mask) : nullptr),
|
||||
(pal ? new Palette(*pal) : nullptr),
|
||||
Tileset::MakeCopyCopyingImages(tileset),
|
||||
nullptr,
|
||||
true,
|
||||
false);
|
||||
}
|
||||
|
@ -428,6 +447,7 @@ void Clipboard::copyPalette(const Palette* palette, const PalettePicks& picks)
|
|||
nullptr,
|
||||
new Palette(*palette),
|
||||
nullptr,
|
||||
nullptr,
|
||||
false, // Don't touch the native clipboard now
|
||||
false);
|
||||
|
||||
|
@ -438,6 +458,20 @@ void Clipboard::copyPalette(const Palette* palette, const PalettePicks& picks)
|
|||
m_data->picks = picks;
|
||||
}
|
||||
|
||||
void Clipboard::copySlices(const std::vector<Slice*> slices)
|
||||
{
|
||||
if (slices.empty())
|
||||
return;
|
||||
|
||||
setData(nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&slices,
|
||||
false, // Don't touch the native clipboard now
|
||||
false);
|
||||
}
|
||||
|
||||
void Clipboard::paste(Context* ctx, const bool interactive, const gfx::Point* position)
|
||||
{
|
||||
const Site site = ctx->activeSite();
|
||||
|
@ -782,6 +816,26 @@ void Clipboard::paste(Context* ctx, const bool interactive, const gfx::Point* po
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ClipboardFormat::Slices: {
|
||||
auto& slices = m_data->slices;
|
||||
|
||||
if (slices.empty())
|
||||
return;
|
||||
|
||||
ContextWriter writer(ctx);
|
||||
Tx tx(writer, "Paste Slices");
|
||||
editor->clearSlicesSelection();
|
||||
for (auto& s : slices) {
|
||||
Slice* slice = new Slice(s);
|
||||
slice->setName(Strings::general_copy_of(slice->name()));
|
||||
tx(new cmd::AddSlice(dstSpr, slice));
|
||||
editor->selectSlice(slice);
|
||||
}
|
||||
tx.commit();
|
||||
updateDstDoc = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update all editors/views showing this document
|
||||
|
@ -799,7 +853,7 @@ ImageRef Clipboard::getImage(Palette* palette)
|
|||
Tileset* native_tileset = nullptr;
|
||||
getNativeBitmap(&native_image, &native_mask, &native_palette, &native_tileset);
|
||||
if (native_image) {
|
||||
setData(native_image, native_mask, native_palette, native_tileset, false, false);
|
||||
setData(native_image, native_mask, native_palette, native_tileset, nullptr, false, false);
|
||||
}
|
||||
}
|
||||
if (m_data->palette && palette)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue