diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 5a84b7ccb..aca2b8b4b 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -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 diff --git a/src/app/ui/editor/moving_cel_state.cpp b/src/app/ui/editor/moving_cel_state.cpp index 4dd89235d..c65ccb063 100644 --- a/src/app/ui/editor/moving_cel_state.cpp +++ b/src/app/ui/editor/moving_cel_state.cpp @@ -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; diff --git a/src/app/ui/editor/moving_cel_state.h b/src/app/ui/editor/moving_cel_state.h index 6e4010bc9..db3ea36d5 100644 --- a/src/app/ui/editor/moving_cel_state.h +++ b/src/app/ui/editor/moving_cel_state.h @@ -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 @@ -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; diff --git a/src/app/ui/editor/pixels_movement.cpp b/src/app/ui/editor/pixels_movement.cpp index 8a648a90d..cc5b8575b 100644 --- a/src/app/ui/editor/pixels_movement.cpp +++ b/src/app/ui/editor/pixels_movement.cpp @@ -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); diff --git a/src/app/ui/editor/pixels_movement.h b/src/app/ui/editor/pixels_movement.h index c25e49e39..75cf0e0e7 100644 --- a/src/app/ui/editor/pixels_movement.h +++ b/src/app/ui/editor/pixels_movement.h @@ -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 m_initialMask, m_initialMask0; std::unique_ptr m_currentMask; + LockedAxis m_lockedAxis; bool m_opaque; color_t m_maskColor; obs::scoped_connection m_pivotVisConn; diff --git a/src/app/util/moving_utils.cpp b/src/app/util/moving_utils.cpp new file mode 100644 index 000000000..fb6d59018 --- /dev/null +++ b/src/app/util/moving_utils.cpp @@ -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 + +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 diff --git a/src/app/util/moving_utils.h b/src/app/util/moving_utils.h new file mode 100644 index 000000000..579e70a3e --- /dev/null +++ b/src/app/util/moving_utils.h @@ -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