Skip to content

Add UI Mask && Clean up Mask code#2912

Draft
cptbtptpbcptdtptp wants to merge 8 commits intogalacean:dev/2.0from
cptbtptpbcptdtptp:feat/UIMask
Draft

Add UI Mask && Clean up Mask code#2912
cptbtptpbcptdtptp wants to merge 8 commits intogalacean:dev/2.0from
cptbtptpbcptdtptp:feat/UIMask

Conversation

@cptbtptpbcptdtptp
Copy link
Collaborator

@cptbtptpbcptdtptp cptbtptpbcptdtptp commented Mar 5, 2026

Please check if the PR fulfills these requirements

  • The commit message follows our guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)

What is the current behavior? (You can also link to an open issue here)

What is the new behavior (if this is a feature change)?

Does this PR introduce a breaking change? (What changes might users need to make in their application due to this PR?)

Other information:

Summary by CodeRabbit

Release Notes

  • New Features

    • Added UI Mask component for sprite-based masking with alpha cutoff support
    • Added RectMask2D component for rectangular clipping with soft edges and hard clipping options
    • Added ScrollView component with vertical/horizontal scrolling, inertia, and drag interactions
    • Enhanced UI renderers with mask layer and mask interaction support
    • Added screen-to-local point conversion utility for UI coordinate transformations
    • Included new example demonstrating UI masking with interactive counters
  • Tests

    • Added UI masking and raycasting validation tests
    • Added RectMask2D functionality tests

@github-actions github-actions bot added the documentation Improvements or additions to documentation label Mar 5, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8daef01e-f742-4888-9ee8-0a1aac3f6f14

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Adds sprite masking and rectangular masking capabilities to the UI system through a new MaskRenderable mixin, UI Mask and RectMask2D components, ScrollView functionality, and supporting utilities. Refactors SpriteMask to use the shared mixin pattern and enhances rendering pipeline with mask visibility checks and batching awareness.

Changes

Cohort / File(s) Summary
Core Masking Infrastructure
packages/core/src/2d/sprite/MaskRenderable.ts, packages/core/src/2d/sprite/SpriteMaskUtils.ts, packages/core/src/RenderPipeline/MaskManager.ts
Introduces MaskRenderable mixin providing shared mask rendering logic (state management, lifecycle hooks, sprite handling, alphaCutoff integration). SpriteMaskUtils aggregates utility helpers for sprite resource management and rendering. MaskManager now uses IMask interface, adds layered mask filtering with caching, and exposes isVisibleByMask method for visibility checks.
SpriteMask Refactoring
packages/core/src/2d/sprite/SpriteMask.ts, packages/core/src/2d/sprite/index.ts
SpriteMask refactored to extend MaskRenderable(Renderer), removing duplicated mask logic. Internal methods now delegate to mixin implementations. Public exports updated to include IMaskRenderable, MaskDirtyFlags, and SpriteMaskUtils.
UI Masking Components
packages/ui/src/component/advanced/Mask.ts, packages/ui/src/component/advanced/RectMask2D.ts, packages/ui/src/component/advanced/ScrollView.ts
New Mask component extends MaskRenderable for UI rendering with custom chunk manager integration. RectMask2D provides axis-aligned rectangular clipping with soft/hard clipping support. ScrollView component implements drag, inertia, and boundary-constrained scrolling with listener callbacks.
UI Rendering Enhancements
packages/ui/src/component/UIRenderer.ts, packages/ui/src/component/UICanvas.ts, packages/ui/src/Utils.ts
UIRenderer adds sprite mask support (maskLayer, maskInteraction accessors) and rect mask clipping (internal shader properties, raycast visibility checks). UICanvas walk extended to track and collect RectMask2D instances. Utils introduces screenToLocalPoint for screen-to-local coordinate conversion supporting multiple render modes.
Rendering Pipeline Exports & Utilities
packages/core/src/RenderPipeline/index.ts, packages/core/src/RenderPipeline/BatchUtils.ts, packages/core/src/Renderer.ts
RenderElement and SubRenderElement now exported from RenderPipeline barrel. BatchUtils augmented with rect-mask state comparison in batching logic. Minor documentation cleanup in Renderer.
UI Component Updates
packages/ui/src/component/advanced/Text.ts, packages/ui/src/component/index.ts
Text component maskLayer accessors removed. Component index exports reordered (UICanvas, UIGroup, UIRenderer, UITransform moved to bottom).
Example & Tests
examples/src/ui-mask.ts, tests/src/ui/Mask.test.ts, tests/src/ui/RectMask2D.test.ts
New ui-mask example demonstrates scene setup with circular mask controlling panel visibility and click counting. Test suites validate masking with raycasting, layer influence changes, and RectMask2D boundary enforcement.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Pointer Event
    participant UIRenderer
    participant MaskManager
    participant SpriteMask as Mask Instance
    participant RectMask2D
    
    Client->>UIRenderer: raycast(worldPoint)
    UIRenderer->>UIRenderer: _isRaycastVisibleByMask()
    UIRenderer->>MaskManager: isVisibleByMask(interaction, layer, point)
    MaskManager->>MaskManager: _getMasksByLayer(layer)
    MaskManager->>SpriteMask: _containsWorldPoint(point)
    SpriteMask-->>MaskManager: contains: boolean
    MaskManager-->>UIRenderer: visible: boolean
    UIRenderer->>UIRenderer: _isRaycastVisibleByRectMask()
    UIRenderer->>RectMask2D: _containsWorldPoint(point)
    RectMask2D-->>UIRenderer: contains: boolean
    UIRenderer-->>Client: hit/miss result
Loading
sequenceDiagram
    participant Engine
    participant UICanvas
    participant RectMaskCollector as RectMask2D Collector
    participant UIRenderer as Renderer Stack
    
    Engine->>UICanvas: _walk(renderers)
    UICanvas->>RectMaskCollector: collect RectMask2D instances
    RectMaskCollector-->>UICanvas: rectMaskList
    UICanvas->>UIRenderer: _setRectMasks(masks, count)
    UIRenderer->>UIRenderer: _updateRectMaskClipState()
    UIRenderer->>UIRenderer: apply rect mask shader properties
    Engine->>UIRenderer: render()
    UIRenderer-->>Engine: rendered with clipping applied
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Whiskers twitch with masked delight,
Circles clip and rects take flight,
Sprites now share their rendering way,
ScrollView dances through the UI display,
Masks upon masks, layers intertwine,
What hides, what shows—a design so fine!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% 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 PR title 'Add UI Mask && Clean up Mask code' accurately describes the primary changes: introducing a new UI Mask component and refactoring mask-related code into reusable utilities.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@cptbtptpbcptdtptp cptbtptpbcptdtptp marked this pull request as draft March 5, 2026 08:16
@codecov
Copy link

codecov bot commented Mar 5, 2026

Codecov Report

❌ Patch coverage is 61.71338% with 724 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.07%. Comparing base (e237cdc) to head (15a08bf).
⚠️ Report is 1 commits behind head on dev/2.0.

Files with missing lines Patch % Lines
packages/ui/src/component/advanced/ScrollView.ts 37.07% 168 Missing ⚠️
examples/src/ui-mask.ts 0.00% 123 Missing and 1 partial ⚠️
examples/src/ui-scrollview.ts 0.00% 112 Missing and 1 partial ⚠️
e2e/case/ui-rectMask-scrollView.ts 0.00% 106 Missing and 1 partial ⚠️
packages/ui/src/Utils.ts 21.05% 45 Missing ⚠️
packages/core/src/2d/sprite/SpriteMaskUtils.ts 82.84% 41 Missing ⚠️
packages/ui/src/component/UIRenderer.ts 82.09% 41 Missing ⚠️
packages/core/src/RenderPipeline/BatchUtils.ts 20.68% 23 Missing ⚠️
packages/ui/src/component/advanced/Mask.ts 77.33% 17 Missing ⚠️
packages/core/src/2d/sprite/MaskRenderable.ts 95.86% 15 Missing ⚠️
... and 4 more
Additional details and impacted files
@@             Coverage Diff             @@
##           dev/2.0    #2912      +/-   ##
===========================================
- Coverage    78.31%   78.07%   -0.25%     
===========================================
  Files          883      889       +6     
  Lines        96714    98323    +1609     
  Branches      9697     9900     +203     
===========================================
+ Hits         75739    76762    +1023     
- Misses       20812    21393     +581     
- Partials       163      168       +5     
Flag Coverage Δ
unittests 78.07% <61.71%> (-0.25%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@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.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ui/src/component/UICanvas.ts (1)

531-562: ⚠️ Potential issue | 🟡 Minor

Drops additional RectMask2D components when multiple exist on the same entity.

Line 531 stores a single rectMask variable. The loop at lines 540-549 overwrites this variable each time a RectMask2D is encountered (line 548), keeping only the last one. Lines 555-557 then append only this final mask to the list. If multiple RectMask2D components are present on an entity, all but the last are discarded. The RectMask2D class has no disallowMultiple constraint, indicating multiple masks per entity should be supported.

Directly append each RectMask2D found to tempRectMaskList:

Suggested fix
-    let rectMask: RectMask2D = null;
     let groupAbleCount = 0;
     for (let i = 0, n = components.length; i < n; i++) {
       const component = components[i];
       if (!component.enabled) continue;
       if (component instanceof UIRenderer) {
         renderers[depth] = component;
         ++depth;
         component._isRootCanvasDirty && Utils.setRootCanvas(component, this);
         if (component._isGroupDirty) {
           tempGroupAbleList[groupAbleCount++] = component;
         }
         component._setRectMasks(tempRectMaskList, rectMaskCount);
       } else if (component instanceof UIInteractive) {
         component._isRootCanvasDirty && Utils.setRootCanvas(component, this);
         if (component._isGroupDirty) {
           tempGroupAbleList[groupAbleCount++] = component;
         }
       } else if (component instanceof RectMask2D) {
+        tempRectMaskList[rectMaskCount++] = component;
       } else if (component instanceof UIGroup) {
         component._isRootCanvasDirty && Utils.setRootCanvas(component, this);
         component._isGroupDirty && Utils.setGroup(component, group);
         group = component;
       }
     }
     for (let i = 0; i < groupAbleCount; i++) {
       Utils.setGroup(tempGroupAbleList[i], group);
     }
-    if (rectMask) {
-      tempRectMaskList[rectMaskCount++] = rectMask;
-    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/component/UICanvas.ts` around lines 531 - 562, The loop
currently stores only the last RectMask2D in the local variable rectMask (set on
encountering RectMask2D) and later pushes just that one into tempRectMaskList,
dropping earlier masks; change the logic in the components iteration so that
whenever a RectMask2D is encountered you directly append it to tempRectMaskList
and increment rectMaskCount (use the same tempRectMaskList and rectMaskCount
variables used by _setRectMasks), instead of assigning to rectMask, and remove
the later single rectMask push at the end so all RectMask2D instances found (not
just the last) are preserved.
🧹 Nitpick comments (4)
packages/ui/src/Utils.ts (2)

155-158: Avoid exposing mutable temp buffers on exported API surface.

Line [155]-Line [158] are public mutable statics on Utils. External reassignment can silently corrupt math behavior. Prefer private static readonly.

Proposed change
-  static _tempRay: Ray = new Ray();
-  static _tempPlane: Plane = new Plane();
-  static _tempVec3: Vector3 = new Vector3();
-  static _tempMat: Matrix = new Matrix();
+  private static readonly _tempRay: Ray = new Ray();
+  private static readonly _tempPlane: Plane = new Plane();
+  private static readonly _tempVec3: Vector3 = new Vector3();
+  private static readonly _tempMat: Matrix = new Matrix();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/Utils.ts` around lines 155 - 158, The public mutable static
temps on Utils (_tempRay, _tempPlane, _tempVec3, _tempMat) expose internal
buffers that callers can reassign and corrupt math state; change each to private
static readonly (keep their types Ray/Plane/Vector3/Matrix and current
initializers) so they remain internal, non-reassignable implementation details;
update any internal references in Utils to use the new private names (or add
private getters if needed) so external API no longer exposes these mutable
buffers.

162-162: Use primitive boolean instead of wrapper Boolean.

The return type on line 162 should use boolean; Boolean is non-idiomatic TypeScript and can cause unintended type widening.

Proposed change
-  static screenToLocalPoint(position: Vector2, transform: UITransform, out: Vector3): Boolean {
+  static screenToLocalPoint(position: Vector2, transform: UITransform, out: Vector3): boolean {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/Utils.ts` at line 162, The return type for
UIUtils.screenToLocalPoint is using the wrapper type Boolean; change the
function signature return type from Boolean to the primitive boolean (i.e.,
update the declaration of screenToLocalPoint(position: Vector2, transform:
UITransform, out: Vector3): Boolean to use boolean) to follow TypeScript best
practices and avoid type widening—also search for any other occurrences of the
wrapper Boolean in the same file and replace them with the primitive boolean to
keep consistency.
packages/ui/src/component/advanced/Mask.ts (1)

58-61: Remove redundant _subChunk cleanup after super._onDestroy().

super._onDestroy() (from UIRenderer) already frees and nulls _subChunk, so this block is dead code.

♻️ Suggested simplification
   protected override _onDestroy(): void {
     this._destroyMaskResources();

     super._onDestroy();
-
-    if (this._subChunk) {
-      this._getChunkManager().freeSubChunk(this._subChunk);
-      this._subChunk = null;
-    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/component/advanced/Mask.ts` around lines 58 - 61, Remove the
redundant cleanup that frees and nulls _subChunk after calling
super._onDestroy() in Mask (the block invoking
this._getChunkManager().freeSubChunk(this._subChunk); this._subChunk = null;).
UIRenderer.super._onDestroy already handles freeing and nulling _subChunk, so
delete that conditional block (reference: Mask._onDestroy, property _subChunk,
and method _getChunkManager().freeSubChunk) to avoid dead code.
packages/ui/src/component/UIRenderer.ts (1)

380-395: Deduplicate rect-mask reset logic in _updateRectMaskClipState().

The count <= 0 and !hasActiveMask branches perform the same reset sequence. Extracting a helper will reduce drift risk when this state logic evolves.

Also applies to: 443-458

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

In `@packages/ui/src/component/UIRenderer.ts` around lines 380 - 395, The reset
sequence for rect-mask state is duplicated in
UIRenderer._updateRectMaskClipState() (branches for count <= 0 and
!hasActiveMask); extract that sequence into a private helper (e.g.,
_resetRectMaskState or similar) that clears this._rectMaskEnabled,
this._rectMaskHardClip, zeroes this._rectMaskSoftness vector, and updates
shaderData via shaderData.setFloat(UIRenderer._rectClipEnabledProperty),
shaderData.setVector4(UIRenderer._rectClipSoftnessProperty), and
shaderData.setFloat(UIRenderer._rectClipHardClipProperty); then replace both
duplicated blocks (the existing code around count <= 0 and the similar block at
lines ~443-458) with a single call to the new helper to keep behavior identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/src/ui-mask.ts`:
- Line 93: Prettier is flagging formatting issues in this example; reformat the
createLabel calls and the block around lines where createLabel is used (notably
the createLabel invocation assigning insideCountLabel and the subsequent block
around lines 144-151) to match project prettier rules—run the configured
formatter or adjust whitespace/commas/argument spacing so the
createLabel(canvasEntity, "InsideCount", "Inside hits: 0", -260, 30, new
Color(1, 0.85, 0.8, 1)) call and the code block at 144-151 conform to the repo’s
Prettier style (consistent spacing, commas, and line breaks) ensuring no
eslint/prettier errors remain.

In `@packages/core/src/2d/sprite/MaskRenderable.ts`:
- Around line 219-221: _cloning only copies the sprite which bypasses the
_alphaCutoff setter side-effect that calls shaderData.setFloat(...), leaving the
cloned MaskRenderableBase with a stale shader uniform; update _cloneMaskData to
also apply the alpha cutoff via the proper setter (or explicitly call
shaderData.setFloat on the target) so the cloned instance's shader uniform is
resynced (use _cloneMaskData, MaskRenderableBase, _alphaCutoff, and
shaderData.setFloat references to find the code).

In `@packages/core/src/RenderPipeline/index.ts`:
- Line 8: Remove the extra blank line at the end of the module so the file ends
immediately after the last token; open the RenderPipeline index file (module
RenderPipeline) and delete the trailing newline/empty line at EOF so lint no
longer reports an extra blank line.

In `@packages/ui/src/component/advanced/ScrollView.ts`:
- Around line 9-260: This file has many Prettier/lint formatting violations; run
the project's formatter and linter auto-fix (e.g. prettier --write and eslint
--fix or your repo's format script) on
packages/ui/src/component/advanced/ScrollView.ts to normalize indentation,
semicolons, spacing and line breaks; ensure you keep existing logic intact in
getters/setters (mode, viewport, content), methods
onPointerBeginDrag/onPointerDrag/onPointerEndDrag, _computeScrolling,
_applyScrolling, _updateRect and onUpdate/onDestroy while committing the
formatted result so CI lint/prettier checks pass.
- Around line 83-89: The onPointerBeginDrag method currently calls
Utils.screenToLocalPoint(eventData.pointer.position, viewportTransform,
this._startPoint) and ignores its boolean return, which can leave
_startPoint/_fromPoint stale when conversion fails; update onPointerBeginDrag
(and the similar pointer-move/drag handlers that call Utils.screenToLocalPoint)
to check the returned boolean and abort the drag setup (return early, do not set
_isDragging or copy to _fromPoint) if the conversion fails, and ensure you only
use this._startPoint and this._fromPoint after a successful conversion with
valid this._viewportTransform and this._contentTransform.
- Around line 153-160: In _applyScrolling, percentH/percentV are computed using
stale rects and can produce NaN/Infinity when content size ≤ viewport; after
updating this._contentTransform.position (contentPosition) recompute or derive
the updated content rect (use the post-move values of _contentRect or calculate
from contentPosition) and then compute percentH and percentV using those
post-move rects; guard the denominators by checking (contentRect.width -
viewportRect.width) and (contentRect.height - viewportRect.height) > 0 before
dividing (or use a clamp/fallback to 0), and finally clamp percentH/percentV
into [0,1] before using _listeners.getLoopArray() so you never pass NaN/Infinity
to listeners.
- Around line 71-80: The removeOnScroll signature must match addOnScroll: change
removeOnScroll to accept the same listener type (listener: (percentH: number,
percentV: number) => void) so the same function reference can be found and
removed; keep the removal logic that locates entries in this._listeners (the
predicate comparing value.fn === listener and marking value.destroyed) but
update the method signature and any related typings to use the two-parameter
(percentH, percentV) callback type referenced in addOnScroll and the stored
listener objects.

In `@packages/ui/src/component/UIRenderer.ts`:
- Line 386: The long conditional checking rectMaskSoftness should be reformatted
to match Prettier by breaking the OR comparisons onto separate lines inside a
parenthesized if-condition; update the occurrence that uses
rectMaskSoftness.x/y/z/w so each comparison (rectMaskSoftness.x !== 0 || ...) is
on its own line and the closing parenthesis/brace are placed as Prettier
expects, and make the same change for the second identical occurrence in the
file so both lint violations are resolved.

In `@packages/ui/src/Utils.ts`:
- Around line 167-177: The loop in Utils.ts that walks parent entities
overwrites rootCanvas by continuing traversal after a match; change the logic in
the while(entity) loop so it stops when the first matching UICanvas with
_isRootCanvas is found (mirroring searchRootCanvasInParents). Specifically,
inside the inner for-loop when you detect component.enabled && component
instanceof UICanvas && component._isRootCanvas, set rootCanvas and immediately
stop further processing of components and parent traversal (either by returning
the found canvas, breaking both loops via a flag, or otherwise exiting the
function), ensuring the first (closest) match is returned.

In `@tests/src/ui/Mask.test.ts`:
- Line 1: The single-line import of multiple symbols (PointerEventData, Script,
Sprite, SpriteMaskInteraction, SpriteMaskLayer, Texture2D) at the top of
Mask.test.ts violates Prettier; reformat the import from "@galacean/engine-core"
so it conforms to project Prettier rules (either split named imports across
lines or run the project's Prettier formatter/ESLint autofix) and ensure the
named imports are ordered/wrapped as configured so the file passes linting.

---

Outside diff comments:
In `@packages/ui/src/component/UICanvas.ts`:
- Around line 531-562: The loop currently stores only the last RectMask2D in the
local variable rectMask (set on encountering RectMask2D) and later pushes just
that one into tempRectMaskList, dropping earlier masks; change the logic in the
components iteration so that whenever a RectMask2D is encountered you directly
append it to tempRectMaskList and increment rectMaskCount (use the same
tempRectMaskList and rectMaskCount variables used by _setRectMasks), instead of
assigning to rectMask, and remove the later single rectMask push at the end so
all RectMask2D instances found (not just the last) are preserved.

---

Nitpick comments:
In `@packages/ui/src/component/advanced/Mask.ts`:
- Around line 58-61: Remove the redundant cleanup that frees and nulls _subChunk
after calling super._onDestroy() in Mask (the block invoking
this._getChunkManager().freeSubChunk(this._subChunk); this._subChunk = null;).
UIRenderer.super._onDestroy already handles freeing and nulling _subChunk, so
delete that conditional block (reference: Mask._onDestroy, property _subChunk,
and method _getChunkManager().freeSubChunk) to avoid dead code.

In `@packages/ui/src/component/UIRenderer.ts`:
- Around line 380-395: The reset sequence for rect-mask state is duplicated in
UIRenderer._updateRectMaskClipState() (branches for count <= 0 and
!hasActiveMask); extract that sequence into a private helper (e.g.,
_resetRectMaskState or similar) that clears this._rectMaskEnabled,
this._rectMaskHardClip, zeroes this._rectMaskSoftness vector, and updates
shaderData via shaderData.setFloat(UIRenderer._rectClipEnabledProperty),
shaderData.setVector4(UIRenderer._rectClipSoftnessProperty), and
shaderData.setFloat(UIRenderer._rectClipHardClipProperty); then replace both
duplicated blocks (the existing code around count <= 0 and the similar block at
lines ~443-458) with a single call to the new helper to keep behavior identical.

In `@packages/ui/src/Utils.ts`:
- Around line 155-158: The public mutable static temps on Utils (_tempRay,
_tempPlane, _tempVec3, _tempMat) expose internal buffers that callers can
reassign and corrupt math state; change each to private static readonly (keep
their types Ray/Plane/Vector3/Matrix and current initializers) so they remain
internal, non-reassignable implementation details; update any internal
references in Utils to use the new private names (or add private getters if
needed) so external API no longer exposes these mutable buffers.
- Line 162: The return type for UIUtils.screenToLocalPoint is using the wrapper
type Boolean; change the function signature return type from Boolean to the
primitive boolean (i.e., update the declaration of screenToLocalPoint(position:
Vector2, transform: UITransform, out: Vector3): Boolean to use boolean) to
follow TypeScript best practices and avoid type widening—also search for any
other occurrences of the wrapper Boolean in the same file and replace them with
the primitive boolean to keep consistency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2106c9cc-8831-422c-9ecd-676eef5e3f7e

📥 Commits

Reviewing files that changed from the base of the PR and between e237cdc and 6b8b328.

⛔ Files ignored due to path filters (4)
  • packages/core/src/shaderlib/extra/text.fs.glsl is excluded by !**/*.glsl
  • packages/core/src/shaderlib/extra/text.vs.glsl is excluded by !**/*.glsl
  • packages/ui/src/shader/uiDefault.fs.glsl is excluded by !**/*.glsl
  • packages/ui/src/shader/uiDefault.vs.glsl is excluded by !**/*.glsl
📒 Files selected for processing (19)
  • examples/src/ui-mask.ts
  • packages/core/src/2d/sprite/MaskRenderable.ts
  • packages/core/src/2d/sprite/SpriteMask.ts
  • packages/core/src/2d/sprite/SpriteMaskUtils.ts
  • packages/core/src/2d/sprite/index.ts
  • packages/core/src/RenderPipeline/BatchUtils.ts
  • packages/core/src/RenderPipeline/MaskManager.ts
  • packages/core/src/RenderPipeline/index.ts
  • packages/core/src/Renderer.ts
  • packages/ui/src/Utils.ts
  • packages/ui/src/component/UICanvas.ts
  • packages/ui/src/component/UIRenderer.ts
  • packages/ui/src/component/advanced/Mask.ts
  • packages/ui/src/component/advanced/RectMask2D.ts
  • packages/ui/src/component/advanced/ScrollView.ts
  • packages/ui/src/component/advanced/Text.ts
  • packages/ui/src/component/index.ts
  • tests/src/ui/Mask.test.ts
  • tests/src/ui/RectMask2D.test.ts
💤 Files with no reviewable changes (2)
  • packages/ui/src/component/advanced/Text.ts
  • packages/core/src/Renderer.ts

new Color(1, 1, 1, 0.95)
);

const insideCountLabel = createLabel(canvasEntity, "InsideCount", "Inside hits: 0", -260, 30, new Color(1, 0.85, 0.8, 1));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Prettier violations are failing lint in this example file.

Please format Line 93 and Lines 144-151 to match the current prettier config.

🎯 Formatting patch
-  const insideCountLabel = createLabel(canvasEntity, "InsideCount", "Inside hits: 0", -260, 30, new Color(1, 0.85, 0.8, 1));
+  const insideCountLabel = createLabel(
+    canvasEntity,
+    "InsideCount",
+    "Inside hits: 0",
+    -260,
+    30,
+    new Color(1, 0.85, 0.8, 1)
+  );
@@
-function createLabel(
-  parent: Entity,
-  name: string,
-  content: string,
-  y: number,
-  fontSize: number,
-  color: Color
-): Text {
+function createLabel(parent: Entity, name: string, content: string, y: number, fontSize: number, color: Color): Text {

Also applies to: 144-151

🧰 Tools
🪛 ESLint

[error] 93-93: Replace canvasEntity,·"InsideCount",·"Inside·hits:·0",·-260,·30,·new·Color(1,·0.85,·0.8,·1) with ⏎····canvasEntity,⏎····"InsideCount",⏎····"Inside·hits:·0",⏎····-260,⏎····30,⏎····new·Color(1,·0.85,·0.8,·1)⏎··

(prettier/prettier)

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

In `@examples/src/ui-mask.ts` at line 93, Prettier is flagging formatting issues
in this example; reformat the createLabel calls and the block around lines where
createLabel is used (notably the createLabel invocation assigning
insideCountLabel and the subsequent block around lines 144-151) to match project
prettier rules—run the configured formatter or adjust whitespace/commas/argument
spacing so the createLabel(canvasEntity, "InsideCount", "Inside hits: 0", -260,
30, new Color(1, 0.85, 0.8, 1)) call and the code block at 144-151 conform to
the repo’s Prettier style (consistent spacing, commas, and line breaks) ensuring
no eslint/prettier errors remain.

Comment on lines +219 to +221
_cloneMaskData(target: MaskRenderableBase): void {
target.sprite = this._sprite;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Clone path does not resync the alpha-cutoff shader uniform.

_cloneMaskData() copies sprite, but cloned _alphaCutoff values can bypass the setter’s shaderData.setFloat(...) side effect, leaving cloned masks with stale cutoff uniforms.

🐛 Suggested fix
 _cloneMaskData(target: MaskRenderableBase): void {
   target.sprite = this._sprite;
+  target.shaderData.setFloat(_alphaCutoffProperty, target._alphaCutoff);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_cloneMaskData(target: MaskRenderableBase): void {
target.sprite = this._sprite;
}
_cloneMaskData(target: MaskRenderableBase): void {
target.sprite = this._sprite;
target.shaderData.setFloat(_alphaCutoffProperty, target._alphaCutoff);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/2d/sprite/MaskRenderable.ts` around lines 219 - 221,
_cloning only copies the sprite which bypasses the _alphaCutoff setter
side-effect that calls shaderData.setFloat(...), leaving the cloned
MaskRenderableBase with a stale shader uniform; update _cloneMaskData to also
apply the alpha cutoff via the proper setter (or explicitly call
shaderData.setFloat on the target) so the cloned instance's shader uniform is
resynced (use _cloneMaskData, MaskRenderableBase, _alphaCutoff, and
shaderData.setFloat references to find the code).

export { RenderElement } from "./RenderElement";
export { RenderQueue } from "./RenderQueue";
export { SubRenderElement } from "./SubRenderElement";

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove trailing newline to fix lint failure.

Static analysis indicates an extra blank line at the end of the file.

🧹 Proposed fix
 export { SubRenderElement } from "./SubRenderElement";
-
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export { SubRenderElement } from "./SubRenderElement";
🧰 Tools
🪛 ESLint

[error] 8-9: Delete

(prettier/prettier)

🪛 GitHub Check: lint

[failure] 8-8:
Delete

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

In `@packages/core/src/RenderPipeline/index.ts` at line 8, Remove the extra blank
line at the end of the module so the file ends immediately after the last token;
open the RenderPipeline index file (module RenderPipeline) and delete the
trailing newline/empty line at EOF so lint no longer reports an extra blank
line.

Comment on lines +9 to +260
// 视口
private _viewportTransform: UITransform;
// 内容
private _contentTransform: UITransform;
// 类型
private _mode: ScrollViewMode = ScrollViewMode.VerticalAndHorizontal;
// 滑动类型
private _movementType: MovementType = MovementType.Clamped;
// 惯性
private _inertia: boolean = true;
// 当前滑动速度
private _velocity: Vector2 = new Vector2();
// 惯性速度阈值
private _threshold = 10;
// 惯性衰减系数
private _decelerationRate: number = 0.05;
// 计算惯性时把每帧分成多个 1 / 240 秒时间片,模拟微积分
private _inertiaBaseTimeInterval = 1 / 240;

// 正在拖拽
private _isDragging: boolean = false;
// 计算边界
private _viewportRect: Rect = new Rect();
private _contentRect: Rect = new Rect();
@ignoreClone
private _listeners: SafeLoopArray<IScrollListener> = new SafeLoopArray<IScrollListener>();

// 拖动行为的起始点(以 viewport 的局部坐标系为参考坐标系)
private _startPoint: Vector3 = new Vector3();
// **本帧**移动的起点(以 viewport 的局部坐标系为参考坐标系)
private _fromPoint: Vector3 = new Vector3();
// **本帧**移动的终点(以 viewport 的局部坐标系为参考坐标系)
private _toPoint: Vector3 = new Vector3();

get mode(): ScrollViewMode {
return this._mode;
}

set mode(value: number) {
this._mode = value;
}

get viewport() {
return this._viewportTransform?.entity;
}

set viewport(entity: Entity) {
this._viewportTransform = entity.transform as UITransform;
}

get content(): Entity {
return this._contentTransform?.entity;;
}

set content(entity: Entity) {
this._contentTransform = entity.transform as UITransform;
}

/**
* 添加滑动回调
* @param listener - The listening function
*/
addOnScroll(listener: (percentH: number, percentV: number) => void): void {
this._listeners.push({ fn: listener });
}

/**
* 移除滑动回调
* @param listener - The listening function
*/
removeOnScroll(listener: (val: number) => void): void {
this._listeners.findAndRemove((value) => (value.fn === listener ? (value.destroyed = true) : false));
}

override onPointerBeginDrag(eventData: PointerEventData): void {
this._isDragging = true;
const { _viewportTransform: viewportTransform } = this;
if (!viewportTransform || !this._contentTransform) return;
// 初始化拖动的起始点
Utils.screenToLocalPoint(eventData.pointer.position, viewportTransform, this._startPoint);
this._fromPoint.copyFrom(this._startPoint);
}

override onPointerDrag(eventData: PointerEventData): void {
const { _viewportTransform: viewportTransform, _contentTransform: contentTransform } = this;
if (!viewportTransform || !contentTransform) return;
const { _fromPoint: fromPoint, _toPoint: toPoint } = this;
// 更新 toPoint
Utils.screenToLocalPoint(eventData.pointer.position, viewportTransform, toPoint);
// 从 fromPoint 到 toPoint
this._updateRect();
this._computeScrolling(fromPoint, toPoint);
// 更新 fromPoint
fromPoint.copyFrom(toPoint)
}

private _computeScrolling(from: Vector3, to: Vector3) {
const { _contentTransform: contentTransform, _mode: mode } = this;
const contentScale = contentTransform.scale;
// 根据 mode 计算 delta
let deltaX = (mode & ScrollViewMode.Horizontal) ? (to.x - from.x) * contentScale.x : 0;
let deltaY = (mode & ScrollViewMode.Vertical) ? (to.y - from.y) * contentScale.y : 0;
// 根据滑动类型计算滑块的运动
switch (this._movementType) {
case MovementType.Clamped:
const { _viewportRect: viewportRect, _contentRect: contentRect } = this;
if (mode & ScrollViewMode.Horizontal) {
if (deltaX > 0) {
if (contentRect.x + deltaX > viewportRect.x) {
deltaX = viewportRect.x - contentRect.x;
}
} else if (deltaX < 0) {
if (contentRect.x + contentRect.width + deltaX < viewportRect.x + viewportRect.width) {
deltaX = viewportRect.x + viewportRect.width - contentRect.x - contentRect.width;
}
}
}
if (mode & ScrollViewMode.Vertical) {
if (deltaY > 0) {
if (contentRect.y + deltaY > viewportRect.y) {
deltaY = viewportRect.y - contentRect.y;
}
} else if (deltaY < 0) {
if (contentRect.y + contentRect.height + deltaY < viewportRect.y + viewportRect.height) {
deltaY = viewportRect.y + viewportRect.height - contentRect.y - contentRect.height;
}
}
}
break;
case MovementType.Elastic:
// 暂未实现
break;
default:
break;
}
this._applyScrolling(deltaX, deltaY);
// 记录此时的速度,可以计算惯性
const delta = this.engine.time.actualDeltaTime;
const invDeltaTime = 1 / delta;
const velocity = this._velocity;
velocity.set(deltaX * invDeltaTime, deltaY * invDeltaTime);
velocity.scale(Math.pow(this._decelerationRate, delta))
}

private _applyScrolling(dx: number, dy: number): void {
const contentPosition = this._contentTransform.position;
const { _contentRect: contentRect, _viewportRect: viewportRect } = this;
// 计算是否碰到边缘
contentPosition.set(contentPosition.x + dx, contentPosition.y + dy, contentPosition.z);
const percentH = (viewportRect.x - contentRect.x) / (contentRect.width - viewportRect.width);
const percentV = (contentRect.y + contentRect.height - viewportRect.y - viewportRect.height) / (contentRect.height - viewportRect.height)
const listeners = this._listeners.getLoopArray();
for (let i = 0, n = listeners.length; i < n; i++) {
const listener = listeners[i];
!listener.destroyed && listener.fn(percentH, percentV);
}
}

override onPointerEndDrag(eventData: PointerEventData): void {
this._isDragging = false;
}

override onUpdate(deltaTime: number): void {
if (this._isDragging || !this._inertia) return;
const velocity = this._velocity;
if (velocity.lengthSquared() <= this._threshold ** 2) {
velocity.set(0, 0);
return;
}
let dx = 0;
let dy = 0;
const { _inertiaBaseTimeInterval: inertiaBaseTimeInterval, _decelerationRate: decelerationRate } = this;
while (deltaTime > 0) {
const timeSlice = deltaTime > inertiaBaseTimeInterval ? inertiaBaseTimeInterval : deltaTime;
velocity.scale(Math.pow(decelerationRate, timeSlice));
dx += velocity.x * timeSlice;
dy += velocity.y * timeSlice;
deltaTime -= timeSlice;
}
this._updateRect();
if (this._movementType !== MovementType.Unrestricted) {
const { _contentRect: contentRect, _viewportRect: viewportRect } = this;
if (dx > 0) {
if (contentRect.x + dx > viewportRect.x) {
dx = viewportRect.x - contentRect.x;
velocity.x = 0;
}
} else if (dx < 0) {
if (contentRect.x + contentRect.width + dx < viewportRect.x + viewportRect.width) {
dx = viewportRect.x + viewportRect.width - contentRect.x - contentRect.width;
velocity.x = 0;
}
}
if (dy > 0) {
if (contentRect.y + dy > viewportRect.y) {
dy = viewportRect.y - contentRect.y;
velocity.y = 0;
}
} else if (dy < 0) {
if (contentRect.y + contentRect.height + dy < viewportRect.y + viewportRect.height) {
dy = viewportRect.y + viewportRect.height - contentRect.y - contentRect.height;
velocity.y = 0;
}
}
}
this._applyScrolling(dx, dy);

}

private _updateRect(): void {
const { _viewportTransform: viewportTransform, _contentTransform: contentTransform } = this;
if (viewportTransform && contentTransform) {
const { _viewportRect: viewportRect, _contentRect: contentRect } = this;
// viewport 的 localRect
const viewportPivot = viewportTransform.pivot;
const viewportSize = viewportTransform.size;
viewportRect.set(-viewportPivot.x * viewportSize.x, -viewportPivot.y * viewportSize.y, viewportSize.x, viewportSize.y);
// content 相对于 viewport 的 Rect
const contentPosition = contentTransform.position;
const contentScale = contentTransform.scale;
const contentPivot = contentTransform.pivot;
const contentSize = contentTransform.size;
contentRect.set(
contentPosition.x - contentPivot.x * contentSize.x * contentScale.x,
contentPosition.y - contentPivot.y * contentSize.y * contentScale.y,
contentSize.x * contentScale.x,
contentSize.y * contentScale.y
);
}
}

override onDestroy(): void {
super.onDestroy();
this._listeners.findAndRemove((value) => (value.destroyed = true));
}
}

export enum MovementType {
Elastic,
Clamped,
Unrestricted
}

export interface IScrollListener {
fn: (percentH: number, percentV: number) => void;
destroyed?: boolean;
}

export enum ScrollViewMode {
Vertical = 0x1,
Horizontal = 0x2,
VerticalAndHorizontal = 0x3
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Resolve lint/prettier violations before merge.

Static analysis shows widespread prettier/prettier failures (indentation, semicolons, line wrapping), and lint is failing. Please run formatting for this file.

🧰 Tools
🪛 ESLint

[error] 9-9: Delete ··

(prettier/prettier)


[error] 10-10: Delete ··

(prettier/prettier)


[error] 11-11: Replace ···· with ··

(prettier/prettier)


[error] 12-12: Delete ··

(prettier/prettier)


[error] 13-13: Replace ···· with ··

(prettier/prettier)


[error] 14-14: Delete ··

(prettier/prettier)


[error] 15-15: Delete ··

(prettier/prettier)


[error] 16-16: Delete ··

(prettier/prettier)


[error] 17-17: Delete ··

(prettier/prettier)


[error] 18-18: Delete ··

(prettier/prettier)


[error] 19-19: Delete ··

(prettier/prettier)


[error] 20-20: Replace ···· with ··

(prettier/prettier)


[error] 21-21: Delete ··

(prettier/prettier)


[error] 22-22: Replace ···· with ··

(prettier/prettier)


[error] 23-23: Delete ··

(prettier/prettier)


[error] 24-24: Delete ··

(prettier/prettier)


[error] 25-25: Delete ··

(prettier/prettier)


[error] 26-26: Delete ··

(prettier/prettier)


[error] 28-28: Delete ··

(prettier/prettier)


[error] 29-29: Delete ··

(prettier/prettier)


[error] 30-30: Replace ···· with ··

(prettier/prettier)


[error] 31-31: Delete ··

(prettier/prettier)


[error] 32-32: Replace ···· with ··

(prettier/prettier)


[error] 33-33: Delete ··

(prettier/prettier)


[error] 34-34: Delete ··

(prettier/prettier)


[error] 36-36: Delete ··

(prettier/prettier)


[error] 37-37: Delete ··

(prettier/prettier)


[error] 38-38: Delete ··

(prettier/prettier)


[error] 39-39: Delete ··

(prettier/prettier)


[error] 40-40: Replace ···· with ··

(prettier/prettier)


[error] 41-41: Delete ··

(prettier/prettier)


[error] 43-43: Replace ···· with ··

(prettier/prettier)


[error] 44-44: Delete ····

(prettier/prettier)


[error] 45-45: Delete ··

(prettier/prettier)


[error] 47-47: Replace ···· with ··

(prettier/prettier)


[error] 48-48: Delete ····

(prettier/prettier)


[error] 49-49: Delete ··

(prettier/prettier)


[error] 51-51: Delete ··

(prettier/prettier)


[error] 52-52: Delete ····

(prettier/prettier)


[error] 53-53: Replace ···· with ··

(prettier/prettier)


[error] 55-55: Delete ··

(prettier/prettier)


[error] 56-56: Replace ········ with ····

(prettier/prettier)


[error] 57-57: Delete ··

(prettier/prettier)


[error] 59-59: Delete ··

(prettier/prettier)


[error] 60-60: Replace ········return·this._contentTransform?.entity;; with ····return·this._contentTransform?.entity;

(prettier/prettier)


[error] 61-61: Replace ···· with ··

(prettier/prettier)


[error] 63-63: Delete ··

(prettier/prettier)


[error] 64-64: Replace ········ with ····

(prettier/prettier)


[error] 65-65: Delete ··

(prettier/prettier)


[error] 67-67: Replace ···· with ··

(prettier/prettier)


[error] 68-68: Delete ··

(prettier/prettier)


[error] 69-69: Delete ··

(prettier/prettier)


[error] 70-70: Delete ··

(prettier/prettier)


[error] 71-71: Delete ··

(prettier/prettier)


[error] 72-72: Replace ········ with ····

(prettier/prettier)


[error] 73-73: Delete ··

(prettier/prettier)


[error] 75-75: Delete ··

(prettier/prettier)


[error] 76-76: Delete ··

(prettier/prettier)


[error] 77-77: Delete ··

(prettier/prettier)


[error] 78-78: Delete ··

(prettier/prettier)


[error] 79-79: Delete ··

(prettier/prettier)


[error] 80-80: Delete ····

(prettier/prettier)


[error] 81-81: Delete ··

(prettier/prettier)


[error] 83-83: Delete ··

(prettier/prettier)


[error] 84-84: Delete ····

(prettier/prettier)


[error] 85-85: Replace ········ with ····

(prettier/prettier)


[error] 86-86: Replace ········ with ····

(prettier/prettier)


[error] 87-87: Replace ········ with ····

(prettier/prettier)


[error] 88-88: Delete ····

(prettier/prettier)


[error] 89-89: Replace ········ with ····

(prettier/prettier)


[error] 90-90: Delete ··

(prettier/prettier)


[error] 92-92: Delete ··

(prettier/prettier)


[error] 93-93: Delete ····

(prettier/prettier)


[error] 94-94: Delete ····

(prettier/prettier)


[error] 95-95: Delete ····

(prettier/prettier)


[error] 96-96: Replace ········ with ····

(prettier/prettier)


[error] 97-97: Delete ····

(prettier/prettier)


[error] 98-98: Replace ········ with ····

(prettier/prettier)


[error] 99-99: Delete ····

(prettier/prettier)


[error] 100-100: Replace ········ with ····

(prettier/prettier)


[error] 101-101: Delete ····

(prettier/prettier)


[error] 102-102: Replace ········fromPoint.copyFrom(toPoint) with ····fromPoint.copyFrom(toPoint);

(prettier/prettier)


[error] 103-103: Delete ··

(prettier/prettier)


[error] 105-105: Delete ··

(prettier/prettier)


[error] 106-106: Replace ········ with ····

(prettier/prettier)


[error] 107-107: Delete ····

(prettier/prettier)


[error] 108-108: Delete ····

(prettier/prettier)


[error] 109-109: Replace ····let·deltaX·=·(mode·&·ScrollViewMode.Horizontal) with let·deltaX·=·mode·&·ScrollViewMode.Horizontal

(prettier/prettier)


[error] 110-110: Replace ········let·deltaY·=·(mode·&·ScrollViewMode.Vertical) with ····let·deltaY·=·mode·&·ScrollViewMode.Vertical

(prettier/prettier)


[error] 111-111: Replace ········ with ····

(prettier/prettier)


[error] 112-112: Replace ········ with ····

(prettier/prettier)


[error] 113-113: Delete ······

(prettier/prettier)


[error] 114-114: Delete ········

(prettier/prettier)


[error] 115-115: Replace ················ with ········

(prettier/prettier)


[error] 116-116: Delete ··········

(prettier/prettier)


[error] 117-117: Delete ············

(prettier/prettier)


[error] 118-118: Replace ···························· with ··············

(prettier/prettier)


[error] 119-119: Replace ························ with ············

(prettier/prettier)


[error] 120-120: Delete ··········

(prettier/prettier)


[error] 121-121: Delete ············

(prettier/prettier)


[error] 122-122: Replace ···························· with ··············

(prettier/prettier)


[error] 123-123: Delete ············

(prettier/prettier)


[error] 124-124: Replace ···················· with ··········

(prettier/prettier)


[error] 125-125: Replace ················ with ········

(prettier/prettier)


[error] 126-126: Replace ················ with ········

(prettier/prettier)


[error] 127-127: Delete ··········

(prettier/prettier)


[error] 128-128: Replace ························ with ············

(prettier/prettier)


[error] 129-129: Replace ···························· with ··············

(prettier/prettier)


[error] 130-130: Replace ························ with ············

(prettier/prettier)


[error] 131-131: Delete ··········

(prettier/prettier)


[error] 132-132: Replace ························ with ············

(prettier/prettier)


[error] 133-133: Replace ···························· with ··············

(prettier/prettier)


[error] 134-134: Delete ············

(prettier/prettier)


[error] 135-135: Replace ···················· with ··········

(prettier/prettier)


[error] 136-136: Replace ················ with ········

(prettier/prettier)


[error] 137-137: Replace ················ with ········

(prettier/prettier)


[error] 138-138: Delete ······

(prettier/prettier)


[error] 139-139: Replace ················ with ········

(prettier/prettier)


[error] 140-140: Replace ················ with ········

(prettier/prettier)


[error] 141-141: Replace ············ with ······

(prettier/prettier)


[error] 142-142: Replace ················ with ········

(prettier/prettier)


[error] 143-143: Replace ········ with ····

(prettier/prettier)


[error] 144-144: Delete ····

(prettier/prettier)


[error] 145-145: Replace ········ with ····

(prettier/prettier)


[error] 146-146: Delete ····

(prettier/prettier)


[error] 147-147: Delete ····

(prettier/prettier)


[error] 148-148: Delete ····

(prettier/prettier)


[error] 149-149: Delete ····

(prettier/prettier)


[error] 150-150: Replace ········velocity.scale(Math.pow(this._decelerationRate,·delta)) with ····velocity.scale(Math.pow(this._decelerationRate,·delta));

(prettier/prettier)


[error] 151-151: Replace ···· with ··

(prettier/prettier)


[error] 153-153: Delete ··

(prettier/prettier)


[error] 154-154: Replace ········ with ····

(prettier/prettier)


[error] 155-155: Delete ····

(prettier/prettier)


[error] 156-156: Replace ········ with ····

(prettier/prettier)


[error] 157-157: Delete ····

(prettier/prettier)


[error] 158-158: Replace ········ with ····

(prettier/prettier)


[error] 159-159: Replace ····const·percentV·=·(contentRect.y·+·contentRect.height·-·viewportRect.y·-·viewportRect.height)·/·(contentRect.height·-·viewportRect.height) with const·percentV·=⏎······(contentRect.y·+·contentRect.height·-·viewportRect.y·-·viewportRect.height)·/⏎······(contentRect.height·-·viewportRect.height);

(prettier/prettier)


[error] 160-160: Delete ····

(prettier/prettier)


[error] 161-161: Delete ····

(prettier/prettier)


[error] 162-162: Delete ······

(prettier/prettier)


[error] 163-163: Delete ······

(prettier/prettier)


[error] 164-164: Delete ····

(prettier/prettier)


[error] 165-165: Delete ··

(prettier/prettier)


[error] 167-167: Delete ··

(prettier/prettier)


[error] 168-168: Delete ····

(prettier/prettier)


[error] 169-169: Delete ··

(prettier/prettier)


[error] 171-171: Replace ···· with ··

(prettier/prettier)


[error] 172-172: Replace ········ with ····

(prettier/prettier)


[error] 173-173: Replace ········ with ····

(prettier/prettier)


[error] 174-174: Delete ····

(prettier/prettier)


[error] 175-175: Replace ············ with ······

(prettier/prettier)


[error] 176-176: Delete ······

(prettier/prettier)


[error] 177-177: Replace ········ with ····

(prettier/prettier)


[error] 178-178: Delete ····

(prettier/prettier)


[error] 179-179: Replace ········ with ····

(prettier/prettier)


[error] 180-180: Delete ····

(prettier/prettier)


[error] 181-181: Replace ········ with ····

(prettier/prettier)


[error] 182-182: Delete ······

(prettier/prettier)


[error] 183-183: Delete ······

(prettier/prettier)


[error] 184-184: Replace ············ with ······

(prettier/prettier)


[error] 185-185: Replace ············ with ······

(prettier/prettier)


[error] 186-186: Replace ············ with ······

(prettier/prettier)


[error] 187-187: Replace ········ with ····

(prettier/prettier)


[error] 188-188: Delete ····

(prettier/prettier)


[error] 189-189: Delete ····

(prettier/prettier)


[error] 190-190: Delete ······

(prettier/prettier)


[error] 191-191: Replace ············ with ······

(prettier/prettier)


[error] 192-192: Delete ········

(prettier/prettier)


[error] 193-193: Replace ···················· with ··········

(prettier/prettier)


[error] 194-194: Delete ··········

(prettier/prettier)


[error] 195-195: Replace ················ with ········

(prettier/prettier)


[error] 196-196: Delete ······

(prettier/prettier)


[error] 197-197: Replace ················ with ········

(prettier/prettier)


[error] 198-198: Replace ···················· with ··········

(prettier/prettier)


[error] 199-199: Replace ···················· with ··········

(prettier/prettier)


[error] 200-200: Delete ········

(prettier/prettier)


[error] 201-201: Delete ······

(prettier/prettier)


[error] 202-202: Delete ······

(prettier/prettier)


[error] 203-203: Delete ········

(prettier/prettier)


[error] 204-204: Replace ···················· with ··········

(prettier/prettier)


[error] 205-205: Delete ··········

(prettier/prettier)


[error] 206-206: Replace ················ with ········

(prettier/prettier)


[error] 207-207: Delete ······

(prettier/prettier)


[error] 208-208: Replace ················ with ········

(prettier/prettier)


[error] 209-209: Replace ···················· with ··········

(prettier/prettier)


[error] 210-210: Replace ···················· with ··········

(prettier/prettier)


[error] 211-211: Delete ········

(prettier/prettier)


[error] 212-212: Replace ············ with ······

(prettier/prettier)


[error] 213-213: Delete ····

(prettier/prettier)


[error] 214-214: Delete ····

(prettier/prettier)


[error] 215-216: Delete ⏎··

(prettier/prettier)


[error] 218-218: Delete ··

(prettier/prettier)


[error] 219-219: Replace ········ with ····

(prettier/prettier)


[error] 220-220: Delete ····

(prettier/prettier)


[error] 221-221: Replace ············ with ······

(prettier/prettier)


[error] 222-222: Delete ······

(prettier/prettier)


[error] 223-223: Replace ············ with ······

(prettier/prettier)


[error] 224-224: Replace ············ with ······

(prettier/prettier)


[error] 225-225: Replace ············viewportRect.set(-viewportPivot.x·*·viewportSize.x,·-viewportPivot.y·*·viewportSize.y,·viewportSize.x,·viewportSize.y); with ······viewportRect.set(⏎········-viewportPivot.x·*·viewportSize.x,⏎········-viewportPivot.y·*·viewportSize.y,⏎········viewportSize.x,⏎········viewportSize.y

(prettier/prettier)


[error] 226-226: Insert );⏎

(prettier/prettier)


[error] 227-227: Delete ······

(prettier/prettier)


[error] 228-228: Delete ······

(prettier/prettier)


[error] 229-229: Replace ············ with ······

(prettier/prettier)


[error] 230-230: Delete ······

(prettier/prettier)


[error] 231-231: Delete ······

(prettier/prettier)


[error] 232-232: Replace ················ with ········

(prettier/prettier)


[error] 233-233: Replace ················ with ········

(prettier/prettier)


[error] 234-234: Replace ················ with ········

(prettier/prettier)


[error] 235-235: Replace ················ with ········

(prettier/prettier)


[error] 236-236: Replace ············ with ······

(prettier/prettier)


[error] 237-237: Delete ····

(prettier/prettier)


[error] 238-238: Delete ··

(prettier/prettier)


[error] 240-240: Delete ··

(prettier/prettier)


[error] 241-241: Delete ····

(prettier/prettier)


[error] 242-242: Delete ····

(prettier/prettier)


[error] 243-243: Replace ···· with ··

(prettier/prettier)


[error] 247-247: Delete ··

(prettier/prettier)


[error] 248-248: Delete ··

(prettier/prettier)


[error] 249-249: Delete ··

(prettier/prettier)


[error] 253-253: Delete ··

(prettier/prettier)


[error] 254-254: Delete ··

(prettier/prettier)


[error] 258-258: Delete ··

(prettier/prettier)


[error] 259-259: Replace ···· with ··

(prettier/prettier)


[error] 260-260: Delete ··

(prettier/prettier)

🪛 GitHub Check: lint

[failure] 15-15:
Delete ··


[failure] 14-14:
Delete ··


[failure] 13-13:
Replace ···· with ··


[failure] 12-12:
Delete ··


[failure] 11-11:
Replace ···· with ··


[failure] 10-10:
Delete ··


[failure] 9-9:
Delete ··

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

In `@packages/ui/src/component/advanced/ScrollView.ts` around lines 9 - 260, This
file has many Prettier/lint formatting violations; run the project's formatter
and linter auto-fix (e.g. prettier --write and eslint --fix or your repo's
format script) on packages/ui/src/component/advanced/ScrollView.ts to normalize
indentation, semicolons, spacing and line breaks; ensure you keep existing logic
intact in getters/setters (mode, viewport, content), methods
onPointerBeginDrag/onPointerDrag/onPointerEndDrag, _computeScrolling,
_applyScrolling, _updateRect and onUpdate/onDestroy while committing the
formatted result so CI lint/prettier checks pass.

Comment on lines +71 to +80
addOnScroll(listener: (percentH: number, percentV: number) => void): void {
this._listeners.push({ fn: listener });
}

/**
* 移除滑动回调
* @param listener - The listening function
*/
removeOnScroll(listener: (val: number) => void): void {
this._listeners.findAndRemove((value) => (value.fn === listener ? (value.destroyed = true) : false));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Align addOnScroll and removeOnScroll listener signatures.

Line 79 uses a different callback shape than Line 71, which makes removing the same listener reference brittle.

Proposed fix
-  addOnScroll(listener: (percentH: number, percentV: number) => void): void {
+  addOnScroll(listener: IScrollListener["fn"]): void {
     this._listeners.push({ fn: listener });
   }

-  removeOnScroll(listener: (val: number) => void): void {
+  removeOnScroll(listener: IScrollListener["fn"]): void {
     this._listeners.findAndRemove((value) => (value.fn === listener ? (value.destroyed = true) : false));
   }
🧰 Tools
🪛 ESLint

[error] 71-71: Delete ··

(prettier/prettier)


[error] 72-72: Replace ········ with ····

(prettier/prettier)


[error] 73-73: Delete ··

(prettier/prettier)


[error] 75-75: Delete ··

(prettier/prettier)


[error] 76-76: Delete ··

(prettier/prettier)


[error] 77-77: Delete ··

(prettier/prettier)


[error] 78-78: Delete ··

(prettier/prettier)


[error] 79-79: Delete ··

(prettier/prettier)


[error] 80-80: Delete ····

(prettier/prettier)

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

In `@packages/ui/src/component/advanced/ScrollView.ts` around lines 71 - 80, The
removeOnScroll signature must match addOnScroll: change removeOnScroll to accept
the same listener type (listener: (percentH: number, percentV: number) => void)
so the same function reference can be found and removed; keep the removal logic
that locates entries in this._listeners (the predicate comparing value.fn ===
listener and marking value.destroyed) but update the method signature and any
related typings to use the two-parameter (percentH, percentV) callback type
referenced in addOnScroll and the stored listener objects.

Comment on lines +83 to +89
override onPointerBeginDrag(eventData: PointerEventData): void {
this._isDragging = true;
const { _viewportTransform: viewportTransform } = this;
if (!viewportTransform || !this._contentTransform) return;
// 初始化拖动的起始点
Utils.screenToLocalPoint(eventData.pointer.position, viewportTransform, this._startPoint);
this._fromPoint.copyFrom(this._startPoint);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle failed screen-to-local conversion during drag.

Lines 88 and 97 ignore the boolean return from Utils.screenToLocalPoint(). On failure, stale points are used for scrolling.

Proposed fix
   override onPointerBeginDrag(eventData: PointerEventData): void {
     this._isDragging = true;
     const { _viewportTransform: viewportTransform } = this;
     if (!viewportTransform || !this._contentTransform) return;
-    Utils.screenToLocalPoint(eventData.pointer.position, viewportTransform, this._startPoint);
+    if (!Utils.screenToLocalPoint(eventData.pointer.position, viewportTransform, this._startPoint)) {
+      this._isDragging = false;
+      return;
+    }
     this._fromPoint.copyFrom(this._startPoint);
   }

   override onPointerDrag(eventData: PointerEventData): void {
     const { _viewportTransform: viewportTransform, _contentTransform: contentTransform } = this;
     if (!viewportTransform || !contentTransform) return;
     const { _fromPoint: fromPoint, _toPoint: toPoint } = this;
-    Utils.screenToLocalPoint(eventData.pointer.position, viewportTransform, toPoint);
+    if (!Utils.screenToLocalPoint(eventData.pointer.position, viewportTransform, toPoint)) return;
     this._updateRect();
     this._computeScrolling(fromPoint, toPoint);
     fromPoint.copyFrom(toPoint)
   }

Also applies to: 92-97

🧰 Tools
🪛 ESLint

[error] 83-83: Delete ··

(prettier/prettier)


[error] 84-84: Delete ····

(prettier/prettier)


[error] 85-85: Replace ········ with ····

(prettier/prettier)


[error] 86-86: Replace ········ with ····

(prettier/prettier)


[error] 87-87: Replace ········ with ····

(prettier/prettier)


[error] 88-88: Delete ····

(prettier/prettier)


[error] 89-89: Replace ········ with ····

(prettier/prettier)

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

In `@packages/ui/src/component/advanced/ScrollView.ts` around lines 83 - 89, The
onPointerBeginDrag method currently calls
Utils.screenToLocalPoint(eventData.pointer.position, viewportTransform,
this._startPoint) and ignores its boolean return, which can leave
_startPoint/_fromPoint stale when conversion fails; update onPointerBeginDrag
(and the similar pointer-move/drag handlers that call Utils.screenToLocalPoint)
to check the returned boolean and abort the drag setup (return early, do not set
_isDragging or copy to _fromPoint) if the conversion fails, and ensure you only
use this._startPoint and this._fromPoint after a successful conversion with
valid this._viewportTransform and this._contentTransform.

Comment on lines +153 to +160
private _applyScrolling(dx: number, dy: number): void {
const contentPosition = this._contentTransform.position;
const { _contentRect: contentRect, _viewportRect: viewportRect } = this;
// 计算是否碰到边缘
contentPosition.set(contentPosition.x + dx, contentPosition.y + dy, contentPosition.z);
const percentH = (viewportRect.x - contentRect.x) / (contentRect.width - viewportRect.width);
const percentV = (contentRect.y + contentRect.height - viewportRect.y - viewportRect.height) / (contentRect.height - viewportRect.height)
const listeners = this._listeners.getLoopArray();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix percent computation to use post-move rect and avoid NaN/Infinity.

Line 157 updates position, but Lines 158-159 still calculate with old rect values; also ranges can be 0/negative when content size is not larger than viewport.

Proposed fix
   private _applyScrolling(dx: number, dy: number): void {
     const contentPosition = this._contentTransform.position;
     const { _contentRect: contentRect, _viewportRect: viewportRect } = this;
-    contentPosition.set(contentPosition.x + dx, contentPosition.y + dy, contentPosition.z);
-    const percentH = (viewportRect.x - contentRect.x) / (contentRect.width - viewportRect.width);
-    const percentV = (contentRect.y + contentRect.height - viewportRect.y - viewportRect.height) / (contentRect.height - viewportRect.height)
+    contentPosition.set(contentPosition.x + dx, contentPosition.y + dy, contentPosition.z);
+    contentRect.x += dx;
+    contentRect.y += dy;
+
+    const horizontalRange = contentRect.width - viewportRect.width;
+    const verticalRange = contentRect.height - viewportRect.height;
+    const percentH =
+      horizontalRange > 0 ? Math.min(1, Math.max(0, (viewportRect.x - contentRect.x) / horizontalRange)) : 0;
+    const percentV =
+      verticalRange > 0
+        ? Math.min(
+            1,
+            Math.max(
+              0,
+              (contentRect.y + contentRect.height - viewportRect.y - viewportRect.height) / verticalRange
+            )
+          )
+        : 0;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private _applyScrolling(dx: number, dy: number): void {
const contentPosition = this._contentTransform.position;
const { _contentRect: contentRect, _viewportRect: viewportRect } = this;
// 计算是否碰到边缘
contentPosition.set(contentPosition.x + dx, contentPosition.y + dy, contentPosition.z);
const percentH = (viewportRect.x - contentRect.x) / (contentRect.width - viewportRect.width);
const percentV = (contentRect.y + contentRect.height - viewportRect.y - viewportRect.height) / (contentRect.height - viewportRect.height)
const listeners = this._listeners.getLoopArray();
private _applyScrolling(dx: number, dy: number): void {
const contentPosition = this._contentTransform.position;
const { _contentRect: contentRect, _viewportRect: viewportRect } = this;
// 计算是否碰到边缘
contentPosition.set(contentPosition.x + dx, contentPosition.y + dy, contentPosition.z);
contentRect.x += dx;
contentRect.y += dy;
const horizontalRange = contentRect.width - viewportRect.width;
const verticalRange = contentRect.height - viewportRect.height;
const percentH =
horizontalRange > 0 ? Math.min(1, Math.max(0, (viewportRect.x - contentRect.x) / horizontalRange)) : 0;
const percentV =
verticalRange > 0
? Math.min(
1,
Math.max(
0,
(contentRect.y + contentRect.height - viewportRect.y - viewportRect.height) / verticalRange
)
)
: 0;
const listeners = this._listeners.getLoopArray();
🧰 Tools
🪛 ESLint

[error] 153-153: Delete ··

(prettier/prettier)


[error] 154-154: Replace ········ with ····

(prettier/prettier)


[error] 155-155: Delete ····

(prettier/prettier)


[error] 156-156: Replace ········ with ····

(prettier/prettier)


[error] 157-157: Delete ····

(prettier/prettier)


[error] 158-158: Replace ········ with ····

(prettier/prettier)


[error] 159-159: Replace ····const·percentV·=·(contentRect.y·+·contentRect.height·-·viewportRect.y·-·viewportRect.height)·/·(contentRect.height·-·viewportRect.height) with const·percentV·=⏎······(contentRect.y·+·contentRect.height·-·viewportRect.y·-·viewportRect.height)·/⏎······(contentRect.height·-·viewportRect.height);

(prettier/prettier)


[error] 160-160: Delete ····

(prettier/prettier)

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

In `@packages/ui/src/component/advanced/ScrollView.ts` around lines 153 - 160, In
_applyScrolling, percentH/percentV are computed using stale rects and can
produce NaN/Infinity when content size ≤ viewport; after updating
this._contentTransform.position (contentPosition) recompute or derive the
updated content rect (use the post-move values of _contentRect or calculate from
contentPosition) and then compute percentH and percentV using those post-move
rects; guard the denominators by checking (contentRect.width -
viewportRect.width) and (contentRect.height - viewportRect.height) > 0 before
dividing (or use a clamp/fallback to 0), and finally clamp percentH/percentV
into [0,1] before using _listeners.getLoopArray() so you never pass NaN/Infinity
to listeners.

this.shaderData.setFloat(UIRenderer._rectClipEnabledProperty, 0);
}
const rectMaskSoftness = this._rectMaskSoftness;
if (rectMaskSoftness.x !== 0 || rectMaskSoftness.y !== 0 || rectMaskSoftness.z !== 0 || rectMaskSoftness.w !== 0) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix the two Prettier violations that are currently failing lint.

The long rectMaskSoftness zero-check expressions need to match formatter output to unblock CI.

🔧 Suggested formatting patch
-      if (rectMaskSoftness.x !== 0 || rectMaskSoftness.y !== 0 || rectMaskSoftness.z !== 0 || rectMaskSoftness.w !== 0) {
+      if (
+        rectMaskSoftness.x !== 0 ||
+        rectMaskSoftness.y !== 0 ||
+        rectMaskSoftness.z !== 0 ||
+        rectMaskSoftness.w !== 0
+      ) {
         rectMaskSoftness.set(0, 0, 0, 0);
         this.shaderData.setVector4(UIRenderer._rectClipSoftnessProperty, rectMaskSoftness);
       }
@@
-      if (rectMaskSoftness.x !== 0 || rectMaskSoftness.y !== 0 || rectMaskSoftness.z !== 0 || rectMaskSoftness.w !== 0) {
+      if (
+        rectMaskSoftness.x !== 0 ||
+        rectMaskSoftness.y !== 0 ||
+        rectMaskSoftness.z !== 0 ||
+        rectMaskSoftness.w !== 0
+      ) {
         rectMaskSoftness.set(0, 0, 0, 0);
         this.shaderData.setVector4(UIRenderer._rectClipSoftnessProperty, rectMaskSoftness);
       }

Also applies to: 449-449

🧰 Tools
🪛 ESLint

[error] 386-386: Replace rectMaskSoftness.x·!==·0·||·rectMaskSoftness.y·!==·0·||·rectMaskSoftness.z·!==·0·||·rectMaskSoftness.w·!==·0 with ⏎········rectMaskSoftness.x·!==·0·||⏎········rectMaskSoftness.y·!==·0·||⏎········rectMaskSoftness.z·!==·0·||⏎········rectMaskSoftness.w·!==·0⏎······

(prettier/prettier)

🪛 GitHub Check: lint

[failure] 386-386:
Replace rectMaskSoftness.x·!==·0·||·rectMaskSoftness.y·!==·0·||·rectMaskSoftness.z·!==·0·||·rectMaskSoftness.w·!==·0 with ⏎········rectMaskSoftness.x·!==·0·||⏎········rectMaskSoftness.y·!==·0·||⏎········rectMaskSoftness.z·!==·0·||⏎········rectMaskSoftness.w·!==·0⏎······

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

In `@packages/ui/src/component/UIRenderer.ts` at line 386, The long conditional
checking rectMaskSoftness should be reformatted to match Prettier by breaking
the OR comparisons onto separate lines inside a parenthesized if-condition;
update the occurrence that uses rectMaskSoftness.x/y/z/w so each comparison
(rectMaskSoftness.x !== 0 || ...) is on its own line and the closing
parenthesis/brace are placed as Prettier expects, and make the same change for
the second identical occurrence in the file so both lint violations are
resolved.

Comment on lines +167 to +177
while (entity) {
// @ts-ignore
const components = entity._components;
for (let i = 0, n = components.length; i < n; i++) {
const component = components[i];
if (component.enabled && component instanceof UICanvas && component._isRootCanvas) {
rootCanvas = component;
}
}
entity = entity.parent;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Root-canvas selection currently prefers the farthest ancestor.

Line [167]-Line [177] keeps traversing even after a match, so later ancestors overwrite rootCanvas. This diverges from searchRootCanvasInParents (Line [30]-Line [44], first match wins) and can produce wrong render-mode/camera selection in nested-canvas hierarchies.

Proposed change
     while (entity) {
       // `@ts-ignore`
       const components = entity._components;
       for (let i = 0, n = components.length; i < n; i++) {
         const component = components[i];
         if (component.enabled && component instanceof UICanvas && component._isRootCanvas) {
           rootCanvas = component;
+          break;
         }
       }
+      if (rootCanvas) break;
       entity = entity.parent;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
while (entity) {
// @ts-ignore
const components = entity._components;
for (let i = 0, n = components.length; i < n; i++) {
const component = components[i];
if (component.enabled && component instanceof UICanvas && component._isRootCanvas) {
rootCanvas = component;
}
}
entity = entity.parent;
}
while (entity) {
// `@ts-ignore`
const components = entity._components;
for (let i = 0, n = components.length; i < n; i++) {
const component = components[i];
if (component.enabled && component instanceof UICanvas && component._isRootCanvas) {
rootCanvas = component;
break;
}
}
if (rootCanvas) break;
entity = entity.parent;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/Utils.ts` around lines 167 - 177, The loop in Utils.ts that
walks parent entities overwrites rootCanvas by continuing traversal after a
match; change the logic in the while(entity) loop so it stops when the first
matching UICanvas with _isRootCanvas is found (mirroring
searchRootCanvasInParents). Specifically, inside the inner for-loop when you
detect component.enabled && component instanceof UICanvas &&
component._isRootCanvas, set rootCanvas and immediately stop further processing
of components and parent traversal (either by returning the found canvas,
breaking both loops via a flag, or otherwise exiting the function), ensuring the
first (closest) match is returned.

@@ -0,0 +1,147 @@
import { PointerEventData, Script, Sprite, SpriteMaskInteraction, SpriteMaskLayer, Texture2D } from "@galacean/engine-core";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Prettier import formatting issue at file header.

Line 1 is currently violating the prettier rule reported by ESLint.

🎯 Formatting patch
-import { PointerEventData, Script, Sprite, SpriteMaskInteraction, SpriteMaskLayer, Texture2D } from "@galacean/engine-core";
+import {
+  PointerEventData,
+  Script,
+  Sprite,
+  SpriteMaskInteraction,
+  SpriteMaskLayer,
+  Texture2D
+} from "@galacean/engine-core";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { PointerEventData, Script, Sprite, SpriteMaskInteraction, SpriteMaskLayer, Texture2D } from "@galacean/engine-core";
import {
PointerEventData,
Script,
Sprite,
SpriteMaskInteraction,
SpriteMaskLayer,
Texture2D
} from "@galacean/engine-core";
🧰 Tools
🪛 ESLint

[error] 1-1: Replace ·PointerEventData,·Script,·Sprite,·SpriteMaskInteraction,·SpriteMaskLayer,·Texture2D· with ⏎··PointerEventData,⏎··Script,⏎··Sprite,⏎··SpriteMaskInteraction,⏎··SpriteMaskLayer,⏎··Texture2D⏎

(prettier/prettier)

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

In `@tests/src/ui/Mask.test.ts` at line 1, The single-line import of multiple
symbols (PointerEventData, Script, Sprite, SpriteMaskInteraction,
SpriteMaskLayer, Texture2D) at the top of Mask.test.ts violates Prettier;
reformat the import from "@galacean/engine-core" so it conforms to project
Prettier rules (either split named imports across lines or run the project's
Prettier formatter/ESLint autofix) and ensure the named imports are
ordered/wrapped as configured so the file passes linting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant