fix: suppress idle overlay reactivation after mouse release during input replay#827
Conversation
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.
📝 WalkthroughWalkthroughThis 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (4)
Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiOverlayState.cs (1)
20-23: Animation request flags intentionally surviveClear()— 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 inClear().// 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 sinceoverlayis 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_DURATIONdirectly 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
PlayExpandAnimationandPlayDissipateAnimation, useSimulateMouseUiAnimationConstants.EXPAND_DURATIONetc. 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),
ConsumePendingAnimationRequestswill process both: expand sets state toExpandingwith start scale 1.5x, then dissipate immediately overwrites toDissipating. 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
⛔ Files ignored due to path filters (1)
Packages/src/Runtime/SimulateMouseUi/SimulateMouseUiAnimationConstants.cs.metais excluded by none and included by none
📒 Files selected for processing (5)
Packages/src/Editor/Api/McpTools/ReplayInput/InputReplayer.csPackages/src/Editor/Api/McpTools/SimulateMouseUi/SimulateMouseUiTool.csPackages/src/Runtime/SimulateMouseUi/SimulateMouseUiAnimationConstants.csPackages/src/Runtime/SimulateMouseUi/SimulateMouseUiOverlay.csPackages/src/Runtime/SimulateMouseUi/SimulateMouseUiOverlayState.cs
Summary
SimulateMouseUiOverlayState.Update(), making the release animation invisible_suppressIdleUiOverlayflag to skip idle overlay updates until the pointer moves to a new positionDetails
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 inApplyUiEvents()unconditionally calledSimulateMouseUiOverlayState.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
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
_suppressIdleUiOverlayflag.New Features
SimulateMouseUiOverlayState, consumed bySimulateMouseUiOverlayin LateUpdate.SimulateMouseUiAnimationConstantsand uses them in the overlay andSimulateMouseUiTool.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:
Shared Animation Constants: A new
SimulateMouseUiAnimationConstantsclass centralizes animation timing and scale values (EXPAND_DURATION,EXPAND_START_SCALE,DISSIPATE_DURATION) used across the UI simulation system.Self-Driven Animation System: The
SimulateMouseUiOverlayclass gains internal animation control with a state machine (None,Expanding,Dissipating) that independently handles expand and dissipate animations without requiring external animation management.Idle Overlay Suppression: The
InputReplayerintroduces a_suppressIdleUiOverlayflag 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| SimulateMouseUiAnimationConstantsKey Changes by File
InputReplayer.cs
_previousReplayMousePositionto track mouse position across frames_suppressIdleUiOverlayflag to suppress idle overlay updates after releaseApplyUiEvents()to:SimulateMouseUiOverlayState.Update()when suppression is active and position hasn't changedjustPressed)justReleased) and set suppression flagSimulateMouseUiAnimationConstants.cs (New)
EXPAND_DURATION = 0.1fEXPAND_START_SCALE = 1.5fDISSIPATE_DURATION = 0.1fSimulateMouseUiOverlay.cs
SelfAnimStateenum:None,Expanding,Dissipating)_selfAnimStartTimeto track animation progressLateUpdate()to:SimulateMouseUiOverlayState_canvasGroup.alpha = 1when consuming expand requestSetCursorScale()andSetAlpha()to cancel self-driven animation by resetting_selfAnimStateSimulateMouseUiOverlayState.cs
_pendingExpandAnimationand_pendingDissipateAnimationstatic flagsRequestExpandAnimation()- Sets expand animation request flagRequestDissipateAnimation()- Sets dissipate animation request flagConsumePendingExpandAnimation()- Atomically checks and resets expand flagConsumePendingDissipateAnimation()- Atomically checks and resets dissipate flagClear()calls to survive overlay state resetsSimulateMouseUiTool.cs
SimulateMouseUiAnimationConstants:PlayExpandAnimation()now usesSimulateMouseUiAnimationConstants.EXPAND_DURATIONandEXPAND_START_SCALEPlayDissipateAnimation()now usesSimulateMouseUiAnimationConstants.DISSIPATE_DURATIONTest Plan