Compare commits

...

8 Commits

Author SHA1 Message Date
Gaspar Capello ffb5b0999b
Merge c98a2056e5 into 80fa065bd5 2025-07-28 00:15:51 -03:00
Christian Kaiser 80fa065bd5 [lua] Add sprite.undoHistory
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-07-25 13:58:52 -03:00
David Capello de1ccb24dd [win] Don't drop text when IME dialog composition is accepted w/Enter
build / build (Debug, macos-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, macos-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, lua, cli) (push) Waiting to run Details
build / build (Debug, windows-latest, noscripts, cli) (push) Waiting to run Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Waiting to run Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Waiting to run Details
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
With #5230, now that we can show the IME dialog on Windows, when we
are selecting a specific word/composition in the IME dialog, if we
press Enter we'll receive that Enter onKeyUp(). It's better if we
process the Enter key onKeyDown() (as the IME enter key is not
received in that case).
2025-07-25 09:19:50 -03:00
David Capello 7d91c4b9d9 [win] Fix dead keys on Windows 2025-07-24 17:45:46 -03:00
Cerallin 6d89a6bc15 fix entry
build-auto / build-auto (Debug, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (Debug, windows-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, macos-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, ubuntu-latest) (push) Has been cancelled Details
build-auto / build-auto (RelWithDebInfo, windows-latest) (push) Has been cancelled Details
build / build (Debug, macos-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, macos-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, ubuntu-latest, noscripts, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, lua, cli) (push) Has been cancelled Details
build / build (Debug, windows-latest, noscripts, cli) (push) Has been cancelled Details
build / build (RelWithDebInfo, macos-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, ubuntu-latest, lua, gui) (push) Has been cancelled Details
build / build (RelWithDebInfo, windows-latest, lua, gui) (push) Has been cancelled Details
2025-07-24 12:50:32 -03:00
Cerallin d4e97b5a96 Add const method Entry::caretPosOnScreen()
This method is for Entry::setTextInput() and IME positioning.
2025-07-24 12:50:32 -03:00
Cerallin 205b18dc0f Make Entry::getCharBoxBounds() a const method 2025-07-24 12:50:32 -03:00
Gaspar Capello c98a2056e5 Add Lock Axis alternative (fix #5251)
The lock axis is selected and held as soon as SHIFT is pressed.
A diagonal translation lock has also been added.
2025-07-04 15:54:49 -03:00
14 changed files with 232 additions and 29 deletions

2
laf

@ -1 +1 @@
Subproject commit a2bb9ec7fb98354279a2c49870a4a47a67a8e86e
Subproject commit 8ec4b553f1618f7a4b47cdcf4cfc2663266111ac

View File

@ -705,6 +705,7 @@ target_sources(app-lib PRIVATE
util/filetoks.cpp
util/layer_boundaries.cpp
util/layer_utils.cpp
util/moving_utils.cpp
util/msk_file.cpp
util/new_image_from_mask.cpp
util/open_file_job.cpp

View File

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

View File

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

View File

@ -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
@ -247,14 +247,10 @@ void MovingCelState::onCommitMouseMove(Editor* editor, const gfx::PointF& newCur
m_celOffset = newCursorPos - m_cursorStart;
if (int(editor->getCustomizationDelegate()->getPressedKeyAction(
KeyContext::TranslatingSelection) &
KeyAction::LockAxis)) {
if (ABS(m_celOffset.x) < ABS(m_celOffset.y)) {
m_celOffset.x = 0;
}
else {
m_celOffset.y = 0;
}
}
KeyAction::LockAxis))
app::lockAxis(m_lockedAxis, m_celOffset);
else
m_lockedAxis = NONE;
if (!m_moved && intCelOffset() != gfx::Point(0, 0))
m_moved = true;
break;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2021-2022 Igara Studio S.A.
// Copyright (C) 2021-2025 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -14,6 +14,7 @@
#include "app/context_access.h"
#include "app/ui/editor/delayed_mouse_move.h"
#include "app/ui/editor/handle_type.h"
#include "app/util/moving_utils.h"
#include "doc/cel_list.h"
#include <vector>
@ -80,6 +81,7 @@ private:
gfx::PointF m_celOffset;
gfx::SizeF m_celMainSize;
gfx::SizeF m_celScale;
LockedAxis m_lockedAxis;
bool m_maskVisible;
bool m_hasReference = false;
bool m_moved = false;

View File

@ -376,6 +376,7 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
switch (m_handle) {
case MovePixelsHandle: {
double dx, dy;
const bool isLockAxis = ((moveModifier & LockAxisMovement) == LockAxisMovement);
if (tilesModeOn) {
if (m_catchPos.x == 0 && m_catchPos.y == 0) {
// Movement through keyboard:
@ -393,7 +394,8 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
dy = point.y;
}
}
else if ((moveModifier & FineControl) == 0) {
// FineControl is discarded if 'Locked Axis' is active
else if (isLockAxis || ((moveModifier & FineControl) == 0)) {
dx = (std::floor(pos.x) - std::floor(m_catchPos.x));
dy = (std::floor(pos.y) - std::floor(m_catchPos.y));
}
@ -402,12 +404,10 @@ void PixelsMovement::moveImage(const gfx::PointF& pos, MoveModifier moveModifier
dy = (pos.y - m_catchPos.y);
}
if ((moveModifier & LockAxisMovement) == LockAxisMovement) {
if (std::abs(dx) < std::abs(dy))
dx = 0.0;
else
dy = 0.0;
}
if (isLockAxis)
lockAxis(m_lockedAxis, dx, dy);
else
m_lockedAxis = LockedAxis::NONE;
bounds.offset(dx, dy);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2024 Igara Studio S.A.
// Copyright (C) 2019-2025 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -15,6 +15,7 @@
#include "app/transformation.h"
#include "app/tx.h"
#include "app/ui/editor/handle_type.h"
#include "app/util/moving_utils.h"
#include "doc/algorithm/flip_type.h"
#include "doc/frame.h"
#include "doc/image_ref.h"
@ -190,6 +191,7 @@ private:
Transformation m_currentData;
std::unique_ptr<Mask> m_initialMask, m_initialMask0;
std::unique_ptr<Mask> m_currentMask;
LockedAxis m_lockedAxis;
bool m_opaque;
color_t m_maskColor;
obs::scoped_connection m_pivotVisConn;

View File

@ -443,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) {
@ -457,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;
}

View File

@ -0,0 +1,49 @@
// Aseprite
// Copyright (C) 2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#include "app/util/moving_utils.h"
#include <cmath>
namespace app {
void lockAxis(LockedAxis& lockedAxis, gfx::PointF& delta)
{
double dx = delta.x;
double dy = delta.y;
lockAxis(lockedAxis, dx, dy);
delta.x = dx;
delta.y = dy;
}
void lockAxis(LockedAxis& lockedAxis, double& dx, double& dy)
{
if (lockedAxis == LockedAxis::NONE) {
if (std::abs(dx) * 3 > std::abs(dy) * 7) {
lockedAxis = LockedAxis::Y;
dy = 0.0;
}
else if (std::abs(dy) * 3 > std::abs(dx) * 7) {
lockedAxis = LockedAxis::X;
dx = 0.0;
}
else {
const bool sameSign = std::signbit(dx) == std::signbit(dy);
lockedAxis = sameSign ? LockedAxis::R_DIAG : LockedAxis::L_DIAG;
dy = sameSign ? dx : -dx;
}
}
else {
switch (lockedAxis) {
case LockedAxis::X: dx = 0.0; break;
case LockedAxis::Y: dy = 0.0; break;
case LockedAxis::R_DIAG: dy = dx; break;
case LockedAxis::L_DIAG: dy = -dx; break;
}
}
}
} // namespace app

View File

@ -0,0 +1,29 @@
// Aseprite
// Copyright (C) 2025 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UTIL_MOVING_UTILS_H_INCLUDED
#define APP_UTIL_MOVING_UTILS_H_INCLUDED
#pragma once
#include "gfx/point.h"
namespace app {
enum LockedAxis {
NONE,
X,
Y,
R_DIAG,
L_DIAG,
};
void lockAxis(LockedAxis& lockedAxis, gfx::PointF& delta);
void lockAxis(LockedAxis& lockedAxis, double& dx, double& dy);
} // namespace app
#endif // APP_UTIL_MOVING_UTILS_H_INCLUDED

View File

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

View File

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

View File

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