Skip to content

fix: suppress idle overlay reactivation after mouse release during input replay#827

Merged
hatayama merged 2 commits into
mainfrom
feature/hatayama/fix-replay-mouse
Mar 24, 2026
Merged

fix: suppress idle overlay reactivation after mouse release during input replay#827
hatayama merged 2 commits into
mainfrom
feature/hatayama/fix-replay-mouse

Conversation

@hatayama
Copy link
Copy Markdown
Owner

@hatayama hatayama commented Mar 24, 2026

Summary

  • Add click/release animation (expand on press, dissipate on release) to the mouse cursor overlay during input replay
  • Fix a bug where the dissipate animation was immediately cancelled by the next idle frame calling SimulateMouseUiOverlayState.Update(), making the release animation invisible
  • Introduce _suppressIdleUiOverlay flag to skip idle overlay updates until the pointer moves to a new position

Details

The replay overlay shows a visual indicator at the mouse cursor position during input replay. When the mouse button is released, RequestDissipateAnimation() triggers a fade-out effect. However, on the very next frame, the idle branch in ApplyUiEvents() unconditionally called SimulateMouseUiOverlayState.Update(), which re-activated the overlay and cancelled the fade-out.

The fix tracks the previous mouse position and suppresses idle overlay updates after release until the mouse actually moves. This allows the dissipate animation to play fully while still showing the idle overlay when the cursor moves to a new position.

Test plan

  • Start input replay with overlay enabled
  • Verify click animation (expand) plays on mouse press
  • Verify release animation (dissipate/fade-out) plays on mouse release
  • Verify idle overlay reappears when mouse moves after release
  • Verify drag and long-press overlays still work correctly

Summary by cubic

Fixes the replay cursor overlay so the release fade-out plays instead of being cancelled. Adds click/release animations during replay to match the interactive tool.

  • Bug Fixes

    • Suppresses idle overlay updates after mouse-up until the cursor moves using a _suppressIdleUiOverlay flag.
    • Tracks the previous replay position to avoid reactivating the overlay at the same coordinates.
  • New Features

    • Triggers expand on press and dissipate on release via animation requests in SimulateMouseUiOverlayState, consumed by SimulateMouseUiOverlay in LateUpdate.
    • Extracts shared timings into SimulateMouseUiAnimationConstants and uses them in the overlay and SimulateMouseUiTool.

Written for commit 86e49db. Summary will update on new commits.

Overview

This PR fixes a bug where the dissipate animation of the mouse cursor overlay during input replay was cancelled immediately by the next idle frame unconditionally re-activating the overlay. The fix introduces a suppression mechanism to prevent idle overlay updates after a release until the pointer moves, while also adding explicit press/release animations to the overlay.

Problem Statement

During input replay, when the simulated mouse was released, the dissipate animation (fade-out) of the cursor overlay was interrupted by the subsequent idle frame, which automatically re-activated the overlay. This caused the visual feedback to disappear abruptly without the intended animation.

Solution

The implementation introduces three complementary changes:

  1. Shared Animation Constants: A new SimulateMouseUiAnimationConstants class centralizes animation timing and scale values (EXPAND_DURATION, EXPAND_START_SCALE, DISSIPATE_DURATION) used across the UI simulation system.

  2. Self-Driven Animation System: The SimulateMouseUiOverlay class gains internal animation control with a state machine (None, Expanding, Dissipating) that independently handles expand and dissipate animations without requiring external animation management.

  3. Idle Overlay Suppression: The InputReplayer introduces a _suppressIdleUiOverlay flag that prevents idle overlay updates after a release until the cursor moves to a new position, allowing the dissipate animation to complete before the overlay re-activates.

Architecture

classDiagram
    class InputReplayer {
        -Vector2 _previousReplayMousePosition
        -bool _suppressIdleUiOverlay
        +ApplyUiEvents()
        -TrackMousePositionChange()
        -RequestAnimationOnPress()
        -RequestAnimationOnRelease()
    }
    
    class SimulateMouseUiAnimationConstants {
        +float EXPAND_DURATION
        +float EXPAND_START_SCALE
        +float DISSIPATE_DURATION
    }
    
    class SimulateMouseUiOverlayState {
        -bool _pendingExpandAnimation
        -bool _pendingDissipateAnimation
        +RequestExpandAnimation()
        +RequestDissipateAnimation()
        +ConsumePendingExpandAnimation()
        +ConsumePendingDissipateAnimation()
        +Update()
        +Clear()
    }
    
    class SimulateMouseUiOverlay {
        -SelfAnimState _selfAnimState
        -float _selfAnimStartTime
        +LateUpdate()
        +SetCursorScale()
        +SetAlpha()
        -RunExpandAnimation()
        -RunDissipateAnimation()
    }
    
    class SimulateMouseUiTool {
        +PlayExpandAnimation()
        +PlayDissipateAnimation()
    }
    
    InputReplayer -->|Requests animations via| SimulateMouseUiOverlayState
    SimulateMouseUiOverlay -->|Consumes animation requests from| SimulateMouseUiOverlayState
    SimulateMouseUiOverlay -->|Uses constants from| SimulateMouseUiAnimationConstants
    SimulateMouseUiTool -->|Uses constants from| SimulateMouseUiAnimationConstants
Loading

Key Changes by File

InputReplayer.cs

  • Added _previousReplayMousePosition to track mouse position across frames
  • Added _suppressIdleUiOverlay flag to suppress idle overlay updates after release
  • Modified ApplyUiEvents() to:
    • Compute whether replay mouse position changed since previous frame
    • Suppress SimulateMouseUiOverlayState.Update() when suppression is active and position hasn't changed
    • Request expand animation on left button press (justPressed)
    • Request dissipate animation on left button release (justReleased) and set suppression flag
    • Clear suppression flag when mouse position changes

SimulateMouseUiAnimationConstants.cs (New)

  • Created public static class with centralized animation timing constants:
    • EXPAND_DURATION = 0.1f
    • EXPAND_START_SCALE = 1.5f
    • DISSIPATE_DURATION = 0.1f

SimulateMouseUiOverlay.cs

  • Added internal state machine for self-driven animations (SelfAnimState enum: None, Expanding, Dissipating)
  • Added _selfAnimStartTime to track animation progress
  • Enhanced LateUpdate() to:
    • Consume pending expand/dissipate animation requests from SimulateMouseUiOverlayState
    • Execute dissipate animation with early return to prevent interference
    • Apply expand interpolation to cursor scale during expansion
    • Set _canvasGroup.alpha = 1 when consuming expand request
  • Modified SetCursorScale() and SetAlpha() to cancel self-driven animation by resetting _selfAnimState

SimulateMouseUiOverlayState.cs

  • Added _pendingExpandAnimation and _pendingDissipateAnimation static flags
  • New public methods:
    • RequestExpandAnimation() - Sets expand animation request flag
    • RequestDissipateAnimation() - Sets dissipate animation request flag
    • ConsumePendingExpandAnimation() - Atomically checks and resets expand flag
    • ConsumePendingDissipateAnimation() - Atomically checks and resets dissipate flag
  • Animation flags persist across Clear() calls to survive overlay state resets

SimulateMouseUiTool.cs

  • Replaced hardcoded animation timing constants with references to SimulateMouseUiAnimationConstants:
    • PlayExpandAnimation() now uses SimulateMouseUiAnimationConstants.EXPAND_DURATION and EXPAND_START_SCALE
    • PlayDissipateAnimation() now uses SimulateMouseUiAnimationConstants.DISSIPATE_DURATION

Test Plan

  • Verify click animation (expand) plays on mouse press during replay
  • Verify release animation (dissipate/fade-out) plays on mouse release without interruption
  • Verify idle overlay re-appears when cursor moves to new position
  • Verify drag operations work correctly with the new animation system
  • Verify long-press behaviors are not affected by the changes

