Files
esengine/packages/rendering/fairygui/src/widgets/GGraph.ts

331 lines
9.5 KiB
TypeScript
Raw Normal View History

feat(fairygui): FairyGUI 完整集成 (#314) * feat(fairygui): FairyGUI ECS 集成核心架构 实现 FairyGUI 的 ECS 原生集成,完全替代旧 UI 系统: 核心类: - GObject: UI 对象基类,支持变换、可见性、关联、齿轮 - GComponent: 容器组件,管理子对象和控制器 - GRoot: 根容器,管理焦点、弹窗、输入分发 - GGroup: 组容器,支持水平/垂直布局 抽象层: - DisplayObject: 显示对象基类 - EventDispatcher: 事件分发 - Timer: 计时器 - Stage: 舞台,管理输入和缩放 布局系统: - Relations: 约束关联管理 - RelationItem: 24 种关联类型 基础设施: - Controller: 状态控制器 - Transition: 过渡动画 - ScrollPane: 滚动面板 - UIPackage: 包管理 - ByteBuffer: 二进制解析 * refactor(ui): 删除旧 UI 系统,使用 FairyGUI 替代 * feat(fairygui): 实现 UI 控件 - 添加显示类:Image、TextField、Graph - 添加基础控件:GImage、GTextField、GGraph - 添加交互控件:GButton、GProgressBar、GSlider - 更新 IRenderCollector 支持 Graph 渲染 - 扩展 Controller 添加 selectedPageId - 添加 STATE_CHANGED 事件类型 * feat(fairygui): 现代化架构重构 - 增强 EventDispatcher 支持类型安全、优先级和传播控制 - 添加 PropertyBinding 响应式属性绑定系统 - 添加 ServiceContainer 依赖注入容器 - 添加 UIConfig 全局配置系统 - 添加 UIObjectFactory 对象工厂 - 实现 RenderBridge 渲染桥接层 - 实现 Canvas2DBackend 作为默认渲染后端 - 扩展 IRenderCollector 支持更多图元类型 * feat(fairygui): 九宫格渲染和资源加载修复 - 修复 FGUIUpdateSystem 支持路径和 GUID 两种加载方式 - 修复 GTextInput 同时设置 _displayObject 和 _textField - 实现九宫格渲染展开为 9 个子图元 - 添加 sourceWidth/sourceHeight 用于九宫格计算 - 添加 DOMTextRenderer 文本渲染层(临时方案) * fix(fairygui): 修复 GGraph 颜色读取 * feat(fairygui): 虚拟节点 Inspector 和文本渲染支持 * fix(fairygui): 编辑器状态刷新和遗留引用修复 - 修复切换 FGUI 包后组件列表未刷新问题 - 修复切换组件后 viewport 未清理旧内容问题 - 修复虚拟节点在包加载后未刷新问题 - 重构为事件驱动架构,移除轮询机制 - 修复 @esengine/ui 遗留引用,统一使用 @esengine/fairygui * fix: 移除 tsconfig 中的 @esengine/ui 引用
2025-12-22 10:52:54 +08:00
import { GObject } from '../core/GObject';
import { Graph } from '../display/Graph';
import { EGraphType, EObjectPropID } from '../core/FieldTypes';
import type { ByteBuffer } from '../utils/ByteBuffer';
/**
* GGraph - FairyGUI
*
* Supports rect, ellipse, polygon, and regular polygon shapes.
*
*/
export class GGraph extends GObject {
private _graph!: Graph;
private _type: EGraphType = EGraphType.Empty;
private _lineSize: number = 1;
private _lineColor: string = '#000000';
private _fillColor: string = '#FFFFFF';
private _cornerRadius: number[] | null = null;
private _sides: number = 3;
private _startAngle: number = 0;
private _polygonPoints: number[] | null = null;
private _distances: number[] | null = null;
constructor() {
super();
this.ensureGraph();
}
private ensureGraph(): void {
if (!this._graph) {
this.createDisplayObject();
}
}
protected createDisplayObject(): void {
this._displayObject = this._graph = new Graph();
this._graph.touchable = false;
this._displayObject.gOwner = this;
}
public get type(): EGraphType {
return this._type;
}
public get polygonPoints(): number[] | null {
return this._polygonPoints;
}
public get fillColor(): string {
return this._fillColor;
}
public set fillColor(value: string) {
if (value === this._fillColor) return;
this._fillColor = value;
this.updateGraph();
}
public get lineColor(): string {
return this._lineColor;
}
public set lineColor(value: string) {
if (value === this._lineColor) return;
this._lineColor = value;
this.updateGraph();
}
public get color(): string {
return this._fillColor;
}
public set color(value: string) {
this._fillColor = value;
this.updateGear(4);
if (this._type !== EGraphType.Empty) {
this.updateGraph();
}
}
public get distances(): number[] | null {
return this._distances;
}
public set distances(value: number[] | null) {
this._distances = value;
if (this._type === EGraphType.RegularPolygon) {
this.updateGraph();
}
}
public drawRect(
lineSize: number,
lineColor: string,
fillColor: string,
cornerRadius?: number[]
): void {
this._type = EGraphType.Rect;
this._lineSize = lineSize;
this._lineColor = lineColor;
this._fillColor = fillColor;
this._cornerRadius = cornerRadius || null;
this.updateGraph();
}
public drawEllipse(lineSize: number, lineColor: string, fillColor: string): void {
this._type = EGraphType.Ellipse;
this._lineSize = lineSize;
this._lineColor = lineColor;
this._fillColor = fillColor;
this.updateGraph();
}
public drawRegularPolygon(
lineSize: number,
lineColor: string,
fillColor: string,
sides: number,
startAngle?: number,
distances?: number[]
): void {
this._type = EGraphType.RegularPolygon;
this._lineSize = lineSize;
this._lineColor = lineColor;
this._fillColor = fillColor;
this._sides = sides;
this._startAngle = startAngle || 0;
this._distances = distances || null;
this.updateGraph();
}
public drawPolygon(
lineSize: number,
lineColor: string,
fillColor: string,
points: number[]
): void {
this._type = EGraphType.Polygon;
this._lineSize = lineSize;
this._lineColor = lineColor;
this._fillColor = fillColor;
this._polygonPoints = points;
this.updateGraph();
}
private updateGraph(): void {
this.ensureGraph();
if (!this._graph) return;
this._graph.touchable = this.touchable;
const w = this.width;
const h = this.height;
if (w === 0 || h === 0) {
this._graph.clear();
return;
}
switch (this._type) {
case EGraphType.Rect:
this._graph.drawRect(
this._lineSize,
this._lineColor,
this._fillColor,
this._cornerRadius || undefined
);
break;
case EGraphType.Ellipse:
this._graph.drawEllipse(this._lineSize, this._lineColor, this._fillColor);
break;
case EGraphType.Polygon:
if (this._polygonPoints) {
this._graph.drawPolygon(
this._lineSize,
this._lineColor,
this._fillColor,
this._polygonPoints
);
}
break;
case EGraphType.RegularPolygon:
this.generateRegularPolygonPoints();
if (this._polygonPoints) {
this._graph.drawPolygon(
this._lineSize,
this._lineColor,
this._fillColor,
this._polygonPoints
);
}
break;
default:
this._graph.clear();
break;
}
this._graph.width = w;
this._graph.height = h;
}
private generateRegularPolygonPoints(): void {
const radius = Math.min(this._width, this._height) / 2;
this._polygonPoints = [];
const angle = (this._startAngle * Math.PI) / 180;
const deltaAngle = (2 * Math.PI) / this._sides;
for (let i = 0; i < this._sides; i++) {
let dist = 1;
if (this._distances && this._distances[i] !== undefined) {
dist = this._distances[i];
if (isNaN(dist)) dist = 1;
}
const xv = radius + radius * dist * Math.cos(angle + deltaAngle * i);
const yv = radius + radius * dist * Math.sin(angle + deltaAngle * i);
this._polygonPoints.push(xv, yv);
}
}
public replaceMe(target: GObject): void {
if (!this._parent) {
throw new Error('parent not set');
}
target.name = this.name;
target.alpha = this.alpha;
target.rotation = this.rotation;
target.visible = this.visible;
target.touchable = this.touchable;
target.grayed = this.grayed;
target.setXY(this.x, this.y);
target.setSize(this.width, this.height);
const index = this._parent.getChildIndex(this);
this._parent.addChildAt(target, index);
target.relations.copyFrom(this.relations);
this._parent.removeChild(this, true);
}
public addBeforeMe(target: GObject): void {
if (!this._parent) {
throw new Error('parent not set');
}
const index = this._parent.getChildIndex(this);
this._parent.addChildAt(target, index);
}
/**
* Add target after this object
*
*/
public addAfterMe(target: GObject): void {
if (!this._parent) {
throw new Error('parent not set');
}
const index = this._parent.getChildIndex(this);
this._parent.addChildAt(target, index + 1);
}
public getProp(index: number): any {
if (index === EObjectPropID.Color) {
return this.color;
}
return super.getProp(index);
}
public setProp(index: number, value: any): void {
if (index === EObjectPropID.Color) {
this.color = value;
} else {
super.setProp(index, value);
}
}
protected handleSizeChanged(): void {
super.handleSizeChanged();
if (this._type !== EGraphType.Empty) {
this.updateGraph();
}
}
public setup_beforeAdd(buffer: ByteBuffer, beginPos: number): void {
super.setup_beforeAdd(buffer, beginPos);
buffer.seek(beginPos, 5);
this._type = buffer.readByte();
if (this._type !== EGraphType.Empty) {
this._lineSize = buffer.getInt32();
this._lineColor = buffer.readColorS(true);
this._fillColor = buffer.readColorS(true);
if (buffer.readBool()) {
this._cornerRadius = [];
for (let i = 0; i < 4; i++) {
this._cornerRadius[i] = buffer.getFloat32();
}
}
if (this._type === EGraphType.Polygon) {
const cnt = buffer.getInt16();
this._polygonPoints = [];
for (let i = 0; i < cnt; i++) {
this._polygonPoints[i] = buffer.getFloat32();
}
} else if (this._type === EGraphType.RegularPolygon) {
this._sides = buffer.getInt16();
this._startAngle = buffer.getFloat32();
const cnt = buffer.getInt16();
if (cnt > 0) {
this._distances = [];
for (let i = 0; i < cnt; i++) {
this._distances[i] = buffer.getFloat32();
}
}
}
}
}
public setup_afterAdd(buffer: ByteBuffer, beginPos: number): void {
super.setup_afterAdd(buffer, beginPos);
if (this._type !== EGraphType.Empty) {
this.updateGraph();
}
}
}