Improve click behavior to cancel Text box tool (#4692)

We've moved the logic to interpret "a single click" from DrawingState to
DelayedMouseMove.
This commit is contained in:
David Capello 2024-10-14 13:32:43 -03:00
parent 5b88ea8532
commit 8b7370a77a
5 changed files with 64 additions and 43 deletions

View File

@ -11,9 +11,14 @@
#include "app/ui/editor/delayed_mouse_move.h"
#include "app/ui/editor/editor.h"
#include "base/time.h"
#include "ui/message.h"
namespace app {
static const gfx::Point kNoPosReceived(std::numeric_limits<int>::min(),
std::numeric_limits<int>::min());
DelayedMouseMove::DelayedMouseMove(DelayedMouseMoveDelegate* delegate,
Editor* editor,
const int interval)
@ -22,11 +27,20 @@ DelayedMouseMove::DelayedMouseMove(DelayedMouseMoveDelegate* delegate,
, m_timer(interval)
, m_spritePos(std::numeric_limits<float>::min(),
std::numeric_limits<float>::min())
, m_mouseMoveReceived(false)
, m_mouseDownPos(kNoPosReceived)
, m_mouseDownTime(base::current_tick())
{
ASSERT(m_delegate);
m_timer.Tick.connect([this] { commitMouseMove(); });
}
void DelayedMouseMove::reset()
{
m_mouseMoveReceived = false;
m_mouseDownPos = kNoPosReceived;
}
void DelayedMouseMove::initSpritePos(const gfx::PointF& pos)
{
m_spritePos = pos;
@ -34,11 +48,23 @@ void DelayedMouseMove::initSpritePos(const gfx::PointF& pos)
void DelayedMouseMove::onMouseDown(const ui::MouseMessage* msg)
{
if (m_mouseDownPos == kNoPosReceived) {
m_mouseDownPos = msg->position();
}
updateSpritePos(msg);
}
bool DelayedMouseMove::onMouseMove(const ui::MouseMessage* msg)
{
// Indicate that we've received a real mouse movement event here
// (used in the Rectangular Marquee to deselect when we just do a
// simple click without moving the mouse).
m_mouseMoveReceived = true;
gfx::Point delta = (msg->position() - m_mouseDownPos);
m_mouseMaxDelta.x = std::max(m_mouseMaxDelta.x, std::abs(delta.x));
m_mouseMaxDelta.y = std::max(m_mouseMaxDelta.y, std::abs(delta.y));
if (!updateSpritePos(msg))
return false;
@ -66,6 +92,15 @@ void DelayedMouseMove::stopTimer()
m_timer.stop();
}
bool DelayedMouseMove::canInterpretMouseMovementAsJustOneClick() const
{
return
!m_mouseMoveReceived ||
(m_mouseMaxDelta.x < 4 &&
m_mouseMaxDelta.y < 4 &&
(base::current_tick() - m_mouseDownTime < 250));
}
void DelayedMouseMove::commitMouseMove()
{
if (m_timer.isRunning())

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2022 Igara Studio S.A.
// Copyright (C) 2022-2024 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -41,6 +41,10 @@ namespace app {
Editor* editor,
const int interval);
// Resets internals to receive an onMouseDown() again and
// interpret a "one click" correctly again.
void reset();
// In case the event wasn't started with onMouseDown() we can
// initialize the sprite position directly (e.g. starting a line
// from last painted point with Shift+click with Pencil tool).
@ -57,6 +61,11 @@ namespace app {
const gfx::PointF& spritePos() const;
// If the user clicked (pressed and released the mouse button) in
// less than 250 milliseconds in "the same place" (inside a 4
// pixels rectangle actually, to detect stylus shake).
bool canInterpretMouseMovementAsJustOneClick() const;
private:
void commitMouseMove();
bool updateSpritePos(const ui::MouseMessage* msg);
@ -65,6 +74,14 @@ namespace app {
Editor* m_editor;
ui::Timer m_timer;
// These fields are used to detect a single click, e.g. in a
// selection tool, a single click deselect (press and release the
// mouse button in the "same location" approximately).
bool m_mouseMoveReceived;
gfx::Point m_mouseMaxDelta;
gfx::Point m_mouseDownPos;
base::tick_t m_mouseDownTime;
// Position of the mouse in the canvas to avoid redrawing when the
// mouse position changes (only we redraw when the canvas position
// changes).

View File

@ -68,7 +68,6 @@ DrawingState::DrawingState(Editor* editor,
get_delay_interval_for_tool_loop(toolLoop))
, m_toolLoop(toolLoop)
, m_toolLoopManager(new tools::ToolLoopManager(toolLoop))
, m_mouseMoveReceived(false)
, m_mousePressedReceived(false)
, m_processScrollChange(true)
{
@ -117,9 +116,6 @@ void DrawingState::initToolLoop(Editor* editor,
m_velocity.reset();
m_lastPointer = pointer;
m_mouseDownPos = (msg ? msg->position():
editor->editorToScreen(pointer.point()));
m_mouseDownTime = base::current_tick();
m_toolLoopManager->prepareLoop(pointer);
m_toolLoopManager->pressButton(pointer);
@ -129,7 +125,7 @@ void DrawingState::initToolLoop(Editor* editor,
editor->captureMouse();
}
void DrawingState::disableMouseStabilizer()
void DrawingState::disableMouseStabilizer()
{
ASSERT(m_toolLoopManager);
m_toolLoopManager->disableMouseStabilizer();
@ -216,7 +212,7 @@ bool DrawingState::onMouseUp(Editor* editor, MouseMessage* msg)
// one click).
if (!m_toolLoop->getInk()->isSelection() ||
m_toolLoop->getController()->isOnePoint() ||
!canInterpretMouseMovementAsJustOneClick() ||
!m_delayedMouseMove.canInterpretMouseMovementAsJustOneClick() ||
// In case of double-click (to select tiles) we don't want to
// deselect if the mouse is not moved. In this case the tile
// will be selected anyway even if the mouse is not moved.
@ -271,14 +267,6 @@ bool DrawingState::onMouseMove(Editor* editor, MouseMessage* msg)
msg->pointerType(),
msg->pressure());
// Indicate that we've received a real mouse movement event here
// (used in the Rectangular Marquee to deselect when we just do a
// simple click without moving the mouse).
m_mouseMoveReceived = true;
gfx::Point delta = (msg->position() - m_mouseDownPos);
m_mouseMaxDelta.x = std::max(m_mouseMaxDelta.x, std::abs(delta.x));
m_mouseMaxDelta.y = std::max(m_mouseMaxDelta.y, std::abs(delta.y));
// Use DelayedMouseMove for tools like line, rectangle, etc. (that
// use the only the last mouse position) to filter out rapid mouse
// movement.
@ -395,18 +383,6 @@ void DrawingState::handleMouseMovement()
m_toolLoopManager->movement(m_lastPointer);
}
bool DrawingState::canInterpretMouseMovementAsJustOneClick()
{
// If the user clicked (pressed and released the mouse button) in
// less than 250 milliseconds in "the same place" (inside a 7 pixels
// rectangle actually, to detect stylus shake).
return
!m_mouseMoveReceived ||
(m_mouseMaxDelta.x < 4 &&
m_mouseMaxDelta.y < 4 &&
(base::current_tick() - m_mouseDownTime < 250));
}
bool DrawingState::canExecuteCommands()
{
// Returning true here means that the user can trigger commands with

View File

@ -72,7 +72,6 @@ namespace app {
private:
void handleMouseMovement();
bool canInterpretMouseMovementAsJustOneClick();
bool canExecuteCommands();
void onBeforeCommandExecution(CommandExecutionEvent& ev);
void destroyLoopIfCanceled(Editor* editor);
@ -92,14 +91,6 @@ namespace app {
// Tool-loop manager
std::unique_ptr<tools::ToolLoopManager> m_toolLoopManager;
// These fields are used to detect a selection tool cancelation
// (deselect command) when the user just click (press and release
// the mouse button in the "same location" approximately).
bool m_mouseMoveReceived;
gfx::Point m_mouseMaxDelta;
gfx::Point m_mouseDownPos;
base::tick_t m_mouseDownTime;
// Stores the last mouse pointer, used to re-use the latest mouse
// button when onScrollChange() event is received.
tools::Pointer m_lastPointer;

View File

@ -308,12 +308,14 @@ WritingTextState::~WritingTextState()
bool WritingTextState::onMouseDown(Editor* editor, MouseMessage* msg)
{
if (!editor->hasCapture())
m_delayedMouseMove.reset();
m_delayedMouseMove.onMouseDown(msg);
m_hit = calcHit(editor, msg->position());
if (msg->left()) {
if (m_hit == Hit::Edges) {
m_mouseMoveReceived = false;
m_movingBounds = true;
m_cursorStart = editor->screenToEditorF(msg->position());
m_boundsOrigin = m_bounds.origin();
@ -322,6 +324,8 @@ bool WritingTextState::onMouseDown(Editor* editor, MouseMessage* msg)
return true;
}
// On mouse down with the left button, we just drop the text
// directly when we click outside the edges.
drop();
return true;
}
@ -338,12 +342,12 @@ bool WritingTextState::onMouseUp(Editor* editor, MouseMessage* msg)
m_delayedMouseMove.onMouseUp(msg);
const bool result = StandbyState::onMouseUp(editor, msg);
if (m_movingBounds) {
if (m_movingBounds)
m_movingBounds = false;
// Drop if the user just clicked (so other text box is created)
if (!m_mouseMoveReceived)
drop();
// Drop if the user just clicked (so other text box is created)
if (m_delayedMouseMove.canInterpretMouseMovementAsJustOneClick()) {
drop();
}
return result;
@ -367,8 +371,6 @@ void WritingTextState::onCommitMouseMove(Editor* editor,
if (delta.x == 0 && delta.y == 0)
return;
m_mouseMoveReceived = true;
m_bounds.setOrigin(gfx::Point(delta + m_boundsOrigin));
m_entry->setExtraCelBounds(m_bounds);
m_entry->setBounds(calcEntryBounds());