Add UI Mask && Clean up Mask code#2912
Add UI Mask && Clean up Mask code#2912cptbtptpbcptdtptp wants to merge 8 commits intogalacean:dev/2.0from
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughAdds 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 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 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 |
Codecov Report❌ Patch coverage is 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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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 | 🟡 MinorDrops additional
RectMask2Dcomponents when multiple exist on the same entity.Line 531 stores a single
rectMaskvariable. 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 nodisallowMultipleconstraint, 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. Preferprivate 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 primitivebooleaninstead of wrapperBoolean.The return type on line 162 should use
boolean;Booleanis 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_subChunkcleanup aftersuper._onDestroy().
super._onDestroy()(fromUIRenderer) 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 <= 0and!hasActiveMaskbranches 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
⛔ Files ignored due to path filters (4)
packages/core/src/shaderlib/extra/text.fs.glslis excluded by!**/*.glslpackages/core/src/shaderlib/extra/text.vs.glslis excluded by!**/*.glslpackages/ui/src/shader/uiDefault.fs.glslis excluded by!**/*.glslpackages/ui/src/shader/uiDefault.vs.glslis excluded by!**/*.glsl
📒 Files selected for processing (19)
examples/src/ui-mask.tspackages/core/src/2d/sprite/MaskRenderable.tspackages/core/src/2d/sprite/SpriteMask.tspackages/core/src/2d/sprite/SpriteMaskUtils.tspackages/core/src/2d/sprite/index.tspackages/core/src/RenderPipeline/BatchUtils.tspackages/core/src/RenderPipeline/MaskManager.tspackages/core/src/RenderPipeline/index.tspackages/core/src/Renderer.tspackages/ui/src/Utils.tspackages/ui/src/component/UICanvas.tspackages/ui/src/component/UIRenderer.tspackages/ui/src/component/advanced/Mask.tspackages/ui/src/component/advanced/RectMask2D.tspackages/ui/src/component/advanced/ScrollView.tspackages/ui/src/component/advanced/Text.tspackages/ui/src/component/index.tstests/src/ui/Mask.test.tstests/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)); |
There was a problem hiding this comment.
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.
| _cloneMaskData(target: MaskRenderableBase): void { | ||
| target.sprite = this._sprite; | ||
| } |
There was a problem hiding this comment.
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.
| _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"; | ||
|
|
There was a problem hiding this comment.
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.
| 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.
| // 视口 | ||
| 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 |
There was a problem hiding this comment.
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.
| 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)); |
There was a problem hiding this comment.
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.
| 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); |
There was a problem hiding this comment.
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.
| 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(); |
There was a problem hiding this comment.
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.
| 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) { |
There was a problem hiding this comment.
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.
| 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; | ||
| } |
There was a problem hiding this comment.
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.
| 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"; | |||
There was a problem hiding this comment.
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.
| 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.
Please check if the PR fulfills these requirements
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
Tests