Group several mouse movement in one on the Editor

Mainly for Line-like tools (which require the last mouse position
only). Related to #3119, possible fix for several performance issues
on Linux mainly.
This commit is contained in:
David Capello 2022-01-06 16:54:39 -03:00
parent bc8e1b36eb
commit 26c1a94b83
14 changed files with 355 additions and 133 deletions

View File

@ -1,5 +1,5 @@
# Aseprite
# Copyright (C) 2018-2021 Igara Studio S.A.
# Copyright (C) 2018-2022 Igara Studio S.A.
# Copyright (C) 2001-2018 David Capello
# Generate a ui::Widget for each widget in a XML file
@ -329,6 +329,7 @@ if(ENABLE_UI)
ui/drop_down_button.cpp
ui/dynamics_popup.cpp
ui/editor/brush_preview.cpp
ui/editor/delayed_mouse_move.cpp
ui/editor/drawing_state.cpp
ui/editor/editor.cpp
ui/editor/editor_observers.cpp

View File

@ -0,0 +1,93 @@
// Aseprite
// Copyright (C) 2022 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/editor/delayed_mouse_move.h"
#include "app/ui/editor/editor.h"
namespace app {
DelayedMouseMove::DelayedMouseMove(DelayedMouseMoveDelegate* delegate,
Editor* editor,
const int interval)
: m_delegate(delegate)
, m_editor(editor)
, m_timer(interval)
, m_spritePos(std::numeric_limits<int>::min(),
std::numeric_limits<int>::min())
{
ASSERT(m_delegate);
m_timer.Tick.connect([this] { commitMouseMove(); });
}
void DelayedMouseMove::onMouseDown(const ui::MouseMessage* msg)
{
updateSpritePos(msg);
}
bool DelayedMouseMove::onMouseMove(const ui::MouseMessage* msg)
{
if (!updateSpritePos(msg))
return false;
if (!m_timer.isRunning()) {
if (m_timer.interval() > 0) {
m_timer.start();
}
else {
// Commit immediately
commitMouseMove();
}
}
return true;
}
void DelayedMouseMove::onMouseUp(const ui::MouseMessage* msg)
{
updateSpritePos(msg);
commitMouseMove();
}
void DelayedMouseMove::commitMouseMove()
{
if (m_timer.isRunning())
m_timer.stop();
m_delegate->onCommitMouseMove(m_editor, spritePos());
}
const gfx::Point& DelayedMouseMove::spritePos() const
{
ASSERT(m_spritePos.x != std::numeric_limits<int>::min() &&
m_spritePos.y != std::numeric_limits<int>::min());
return m_spritePos;
}
bool DelayedMouseMove::updateSpritePos(const ui::MouseMessage* msg)
{
// The autoScroll() function controls the "infinite scroll" when we
// touch the viewport borders.
const gfx::Point mousePos = m_editor->autoScroll(msg, AutoScroll::MouseDir);
const gfx::Point spritePos = m_editor->screenToEditor(mousePos);
// Avoid redrawing everything if the position in the canvas didn't
// change.
//
// TODO Remove this if we add support for anti-aliasing in the
// transformations.
if (m_spritePos != spritePos) {
m_spritePos = spritePos;
return true;
}
else
return false;
}
} // namespace app

View File

@ -0,0 +1,66 @@
// Aseprite
// Copyright (C) 2022 Igara Studio S.A.
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_EDITOR_DELAYED_MOUSE_MOVE_STATE_H_INCLUDED
#define APP_UI_EDITOR_DELAYED_MOUSE_MOVE_STATE_H_INCLUDED
#pragma once
#include "app/ui/editor/standby_state.h"
#include "ui/timer.h"
#include <limits>
namespace ui {
class MouseMessage;
}
namespace app {
class Editor;
class DelayedMouseMoveDelegate {
public:
virtual ~DelayedMouseMoveDelegate() { }
virtual void onCommitMouseMove(Editor* editor,
const gfx::Point& spritePos) = 0;
};
// Helper class to group several onMouseMove() calls into one
// onCommitMouseMove(). Useful in Linux as mouse movement messages
// are queued a high speed (so we can group several mouse messages
// for tools that use only the last position and have a heavy
// calculation per mouse position, e.g. pixel transformations,
// drawing with Line, Rectangle, etc.).
class DelayedMouseMove {
public:
// The "interval" is given in milliseconds, and can be zero if we
// want to disable the delay between onMouseMove() -> onCommitMouseMove()
DelayedMouseMove(DelayedMouseMoveDelegate* delegate,
Editor* editor,
const int interval);
void onMouseDown(const ui::MouseMessage* msg);
bool onMouseMove(const ui::MouseMessage* msg);
void onMouseUp(const ui::MouseMessage* msg);
const gfx::Point& spritePos() const;
private:
void commitMouseMove();
bool updateSpritePos(const ui::MouseMessage* msg);
DelayedMouseMoveDelegate* m_delegate;
Editor* m_editor;
ui::Timer m_timer;
// Position of the mouse in the canvas to avoid redrawing when the
// mouse position changes (only we redraw when the canvas position
// changes).
gfx::Point m_spritePos;
};
} // namespace app
#endif // APP_UI_EDITOR_DELAYED_MOUSE_MOVE_STATE_H_INCLUDED

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -42,11 +42,27 @@ namespace app {
using namespace ui;
static int get_delay_interval_for_tool_loop(tools::ToolLoop* toolLoop)
{
if (toolLoop->getTracePolicy() == tools::TracePolicy::Last) {
// We use the delayed mouse movement for tools like Line,
// Rectangle, etc. (tools that use the last mouse position for its
// shape, so we can discard intermediate positions).
return 5;
}
else {
// Without delay for freehand-like tools
return 0;
}
}
DrawingState::DrawingState(Editor* editor,
tools::ToolLoop* toolLoop,
const DrawingType type)
: m_editor(editor)
, m_type(type)
, m_delayedMouseMove(this, editor,
get_delay_interval_for_tool_loop(toolLoop))
, m_toolLoop(toolLoop)
, m_toolLoopManager(new tools::ToolLoopManager(toolLoop))
, m_mouseMoveReceived(false)
@ -64,8 +80,11 @@ DrawingState::~DrawingState()
}
void DrawingState::initToolLoop(Editor* editor,
const ui::MouseMessage* msg,
const tools::Pointer& pointer)
{
m_delayedMouseMove.onMouseDown(msg);
// Prepare preview image (the destination image will be our preview
// in the tool-loop time, so we can see what we are drawing)
editor->renderEngine().setPreviewImage(
@ -121,6 +140,8 @@ bool DrawingState::onMouseDown(Editor* editor, MouseMessage* msg)
m_velocity.velocity());
m_lastPointer = pointer;
m_delayedMouseMove.onMouseDown(msg);
// Check if this drawing state was started with a Shift+Pencil tool
// and now the user pressed the right button to draw the straight
// line with the background color.
@ -160,6 +181,9 @@ bool DrawingState::onMouseUp(Editor* editor, MouseMessage* msg)
{
ASSERT(m_toolLoopManager != NULL);
m_lastPointer = pointer_from_msg(editor, msg, m_velocity.velocity());
m_delayedMouseMove.onMouseUp(msg);
// Selection tools with Replace mode are cancelled with a simple click.
// ("one point" controller selection tool i.e. the magic wand, and
// selection tools with Add or Subtract mode aren't cancelled with
@ -173,9 +197,6 @@ bool DrawingState::onMouseUp(Editor* editor, MouseMessage* msg)
m_type == DrawingType::SelectTiles ||
(editor->getToolLoopModifiers() != tools::ToolLoopModifiers::kReplaceSelection &&
editor->getToolLoopModifiers() != tools::ToolLoopModifiers::kIntersectSelection)) {
m_lastPointer = pointer_from_msg(editor, msg,
m_velocity.velocity());
// Notify the release of the mouse button to the tool loop
// manager. This is the correct way to say "the user finishes the
// drawing trace correctly".
@ -218,19 +239,28 @@ bool DrawingState::onMouseMove(Editor* editor, MouseMessage* msg)
// Update velocity sensor.
m_velocity.updateWithScreenPoint(msg->position());
// The autoScroll() function controls the "infinite scroll" when we
// touch the viewport borders.
gfx::Point mousePos = editor->autoScroll(msg, AutoScroll::MouseDir);
handleMouseMovement(
tools::Pointer(editor->screenToEditor(mousePos),
m_velocity.velocity(),
button_from_msg(msg),
msg->pointerType(),
msg->pressure()));
m_lastPointer = tools::Pointer(m_delayedMouseMove.spritePos(),
m_velocity.velocity(),
button_from_msg(msg),
msg->pointerType(),
msg->pressure());
// Use DelayedMouseMove for tools like line, rectangle, etc. (that
// use the only the last mouse position) to filter out rapid mouse
// movement.
m_delayedMouseMove.onMouseMove(msg);
return true;
}
void DrawingState::onCommitMouseMove(Editor* editor,
const gfx::Point& spritePos)
{
if (m_toolLoop &&
!m_toolLoop->isCanceled()) {
handleMouseMovement();
}
}
bool DrawingState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
{
if (m_toolLoop->getInk()->isEyedropper()) {
@ -289,12 +319,12 @@ bool DrawingState::onScrollChange(Editor* editor)
// Update velocity sensor.
m_velocity.updateWithScreenPoint(mousePos); // TODO add scroll as velocity?
handleMouseMovement(
tools::Pointer(editor->screenToEditor(mousePos),
m_velocity.velocity(),
m_lastPointer.button(),
tools::Pointer::Type::Unknown,
0.0f));
m_lastPointer = tools::Pointer(editor->screenToEditor(mousePos),
m_velocity.velocity(),
m_lastPointer.button(),
tools::Pointer::Type::Unknown,
0.0f);
handleMouseMovement();
}
return true;
}
@ -312,14 +342,13 @@ void DrawingState::onExposeSpritePixels(const gfx::Region& rgn)
m_toolLoop->validateDstImage(rgn);
}
void DrawingState::handleMouseMovement(const tools::Pointer& pointer)
void DrawingState::handleMouseMovement()
{
m_mouseMoveReceived = true;
m_lastPointer = pointer;
// Notify mouse movement to the tool
ASSERT(m_toolLoopManager);
m_toolLoopManager->movement(pointer);
m_toolLoopManager->movement(m_lastPointer);
}
bool DrawingState::canExecuteCommands()

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -11,6 +11,7 @@
#include "app/tools/pointer.h"
#include "app/tools/velocity.h"
#include "app/ui/editor/delayed_mouse_move.h"
#include "app/ui/editor/standby_state.h"
#include "obs/connection.h"
#include <memory>
@ -23,7 +24,8 @@ namespace app {
class CommandExecutionEvent;
class DrawingState : public StandbyState {
class DrawingState : public StandbyState
, DelayedMouseMoveDelegate {
public:
DrawingState(Editor* editor,
tools::ToolLoop* loop,
@ -45,6 +47,7 @@ namespace app {
virtual bool requireBrushPreview() override { return false; }
void initToolLoop(Editor* editor,
const ui::MouseMessage* msg,
const tools::Pointer& pointer);
// Used to send a movement() to the ToolLoopManager when
@ -54,14 +57,19 @@ namespace app {
void notifyToolLoopModifiersChange(Editor* editor);
private:
void handleMouseMovement(const tools::Pointer& pointer);
void handleMouseMovement();
bool canExecuteCommands();
void onBeforeCommandExecution(CommandExecutionEvent& ev);
void destroyLoopIfCanceled(Editor* editor);
void destroyLoop(Editor* editor);
// DelayedMouseMoveDelegate impl
void onCommitMouseMove(Editor* editor,
const gfx::Point& spritePos) override;
Editor* m_editor;
DrawingType m_type;
DelayedMouseMove m_delayedMouseMove;
// The tool-loop.
std::unique_ptr<tools::ToolLoop> m_toolLoop;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -1300,7 +1300,8 @@ void Editor::flashCurrentLayer()
}
}
gfx::Point Editor::autoScroll(MouseMessage* msg, AutoScroll dir)
gfx::Point Editor::autoScroll(const ui::MouseMessage* msg,
const AutoScroll dir)
{
gfx::Point mousePos = msg->position();
if (!Preferences::instance().editor.autoScroll())

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -204,7 +204,8 @@ namespace app {
void updateStatusBar();
// Control scroll when cursor goes out of the editor viewport.
gfx::Point autoScroll(ui::MouseMessage* msg, AutoScroll dir);
gfx::Point autoScroll(const ui::MouseMessage* msg,
const AutoScroll dir);
tools::Tool* getCurrentEditorTool() const;
tools::Ink* getCurrentEditorInk() const;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -52,20 +52,21 @@
#include "ui/view.h"
#include <cstring>
#include <limits>
namespace app {
using namespace ui;
MovingPixelsState::MovingPixelsState(Editor* editor, MouseMessage* msg, PixelsMovementPtr pixelsMovement, HandleType handle)
MovingPixelsState::MovingPixelsState(Editor* editor,
MouseMessage* msg,
PixelsMovementPtr pixelsMovement,
HandleType handle)
: m_pixelsMovement(pixelsMovement)
, m_delayedMouseMove(this, editor, 5)
, m_editor(editor)
, m_observingEditor(false)
, m_discarded(false)
, m_renderTimer(50)
, m_oldSpritePos(std::numeric_limits<int>::min(),
std::numeric_limits<int>::min())
{
// MovingPixelsState needs a selection tool to avoid problems
// sharing the extra cel between the drawing cursor preview and the
@ -270,6 +271,8 @@ bool MovingPixelsState::onMouseDown(Editor* editor, MouseMessage* msg)
ASSERT(m_pixelsMovement);
ASSERT(editor == m_editor);
m_delayedMouseMove.onMouseDown(msg);
// Set this editor as the active one and setup the ContextBar for
// moving pixels. This is needed in case that the user is working
// with a couple of Editors, in one is moving pixels and the other
@ -357,6 +360,8 @@ bool MovingPixelsState::onMouseUp(Editor* editor, MouseMessage* msg)
ASSERT(m_pixelsMovement);
ASSERT(editor == m_editor);
m_delayedMouseMove.onMouseUp(msg);
// Drop the image temporarily in this location (where the user releases the mouse)
m_pixelsMovement->dropImageTemporarily();
@ -374,78 +379,8 @@ bool MovingPixelsState::onMouseMove(Editor* editor, MouseMessage* msg)
// If there is a button pressed
if (m_pixelsMovement->isDragging()) {
// Auto-scroll
gfx::Point mousePos = editor->autoScroll(msg, AutoScroll::MouseDir);
// Get the position of the mouse in the sprite
gfx::Point spritePos = editor->screenToEditor(mousePos);
if (spritePos == m_oldSpritePos) {
// Avoid redrawing everything if the position in the canvas didn't change.
// TODO remove this if we add support for anti-aliasing in the
// transformations
return true;
}
m_oldSpritePos = spritePos;
m_renderTimer.start();
m_pixelsMovement->setFastMode(true);
// Get the customization for the pixels movement (snap to grid, angle snap, etc.).
KeyContext keyContext = KeyContext::Normal;
switch (m_pixelsMovement->handle()) {
case MovePixelsHandle:
keyContext = KeyContext::TranslatingSelection;
break;
case ScaleNWHandle:
case ScaleNHandle:
case ScaleNEHandle:
case ScaleWHandle:
case ScaleEHandle:
case ScaleSWHandle:
case ScaleSHandle:
case ScaleSEHandle:
keyContext = KeyContext::ScalingSelection;
break;
case RotateNWHandle:
case RotateNHandle:
case RotateNEHandle:
case RotateWHandle:
case RotateEHandle:
case RotateSWHandle:
case RotateSHandle:
case RotateSEHandle:
keyContext = KeyContext::RotatingSelection;
break;
}
PixelsMovement::MoveModifier moveModifier = PixelsMovement::NormalMovement;
KeyAction action = editor->getCustomizationDelegate()
->getPressedKeyAction(keyContext);
if (int(action & KeyAction::SnapToGrid))
moveModifier |= PixelsMovement::SnapToGridMovement;
if (int(action & KeyAction::AngleSnap))
moveModifier |= PixelsMovement::AngleSnapMovement;
if (int(action & KeyAction::MaintainAspectRatio))
moveModifier |= PixelsMovement::MaintainAspectRatioMovement;
if (int(action & KeyAction::ScaleFromCenter))
moveModifier |= PixelsMovement::ScaleFromPivot;
if (int(action & KeyAction::LockAxis))
moveModifier |= PixelsMovement::LockAxisMovement;
// Invalidate handles
Decorator* decorator = static_cast<Decorator*>(editor->decorator());
TransformHandles* transfHandles = decorator->getTransformHandles(editor);
transfHandles->invalidateHandles(editor, m_pixelsMovement->getTransformation());
// Drag the image to that position
m_pixelsMovement->moveImage(spritePos, moveModifier);
editor->updateStatusBar();
if (m_delayedMouseMove.onMouseMove(msg))
m_renderTimer.start();
return true;
}
@ -453,6 +388,69 @@ bool MovingPixelsState::onMouseMove(Editor* editor, MouseMessage* msg)
return StandbyState::onMouseMove(editor, msg);
}
void MovingPixelsState::onCommitMouseMove(Editor* editor,
const gfx::Point& spritePos)
{
m_pixelsMovement->setFastMode(true);
// Get the customization for the pixels movement (snap to grid, angle snap, etc.).
KeyContext keyContext = KeyContext::Normal;
switch (m_pixelsMovement->handle()) {
case MovePixelsHandle:
keyContext = KeyContext::TranslatingSelection;
break;
case ScaleNWHandle:
case ScaleNHandle:
case ScaleNEHandle:
case ScaleWHandle:
case ScaleEHandle:
case ScaleSWHandle:
case ScaleSHandle:
case ScaleSEHandle:
keyContext = KeyContext::ScalingSelection;
break;
case RotateNWHandle:
case RotateNHandle:
case RotateNEHandle:
case RotateWHandle:
case RotateEHandle:
case RotateSWHandle:
case RotateSHandle:
case RotateSEHandle:
keyContext = KeyContext::RotatingSelection;
break;
}
PixelsMovement::MoveModifier moveModifier = PixelsMovement::NormalMovement;
KeyAction action = m_editor->getCustomizationDelegate()
->getPressedKeyAction(keyContext);
if (int(action & KeyAction::SnapToGrid))
moveModifier |= PixelsMovement::SnapToGridMovement;
if (int(action & KeyAction::AngleSnap))
moveModifier |= PixelsMovement::AngleSnapMovement;
if (int(action & KeyAction::MaintainAspectRatio))
moveModifier |= PixelsMovement::MaintainAspectRatioMovement;
if (int(action & KeyAction::ScaleFromCenter))
moveModifier |= PixelsMovement::ScaleFromPivot;
if (int(action & KeyAction::LockAxis))
moveModifier |= PixelsMovement::LockAxisMovement;
// Invalidate handles
Decorator* decorator = static_cast<Decorator*>(m_editor->decorator());
TransformHandles* transfHandles = decorator->getTransformHandles(m_editor);
transfHandles->invalidateHandles(m_editor, m_pixelsMovement->getTransformation());
// Drag the image to that position
m_pixelsMovement->moveImage(spritePos, moveModifier);
m_editor->updateStatusBar();
}
bool MovingPixelsState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos)
{
ASSERT(m_pixelsMovement);

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019-2021 Igara Studio S.A.
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This program is distributed under the terms of
@ -10,6 +10,7 @@
#pragma once
#include "app/ui/context_bar_observer.h"
#include "app/ui/editor/delayed_mouse_move.h"
#include "app/ui/editor/editor_observer.h"
#include "app/ui/editor/handle_type.h"
#include "app/ui/editor/pixels_movement.h"
@ -31,9 +32,13 @@ namespace app {
: public StandbyState
, EditorObserver
, TimelineObserver
, ContextBarObserver {
, ContextBarObserver
, DelayedMouseMoveDelegate {
public:
MovingPixelsState(Editor* editor, ui::MouseMessage* msg, PixelsMovementPtr pixelsMovement, HandleType handle);
MovingPixelsState(Editor* editor,
ui::MouseMessage* msg,
PixelsMovementPtr pixelsMovement,
HandleType handle);
virtual ~MovingPixelsState();
bool canHandleFrameChange() const {
@ -74,6 +79,10 @@ namespace app {
virtual Transformation getTransformation(Editor* editor) override;
private:
// DelayedMouseMoveDelegate impl
void onCommitMouseMove(Editor* editor,
const gfx::Point& spritePos) override;
void onTransparentColorChange();
void onRenderTimer();
@ -91,6 +100,7 @@ namespace app {
// Helper member to move/translate selection and pixels.
PixelsMovementPtr m_pixelsMovement;
DelayedMouseMove m_delayedMouseMove;
Editor* m_editor;
bool m_observingEditor;
@ -100,11 +110,6 @@ namespace app {
ui::Timer m_renderTimer;
// Position of the mouse in the canvas to avoid redrawing when the
// mouse position changes (only we redraw when the canvas position
// changes).
gfx::Point m_oldSpritePos;
obs::connection m_ctxConn;
obs::connection m_opaqueConn;
obs::connection m_transparentConn;

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -341,7 +341,7 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
if (layerEdges)
layerEdgesOption(false);
startDrawingState(editor,
startDrawingState(editor, msg,
DrawingType::Regular,
pointer_from_msg(editor, msg));
@ -390,7 +390,7 @@ bool StandbyState::onDoubleClick(Editor* editor, MouseMessage* msg)
editor->backToPreviousState();
// Start a tool-loop selecting tiles.
startDrawingState(editor,
startDrawingState(editor, msg,
DrawingType::SelectTiles,
pointer_from_msg(editor, msg));
return true;
@ -619,6 +619,7 @@ bool StandbyState::onUpdateStatusBar(Editor* editor)
DrawingState* StandbyState::startDrawingState(
Editor* editor,
const ui::MouseMessage* msg,
const DrawingType drawingType,
const tools::Pointer& pointer)
{
@ -644,8 +645,7 @@ DrawingState* StandbyState::startDrawingState(
editor->setState(newState);
static_cast<DrawingState*>(newState.get())
->initToolLoop(editor,
pointer);
->initToolLoop(editor, msg, pointer);
return static_cast<DrawingState*>(newState.get());
}
@ -660,7 +660,7 @@ bool StandbyState::checkStartDrawingStraightLine(Editor* editor,
(msg ? button_from_msg(msg): tools::Pointer::Left);
DrawingState* drawingState =
startDrawingState(editor,
startDrawingState(editor, msg,
DrawingType::LineFreehand,
tools::Pointer(
editor->document()->lastDrawingPoint(),

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
@ -87,6 +87,7 @@ namespace app {
private:
DrawingState* startDrawingState(Editor* editor,
const ui::MouseMessage* msg,
const DrawingType drawingType,
const tools::Pointer& pointer);
void transformSelection(Editor* editor, ui::MouseMessage* msg, HandleType handle);

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -1003,15 +1003,20 @@ void Manager::removeMessagesForTimer(Timer* timer)
ASSERT(manager_thread == base::this_thread::native_id());
#endif
for (auto it=msg_queue.begin(); it != msg_queue.end(); ) {
Message* msg = *it;
for (Message* msg : msg_queue) {
if (msg->type() == kTimerMessage &&
static_cast<TimerMessage*>(msg)->timer() == timer) {
delete msg;
it = msg_queue.erase(it);
msg->removeRecipient(msg->recipient());
static_cast<TimerMessage*>(msg)->_resetTimer();
}
}
for (Message* msg : used_msg_queue) {
if (msg->type() == kTimerMessage &&
static_cast<TimerMessage*>(msg)->timer() == timer) {
msg->removeRecipient(msg->recipient());
static_cast<TimerMessage*>(msg)->_resetTimer();
}
else
++it;
}
}
@ -1411,8 +1416,9 @@ int Manager::pumpQueue()
// Call Timer::tick() if this is a tick message.
if (msg->type() == kTimerMessage) {
ASSERT(static_cast<TimerMessage*>(msg)->timer() != nullptr);
static_cast<TimerMessage*>(msg)->timer()->tick();
// The timer can be nullptr if it was removed with removeMessagesForTimer()
if (auto timer = static_cast<TimerMessage*>(msg)->timer())
timer->tick();
}
bool done = false;

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2018-2020 Igara Studio S.A.
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
@ -179,6 +179,13 @@ namespace ui {
int count() const { return m_count; }
Timer* timer() { return m_timer; }
// Used by Manager::removeMessagesForTimer() to invalidate the
// message. It's like removing the message from the queue, but
// without touching the queue in case that we're iterating it
// (which can happen if we remove the timer from a kTimerMessage
// handler).
void _resetTimer() { m_timer = nullptr; }
private:
int m_count; // Accumulated calls
Timer* m_timer; // Timer handle

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2018-2022 Igara Studio S.A.
// Copyright (C) 2001-2017 David Capello
//
// This file is released under the terms of the MIT license.
@ -57,6 +57,10 @@ void Timer::start()
{
assert_ui_thread();
// Infinite timer? Do nothing.
if (m_interval == 0)
return;
m_lastTick = base::current_tick();
if (!m_running) {
m_running = true;
@ -109,6 +113,8 @@ void Timer::pollTimers()
for (auto timer : timers) {
if (timer && timer->isRunning()) {
ASSERT(timer->interval() > 0);
int64_t count = ((t - timer->m_lastTick) / timer->m_interval);
if (count > 0) {
timer->m_lastTick += count * timer->m_interval;