mirror of https://github.com/aseprite/aseprite.git
Compare commits
29 Commits
95a42fb0ad
...
2bffe5b2a3
Author | SHA1 | Date |
---|---|---|
|
2bffe5b2a3 | |
|
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 | |
|
27f19959bd |
|
@ -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.
|
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_url=$(bash laf/misc/skia-url.sh $skia_build)
|
||||||
skia_file=$(basename $skia_url)
|
skia_file=$(basename $skia_url)
|
||||||
if [ ! -f "$skia_dir/$skia_file" ] ; then
|
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"
|
curl --ssl-revoke-best-effort -L -o "$skia_dir/$skia_file" "$skia_url"
|
||||||
fi
|
fi
|
||||||
if [ ! -d "$skia_library_dir" ] ; then
|
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"
|
unzip -n -d "$skia_dir" "$skia_dir/$skia_file"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
|
|
|
@ -146,6 +146,10 @@
|
||||||
<value id="KEEP_AS_IS" value="1" />
|
<value id="KEEP_AS_IS" value="1" />
|
||||||
<value id="RAW_IMAGE" value="2" />
|
<value id="RAW_IMAGE" value="2" />
|
||||||
</enum>
|
</enum>
|
||||||
|
<enum id="CancelSelection">
|
||||||
|
<value id="DISCARD" value="0" />
|
||||||
|
<value id="DESELECT" value="1" />
|
||||||
|
</enum>
|
||||||
</types>
|
</types>
|
||||||
|
|
||||||
<global>
|
<global>
|
||||||
|
@ -325,6 +329,7 @@
|
||||||
<option id="force_rotsprite" type="bool" default="false" />
|
<option id="force_rotsprite" type="bool" default="false" />
|
||||||
<option id="multicel_when_layers_or_frames" type="bool" default="true" />
|
<option id="multicel_when_layers_or_frames" type="bool" default="true" />
|
||||||
<option id="snap_to_grid" type="bool" default="true" />
|
<option id="snap_to_grid" type="bool" default="true" />
|
||||||
|
<option id="cancel_selection" type="CancelSelection" default="CancelSelection::DISCARD" />
|
||||||
</section>
|
</section>
|
||||||
<section id="quantization">
|
<section id="quantization">
|
||||||
<option id="with_alpha" type="bool" default="true" />
|
<option id="with_alpha" type="bool" default="true" />
|
||||||
|
|
|
@ -554,6 +554,8 @@ amount = Amount:
|
||||||
flatten = Merge layers
|
flatten = Merge layers
|
||||||
|
|
||||||
[context_bar]
|
[context_bar]
|
||||||
|
discard_changes = Discard Changes
|
||||||
|
deselect = Deselect
|
||||||
center = Center
|
center = Center
|
||||||
fit_screen = Fit Screen
|
fit_screen = Fit Screen
|
||||||
back = Back
|
back = Back
|
||||||
|
@ -582,7 +584,7 @@ pixel_perfect = Pixel-perfect
|
||||||
linear_gradient = Linear Gradient
|
linear_gradient = Linear Gradient
|
||||||
radial_gradient = Radial Gradient
|
radial_gradient = Radial Gradient
|
||||||
drop_pixel = Drop pixels here (Enter)
|
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
|
auto_select_layer = Auto Select Layer
|
||||||
all = All
|
all = All
|
||||||
none = None
|
none = None
|
||||||
|
@ -621,6 +623,14 @@ current_layer = Current Layer
|
||||||
first_ref_layer = First Reference Layer
|
first_ref_layer = First Reference Layer
|
||||||
pick = Pick:
|
pick = Pick:
|
||||||
sample = Sample:
|
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]
|
[convolution_matrix]
|
||||||
reload_stock = &Reload Stock
|
reload_stock = &Reload Stock
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Aseprite -->
|
<!-- Aseprite -->
|
||||||
<!-- Copyright (C) 2019-2024 Igara Studio S.A. -->
|
<!-- Copyright (C) 2019-2025 Igara Studio S.A. -->
|
||||||
<!-- Copyright (C) 2017-2018 David Capello -->
|
<!-- Copyright (C) 2017-2018 David Capello -->
|
||||||
<gui>
|
<gui>
|
||||||
<window id="slice_properties" text="@.title" help="slices#slice-properties">
|
<window id="slice_properties" text="@.title" help="slices#slice-properties">
|
||||||
<vbox>
|
<vbox expansive="true">
|
||||||
<grid id="properties_grid" columns="3">
|
<grid id="properties_grid" columns="3">
|
||||||
<label id="label1" text="@.name" />
|
<label id="label1" text="@.name" />
|
||||||
<entry id="name" maxsize="256" magnet="true" cell_align="horizontal" expansive="true" />
|
<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" />
|
<button id="user_data" icon="icon_user_data" maxsize="32" tooltip="@.user_data_tooltip" />
|
||||||
</grid>
|
</grid>
|
||||||
<grid columns="2">
|
<grid columns="3" expansive="true">
|
||||||
<separator horizontal="true" cell_hspan="2" />
|
<separator horizontal="true" cell_hspan="3" />
|
||||||
|
|
||||||
<box />
|
<box />
|
||||||
<hbox homogeneous="true">
|
<hbox homogeneous="true">
|
||||||
|
@ -20,6 +20,7 @@
|
||||||
<label text="@.width" />
|
<label text="@.width" />
|
||||||
<label text="@.height" />
|
<label text="@.height" />
|
||||||
</hbox>
|
</hbox>
|
||||||
|
<boxfiller cell_align="horizontal" />
|
||||||
|
|
||||||
<label text="@.bounds" />
|
<label text="@.bounds" />
|
||||||
<hbox homogeneous="true">
|
<hbox homogeneous="true">
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
<expr id="bounds_w" />
|
<expr id="bounds_w" />
|
||||||
<expr id="bounds_h" />
|
<expr id="bounds_h" />
|
||||||
</hbox>
|
</hbox>
|
||||||
|
<boxfiller />
|
||||||
|
|
||||||
<check text="@.center" id="center" />
|
<check text="@.center" id="center" />
|
||||||
<hbox homogeneous="true">
|
<hbox homogeneous="true">
|
||||||
|
@ -36,16 +38,18 @@
|
||||||
<expr id="center_w" />
|
<expr id="center_w" />
|
||||||
<expr id="center_h" />
|
<expr id="center_h" />
|
||||||
</hbox>
|
</hbox>
|
||||||
|
<boxfiller />
|
||||||
|
|
||||||
<check text="@.pivot" id="pivot" />
|
<check text="@.pivot" id="pivot" />
|
||||||
<hbox>
|
<hbox>
|
||||||
<expr id="pivot_x" />
|
<expr id="pivot_x" />
|
||||||
<expr id="pivot_y" />
|
<expr id="pivot_y" />
|
||||||
</hbox>
|
</hbox>
|
||||||
|
<boxfiller />
|
||||||
|
|
||||||
<separator horizontal="true" cell_hspan="2" />
|
<boxfiller cell_align="vertical" cell_hspan="3" />
|
||||||
|
<separator horizontal="true" cell_hspan="3" cell_align="horizontal" />
|
||||||
<hbox cell_hspan="2">
|
<hbox cell_hspan="3">
|
||||||
<boxfiller />
|
<boxfiller />
|
||||||
<hbox homogeneous="true">
|
<hbox homogeneous="true">
|
||||||
<button text="@general.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />
|
<button text="@general.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Aseprite -->
|
<!-- Aseprite -->
|
||||||
<!-- Copyright (C) 2019-2021 Igara Studio S.A. -->
|
<!-- Copyright (C) 2019-2025 Igara Studio S.A. -->
|
||||||
<!-- Copyright (C) 2015-2018 David Capello -->
|
<!-- Copyright (C) 2015-2018 David Capello -->
|
||||||
<gui>
|
<gui>
|
||||||
<window id="tag_properties" text="@.title">
|
<window id="tag_properties" text="@.title">
|
||||||
|
@ -23,13 +23,14 @@
|
||||||
<check text="@.repeat" id="limit_repeat" />
|
<check text="@.repeat" id="limit_repeat" />
|
||||||
<vbox id="repeat_placeholder" cell_hspan="2" />
|
<vbox id="repeat_placeholder" cell_hspan="2" />
|
||||||
</grid>
|
</grid>
|
||||||
|
<boxfiller />
|
||||||
<grid columns="2">
|
<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.ok" closewindow="true" id="ok" magnet="true" minwidth="60" />
|
||||||
<button text="@general.cancel" closewindow="true" />
|
<button text="@general.cancel" closewindow="true" />
|
||||||
</box>
|
</hbox>
|
||||||
</grid>
|
</grid>
|
||||||
</vbox>
|
</vbox>
|
||||||
</window>
|
</window>
|
||||||
|
|
2
laf
2
laf
|
@ -1 +1 @@
|
||||||
Subproject commit 8ec4b553f1618f7a4b47cdcf4cfc2663266111ac
|
Subproject commit 39f6dca9c623e2d005a5c5609afa97ac749b4e33
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2020-2023 Igara Studio S.A.
|
// Copyright (C) 2020-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -101,12 +101,12 @@ public:
|
||||||
|
|
||||||
if (countCels() > 0) {
|
if (countCels() > 0) {
|
||||||
m_userDataView.configureAndSet((m_cel ? m_cel->data()->userData() : UserData()),
|
m_userDataView.configureAndSet((m_cel ? m_cel->data()->userData() : UserData()),
|
||||||
g_window->propertiesGrid());
|
propertiesGrid());
|
||||||
}
|
}
|
||||||
else if (!m_cel)
|
else if (!m_cel)
|
||||||
m_userDataView.setVisible(false, false);
|
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();
|
updateFromCel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,7 +281,7 @@ private:
|
||||||
{
|
{
|
||||||
if (countCels() > 0) {
|
if (countCels() > 0) {
|
||||||
m_userDataView.toggleVisibility();
|
m_userDataView.toggleVisibility();
|
||||||
g_window->expandWindow(gfx::Size(g_window->bounds().w, g_window->sizeHint().h));
|
expandWindow(gfx::Size(bounds().w, sizeHint().h));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2020-2024 Igara Studio S.A.
|
// Copyright (C) 2020-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -166,7 +166,7 @@ public:
|
||||||
m_document->add_observer(this);
|
m_document->add_observer(this);
|
||||||
|
|
||||||
if (countLayers() > 0) {
|
if (countLayers() > 0) {
|
||||||
m_userDataView.configureAndSet(m_layer->userData(), g_window->propertiesGrid());
|
m_userDataView.configureAndSet(m_layer->userData(), propertiesGrid());
|
||||||
if (m_remapAfterConfigure) {
|
if (m_remapAfterConfigure) {
|
||||||
remapWindow();
|
remapWindow();
|
||||||
centerWindow();
|
centerWindow();
|
||||||
|
@ -368,8 +368,7 @@ private:
|
||||||
{
|
{
|
||||||
if (m_layer) {
|
if (m_layer) {
|
||||||
m_userDataView.toggleVisibility();
|
m_userDataView.toggleVisibility();
|
||||||
g_window->remapWindow();
|
expandWindow(gfx::Size(bounds().w, sizeHint().h));
|
||||||
manager()->invalidate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2018-2024 Igara Studio S.A.
|
// Copyright (C) 2018-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -188,8 +188,7 @@ private:
|
||||||
void onToggleUserData()
|
void onToggleUserData()
|
||||||
{
|
{
|
||||||
m_userDataView.toggleVisibility();
|
m_userDataView.toggleVisibility();
|
||||||
remapWindow();
|
expandWindow(gfx::Size(bounds().w, sizeHint().h));
|
||||||
manager()->invalidate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTilesedDuplicated(const Tileset* tilesetClone)
|
void onTilesedDuplicated(const Tileset* tilesetClone)
|
||||||
|
|
|
@ -81,7 +81,7 @@ public:
|
||||||
|
|
||||||
~ConsoleWindow() { TRACE_CON("CON: ~ConsoleWindow this=", this); }
|
~ConsoleWindow() { TRACE_CON("CON: ~ConsoleWindow this=", this); }
|
||||||
|
|
||||||
void addMessage(const std::string& msg)
|
void addMessage(std::string msg)
|
||||||
{
|
{
|
||||||
if (!m_hasText) {
|
if (!m_hasText) {
|
||||||
m_hasText = true;
|
m_hasText = true;
|
||||||
|
@ -93,6 +93,17 @@ public:
|
||||||
gfx::Point pt = m_view.viewScroll();
|
gfx::Point pt = m_view.viewScroll();
|
||||||
const bool autoScroll = (pt.y >= maxSize.h - visible.h);
|
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);
|
m_textbox.setText(m_textbox.text() + msg);
|
||||||
|
|
||||||
if (autoScroll) {
|
if (autoScroll) {
|
||||||
|
|
|
@ -1095,6 +1095,9 @@ public:
|
||||||
gifframe_t nframes = totalFrames();
|
gifframe_t nframes = totalFrames();
|
||||||
for (gifframe_t gifFrame = 0; gifFrame < nframes; ++gifFrame) {
|
for (gifframe_t gifFrame = 0; gifFrame < nframes; ++gifFrame) {
|
||||||
ASSERT(frame_it != frame_end);
|
ASSERT(frame_it != frame_end);
|
||||||
|
if (m_fop->isStop())
|
||||||
|
break;
|
||||||
|
|
||||||
frame_t frame = *frame_it;
|
frame_t frame = *frame_it;
|
||||||
++frame_it;
|
++frame_it;
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,6 @@
|
||||||
|
|
||||||
// Increment this value if the scripting API is modified between two
|
// Increment this value if the scripting API is modified between two
|
||||||
// released Aseprite versions.
|
// released Aseprite versions.
|
||||||
#define API_VERSION 34
|
#define API_VERSION 35
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -34,10 +34,10 @@
|
||||||
#include "app/tools/tool_loop_manager.h"
|
#include "app/tools/tool_loop_manager.h"
|
||||||
#include "app/tx.h"
|
#include "app/tx.h"
|
||||||
#include "app/ui/context_bar.h"
|
#include "app/ui/context_bar.h"
|
||||||
#include "app/ui/doc_view.h"
|
|
||||||
#include "app/ui/editor/editor.h"
|
#include "app/ui/editor/editor.h"
|
||||||
#include "app/ui/editor/tool_loop_impl.h"
|
#include "app/ui/editor/tool_loop_impl.h"
|
||||||
#include "app/ui/main_window.h"
|
#include "app/ui/main_window.h"
|
||||||
|
#include "app/ui/status_bar.h"
|
||||||
#include "app/ui/timeline/timeline.h"
|
#include "app/ui/timeline/timeline.h"
|
||||||
#include "app/ui_context.h"
|
#include "app/ui_context.h"
|
||||||
#include "base/fs.h"
|
#include "base/fs.h"
|
||||||
|
@ -498,6 +498,44 @@ int App_useTool(lua_State* L)
|
||||||
return 0;
|
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)
|
int App_get_events(lua_State* L)
|
||||||
{
|
{
|
||||||
push_app_events(L);
|
push_app_events(L);
|
||||||
|
@ -820,6 +858,7 @@ const luaL_Reg App_methods[] = {
|
||||||
{ "alert", App_alert },
|
{ "alert", App_alert },
|
||||||
{ "refresh", App_refresh },
|
{ "refresh", App_refresh },
|
||||||
{ "useTool", App_useTool },
|
{ "useTool", App_useTool },
|
||||||
|
{ "tip", App_tip },
|
||||||
{ nullptr, nullptr }
|
{ nullptr, nullptr }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,8 @@ namespace app { namespace script {
|
||||||
|
|
||||||
using namespace ui;
|
using namespace ui;
|
||||||
|
|
||||||
|
static constexpr const int kDefaultAutofit = ui::LEFT | ui::TOP;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class DialogWindow : public WindowWithHand {
|
class DialogWindow : public WindowWithHand {
|
||||||
|
@ -107,6 +109,7 @@ struct Dialog {
|
||||||
std::map<std::string, ui::Widget*> dataWidgets;
|
std::map<std::string, ui::Widget*> dataWidgets;
|
||||||
std::map<std::string, ui::Widget*> labelWidgets;
|
std::map<std::string, ui::Widget*> labelWidgets;
|
||||||
int currentRadioGroup = 0;
|
int currentRadioGroup = 0;
|
||||||
|
int autofit = kDefaultAutofit;
|
||||||
|
|
||||||
// Member used to hold current state about the creation of a tabs
|
// 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
|
// widget. After creation it is reset to null to be ready for the
|
||||||
|
@ -193,6 +196,13 @@ struct Dialog {
|
||||||
it->second->setText(text);
|
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() const
|
||||||
{
|
{
|
||||||
Display* parentDisplay = window.parentDisplay();
|
Display* parentDisplay = window.parentDisplay();
|
||||||
|
@ -231,8 +241,10 @@ struct Dialog {
|
||||||
window.display()->nativeWindow()->setFrame(frame);
|
window.display()->nativeWindow()->setFrame(frame);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
gfx::Rect oldBounds(window.bounds());
|
||||||
window.setBounds(rc);
|
window.setBounds(rc);
|
||||||
window.invalidate();
|
window.invalidate();
|
||||||
|
parentDisplay()->invalidateRect(oldBounds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,6 +379,7 @@ int Dialog_new(lua_State* L)
|
||||||
ui::Window::Type windowType = ui::Window::WithTitleBar;
|
ui::Window::Type windowType = ui::Window::WithTitleBar;
|
||||||
std::string title = "Script";
|
std::string title = "Script";
|
||||||
bool sizeable = true;
|
bool sizeable = true;
|
||||||
|
int autofit = kDefaultAutofit;
|
||||||
if (lua_isstring(L, 1)) {
|
if (lua_isstring(L, 1)) {
|
||||||
title = lua_tostring(L, 1);
|
title = lua_tostring(L, 1);
|
||||||
}
|
}
|
||||||
|
@ -385,9 +398,16 @@ int Dialog_new(lua_State* L)
|
||||||
if (type != LUA_TNIL && !lua_toboolean(L, -1))
|
if (type != LUA_TNIL && !lua_toboolean(L, -1))
|
||||||
sizeable = false;
|
sizeable = false;
|
||||||
lua_pop(L, 1);
|
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, sizeable);
|
auto dlg = push_new<Dialog>(L, windowType, title, sizeable);
|
||||||
|
dlg->setAutofit(autofit);
|
||||||
|
|
||||||
// The uservalue of the dialog userdata will contain a table that
|
// The uservalue of the dialog userdata will contain a table that
|
||||||
// stores all the callbacks to handle events. As these callbacks can
|
// stores all the callbacks to handle events. As these callbacks can
|
||||||
|
@ -1516,6 +1536,10 @@ int Dialog_modify(lua_State* L)
|
||||||
type = lua_getfield(L, 2, "text");
|
type = lua_getfield(L, 2, "text");
|
||||||
if (const char* s = lua_tostring(L, -1)) {
|
if (const char* s = lua_tostring(L, -1)) {
|
||||||
widget->setText(s);
|
widget->setText(s);
|
||||||
|
|
||||||
|
// Re-process mnemonics for buttons
|
||||||
|
if (widget->type() == WidgetType::kButtonWidget)
|
||||||
|
widget->processMnemonicFromText();
|
||||||
relayout = true;
|
relayout = true;
|
||||||
}
|
}
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
@ -1650,8 +1674,26 @@ int Dialog_modify(lua_State* L)
|
||||||
if (relayout && !dlg->window.isResizing()) {
|
if (relayout && !dlg->window.isResizing()) {
|
||||||
dlg->window.layout();
|
dlg->window.layout();
|
||||||
|
|
||||||
gfx::Rect bounds(dlg->window.bounds().w, dlg->window.sizeHint().h);
|
if (dlg->autofit > 0) {
|
||||||
dlg->window.expandWindow(bounds.size());
|
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);
|
lua_pushvalue(L, 1);
|
||||||
|
@ -1873,6 +1915,27 @@ int Dialog_get_bounds(lua_State* L)
|
||||||
return 1;
|
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)
|
int Dialog_set_bounds(lua_State* L)
|
||||||
{
|
{
|
||||||
auto dlg = get_obj<Dialog>(L, 1);
|
auto dlg = get_obj<Dialog>(L, 1);
|
||||||
|
@ -1914,9 +1977,11 @@ const luaL_Reg Dialog_methods[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const Property Dialog_properties[] = {
|
const Property Dialog_properties[] = {
|
||||||
{ "data", Dialog_get_data, Dialog_set_data },
|
{ "data", Dialog_get_data, Dialog_set_data },
|
||||||
{ "bounds", Dialog_get_bounds, Dialog_set_bounds },
|
{ "bounds", Dialog_get_bounds, Dialog_set_bounds },
|
||||||
{ nullptr, nullptr, nullptr }
|
{ "autofit", Dialog_get_autofit, Dialog_set_autofit },
|
||||||
|
{ "sizeHint", Dialog_get_sizeHint, nullptr },
|
||||||
|
{ nullptr, nullptr, nullptr }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
|
@ -452,6 +452,7 @@ Engine::Engine() : L(luaL_newstate()), m_delegate(nullptr), m_printLastResult(fa
|
||||||
lua_setglobal(L, "FlipType");
|
lua_setglobal(L, "FlipType");
|
||||||
setfield_integer(L, "HORIZONTAL", doc::algorithm::FlipType::FlipHorizontal);
|
setfield_integer(L, "HORIZONTAL", doc::algorithm::FlipType::FlipHorizontal);
|
||||||
setfield_integer(L, "VERTICAL", doc::algorithm::FlipType::FlipVertical);
|
setfield_integer(L, "VERTICAL", doc::algorithm::FlipType::FlipVertical);
|
||||||
|
setfield_integer(L, "DIAGONAL", doc::algorithm::FlipType::FlipDiagonal);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
|
|
||||||
lua_newtable(L);
|
lua_newtable(L);
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include "app/script/luacpp.h"
|
#include "app/script/luacpp.h"
|
||||||
#include "app/script/userdata.h"
|
#include "app/script/userdata.h"
|
||||||
#include "app/tx.h"
|
#include "app/tx.h"
|
||||||
|
#include "app/ui/timeline/timeline.h"
|
||||||
#include "doc/layer.h"
|
#include "doc/layer.h"
|
||||||
#include "doc/layer_tilemap.h"
|
#include "doc/layer_tilemap.h"
|
||||||
#include "doc/sprite.h"
|
#include "doc/sprite.h"
|
||||||
|
@ -355,6 +356,9 @@ int Layer_set_isCollapsed(lua_State* L)
|
||||||
{
|
{
|
||||||
auto layer = get_docobj<Layer>(L, 1);
|
auto layer = get_docobj<Layer>(L, 1);
|
||||||
layer->setCollapsed(lua_toboolean(L, 2));
|
layer->setCollapsed(lua_toboolean(L, 2));
|
||||||
|
|
||||||
|
if (auto* timeline = App::instance()->timeline())
|
||||||
|
timeline->refresh();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,6 +366,9 @@ int Layer_set_isExpanded(lua_State* L)
|
||||||
{
|
{
|
||||||
auto layer = get_docobj<Layer>(L, 1);
|
auto layer = get_docobj<Layer>(L, 1);
|
||||||
layer->setCollapsed(!lua_toboolean(L, 2));
|
layer->setCollapsed(!lua_toboolean(L, 2));
|
||||||
|
|
||||||
|
if (auto* timeline = App::instance()->timeline())
|
||||||
|
timeline->refresh();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,11 +29,16 @@ struct Plugin {
|
||||||
|
|
||||||
class PluginCommand : public Command {
|
class PluginCommand : public Command {
|
||||||
public:
|
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)
|
: Command(id.c_str(), CmdUIOnlyFlag)
|
||||||
, m_title(title)
|
, m_title(title)
|
||||||
, m_onclickRef(onclickRef)
|
, m_onclickRef(onclickRef)
|
||||||
, m_onenabledRef(onenabledRef)
|
, m_onenabledRef(onenabledRef)
|
||||||
|
, m_oncheckedRef(oncheckedRef)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,28 +77,44 @@ protected:
|
||||||
bool onEnabled(Context* context) override
|
bool onEnabled(Context* context) override
|
||||||
{
|
{
|
||||||
if (m_onenabledRef) {
|
if (m_onenabledRef) {
|
||||||
script::Engine* engine = App::instance()->scriptEngine();
|
return callScriptRef(m_onenabledRef);
|
||||||
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 true;
|
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;
|
std::string m_title;
|
||||||
int m_onclickRef;
|
int m_onclickRef;
|
||||||
int m_onenabledRef;
|
int m_onenabledRef;
|
||||||
|
int m_oncheckedRef;
|
||||||
};
|
};
|
||||||
|
|
||||||
void deleteCommandIfExistent(Extension* ext, const std::string& id)
|
void deleteCommandIfExistent(Extension* ext, const std::string& id)
|
||||||
|
@ -126,6 +147,7 @@ int Plugin_newCommand(lua_State* L)
|
||||||
if (lua_istable(L, 2)) {
|
if (lua_istable(L, 2)) {
|
||||||
std::string id, title, group;
|
std::string id, title, group;
|
||||||
int onenabledRef = 0;
|
int onenabledRef = 0;
|
||||||
|
int oncheckedRef = 0;
|
||||||
|
|
||||||
lua_getfield(L, 2, "id");
|
lua_getfield(L, 2, "id");
|
||||||
if (const char* s = lua_tostring(L, -1)) {
|
if (const char* s = lua_tostring(L, -1)) {
|
||||||
|
@ -156,6 +178,14 @@ int Plugin_newCommand(lua_State* L)
|
||||||
lua_pop(L, 1);
|
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");
|
type = lua_getfield(L, 2, "onclick");
|
||||||
if (type == LUA_TFUNCTION) {
|
if (type == LUA_TFUNCTION) {
|
||||||
int onclickRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
int onclickRef = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||||
|
@ -164,7 +194,7 @@ int Plugin_newCommand(lua_State* L)
|
||||||
// overwriting a previous registered command)
|
// overwriting a previous registered command)
|
||||||
deleteCommandIfExistent(plugin->ext, id);
|
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);
|
Commands::instance()->add(cmd);
|
||||||
plugin->ext->addCommand(id);
|
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 (!group.empty() && App::instance()->isGui()) { // On CLI menus do not make sense
|
||||||
if (auto appMenus = AppMenus::instance()) {
|
if (auto appMenus = AppMenus::instance()) {
|
||||||
auto menuItem = std::make_unique<AppMenuItem>(title, id);
|
auto menuItem = std::make_unique<AppMenuItem>(title, id);
|
||||||
|
menuItem->processMnemonicFromText();
|
||||||
appMenus->addMenuItemIntoGroup(group, std::move(menuItem));
|
appMenus->addMenuItemIntoGroup(group, std::move(menuItem));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -457,7 +457,7 @@ int Sprite_newCel(lua_State* L)
|
||||||
auto sprite = get_docobj<Sprite>(L, 1);
|
auto sprite = get_docobj<Sprite>(L, 1);
|
||||||
auto layerBase = get_docobj<Layer>(L, 2);
|
auto layerBase = get_docobj<Layer>(L, 2);
|
||||||
if (!layerBase->isImage())
|
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);
|
frame_t frame = get_frame_number_from_arg(L, 3);
|
||||||
if (frame < 0 || frame > sprite->lastFrame())
|
if (frame < 0 || frame > sprite->lastFrame())
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// 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
|
// This program is distributed under the terms of
|
||||||
// the End-User License Agreement for Aseprite.
|
// 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::ToGrayAlgorithm)
|
||||||
FOR_ENUM(app::gen::WindowColorProfile)
|
FOR_ENUM(app::gen::WindowColorProfile)
|
||||||
FOR_ENUM(app::gen::AlphaRange)
|
FOR_ENUM(app::gen::AlphaRange)
|
||||||
|
FOR_ENUM(app::gen::CancelSelection)
|
||||||
FOR_ENUM(app::tools::ColorFromTo)
|
FOR_ENUM(app::tools::ColorFromTo)
|
||||||
FOR_ENUM(app::tools::DynamicSensor)
|
FOR_ENUM(app::tools::DynamicSensor)
|
||||||
FOR_ENUM(app::tools::FreehandAlgorithm)
|
FOR_ENUM(app::tools::FreehandAlgorithm)
|
||||||
|
|
|
@ -1001,12 +1001,12 @@ public:
|
||||||
m_angle.setSuffix("°");
|
m_angle.setSuffix("°");
|
||||||
m_skew.setSuffix("°");
|
m_skew.setSuffix("°");
|
||||||
|
|
||||||
addChild(new Label("P:"));
|
addChild(new Label(Strings::context_bar_position_label()));
|
||||||
addChild(&m_x);
|
addChild(&m_x);
|
||||||
addChild(&m_y);
|
addChild(&m_y);
|
||||||
addChild(&m_w);
|
addChild(&m_w);
|
||||||
addChild(&m_h);
|
addChild(&m_h);
|
||||||
addChild(new Label("R:"));
|
addChild(new Label(Strings::context_bar_rotation_label()));
|
||||||
addChild(&m_angle);
|
addChild(&m_angle);
|
||||||
addChild(&m_skew);
|
addChild(&m_skew);
|
||||||
|
|
||||||
|
@ -1047,6 +1047,16 @@ public:
|
||||||
m_skew.Change.connect([this] { onChangeSkew(); });
|
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)
|
void update(const Transformation& t)
|
||||||
{
|
{
|
||||||
auto rc = t.bounds();
|
auto rc = t.bounds();
|
||||||
|
@ -1397,6 +1407,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
obs::signal<void(ContextBarObserver::DropAction)> DropPixels;
|
obs::signal<void(ContextBarObserver::DropAction)> DropPixels;
|
||||||
|
obs::signal<void(ContextBarObserver::DropAction, const gfx::Point&)> ConfigureDropPixels;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void onItemChange(Item* item) override
|
void onItemChange(Item* item) override
|
||||||
|
@ -1408,6 +1419,21 @@ protected:
|
||||||
case 1: DropPixels(ContextBarObserver::CancelDrag); break;
|
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 {
|
class ContextBar::EyedropperField : public HBox {
|
||||||
|
@ -1942,6 +1968,8 @@ ContextBar::ContextBar(TooltipManager* tooltipManager, ColorBar* colorBar)
|
||||||
m_keysConn = KeyboardShortcuts::instance()->UserChange.connect(
|
m_keysConn = KeyboardShortcuts::instance()->UserChange.connect(
|
||||||
[this, tooltipManager] { setupTooltips(tooltipManager); });
|
[this, tooltipManager] { setupTooltips(tooltipManager); });
|
||||||
m_dropPixelsConn = m_dropPixels->DropPixels.connect(&ContextBar::onDropPixels, this);
|
m_dropPixelsConn = m_dropPixels->DropPixels.connect(&ContextBar::onDropPixels, this);
|
||||||
|
m_configureDropPixelsConn =
|
||||||
|
m_dropPixels->ConfigureDropPixels.connect(&ContextBar::onConfigureDropPixels, this);
|
||||||
|
|
||||||
setActiveBrush(createBrushFromPreferences());
|
setActiveBrush(createBrushFromPreferences());
|
||||||
|
|
||||||
|
@ -2086,6 +2114,14 @@ void ContextBar::onDropPixels(ContextBarObserver::DropAction action)
|
||||||
notify_observers(&ContextBarObserver::onDropPixels, 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)
|
void ContextBar::updateSliceFields(const Site& site)
|
||||||
{
|
{
|
||||||
if (site.sprite())
|
if (site.sprite())
|
||||||
|
@ -2626,6 +2662,7 @@ void ContextBar::setupTooltips(TooltipManager* tooltipManager)
|
||||||
m_dropPixels->setupTooltips(tooltipManager);
|
m_dropPixels->setupTooltips(tooltipManager);
|
||||||
m_symmetry->setupTooltips(tooltipManager);
|
m_symmetry->setupTooltips(tooltipManager);
|
||||||
m_sliceFields->setupTooltips(tooltipManager);
|
m_sliceFields->setupTooltips(tooltipManager);
|
||||||
|
m_transformation->setupTooltips(tooltipManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContextBar::registerCommands()
|
void ContextBar::registerCommands()
|
||||||
|
|
|
@ -129,6 +129,7 @@ private:
|
||||||
void onFgOrBgColorChange(doc::Brush::ImageColor imageColor);
|
void onFgOrBgColorChange(doc::Brush::ImageColor imageColor);
|
||||||
void onOpacityRangeChange();
|
void onOpacityRangeChange();
|
||||||
void onDropPixels(ContextBarObserver::DropAction action);
|
void onDropPixels(ContextBarObserver::DropAction action);
|
||||||
|
void onConfigureDropPixels(ContextBarObserver::DropAction action, const gfx::Point& pt);
|
||||||
void updateSliceFields(const Site& site);
|
void updateSliceFields(const Site& site);
|
||||||
|
|
||||||
// ActiveToolObserver impl
|
// ActiveToolObserver impl
|
||||||
|
@ -213,6 +214,7 @@ private:
|
||||||
obs::scoped_connection m_alphaRangeConn;
|
obs::scoped_connection m_alphaRangeConn;
|
||||||
obs::scoped_connection m_keysConn;
|
obs::scoped_connection m_keysConn;
|
||||||
obs::scoped_connection m_dropPixelsConn;
|
obs::scoped_connection m_dropPixelsConn;
|
||||||
|
obs::scoped_connection m_configureDropPixelsConn;
|
||||||
obs::scoped_connection m_sizeConn;
|
obs::scoped_connection m_sizeConn;
|
||||||
obs::scoped_connection m_angleConn;
|
obs::scoped_connection m_angleConn;
|
||||||
obs::scoped_connection m_opacityConn;
|
obs::scoped_connection m_opacityConn;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
|
// Copyright (C) 2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2015 David Capello
|
// Copyright (C) 2001-2015 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -8,6 +9,8 @@
|
||||||
#define APP_CONTEXT_BAR_OBSERVER_H_INCLUDED
|
#define APP_CONTEXT_BAR_OBSERVER_H_INCLUDED
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "gfx/fwd.h"
|
||||||
|
|
||||||
namespace app {
|
namespace app {
|
||||||
|
|
||||||
class ContextBarObserver {
|
class ContextBarObserver {
|
||||||
|
@ -16,6 +19,7 @@ public:
|
||||||
|
|
||||||
virtual ~ContextBarObserver() {}
|
virtual ~ContextBarObserver() {}
|
||||||
virtual void onDropPixels(DropAction action) {}
|
virtual void onDropPixels(DropAction action) {}
|
||||||
|
virtual void onConfigureDropPixels(DropAction action, const gfx::Point& pt) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
#include "doc/sprite.h"
|
#include "doc/sprite.h"
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
#include "ui/alert.h"
|
#include "ui/alert.h"
|
||||||
|
#include "ui/display.h"
|
||||||
#include "ui/menu.h"
|
#include "ui/menu.h"
|
||||||
#include "ui/message.h"
|
#include "ui/message.h"
|
||||||
#include "ui/shortcut.h"
|
#include "ui/shortcut.h"
|
||||||
|
@ -306,6 +307,11 @@ bool DocView::onCloseView(Workspace* workspace, bool quitting)
|
||||||
|
|
||||||
// See if the sprite has changes
|
// See if the sprite has changes
|
||||||
while (m_document->isModified()) {
|
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
|
// ask what want to do the user with the changes in the sprite
|
||||||
int ret = Alert::show(Strings::alerts_save_sprite_changes(
|
int ret = Alert::show(Strings::alerts_save_sprite_changes(
|
||||||
m_document->name(),
|
m_document->name(),
|
||||||
|
|
|
@ -2748,7 +2748,10 @@ void Editor::setZoomAndCenterInMouse(const Zoom& zoom,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Editor::pasteImage(const Image* image, const Mask* mask, const gfx::Point* position)
|
void Editor::pasteImage(const Image* image,
|
||||||
|
const Mask* mask,
|
||||||
|
const gfx::Point* position,
|
||||||
|
const Tileset* srcTileset)
|
||||||
{
|
{
|
||||||
ASSERT(image);
|
ASSERT(image);
|
||||||
|
|
||||||
|
@ -2840,6 +2843,10 @@ void Editor::pasteImage(const Image* image, const Mask* mask, const gfx::Point*
|
||||||
PixelsMovementPtr pixelsMovement(
|
PixelsMovementPtr pixelsMovement(
|
||||||
new PixelsMovement(UIContext::instance(), site, image, &mask2, "Paste"));
|
new PixelsMovement(UIContext::instance(), site, image, &mask2, "Paste"));
|
||||||
|
|
||||||
|
// Adjust image when copying between tilemap layers
|
||||||
|
if (site.tilemapMode() == TilemapMode::Tiles && srcTileset != nullptr)
|
||||||
|
pixelsMovement->remapTilesForPaste(srcTileset);
|
||||||
|
|
||||||
setState(EditorStatePtr(new MovingPixelsState(this, NULL, pixelsMovement, NoHandle)));
|
setState(EditorStatePtr(new MovingPixelsState(this, NULL, pixelsMovement, NoHandle)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -244,7 +244,8 @@ public:
|
||||||
|
|
||||||
void pasteImage(const Image* image,
|
void pasteImage(const Image* image,
|
||||||
const Mask* mask = nullptr,
|
const Mask* mask = nullptr,
|
||||||
const gfx::Point* position = nullptr);
|
const gfx::Point* position = nullptr,
|
||||||
|
const Tileset* srcTileset = nullptr);
|
||||||
|
|
||||||
void startSelectionTransformation(const gfx::Point& move, double angle);
|
void startSelectionTransformation(const gfx::Point& move, double angle);
|
||||||
void startFlipTransformation(doc::algorithm::FlipType flipType);
|
void startFlipTransformation(doc::algorithm::FlipType flipType);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -22,6 +22,7 @@
|
||||||
#include "app/commands/commands.h"
|
#include "app/commands/commands.h"
|
||||||
#include "app/commands/move_thing.h"
|
#include "app/commands/move_thing.h"
|
||||||
#include "app/console.h"
|
#include "app/console.h"
|
||||||
|
#include "app/i18n/strings.h"
|
||||||
#include "app/modules/gui.h"
|
#include "app/modules/gui.h"
|
||||||
#include "app/pref/preferences.h"
|
#include "app/pref/preferences.h"
|
||||||
#include "app/tools/ink.h"
|
#include "app/tools/ink.h"
|
||||||
|
@ -48,6 +49,7 @@
|
||||||
#include "fmt/format.h"
|
#include "fmt/format.h"
|
||||||
#include "gfx/rect.h"
|
#include "gfx/rect.h"
|
||||||
#include "ui/manager.h"
|
#include "ui/manager.h"
|
||||||
|
#include "ui/menu.h"
|
||||||
#include "ui/message.h"
|
#include "ui/message.h"
|
||||||
#include "ui/system.h"
|
#include "ui/system.h"
|
||||||
#include "ui/view.h"
|
#include "ui/view.h"
|
||||||
|
@ -286,9 +288,6 @@ bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
|
||||||
UIContext* ctx = UIContext::instance();
|
UIContext* ctx = UIContext::instance();
|
||||||
ctx->setActiveView(editor->getDocView());
|
ctx->setActiveView(editor->getDocView());
|
||||||
|
|
||||||
ContextBar* contextBar = App::instance()->contextBar();
|
|
||||||
contextBar->updateForMovingPixels(getTransformation(editor));
|
|
||||||
|
|
||||||
// Start scroll loop
|
// Start scroll loop
|
||||||
if (editor->checkForScroll(msg) || editor->checkForZoom(msg))
|
if (editor->checkForScroll(msg) || editor->checkForZoom(msg))
|
||||||
return true;
|
return true;
|
||||||
|
@ -442,10 +441,6 @@ void MovingPixelsState::onCommitMouseMove(Editor* editor, const gfx::PointF& spr
|
||||||
// Drag the image to that position
|
// Drag the image to that position
|
||||||
m_pixelsMovement->moveImage(spritePos, moveModifier);
|
m_pixelsMovement->moveImage(spritePos, moveModifier);
|
||||||
|
|
||||||
// Update context bar and status bar
|
|
||||||
ContextBar* contextBar = App::instance()->contextBar();
|
|
||||||
contextBar->updateForMovingPixels(transformation);
|
|
||||||
|
|
||||||
m_editor->updateStatusBar();
|
m_editor->updateStatusBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,16 +470,13 @@ bool MovingPixelsState::onKeyDown(Editor* editor, KeyMessage* msg)
|
||||||
// FineControl now (e.g. if we pressed another modifier key).
|
// FineControl now (e.g. if we pressed another modifier key).
|
||||||
m_lockedKeyAction = KeyAction::None;
|
m_lockedKeyAction = KeyAction::None;
|
||||||
|
|
||||||
if (msg->scancode() == kKeyEnter || // TODO make this key customizable
|
// TODO make these keys customizable
|
||||||
msg->scancode() == kKeyEnterPad || msg->scancode() == kKeyEsc) {
|
if (msg->scancode() == kKeyEsc) {
|
||||||
|
cancelDrag();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (msg->scancode() == kKeyEnter || msg->scancode() == kKeyEnterPad) {
|
||||||
dropPixels();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,6 +521,10 @@ bool MovingPixelsState::onUpdateStatusBar(Editor* editor)
|
||||||
const Transformation& transform(getTransformation(editor));
|
const Transformation& transform(getTransformation(editor));
|
||||||
gfx::Size imageSize = m_pixelsMovement->getInitialImageSize();
|
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 w = int(transform.bounds().w);
|
||||||
int h = int(transform.bounds().h);
|
int h = int(transform.bounds().h);
|
||||||
int gcd = base::gcd(w, h);
|
int gcd = base::gcd(w, h);
|
||||||
|
@ -796,14 +792,64 @@ void MovingPixelsState::onDropPixels(ContextBarObserver::DropAction action)
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case ContextBarObserver::DropPixels: dropPixels(); break;
|
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_pixelsMovement->discardImage(PixelsMovement::DontCommitChanges);
|
||||||
m_discarded = true;
|
m_discarded = true;
|
||||||
|
|
||||||
// Quit from MovingPixelsState, back to standby.
|
// Quit from MovingPixelsState, back to standby.
|
||||||
m_editor->backToPreviousState();
|
dropPixels();
|
||||||
break;
|
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
|
// Aseprite
|
||||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2017 David Capello
|
// Copyright (C) 2001-2017 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -77,6 +77,7 @@ public:
|
||||||
|
|
||||||
// ContextBarObserver
|
// ContextBarObserver
|
||||||
void onDropPixels(ContextBarObserver::DropAction action) override;
|
void onDropPixels(ContextBarObserver::DropAction action) override;
|
||||||
|
void onConfigureDropPixels(ContextBarObserver::DropAction action, const gfx::Point& pt) override;
|
||||||
|
|
||||||
// PixelsMovementDelegate
|
// PixelsMovementDelegate
|
||||||
void onPivotChange() override;
|
void onPivotChange() override;
|
||||||
|
@ -95,6 +96,7 @@ private:
|
||||||
|
|
||||||
void setTransparentColor(bool opaque, const app::Color& color);
|
void setTransparentColor(bool opaque, const app::Color& color);
|
||||||
void dropPixels();
|
void dropPixels();
|
||||||
|
void cancelDrag();
|
||||||
|
|
||||||
bool isActiveDocument() const;
|
bool isActiveDocument() const;
|
||||||
bool isActiveEditor() const;
|
bool isActiveEditor() const;
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "app/ui/editor/pixels_movement.h"
|
#include "app/ui/editor/pixels_movement.h"
|
||||||
|
|
||||||
#include "app/app.h"
|
#include "app/app.h"
|
||||||
|
#include "app/cmd/add_tile.h"
|
||||||
#include "app/cmd/clear_mask.h"
|
#include "app/cmd/clear_mask.h"
|
||||||
#include "app/cmd/deselect_mask.h"
|
#include "app/cmd/deselect_mask.h"
|
||||||
#include "app/cmd/set_mask.h"
|
#include "app/cmd/set_mask.h"
|
||||||
|
@ -1356,6 +1357,39 @@ void PixelsMovement::drawTransformedTilemap(const Transformation& transformation
|
||||||
draw_row(dst->height() - 1, src->height() - 1, 1);
|
draw_row(dst->height() - 1, src->height() - 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PixelsMovement::remapTilesForPaste(const Tileset* srcTileset)
|
||||||
|
{
|
||||||
|
// Check that tile size matches before doing anything
|
||||||
|
LayerTilemap* dstLayer = static_cast<LayerTilemap*>(m_site.layer());
|
||||||
|
Tileset* dstTileset = dstLayer->tileset();
|
||||||
|
const doc::Grid& srcGrid = srcTileset->grid();
|
||||||
|
const doc::Grid& dstGrid = dstTileset->grid();
|
||||||
|
if (srcGrid.tileSize() != dstGrid.tileSize())
|
||||||
|
throw base::Exception("Tile size does not match.");
|
||||||
|
|
||||||
|
// Map source to destination
|
||||||
|
tile_index dstSz = dstTileset->size();
|
||||||
|
for (int y = 0; y < m_originalImage->height(); ++y) {
|
||||||
|
for (int x = 0; x < m_originalImage->width(); ++x) {
|
||||||
|
// Blank tile can be skipped
|
||||||
|
const color_t srcTi = m_originalImage->getPixel(x, y);
|
||||||
|
if (!srcTi)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Add tile to destination if missing
|
||||||
|
tile_index dstTi;
|
||||||
|
const ImageRef t = srcTileset->get(srcTi);
|
||||||
|
if (!dstTileset->findTileIndex(t, dstTi)) {
|
||||||
|
m_tx(new cmd::AddTile(dstTileset, t, srcTileset->getTileData(srcTi)));
|
||||||
|
dstTi = dstSz++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update tile index in image
|
||||||
|
m_originalImage->putPixel(x, y, dstTi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void PixelsMovement::onPivotChange()
|
void PixelsMovement::onPivotChange()
|
||||||
{
|
{
|
||||||
set_pivot_from_preferences(m_currentData);
|
set_pivot_from_preferences(m_currentData);
|
||||||
|
|
|
@ -130,6 +130,9 @@ public:
|
||||||
const Transformation& getTransformation() const { return m_currentData; }
|
const Transformation& getTransformation() const { return m_currentData; }
|
||||||
void setTransformation(const Transformation& t);
|
void setTransformation(const Transformation& t);
|
||||||
|
|
||||||
|
// For copy/paste between tilemap layers
|
||||||
|
void remapTilesForPaste(const Tileset* srcTileset);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setTransformationBase(const Transformation& t);
|
void setTransformationBase(const Transformation& t);
|
||||||
void adjustPivot();
|
void adjustPivot();
|
||||||
|
|
|
@ -74,7 +74,11 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
|
||||||
double dz = delta.x + delta.y;
|
double dz = delta.x + delta.y;
|
||||||
WheelAction wheelAction = WheelAction::None;
|
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())
|
if (!Preferences::instance().editor.zoomWithSlide() && msg->preciseWheel())
|
||||||
wheelAction = WheelAction::VScroll;
|
wheelAction = WheelAction::VScroll;
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2017 David Capello
|
// Copyright (C) 2017 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -181,8 +181,7 @@ void SliceWindow::onPivotChange()
|
||||||
void SliceWindow::onToggleUserData()
|
void SliceWindow::onToggleUserData()
|
||||||
{
|
{
|
||||||
m_userDataView.toggleVisibility();
|
m_userDataView.toggleVisibility();
|
||||||
remapWindow();
|
expandWindow(gfx::Size(bounds().w, sizeHint().h));
|
||||||
manager()->invalidate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SliceWindow::onModifyField(ui::Entry* entry, const Mods mods)
|
void SliceWindow::onModifyField(ui::Entry* entry, const Mods mods)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite
|
// Aseprite
|
||||||
// Copyright (C) 2019-2022 Igara Studio S.A.
|
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2016 David Capello
|
// Copyright (C) 2001-2016 David Capello
|
||||||
//
|
//
|
||||||
// This program is distributed under the terms of
|
// This program is distributed under the terms of
|
||||||
|
@ -132,8 +132,7 @@ void TagWindow::onRepeatChange()
|
||||||
void TagWindow::onToggleUserData()
|
void TagWindow::onToggleUserData()
|
||||||
{
|
{
|
||||||
m_userDataView.toggleVisibility();
|
m_userDataView.toggleVisibility();
|
||||||
remapWindow();
|
expandWindow(gfx::Size(bounds().w, sizeHint().h));
|
||||||
manager()->invalidate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace app
|
} // namespace app
|
||||||
|
|
|
@ -4321,6 +4321,12 @@ void Timeline::clearAndInvalidateRange()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Timeline::refresh()
|
||||||
|
{
|
||||||
|
regenerateRows();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
app::gen::GlobalPref::Timeline& Timeline::timelinePref() const
|
app::gen::GlobalPref::Timeline& Timeline::timelinePref() const
|
||||||
{
|
{
|
||||||
return Preferences::instance().timeline;
|
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.
|
// Determine at which frame and layer the content was dropped on.
|
||||||
frame_t frame = m_frame;
|
frame_t frame = m_frame;
|
||||||
layer_t layerIndex = getLayerIndex(m_layer);
|
layer_t layerIndex = m_sprite->root()->getLayerIndex(m_layer);
|
||||||
InsertionPoint insert = InsertionPoint::BeforeLayer;
|
InsertionPoint insert = InsertionPoint::BeforeLayer;
|
||||||
DroppedOn droppedOn = DroppedOn::Unspecified;
|
DroppedOn droppedOn = DroppedOn::Unspecified;
|
||||||
TRACE("m_dropRange.type() %d\n", m_dropRange.type());
|
TRACE("m_dropRange.type() %d\n", m_dropRange.type());
|
||||||
|
@ -4602,7 +4608,7 @@ void Timeline::onDrop(ui::DragEvent& e)
|
||||||
break;
|
break;
|
||||||
case Range::kLayers:
|
case Range::kLayers:
|
||||||
droppedOn = DroppedOn::Layer;
|
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();
|
auto* selectedLayer = *m_dropRange.selectedLayers().begin();
|
||||||
layerIndex = getLayerIndex(selectedLayer);
|
layerIndex = getLayerIndex(selectedLayer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,6 +155,8 @@ public:
|
||||||
|
|
||||||
void clearAndInvalidateRange();
|
void clearAndInvalidateRange();
|
||||||
|
|
||||||
|
void refresh();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool onProcessMessage(ui::Message* msg) override;
|
bool onProcessMessage(ui::Message* msg) override;
|
||||||
void onInitTheme(ui::InitThemeEvent& ev) override;
|
void onInitTheme(ui::InitThemeEvent& ev) override;
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "app/app.h"
|
#include "app/app.h"
|
||||||
|
#include "app/cmd/add_tileset.h"
|
||||||
#include "app/cmd/clear_mask.h"
|
#include "app/cmd/clear_mask.h"
|
||||||
#include "app/cmd/deselect_mask.h"
|
#include "app/cmd/deselect_mask.h"
|
||||||
#include "app/cmd/set_mask.h"
|
#include "app/cmd/set_mask.h"
|
||||||
|
@ -584,7 +585,10 @@ void Clipboard::paste(Context* ctx, const bool interactive, const gfx::Point* po
|
||||||
// TODO add post-command parameters (issue #2324)
|
// TODO add post-command parameters (issue #2324)
|
||||||
|
|
||||||
// Change to MovingTilemapState
|
// Change to MovingTilemapState
|
||||||
editor->pasteImage(m_data->tilemap.get(), m_data->mask.get(), position);
|
editor->pasteImage(m_data->tilemap.get(),
|
||||||
|
m_data->mask.get(),
|
||||||
|
position,
|
||||||
|
m_data->tileset.get());
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// TODO non-interactive version (for scripts)
|
// TODO non-interactive version (for scripts)
|
||||||
|
@ -763,7 +767,14 @@ void Clipboard::paste(Context* ctx, const bool interactive, const gfx::Point* po
|
||||||
afterThis = dstSpr->root()->lastLayer();
|
afterThis = dstSpr->root()->lastLayer();
|
||||||
|
|
||||||
Layer* newLayer = nullptr;
|
Layer* newLayer = nullptr;
|
||||||
if (srcLayer->isImage())
|
if (srcLayer->isTilemap()) {
|
||||||
|
Tileset* srcTileset = static_cast<LayerTilemap*>(srcLayer)->tileset();
|
||||||
|
Tileset* tilesetCopy = Tileset::MakeCopyCopyingImagesForSprite(srcTileset, dstSpr);
|
||||||
|
const tileset_index tsi = dstSpr->tilesets()->size();
|
||||||
|
tx(new cmd::AddTileset(dstSpr, tilesetCopy));
|
||||||
|
newLayer = new LayerTilemap(dstSpr, tsi);
|
||||||
|
}
|
||||||
|
else if (srcLayer->isImage())
|
||||||
newLayer = new LayerImage(dstSpr);
|
newLayer = new LayerImage(dstSpr);
|
||||||
else if (srcLayer->isGroup())
|
else if (srcLayer->isGroup())
|
||||||
newLayer = new LayerGroup(dstSpr);
|
newLayer = new LayerGroup(dstSpr);
|
||||||
|
|
|
@ -597,4 +597,22 @@ void LayerGroup::displaceFrames(frame_t fromThis, frame_t delta)
|
||||||
layer->displaceFrames(fromThis, delta);
|
layer->displaceFrames(fromThis, delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layer_t LayerGroup::getLayerIndex(const Layer* layer, layer_t& index) const
|
||||||
|
{
|
||||||
|
for (Layer* child : this->layers()) {
|
||||||
|
if ((child->isGroup() && static_cast<LayerGroup*>(child)->getLayerIndex(layer, index) != -1) ||
|
||||||
|
(child == layer)) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer_t LayerGroup::getLayerIndex(const Layer* layer) const
|
||||||
|
{
|
||||||
|
layer_t index = 0;
|
||||||
|
return this->getLayerIndex(layer, index);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace doc
|
} // namespace doc
|
||||||
|
|
|
@ -236,9 +236,13 @@ public:
|
||||||
|
|
||||||
bool isBrowsable() const override { return isGroup() && isExpanded() && !m_layers.empty(); }
|
bool isBrowsable() const override { return isGroup() && isExpanded() && !m_layers.empty(); }
|
||||||
|
|
||||||
|
layer_t getLayerIndex(const Layer* layer) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void destroyAllLayers();
|
void destroyAllLayers();
|
||||||
|
|
||||||
|
layer_t getLayerIndex(const Layer* layer, layer_t& index) const;
|
||||||
|
|
||||||
LayerList m_layers;
|
LayerList m_layers;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite Document Library
|
// Aseprite Document Library
|
||||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||||
// Copyright (C) 2001-2018 David Capello
|
// Copyright (C) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// This file is released under the terms of the MIT license.
|
||||||
|
@ -46,24 +46,16 @@ void for_each_mask_pixel(Mask& a, const Mask& b, Func f)
|
||||||
|
|
||||||
Mask::Mask() : Object(ObjectType::Mask)
|
Mask::Mask() : Object(ObjectType::Mask)
|
||||||
{
|
{
|
||||||
initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Mask::Mask(const Mask& mask) : Object(mask)
|
Mask::Mask(const Mask& mask) : Object(mask)
|
||||||
{
|
{
|
||||||
initialize();
|
|
||||||
copyFrom(&mask);
|
copyFrom(&mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
Mask::~Mask()
|
Mask::~Mask()
|
||||||
{
|
{
|
||||||
ASSERT(m_freeze_count == 0);
|
ASSERT(m_freezes == 0);
|
||||||
}
|
|
||||||
|
|
||||||
void Mask::initialize()
|
|
||||||
{
|
|
||||||
m_freeze_count = 0;
|
|
||||||
m_bounds = gfx::Rect(0, 0, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int Mask::getMemSize() const
|
int Mask::getMemSize() const
|
||||||
|
@ -78,17 +70,17 @@ void Mask::setName(const char* name)
|
||||||
|
|
||||||
void Mask::freeze()
|
void Mask::freeze()
|
||||||
{
|
{
|
||||||
ASSERT(m_freeze_count >= 0);
|
ASSERT(m_freezes >= 0);
|
||||||
m_freeze_count++;
|
m_freezes++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mask::unfreeze()
|
void Mask::unfreeze()
|
||||||
{
|
{
|
||||||
ASSERT(m_freeze_count > 0);
|
ASSERT(m_freezes > 0);
|
||||||
m_freeze_count--;
|
m_freezes--;
|
||||||
|
|
||||||
// Shrink just in case
|
// Shrink just in case
|
||||||
if (m_freeze_count == 0)
|
if (m_freezes == 0)
|
||||||
shrink();
|
shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +102,7 @@ bool Mask::isRectangular() const
|
||||||
|
|
||||||
void Mask::copyFrom(const Mask* sourceMask)
|
void Mask::copyFrom(const Mask* sourceMask)
|
||||||
{
|
{
|
||||||
ASSERT(m_freeze_count == 0);
|
ASSERT(m_freezes == 0);
|
||||||
|
|
||||||
clear();
|
clear();
|
||||||
setName(sourceMask->name().c_str());
|
setName(sourceMask->name().c_str());
|
||||||
|
@ -245,10 +237,10 @@ void Mask::intersect(const doc::Mask& mask)
|
||||||
|
|
||||||
void Mask::add(const gfx::Rect& bounds)
|
void Mask::add(const gfx::Rect& bounds)
|
||||||
{
|
{
|
||||||
if (m_freeze_count == 0)
|
if (m_freezes == 0)
|
||||||
reserve(bounds);
|
reserve(bounds);
|
||||||
|
|
||||||
// m_bitmap can be nullptr if we have m_freeze_count > 0
|
// m_bitmap can be nullptr if we have m_freezes > 0
|
||||||
if (!m_bitmap)
|
if (!m_bitmap)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -490,7 +482,7 @@ void Mask::reserve(const gfx::Rect& bounds)
|
||||||
void Mask::shrink()
|
void Mask::shrink()
|
||||||
{
|
{
|
||||||
// If the mask is frozen we avoid the shrinking
|
// If the mask is frozen we avoid the shrinking
|
||||||
if (m_freeze_count > 0)
|
if (m_freezes > 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
#define SHRINK_SIDE(u_begin, u_op, u_final, u_add, v_begin, v_op, v_final, v_add, U, V, var) \
|
#define SHRINK_SIDE(u_begin, u_op, u_final, u_add, v_begin, v_op, v_final, v_add, U, V, var) \
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite Document Library
|
// Aseprite Document Library
|
||||||
// Copyright (c) 2020-2024 Igara Studio S.A.
|
// Copyright (c) 2020-2025 Igara Studio S.A.
|
||||||
// Copyright (c) 2001-2018 David Capello
|
// Copyright (c) 2001-2018 David Capello
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// This file is released under the terms of the MIT license.
|
||||||
|
@ -65,7 +65,7 @@ public:
|
||||||
void unfreeze();
|
void unfreeze();
|
||||||
|
|
||||||
// Returns true if the mask is frozen (See freeze/unfreeze functions).
|
// Returns true if the mask is frozen (See freeze/unfreeze functions).
|
||||||
bool isFrozen() const { return m_freeze_count > 0; }
|
bool isFrozen() const { return m_freezes > 0; }
|
||||||
|
|
||||||
// Returns true if the mask is a rectangular region.
|
// Returns true if the mask is a rectangular region.
|
||||||
bool isRectangular() const;
|
bool isRectangular() const;
|
||||||
|
@ -107,9 +107,7 @@ public:
|
||||||
void offsetOrigin(int dx, int dy);
|
void offsetOrigin(int dx, int dy);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initialize();
|
int m_freezes = 0;
|
||||||
|
|
||||||
int m_freeze_count;
|
|
||||||
std::string m_name; // Mask name
|
std::string m_name; // Mask name
|
||||||
gfx::Rect m_bounds; // Region bounds
|
gfx::Rect m_bounds; // Region bounds
|
||||||
ImageRef m_bitmap; // Bitmapped image mask
|
ImageRef m_bitmap; // Bitmapped image mask
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Aseprite UI Library
|
// Aseprite UI Library
|
||||||
// Copyright (C) 2019-2024 Igara Studio S.A.
|
// Copyright (C) 2019-2025 Igara Studio S.A.
|
||||||
//
|
//
|
||||||
// This file is released under the terms of the MIT license.
|
// This file is released under the terms of the MIT license.
|
||||||
// Read LICENSE.txt for more information.
|
// Read LICENSE.txt for more information.
|
||||||
|
@ -87,7 +87,8 @@ void Display::configureBackLayer()
|
||||||
layerSurface->width() != displaySurface->width() ||
|
layerSurface->width() != displaySurface->width() ||
|
||||||
layerSurface->height() != displaySurface->height()) {
|
layerSurface->height() != displaySurface->height()) {
|
||||||
layerSurface = os::System::instance()->makeSurface(displaySurface->width(),
|
layerSurface = os::System::instance()->makeSurface(displaySurface->width(),
|
||||||
displaySurface->height());
|
displaySurface->height(),
|
||||||
|
displaySurface->colorSpace());
|
||||||
layer->setSurface(layerSurface);
|
layer->setSurface(layerSurface);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 8607be393dfd81614611897a6e3026fe94a3966c
|
Subproject commit d14c2d1764f800d31b51893fb3d1e05d77a9280b
|
Loading…
Reference in New Issue