InputReplayer updated SimulateMouseUiOverlayState but never triggered
the expand (1.5x→1x shrink) or dissipate (fade-out) cursor animations
that SimulateMouseUiTool plays on click/release. This made the cursor
appear static during replay, unlike the interactive tool experience.

- Add frame-based self-animation to SimulateMouseUiOverlay (LateUpdate-driven)
- Add animation request facade to SimulateMouseUiOverlayState so InputReplayer
  triggers animations without traversing the OverlayCanvasFactory object chain
- Extract shared animation constants into SimulateMouseUiAnimationConstants
- SetCursorScale/SetAlpha cancel self-animation to avoid conflicts with
  SimulateMouseUiTool's async-driven animation
…eplay

RequestDissipateAnimation() on mouse-up was immediately cancelled by the
next idle frame calling SimulateMouseUiOverlayState.Update(), making the
release animation invisible. Add _suppressIdleUiOverlay flag to skip idle
overlay updates until the pointer actually moves to a new position.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

This change introduces a centralized animation constants system and a self-driven animation mechanism for the mouse UI overlay. The replay input system is enhanced to track mouse position and suppress idle overlay updates. Animation requests are now decoupled from immediate execution through a request/consume pattern.

Changes

Cohort / File(s) Summary
Animation Constants & Centralization
Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiAnimationConstants.cs, Packages/src/Editor/Api/McpTools/SimulateMouseUi/SimulateMouseUiTool.cs
Introduces new SimulateMouseUiAnimationConstants class with three animation timing values (EXPAND_DURATION, EXPAND_START_SCALE, DISSIPATE_DURATION). Updates SimulateMouseUiTool to reference these centralized constants instead of hardcoded values.
Overlay Animation System
Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiOverlay.cs, Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiOverlayState.cs
Implements self-driven animation state machine in overlay with expand/dissipate animation logic in LateUpdate(). Adds animation request/consume API (RequestExpandAnimation, RequestDissipateAnimation, ConsumePendingExpandAnimation, ConsumePendingDissipateAnimation) to SimulateMouseUiOverlayState for deferred animation handling.
Replay Input Enhancement
Packages/src/Editor/Api/McpTools/ReplayInput/InputReplayer.cs
Adds mouse position tracking fields and idle overlay suppression logic. Implements conditional idle overlay update emission based on mouse movement changes and suppression state. Explicitly requests expand/dissipate animations on pointer press/release events.

Sequence Diagram(s)

sequenceDiagram
    participant IR as InputReplayer
    participant ST as SimulateMouseUiTool
    participant SS as SimulateMouseUiOverlayState
    participant SO as SimulateMouseUiOverlay
    participant CG as Canvas/CursorGroup

    Note over IR,SO: Animation Request Flow (New Pattern)
    
    rect rgba(100, 200, 100, 0.5)
        IR->>SS: RequestExpandAnimation() on press
    end
    
    rect rgba(100, 150, 200, 0.5)
        SO->>SS: ConsumePendingExpandAnimation() in LateUpdate
        SS-->>SO: true (pending exists)
        SO->>SO: _selfAnimState = Expanding
        SO->>CG: Set alpha=1, init scale
    end
    
    rect rgba(150, 200, 100, 0.5)
        loop Animation Frame
            SO->>CG: Update scale via interpolation
        end
    end
    
    rect rgba(200, 100, 100, 0.5)
        IR->>SS: RequestDissipateAnimation() on release
    end
    
    rect rgba(100, 150, 200, 0.5)
        SO->>SS: ConsumePendingDissipateAnimation() in LateUpdate
        SS-->>SO: true (pending exists)
        SO->>SO: _selfAnimState = Dissipating
    end
    
    rect rgba(150, 200, 100, 0.5)
        loop Animation Frame
            SO->>CG: Dissipate animation
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary fix: suppressing idle overlay reactivation after mouse release during input replay, which aligns with the main objective of preventing the dissipate animation from being cancelled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/hatayama/fix-replay-mouse

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 6 files

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiOverlayState.cs (1)

