diff --git a/data/widgets/about.xml b/data/widgets/about.xml index 4d5ddae0d..770fa2f54 100644 --- a/data/widgets/about.xml +++ b/data/widgets/about.xml @@ -1,5 +1,5 @@ - + @@ -25,7 +25,7 @@ - diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index fad98b023..2ae75400e 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -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 @@ -342,6 +342,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 diff --git a/src/app/ui/editor/delayed_mouse_move.cpp b/src/app/ui/editor/delayed_mouse_move.cpp new file mode 100644 index 000000000..d9c44bbc8 --- /dev/null +++ b/src/app/ui/editor/delayed_mouse_move.cpp @@ -0,0 +1,95 @@ +// 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::min(), + std::numeric_limits::min()) +{ + ASSERT(m_delegate); + m_timer.Tick.connect([this] { commitMouseMove(); }); +} + +void DelayedMouseMove::initSpritePos(const gfx::PointF& pos) +{ + m_spritePos = pos; +} + +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::PointF& DelayedMouseMove::spritePos() const +{ + ASSERT(m_spritePos.x != std::numeric_limits::min() && + m_spritePos.y != std::numeric_limits::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::PointF spritePos = m_editor->screenToEditorF(mousePos); + + // Avoid redrawing everything if the position in the canvas didn't + // change. + if (m_spritePos != spritePos) { + m_spritePos = spritePos; + return true; + } + else + return false; +} + +} // namespace app diff --git a/src/app/ui/editor/delayed_mouse_move.h b/src/app/ui/editor/delayed_mouse_move.h new file mode 100644 index 000000000..31dddc486 --- /dev/null +++ b/src/app/ui/editor/delayed_mouse_move.h @@ -0,0 +1,71 @@ +// 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 + +namespace ui { + class MouseMessage; +} + +namespace app { + class Editor; + + class DelayedMouseMoveDelegate { + public: + virtual ~DelayedMouseMoveDelegate() { } + virtual void onCommitMouseMove(Editor* editor, + const gfx::PointF& 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); + + // 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). + void initSpritePos(const gfx::PointF& pos); + + void onMouseDown(const ui::MouseMessage* msg); + bool onMouseMove(const ui::MouseMessage* msg); + void onMouseUp(const ui::MouseMessage* msg); + + const gfx::PointF& 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::PointF m_spritePos; + }; + +} // namespace app + +#endif // APP_UI_EDITOR_DELAYED_MOUSE_MOVE_STATE_H_INCLUDED diff --git a/src/app/ui/editor/drawing_state.cpp b/src/app/ui/editor/drawing_state.cpp index fd6904528..2cb376b81 100644 --- a/src/app/ui/editor/drawing_state.cpp +++ b/src/app/ui/editor/drawing_state.cpp @@ -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,14 @@ DrawingState::~DrawingState() } void DrawingState::initToolLoop(Editor* editor, + const ui::MouseMessage* msg, const tools::Pointer& pointer) { + if (msg) + m_delayedMouseMove.onMouseDown(msg); + else + m_delayedMouseMove.initSpritePos(gfx::PointF(pointer.point())); + Tileset* tileset = m_toolLoop->getDstTileset(); // For selection inks we don't use a "the selected layer" for @@ -130,6 +152,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. @@ -159,7 +183,7 @@ bool DrawingState::onMouseDown(Editor* editor, MouseMessage* msg) // checkStartDrawingStraightLine() with the right-button. if (recreateLoop && isCanceled) { ASSERT(!m_toolLoopManager); - checkStartDrawingStraightLine(editor, &pointer); + checkStartDrawingStraightLine(editor, msg, &pointer); } return true; @@ -169,7 +193,8 @@ bool DrawingState::onMouseUp(Editor* editor, MouseMessage* msg) { ASSERT(m_toolLoopManager != NULL); - tools::Pointer pointer = pointer_from_msg(editor, msg, m_velocity.velocity()); + 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 @@ -184,8 +209,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; - // 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". @@ -207,7 +230,7 @@ bool DrawingState::onMouseUp(Editor* editor, MouseMessage* msg) // button, if the Shift key is pressed, the whole ToolLoop starts // again. if (Preferences::instance().editor.straightLinePreview()) - checkStartDrawingStraightLine(editor, &pointer); + checkStartDrawingStraightLine(editor, msg, &m_lastPointer); return true; } @@ -228,19 +251,29 @@ bool DrawingState::onMouseMove(Editor* editor, MouseMessage* msg) // Update velocity sensor. m_velocity.updateWithDisplayPoint(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(gfx::Point(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::PointF& spritePos) +{ + if (m_toolLoop && + m_toolLoopManager && + !m_toolLoopManager->isCanceled()) { + handleMouseMovement(); + } +} + bool DrawingState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos) { if (m_toolLoop->getInk()->isEyedropper()) { @@ -299,12 +332,12 @@ bool DrawingState::onScrollChange(Editor* editor) // Update velocity sensor. m_velocity.updateWithDisplayPoint(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; } @@ -332,14 +365,13 @@ bool DrawingState::getGridBounds(Editor* editor, gfx::Rect& gridBounds) return false; } -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() diff --git a/src/app/ui/editor/drawing_state.h b/src/app/ui/editor/drawing_state.h index 1c6559046..ce92dfd43 100644 --- a/src/app/ui/editor/drawing_state.h +++ b/src/app/ui/editor/drawing_state.h @@ -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 @@ -23,7 +24,8 @@ namespace app { class CommandExecutionEvent; - class DrawingState : public StandbyState { + class DrawingState : public StandbyState + , DelayedMouseMoveDelegate { public: DrawingState(Editor* editor, tools::ToolLoop* loop, @@ -47,6 +49,7 @@ namespace app { virtual bool getGridBounds(Editor* editor, gfx::Rect& gridBounds) override; void initToolLoop(Editor* editor, + const ui::MouseMessage* msg, const tools::Pointer& pointer); // Used to send a movement() to the ToolLoopManager when @@ -56,14 +59,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::PointF& spritePos) override; + Editor* m_editor; DrawingType m_type; + DelayedMouseMove m_delayedMouseMove; // The tool-loop. std::unique_ptr m_toolLoop; diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index c4e2e70f7..735a4511d 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -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 @@ -1382,7 +1382,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()) diff --git a/src/app/ui/editor/editor.h b/src/app/ui/editor/editor.h index 2fb756b57..6dffc8e41 100644 --- a/src/app/ui/editor/editor.h +++ b/src/app/ui/editor/editor.h @@ -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 @@ -214,7 +214,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; diff --git a/src/app/ui/editor/moving_pixels_state.cpp b/src/app/ui/editor/moving_pixels_state.cpp index ea4d685a3..0b36d9904 100644 --- a/src/app/ui/editor/moving_pixels_state.cpp +++ b/src/app/ui/editor/moving_pixels_state.cpp @@ -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 @@ -53,14 +53,17 @@ #include "ui/view.h" #include -#include 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) @@ -277,6 +280,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 @@ -364,6 +369,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(); @@ -381,81 +388,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::PointF spritePos = editor->screenToEditorF(mousePos); - - // 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 RotateNEHandle: - case RotateSWHandle: - case RotateSEHandle: - keyContext = KeyContext::RotatingSelection; - break; - case SkewNHandle: - case SkewWHandle: - case SkewEHandle: - case SkewSHandle: - keyContext = KeyContext::ScalingSelection; - 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; - - if (int(action & KeyAction::FineControl)) - moveModifier |= PixelsMovement::FineControl; - - m_renderTimer.start(); - m_pixelsMovement->setFastMode(true); - - // Invalidate handles - Decorator* decorator = static_cast(editor->decorator()); - TransformHandles* transfHandles = decorator->getTransformHandles(editor); - const Transformation& transformation = m_pixelsMovement->getTransformation(); - transfHandles->invalidateHandles(editor, transformation); - - // Drag the image to that position - m_pixelsMovement->moveImage(spritePos, moveModifier); - - // Update context bar and status bar - ContextBar* contextBar = App::instance()->contextBar(); - contextBar->updateForMovingPixels(transformation); - - editor->updateStatusBar(); + if (m_delayedMouseMove.onMouseMove(msg)) + m_renderTimer.start(); return true; } @@ -463,6 +397,79 @@ bool MovingPixelsState::onMouseMove(Editor* editor, MouseMessage* msg) return StandbyState::onMouseMove(editor, msg); } +void MovingPixelsState::onCommitMouseMove(Editor* editor, + const gfx::PointF& 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 RotateNEHandle: + case RotateSWHandle: + case RotateSEHandle: + keyContext = KeyContext::RotatingSelection; + break; + case SkewNHandle: + case SkewWHandle: + case SkewEHandle: + case SkewSHandle: + keyContext = KeyContext::ScalingSelection; + 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; + + if (int(action & KeyAction::FineControl)) + moveModifier |= PixelsMovement::FineControl; + + // Invalidate handles + Decorator* decorator = static_cast(m_editor->decorator()); + TransformHandles* transfHandles = decorator->getTransformHandles(m_editor); + const Transformation& transformation = m_pixelsMovement->getTransformation(); + transfHandles->invalidateHandles(m_editor, transformation); + + // Drag the image to that position + m_pixelsMovement->moveImage(spritePos, moveModifier); + + // Update context bar and status bar + ContextBar* contextBar = App::instance()->contextBar(); + contextBar->updateForMovingPixels(transformation); + + m_editor->updateStatusBar(); +} + bool MovingPixelsState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos) { ASSERT(m_pixelsMovement); diff --git a/src/app/ui/editor/moving_pixels_state.h b/src/app/ui/editor/moving_pixels_state.h index 99e3292ee..a7251971f 100644 --- a/src/app/ui/editor/moving_pixels_state.h +++ b/src/app/ui/editor/moving_pixels_state.h @@ -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" @@ -32,9 +33,13 @@ namespace app { , EditorObserver , TimelineObserver , ContextBarObserver - , PixelsMovementDelegate { + , PixelsMovementDelegate + , 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 { @@ -80,6 +85,10 @@ namespace app { virtual Transformation getTransformation(Editor* editor) override; private: + // DelayedMouseMoveDelegate impl + void onCommitMouseMove(Editor* editor, + const gfx::PointF& spritePos) override; + void onTransparentColorChange(); void onRenderTimer(); @@ -97,6 +106,7 @@ namespace app { // Helper member to move/translate selection and pixels. PixelsMovementPtr m_pixelsMovement; + DelayedMouseMove m_delayedMouseMove; Editor* m_editor; bool m_observingEditor; @@ -106,11 +116,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::PointF m_oldSpritePos; - obs::connection m_ctxConn; obs::connection m_opaqueConn; obs::connection m_transparentConn; diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp index fbd5ec298..4cc669366 100644 --- a/src/app/ui/editor/standby_state.cpp +++ b/src/app/ui/editor/standby_state.cpp @@ -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 @@ -319,7 +319,7 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg) // Shift+click on Pencil tool starts a line onMouseDown() when the // preview (onKeyDown) is disabled. if (!Preferences::instance().editor.straightLinePreview() && - checkStartDrawingStraightLine(editor, &pointer)) { + checkStartDrawingStraightLine(editor, msg, &pointer)) { // Send first mouse down to draw the straight line and start the // freehand mode. editor->getState()->onMouseDown(editor, msg); @@ -334,7 +334,7 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg) if (layerEdges) layerEdgesOption(false); - startDrawingState(editor, + startDrawingState(editor, msg, DrawingType::Regular, pointer); @@ -383,7 +383,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; @@ -511,7 +511,7 @@ bool StandbyState::onSetCursor(Editor* editor, const gfx::Point& mouseScreenPos) bool StandbyState::onKeyDown(Editor* editor, KeyMessage* msg) { if (Preferences::instance().editor.straightLinePreview() && - checkStartDrawingStraightLine(editor, nullptr)) + checkStartDrawingStraightLine(editor, nullptr, nullptr)) return false; return false; } @@ -627,6 +627,7 @@ bool StandbyState::onUpdateStatusBar(Editor* editor) DrawingState* StandbyState::startDrawingState( Editor* editor, + const ui::MouseMessage* msg, const DrawingType drawingType, const tools::Pointer& pointer) { @@ -652,13 +653,13 @@ DrawingState* StandbyState::startDrawingState( editor->setState(newState); static_cast(newState.get()) - ->initToolLoop(editor, - pointer); + ->initToolLoop(editor, msg, pointer); return static_cast(newState.get()); } bool StandbyState::checkStartDrawingStraightLine(Editor* editor, + const ui::MouseMessage* msg, const tools::Pointer* pointer) { // Start line preview with shift key @@ -668,7 +669,7 @@ bool StandbyState::checkStartDrawingStraightLine(Editor* editor, (pointer ? pointer->button(): tools::Pointer::Left); DrawingState* drawingState = - startDrawingState(editor, + startDrawingState(editor, msg, DrawingType::LineFreehand, tools::Pointer( editor->document()->lastDrawingPoint(), diff --git a/src/app/ui/editor/standby_state.h b/src/app/ui/editor/standby_state.h index 5679f7052..8eb8f8896 100644 --- a/src/app/ui/editor/standby_state.h +++ b/src/app/ui/editor/standby_state.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2019-2020 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 @@ -55,6 +55,7 @@ namespace app { protected: void callEyedropper(Editor* editor, const ui::MouseMessage* msg); bool checkStartDrawingStraightLine(Editor* editor, + const ui::MouseMessage* msg, const tools::Pointer* pointer); virtual bool canCheckStartDrawingStraightLine() { return true; } @@ -87,6 +88,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); diff --git a/src/main/resources_win32.rc b/src/main/resources_win32.rc index 064dbaebe..62918bc23 100644 --- a/src/main/resources_win32.rc +++ b/src/main/resources_win32.rc @@ -24,7 +24,7 @@ BEGIN VALUE "FileDescription", "Aseprite - Animated sprites editor & pixel art tool" VALUE "FileVersion", "1,3,0,0" VALUE "InternalName", "aseprite" - VALUE "LegalCopyright", "Copyright (C) 2001-2021 Igara Studio S.A." + VALUE "LegalCopyright", "Copyright (C) 2001-2022 Igara Studio S.A." VALUE "OriginalFilename", "aseprite.exe" VALUE "ProductName", "ASEPRITE" VALUE "ProductVersion", "1,3,0,0" diff --git a/src/ui/manager.cpp b/src/ui/manager.cpp index 6ba0d5d14..2170d2d87 100644 --- a/src/ui/manager.cpp +++ b/src/ui/manager.cpp @@ -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. @@ -1154,15 +1154,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(msg)->timer() == timer) { - delete msg; - it = msg_queue.erase(it); + msg->removeRecipient(msg->recipient()); + static_cast(msg)->_resetTimer(); + } + } + + for (Message* msg : used_msg_queue) { + if (msg->type() == kTimerMessage && + static_cast(msg)->timer() == timer) { + msg->removeRecipient(msg->recipient()); + static_cast(msg)->_resetTimer(); } - else - ++it; } } @@ -1769,8 +1774,9 @@ int Manager::pumpQueue() // Call Timer::tick() if this is a tick message. if (msg->type() == kTimerMessage) { - ASSERT(static_cast(msg)->timer() != nullptr); - static_cast(msg)->timer()->tick(); + // The timer can be nullptr if it was removed with removeMessagesForTimer() + if (auto timer = static_cast(msg)->timer()) + timer->tick(); } bool done = false; diff --git a/src/ui/message.h b/src/ui/message.h index be147a04b..c11d389f6 100644 --- a/src/ui/message.h +++ b/src/ui/message.h @@ -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. @@ -192,6 +192,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 diff --git a/src/ui/timer.cpp b/src/ui/timer.cpp index d56559d30..8cbe5219c 100644 --- a/src/ui/timer.cpp +++ b/src/ui/timer.cpp @@ -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; diff --git a/src/ver/info.c b/src/ver/info.c index 87af7bc4b..dbbf9950e 100644 --- a/src/ver/info.c +++ b/src/ver/info.c @@ -1,5 +1,5 @@ /* Aseprite - Copyright (C) 2020-2021 Igara Studio S.A. + Copyright (C) 2020-2022 Igara Studio S.A. This program is distributed under the terms of the End-User License Agreement for Aseprite. */ @@ -8,7 +8,7 @@ #include "generated_version.h" /* It defines the VERSION macro */ #define PACKAGE "Aseprite" -#define COPYRIGHT "Copyright (C) 2001-2021 Igara Studio S.A." +#define COPYRIGHT "Copyright (C) 2001-2022 Igara Studio S.A." #if defined(_WIN32) || defined(__APPLE__) #define HTTP "https"