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): 移除正则表达式中不必要的转义字符
This commit is contained in:
@@ -8,32 +8,38 @@ import { UILayoutComponent, UILayoutType, UIJustifyContent, UIAlignItems } from
|
||||
*
|
||||
* 计算 UI 元素的世界坐标和尺寸
|
||||
* Computes world coordinates and sizes for UI elements
|
||||
*
|
||||
* 注意:canvasWidth/canvasHeight 是 UI 设计的参考尺寸,不是实际渲染视口大小
|
||||
* Note: canvasWidth/canvasHeight is the UI design reference size, not the actual render viewport size
|
||||
*/
|
||||
@ECSSystem('UILayout')
|
||||
export class UILayoutSystem extends EntitySystem {
|
||||
/**
|
||||
* 视口宽度
|
||||
* Viewport width
|
||||
* UI 画布宽度(设计尺寸)
|
||||
* UI Canvas width (design size)
|
||||
*/
|
||||
public viewportWidth: number = 1920;
|
||||
public canvasWidth: number = 1920;
|
||||
|
||||
/**
|
||||
* 视口高度
|
||||
* Viewport height
|
||||
* UI 画布高度(设计尺寸)
|
||||
* UI Canvas height (design size)
|
||||
*/
|
||||
public viewportHeight: number = 1080;
|
||||
public canvasHeight: number = 1080;
|
||||
|
||||
constructor() {
|
||||
super(Matcher.empty().all(UITransformComponent));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置视口尺寸
|
||||
* Set viewport size
|
||||
* 设置 UI 画布尺寸(设计尺寸)
|
||||
* Set UI canvas size (design size)
|
||||
*
|
||||
* 这是 UI 布局计算的参考尺寸,通常是固定的设计分辨率(如 1920x1080)
|
||||
* This is the reference size for UI layout calculation, usually a fixed design resolution (e.g., 1920x1080)
|
||||
*/
|
||||
public setViewport(width: number, height: number): void {
|
||||
this.viewportWidth = width;
|
||||
this.viewportHeight = height;
|
||||
public setCanvasSize(width: number, height: number): void {
|
||||
this.canvasWidth = width;
|
||||
this.canvasHeight = height;
|
||||
|
||||
// 标记所有元素需要重新布局
|
||||
for (const entity of this.entities) {
|
||||
@@ -44,12 +50,27 @@ export class UILayoutSystem extends EntitySystem {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 UI 画布尺寸
|
||||
* Get UI canvas size
|
||||
*/
|
||||
public getCanvasSize(): { width: number; height: number } {
|
||||
return { width: this.canvasWidth, height: this.canvasHeight };
|
||||
}
|
||||
|
||||
protected process(entities: readonly Entity[]): void {
|
||||
// 首先处理根元素(没有父元素的)
|
||||
const rootEntities = entities.filter(e => !e.parent || !e.parent.hasComponent(UITransformComponent));
|
||||
|
||||
// 画布中心为原点,Y 轴向上为正
|
||||
// Canvas center is origin, Y axis points up
|
||||
// 左上角是 (-width/2, +height/2),右下角是 (+width/2, -height/2)
|
||||
// Top-left is (-width/2, +height/2), bottom-right is (+width/2, -height/2)
|
||||
const parentX = -this.canvasWidth / 2;
|
||||
const parentY = this.canvasHeight / 2; // Y 轴向上,所以顶部是正值
|
||||
|
||||
for (const entity of rootEntities) {
|
||||
this.layoutEntity(entity, 0, 0, this.viewportWidth, this.viewportHeight, 1);
|
||||
this.layoutEntity(entity, parentX, parentY, this.canvasWidth, this.canvasHeight, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,10 +90,16 @@ export class UILayoutSystem extends EntitySystem {
|
||||
if (!transform) return;
|
||||
|
||||
// 计算锚点位置
|
||||
// X 轴:向右为正,anchorMinX=0 是左边,anchorMinX=1 是右边
|
||||
// Y 轴:向上为正,anchorMinY=0 是顶部,anchorMinY=1 是底部
|
||||
// X axis: right is positive, anchorMinX=0 is left, anchorMinX=1 is right
|
||||
// Y axis: up is positive, anchorMinY=0 is top, anchorMinY=1 is bottom
|
||||
const anchorMinX = parentX + parentWidth * transform.anchorMinX;
|
||||
const anchorMinY = parentY + parentHeight * transform.anchorMinY;
|
||||
const anchorMaxX = parentX + parentWidth * transform.anchorMaxX;
|
||||
const anchorMaxY = parentY + parentHeight * transform.anchorMaxY;
|
||||
// Y 轴反转:parentY 是顶部(正值),向下减少
|
||||
// Y axis inverted: parentY is top (positive), decreases downward
|
||||
const anchorMinY = parentY - parentHeight * transform.anchorMinY;
|
||||
const anchorMaxY = parentY - parentHeight * transform.anchorMaxY;
|
||||
|
||||
// 计算元素尺寸
|
||||
let width: number;
|
||||
@@ -89,7 +116,9 @@ export class UILayoutSystem extends EntitySystem {
|
||||
if (transform.anchorMinY === transform.anchorMaxY) {
|
||||
height = transform.height;
|
||||
} else {
|
||||
height = anchorMaxY - anchorMinY - transform.y;
|
||||
// 拉伸模式:Y 轴反转,anchorMinY > anchorMaxY
|
||||
// Stretch mode: Y axis inverted, anchorMinY > anchorMaxY
|
||||
height = anchorMinY - anchorMaxY - transform.y;
|
||||
}
|
||||
|
||||
// 应用尺寸约束
|
||||
@@ -98,12 +127,15 @@ export class UILayoutSystem extends EntitySystem {
|
||||
if (transform.minHeight > 0) height = Math.max(height, transform.minHeight);
|
||||
if (transform.maxHeight > 0) height = Math.min(height, transform.maxHeight);
|
||||
|
||||
// 计算世界位置
|
||||
// 计算世界位置(左下角,与 Gizmo origin=(0,0) 对应)
|
||||
// Calculate world position (bottom-left corner, matching Gizmo origin=(0,0))
|
||||
let worldX: number;
|
||||
let worldY: number;
|
||||
|
||||
if (transform.anchorMinX === transform.anchorMaxX) {
|
||||
// 固定锚点模式
|
||||
// anchor 位置 + position 偏移 - pivot 偏移
|
||||
// 结果是矩形左边缘的 X 坐标
|
||||
worldX = anchorMinX + transform.x - width * transform.pivotX;
|
||||
} else {
|
||||
// 拉伸模式
|
||||
@@ -111,9 +143,21 @@ export class UILayoutSystem extends EntitySystem {
|
||||
}
|
||||
|
||||
if (transform.anchorMinY === transform.anchorMaxY) {
|
||||
worldY = anchorMinY + transform.y - height * transform.pivotY;
|
||||
// 固定锚点模式:Y 轴向上
|
||||
// Fixed anchor mode: Y axis up
|
||||
// anchorMinY 是锚点 Y 位置(anchor=0 在顶部,Y=+540)
|
||||
// position.y 是从锚点的偏移(正值向上)
|
||||
// pivot 决定元素哪个点对齐到 (anchor + position)
|
||||
// worldY 是元素底部的 Y 坐标(与 Gizmo origin=(0,0) 对应)
|
||||
// pivotY=0 意味着元素顶部对齐,pivotY=1 意味着元素底部对齐
|
||||
const anchorPosY = anchorMinY + transform.y; // anchor 位置 + 偏移
|
||||
// pivotY=0: 顶部对齐,底部 = anchorPos - height
|
||||
// pivotY=0.5: 中心对齐,底部 = anchorPos - height/2
|
||||
// pivotY=1: 底部对齐,底部 = anchorPos
|
||||
worldY = anchorPosY - height * (1 - transform.pivotY);
|
||||
} else {
|
||||
worldY = anchorMinY + transform.y;
|
||||
// 拉伸模式:worldY 是底部
|
||||
worldY = anchorMaxY - transform.y;
|
||||
}
|
||||
|
||||
// 更新计算后的值
|
||||
@@ -131,6 +175,10 @@ export class UILayoutSystem extends EntitySystem {
|
||||
const children = entity.children.filter(c => c.hasComponent(UITransformComponent));
|
||||
if (children.length === 0) return;
|
||||
|
||||
// 计算子元素的父容器边界
|
||||
// 子元素的 parentY 应该是当前元素的顶部 Y 坐标(worldY 是底部,顶部 = 底部 + 高度)
|
||||
const childParentY = worldY + height;
|
||||
|
||||
// 检查是否有布局组件
|
||||
const layout = entity.getComponent(UILayoutComponent);
|
||||
if (layout && layout.type !== UILayoutType.None) {
|
||||
@@ -141,7 +189,7 @@ export class UILayoutSystem extends EntitySystem {
|
||||
this.layoutEntity(
|
||||
child,
|
||||
worldX,
|
||||
worldY,
|
||||
childParentY,
|
||||
width,
|
||||
height,
|
||||
transform.worldAlpha
|
||||
@@ -160,7 +208,10 @@ export class UILayoutSystem extends EntitySystem {
|
||||
children: Entity[]
|
||||
): void {
|
||||
const contentStartX = parentTransform.worldX + layout.paddingLeft;
|
||||
const contentStartY = parentTransform.worldY + layout.paddingTop;
|
||||
// Y-up 系统:worldY 是底部,顶部 = worldY + height
|
||||
// contentStartY 是内容区域的顶部 Y(从顶部减去 paddingTop)
|
||||
const parentTopY = parentTransform.worldY + parentTransform.computedHeight;
|
||||
const contentStartY = parentTopY - layout.paddingTop;
|
||||
const contentWidth = parentTransform.computedWidth - layout.getHorizontalPadding();
|
||||
const contentHeight = parentTransform.computedHeight - layout.getVerticalPadding();
|
||||
|
||||
@@ -175,12 +226,12 @@ export class UILayoutSystem extends EntitySystem {
|
||||
this.layoutGrid(layout, parentTransform, children, contentStartX, contentStartY, contentWidth, contentHeight);
|
||||
break;
|
||||
default:
|
||||
// 默认按正常方式递归
|
||||
// 默认按正常方式递归(传递顶部 Y)
|
||||
for (const child of children) {
|
||||
this.layoutEntity(
|
||||
child,
|
||||
parentTransform.worldX,
|
||||
parentTransform.worldY,
|
||||
parentTopY,
|
||||
parentTransform.computedWidth,
|
||||
parentTransform.computedHeight,
|
||||
parentTransform.worldAlpha
|
||||
@@ -245,30 +296,35 @@ export class UILayoutSystem extends EntitySystem {
|
||||
}
|
||||
|
||||
// 布局每个子元素
|
||||
// startY 是内容区域的顶部 Y(Y-up 系统)
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]!;
|
||||
const childTransform = child.getComponent(UITransformComponent)!;
|
||||
const size = childSizes[i]!;
|
||||
|
||||
// 计算 Y 位置(基于 alignItems)
|
||||
let childY = startY;
|
||||
// 计算子元素顶部 Y 位置(基于 alignItems)
|
||||
// startY 是内容区域顶部,向下布局意味着 Y 值减小
|
||||
let childTopY = startY; // 默认从顶部开始
|
||||
let childHeight = size.height;
|
||||
|
||||
switch (layout.alignItems) {
|
||||
case UIAlignItems.Center:
|
||||
childY = startY + (contentHeight - childHeight) / 2;
|
||||
// 在内容区域垂直居中:顶部 Y = startY - (contentHeight - childHeight) / 2
|
||||
childTopY = startY - (contentHeight - childHeight) / 2;
|
||||
break;
|
||||
case UIAlignItems.End:
|
||||
childY = startY + contentHeight - childHeight;
|
||||
// 对齐到底部:顶部 Y = startY - contentHeight + childHeight
|
||||
childTopY = startY - contentHeight + childHeight;
|
||||
break;
|
||||
case UIAlignItems.Stretch:
|
||||
childHeight = contentHeight;
|
||||
break;
|
||||
// UIAlignItems.Start: 默认从顶部开始,不需要修改
|
||||
}
|
||||
|
||||
// 直接设置子元素的世界坐标
|
||||
// 直接设置子元素的世界坐标(worldY 是底部 Y)
|
||||
childTransform.worldX = offsetX;
|
||||
childTransform.worldY = childY;
|
||||
childTransform.worldY = childTopY - childHeight; // 底部 Y = 顶部 Y - 高度
|
||||
childTransform.computedWidth = size.width;
|
||||
childTransform.computedHeight = childHeight;
|
||||
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
|
||||
@@ -284,6 +340,7 @@ export class UILayoutSystem extends EntitySystem {
|
||||
/**
|
||||
* 垂直布局
|
||||
* Vertical layout
|
||||
* Y-up 系统:startY 是内容区域的顶部,子元素从上往下排列(Y 值递减)
|
||||
*/
|
||||
private layoutVertical(
|
||||
layout: UILayoutComponent,
|
||||
@@ -304,16 +361,19 @@ export class UILayoutSystem extends EntitySystem {
|
||||
const totalGap = layout.gap * (children.length - 1);
|
||||
const totalHeight = totalChildHeight + totalGap;
|
||||
|
||||
// 计算起始位置
|
||||
let offsetY = startY;
|
||||
// 计算第一个子元素的顶部 Y(Y-up 系统,从顶部开始向下)
|
||||
// startY 是内容区域顶部
|
||||
let currentTopY = startY; // 从顶部开始
|
||||
let gap = layout.gap;
|
||||
|
||||
switch (layout.justifyContent) {
|
||||
case UIJustifyContent.Center:
|
||||
offsetY = startY + (contentHeight - totalHeight) / 2;
|
||||
// 垂直居中:第一个元素的顶部 Y = startY - (contentHeight - totalHeight) / 2
|
||||
currentTopY = startY - (contentHeight - totalHeight) / 2;
|
||||
break;
|
||||
case UIJustifyContent.End:
|
||||
offsetY = startY + contentHeight - totalHeight;
|
||||
// 对齐到底部:第一个元素的顶部 Y = startY - contentHeight + totalHeight
|
||||
currentTopY = startY - contentHeight + totalHeight;
|
||||
break;
|
||||
case UIJustifyContent.SpaceBetween:
|
||||
if (children.length > 1) {
|
||||
@@ -324,19 +384,19 @@ export class UILayoutSystem extends EntitySystem {
|
||||
if (children.length > 0) {
|
||||
const space = (contentHeight - totalChildHeight) / children.length;
|
||||
gap = space;
|
||||
offsetY = startY + space / 2;
|
||||
currentTopY = startY - space / 2;
|
||||
}
|
||||
break;
|
||||
case UIJustifyContent.SpaceEvenly:
|
||||
if (children.length > 0) {
|
||||
const space = (contentHeight - totalChildHeight) / (children.length + 1);
|
||||
gap = space;
|
||||
offsetY = startY + space;
|
||||
currentTopY = startY - space;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 布局每个子元素
|
||||
// 布局每个子元素(从上往下)
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]!;
|
||||
const childTransform = child.getComponent(UITransformComponent)!;
|
||||
@@ -358,8 +418,9 @@ export class UILayoutSystem extends EntitySystem {
|
||||
break;
|
||||
}
|
||||
|
||||
// worldY 是底部 Y = 顶部 Y - 高度
|
||||
childTransform.worldX = childX;
|
||||
childTransform.worldY = offsetY;
|
||||
childTransform.worldY = currentTopY - size.height;
|
||||
childTransform.computedWidth = childWidth;
|
||||
childTransform.computedHeight = size.height;
|
||||
childTransform.worldAlpha = parentTransform.worldAlpha * childTransform.alpha;
|
||||
@@ -367,13 +428,15 @@ export class UILayoutSystem extends EntitySystem {
|
||||
|
||||
this.processChildrenRecursive(child, childTransform);
|
||||
|
||||
offsetY += size.height + gap;
|
||||
// 移动到下一个元素的顶部位置(向下 = Y 减小)
|
||||
currentTopY -= size.height + gap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 网格布局
|
||||
* Grid layout
|
||||
* Y-up 系统:startY 是内容区域的顶部,网格从上往下、从左往右排列
|
||||
*/
|
||||
private layoutGrid(
|
||||
layout: UILayoutComponent,
|
||||
@@ -404,7 +467,11 @@ export class UILayoutSystem extends EntitySystem {
|
||||
const row = Math.floor(i / columns);
|
||||
|
||||
const x = startX + col * (cellWidth + gapX);
|
||||
const y = startY + row * (cellHeight + gapY);
|
||||
// Y-up 系统:第一行在顶部,行号增加 Y 值减小
|
||||
// 单元格顶部 Y = startY - row * (cellHeight + gapY)
|
||||
// 单元格底部 Y = 顶部 Y - cellHeight
|
||||
const cellTopY = startY - row * (cellHeight + gapY);
|
||||
const y = cellTopY - cellHeight; // worldY 是底部 Y
|
||||
|
||||
childTransform.worldX = x;
|
||||
childTransform.worldY = y;
|
||||
@@ -425,6 +492,9 @@ export class UILayoutSystem extends EntitySystem {
|
||||
const children = entity.children.filter(c => c.hasComponent(UITransformComponent));
|
||||
if (children.length === 0) return;
|
||||
|
||||
// 计算子元素的父容器顶部 Y(worldY 是底部,顶部 = 底部 + 高度)
|
||||
const parentTopY = parentTransform.worldY + parentTransform.computedHeight;
|
||||
|
||||
const layout = entity.getComponent(UILayoutComponent);
|
||||
if (layout && layout.type !== UILayoutType.None) {
|
||||
this.layoutChildren(layout, parentTransform, children);
|
||||
@@ -433,7 +503,7 @@ export class UILayoutSystem extends EntitySystem {
|
||||
this.layoutEntity(
|
||||
child,
|
||||
parentTransform.worldX,
|
||||
parentTransform.worldY,
|
||||
parentTopY,
|
||||
parentTransform.computedWidth,
|
||||
parentTransform.computedHeight,
|
||||
parentTransform.worldAlpha
|
||||
|
||||
Reference in New Issue
Block a user