20-23: Animation request flags intentionally survive Clear() — document this design decision.

The pending animation flags are static and persist across Clear() calls by design, allowing the overlay to process animation requests after state reset. This is a subtle but intentional behavior. Consider adding a brief comment explaining why these flags are not cleared in Clear().

         // Animation request flags survive Clear() — they are consumed by the overlay in LateUpdate
+        // Intentionally NOT reset in Clear() so that a dissipate request issued just before Clear()
+        // can still be processed by SimulateMouseUiOverlay.LateUpdate().
         private static bool _pendingExpandAnimation;
         private static bool _pendingDissipateAnimation;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiOverlayState.cs` around
lines 20 - 23, Add a short explanatory comment above the two static flags
(_pendingExpandAnimation and _pendingDissipateAnimation) stating that these
animation-request flags intentionally persist across Clear() so the overlay can
consume them later in LateUpdate; mention that this is by design (so Clear()
resets transient state but does not drop pending animation triggers) to avoid
future confusion.
Packages/src/Editor/Api/McpTools/SimulateMouseUi/SimulateMouseUiTool.cs (2)

775-776: Unnecessary null-forgiving operators.

The overlay! on these lines is redundant since overlay is a non-null local variable assigned on line 763.

♻️ Suggested fix
-            overlay!.SetCursorScale(0f);
-            overlay!.SetAlpha(0f);
+            overlay.SetCursorScale(0f);
+            overlay.SetAlpha(0f);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Packages/src/Editor/Api/McpTools/SimulateMouseUi/SimulateMouseUiTool.cs`
around lines 775 - 776, The two calls overlay!.SetCursorScale(0f) and
overlay!.SetAlpha(0f) use unnecessary null-forgiving operators; remove the '!'
so they read overlay.SetCursorScale(0f) and overlay.SetAlpha(0f) since overlay
is a non-null local (assigned earlier) and the methods SetCursorScale and
SetAlpha expect a non-null instance.

18-21: Consider using the shared constants directly instead of redefining local constants.

The local constants simply alias the shared values. You could reference SimulateMouseUiAnimationConstants.EXPAND_DURATION directly in the animation methods, eliminating the redundant declarations.

♻️ Optional simplification
-        private const float EXPAND_DURATION = SimulateMouseUiAnimationConstants.EXPAND_DURATION;
-        private const float EXPAND_START_SCALE = SimulateMouseUiAnimationConstants.EXPAND_START_SCALE;
-        private const float DISSIPATE_DURATION = SimulateMouseUiAnimationConstants.DISSIPATE_DURATION;

Then in PlayExpandAnimation and PlayDissipateAnimation, use SimulateMouseUiAnimationConstants.EXPAND_DURATION etc. directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Packages/src/Editor/Api/McpTools/SimulateMouseUi/SimulateMouseUiTool.cs`
around lines 18 - 21, The file defines redundant local aliases (EXPAND_DURATION,
EXPAND_START_SCALE, DISSIPATE_DURATION) that mirror
SimulateMouseUiAnimationConstants; remove these private const fields from the
SimulateMouseUiTool class and update usages in PlayExpandAnimation and
PlayDissipateAnimation (and any other methods) to reference
SimulateMouseUiAnimationConstants.EXPAND_DURATION, .EXPAND_START_SCALE and
.DISSIPATE_DURATION directly to eliminate duplication.
Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiOverlay.cs (1)

101-116: Consider consolidating mutually exclusive animation requests.

If both expand and dissipate are requested in the same frame (edge case), ConsumePendingAnimationRequests will process both: expand sets state to Expanding with start scale 1.5x, then dissipate immediately overwrites to Dissipating. This results in the expand setup being discarded.

This is likely fine in practice since a press-then-immediate-release in the same frame is rare, but you could add a comment or short-circuit if dissipate is pending.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiOverlay.cs` around lines
101 - 116, The method ConsumePendingAnimationRequests can process both pending
expand and dissipate in the same frame causing the expand setup to be
immediately overwritten; change the logic so these requests are mutually
exclusive (e.g. check
SimulateMouseUiOverlayState.ConsumePendingDissipateAnimation() first and
short-circuit/return or convert the second if into an else-if) so that once
_selfAnimState is set to Expanding or Dissipating it is not overwritten in the
same call (references: ConsumePendingAnimationRequests,
SimulateMouseUiOverlayState.ConsumePendingExpandAnimation,
SimulateMouseUiOverlayState.ConsumePendingDissipateAnimation, _selfAnimState,
_selfAnimStartTime, _cursorGroup).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Packages/src/Editor/Api/McpTools/SimulateMouseUi/SimulateMouseUiTool.cs`:
- Around line 775-776: The two calls overlay!.SetCursorScale(0f) and
overlay!.SetAlpha(0f) use unnecessary null-forgiving operators; remove the '!'
so they read overlay.SetCursorScale(0f) and overlay.SetAlpha(0f) since overlay
is a non-null local (assigned earlier) and the methods SetCursorScale and
SetAlpha expect a non-null instance.
- Around line 18-21: The file defines redundant local aliases (EXPAND_DURATION,
EXPAND_START_SCALE, DISSIPATE_DURATION) that mirror
SimulateMouseUiAnimationConstants; remove these private const fields from the
SimulateMouseUiTool class and update usages in PlayExpandAnimation and
PlayDissipateAnimation (and any other methods) to reference
SimulateMouseUiAnimationConstants.EXPAND_DURATION, .EXPAND_START_SCALE and
.DISSIPATE_DURATION directly to eliminate duplication.

In `@Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiOverlay.cs`:
- Around line 101-116: The method ConsumePendingAnimationRequests can process
both pending expand and dissipate in the same frame causing the expand setup to
be immediately overwritten; change the logic so these requests are mutually
exclusive (e.g. check
SimulateMouseUiOverlayState.ConsumePendingDissipateAnimation() first and
short-circuit/return or convert the second if into an else-if) so that once
_selfAnimState is set to Expanding or Dissipating it is not overwritten in the
same call (references: ConsumePendingAnimationRequests,
SimulateMouseUiOverlayState.ConsumePendingExpandAnimation,
SimulateMouseUiOverlayState.ConsumePendingDissipateAnimation, _selfAnimState,
_selfAnimStartTime, _cursorGroup).

In `@Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiOverlayState.cs`:
- Around line 20-23: Add a short explanatory comment above the two static flags
(_pendingExpandAnimation and _pendingDissipateAnimation) stating that these
animation-request flags intentionally persist across Clear() so the overlay can
consume them later in LateUpdate; mention that this is by design (so Clear()
resets transient state but does not drop pending animation triggers) to avoid
future confusion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ba9dac3a-27cf-410d-ac9b-9b1f95793136

📥 Commits

Reviewing files that changed from the base of the PR and between e691cca and 86e49db.

⛔ Files ignored due to path filters (1)
  • Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiAnimationConstants.cs.meta is excluded by none and included by none
📒 Files selected for processing (5)
  • Packages/src/Editor/Api/McpTools/ReplayInput/InputReplayer.cs
  • Packages/src/Editor/Api/McpTools/SimulateMouseUi/SimulateMouseUiTool.cs
  • Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiAnimationConstants.cs
  • Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiOverlay.cs
  • Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiOverlayState.cs

@hatayama hatayama merged commit 7133e9c into main Mar 24, 2026
14 checks passed
@hatayama hatayama deleted the feature/hatayama/fix-replay-mouse branch March 24, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant