Files
esengine/packages/ui/src/systems/render/UIRectRenderSystem.ts
YHH 107439d70c Feature/runtime cdn and plugin loader (#240)
* feat(ui): 完善 UI 布局系统和编辑器可视化工具

* refactor: 移除 ModuleRegistry,统一使用 PluginManager 插件系统

* fix: 修复 CodeQL 警告并提升测试覆盖率

* refactor: 分离运行时入口点,解决 runtime bundle 包含 React 的问题

* fix(ci): 添加 editor-core 和 editor-runtime 到 CI 依赖构建步骤

* docs: 完善 ServiceContainer 文档,新增 Symbol.for 模式和 @InjectProperty 说明

* fix(ci): 修复 type-check 失败问题

* fix(ci): 修复类型检查失败问题

* fix(ci): 修复类型检查失败问题

* fix(ci): behavior-tree 构建添加 @tauri-apps 外部依赖

* fix(ci): behavior-tree 添加 @tauri-apps/plugin-fs 类型依赖

* fix(ci): platform-web 添加缺失的 behavior-tree 依赖

* fix(lint): 移除正则表达式中不必要的转义字符
2025-11-27 20:42:46 +08:00

194 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* UI Rect Render System
* UI 矩形渲染系统
*
* Renders basic UIRenderComponent entities (those without specialized widget components)
* by submitting render primitives to the shared UIRenderCollector.
* 通过向共享的 UIRenderCollector 提交渲染原语来渲染基础 UIRenderComponent 实体
* (没有专门 widget 组件的实体)。
*/
import { EntitySystem, Matcher, Entity, ECSSystem } from '@esengine/ecs-framework';
import { UITransformComponent } from '../../components/UITransformComponent';
import { UIRenderComponent } from '../../components/UIRenderComponent';
import { UIButtonComponent } from '../../components/widgets/UIButtonComponent';
import { UIProgressBarComponent } from '../../components/widgets/UIProgressBarComponent';
import { UISliderComponent } from '../../components/widgets/UISliderComponent';
import { UIScrollViewComponent } from '../../components/widgets/UIScrollViewComponent';
import { getUIRenderCollector } from './UIRenderCollector';
/**
* UI Rect Render System
* UI 矩形渲染系统
*
* Handles rendering of basic UI elements with UIRenderComponent that don't have
* specialized widget components (like buttons, progress bars, etc.).
*
* This is the "catch-all" renderer for simple rectangles, images, and panels.
*
* 处理具有 UIRenderComponent 但没有专门 widget 组件(如按钮、进度条等)的基础 UI 元素的渲染。
* 这是简单矩形、图像和面板的"兜底"渲染器。
*/
@ECSSystem('UIRectRender', { updateOrder: 100 })
export class UIRectRenderSystem extends EntitySystem {
constructor() {
super(Matcher.empty().all(UITransformComponent, UIRenderComponent));
}
protected process(entities: readonly Entity[]): void {
const collector = getUIRenderCollector();
for (const entity of entities) {
// Skip if entity has specialized widget components
// (they have their own render systems)
// 如果实体有专门的 widget 组件,跳过(它们有自己的渲染系统)
if (entity.hasComponent(UIButtonComponent) ||
entity.hasComponent(UIProgressBarComponent) ||
entity.hasComponent(UISliderComponent) ||
entity.hasComponent(UIScrollViewComponent)) {
continue;
}
const transform = entity.getComponent(UITransformComponent)!;
const render = entity.getComponent(UIRenderComponent)!;
if (!transform.visible) continue;
const x = transform.worldX ?? transform.x;
const y = transform.worldY ?? transform.y;
const width = (transform.computedWidth ?? transform.width) * transform.scaleX;
const height = (transform.computedHeight ?? transform.height) * transform.scaleY;
const alpha = transform.worldAlpha ?? transform.alpha;
const baseOrder = 100 + transform.zIndex;
// Use top-left position with origin at (0, 0)
// Like Sprite: x,y is anchor position, origin determines where anchor is on the rect
// For UI: x,y is top-left corner, so origin should be (0, 0)
// 使用左上角位置,原点在 (0, 0)
// 类似 Spritex,y 是锚点位置origin 决定锚点在矩形上的位置
// 对于 UIx,y 是左上角,所以 origin 应该是 (0, 0)
// Render shadow if enabled
// 如果启用,渲染阴影
if (render.shadowEnabled && render.shadowAlpha > 0) {
collector.addRect(
x + render.shadowOffsetX - render.shadowBlur,
y + render.shadowOffsetY - render.shadowBlur,
width + render.shadowBlur * 2,
height + render.shadowBlur * 2,
render.shadowColor,
render.shadowAlpha * alpha,
baseOrder - 0.1,
{
rotation: transform.rotation,
pivotX: 0,
pivotY: 0
}
);
}
// Render texture if present
// 如果有纹理,渲染纹理
if (render.texture) {
const texturePath = typeof render.texture === 'string' ? render.texture : undefined;
const textureId = typeof render.texture === 'number' ? render.texture : undefined;
collector.addRect(
x, y,
width, height,
render.textureTint,
alpha,
baseOrder,
{
rotation: transform.rotation,
pivotX: 0,
pivotY: 0,
textureId,
texturePath,
uv: render.textureUV
? [render.textureUV.u0, render.textureUV.v0, render.textureUV.u1, render.textureUV.v1]
: undefined
}
);
}
// Render background color if fill is enabled
// 如果启用填充,渲染背景颜色
else if (render.fillBackground && render.backgroundAlpha > 0) {
collector.addRect(
x, y,
width, height,
render.backgroundColor,
render.backgroundAlpha * alpha,
baseOrder,
{
rotation: transform.rotation,
pivotX: 0,
pivotY: 0
}
);
}
// Render border if present
// 如果有边框,渲染边框
if (render.borderWidth > 0 && render.borderAlpha > 0) {
this.renderBorder(
collector,
x, y, width, height,
render.borderWidth,
render.borderColor,
render.borderAlpha * alpha,
baseOrder + 0.1,
transform.rotation
);
}
}
}
/**
* Render border using top-left coordinates
* 使用左上角坐标渲染边框
*/
private renderBorder(
collector: ReturnType<typeof getUIRenderCollector>,
x: number, y: number,
width: number, height: number,
borderWidth: number,
borderColor: number,
alpha: number,
sortOrder: number,
rotation: number
): void {
// Top border (from top-left corner)
collector.addRect(
x, y,
width, borderWidth,
borderColor, alpha, sortOrder,
{ rotation, pivotX: 0, pivotY: 0 }
);
// Bottom border
collector.addRect(
x, y + height - borderWidth,
width, borderWidth,
borderColor, alpha, sortOrder,
{ rotation, pivotX: 0, pivotY: 0 }
);
// Left border (excluding corners)
collector.addRect(
x, y + borderWidth,
borderWidth, height - borderWidth * 2,
borderColor, alpha, sortOrder,
{ rotation, pivotX: 0, pivotY: 0 }
);
// Right border (excluding corners)
collector.addRect(
x + width - borderWidth, y + borderWidth,
borderWidth, height - borderWidth * 2,
borderColor, alpha, sortOrder,
{ rotation, pivotX: 0, pivotY: 0 }
);
}
}