refactor: reorganize package structure and decouple framework packages (#338)
* refactor: reorganize package structure and decouple framework packages ## Package Structure Reorganization - Reorganized 55 packages into categorized subdirectories: - packages/framework/ - Generic framework (Laya/Cocos compatible) - packages/engine/ - ESEngine core modules - packages/rendering/ - Rendering modules (WASM dependent) - packages/physics/ - Physics modules - packages/streaming/ - World streaming - packages/network-ext/ - Network extensions - packages/editor/ - Editor framework and plugins - packages/rust/ - Rust WASM engine - packages/tools/ - Build tools and SDK ## Framework Package Decoupling - Decoupled behavior-tree and blueprint packages from ESEngine dependencies - Created abstracted interfaces (IBTAssetManager, IBehaviorTreeAssetContent) - ESEngine-specific code moved to esengine/ subpath exports - Framework packages now usable with Cocos/Laya without ESEngine ## CI Configuration - Updated CI to only type-check and lint framework packages - Added type-check:framework and lint:framework scripts ## Breaking Changes - Package import paths changed due to directory reorganization - ESEngine integrations now use subpath imports (e.g., '@esengine/behavior-tree/esengine') * fix: update es-engine file path after directory reorganization * docs: update README to focus on framework over engine * ci: only build framework packages, remove Rust/WASM dependencies * fix: remove esengine subpath from behavior-tree and blueprint builds ESEngine integration code will only be available in full engine builds. Framework packages are now purely engine-agnostic. * fix: move network-protocols to framework, build both in CI * fix: update workflow paths from packages/core to packages/framework/core * fix: exclude esengine folder from type-check in behavior-tree and blueprint * fix: update network tsconfig references to new paths * fix: add test:ci:framework to only test framework packages in CI * fix: only build core and math npm packages in CI * fix: exclude test files from CodeQL and fix string escaping security issue
This commit is contained in:
662
packages/rendering/fairygui/src/widgets/GButton.ts
Normal file
662
packages/rendering/fairygui/src/widgets/GButton.ts
Normal file
@@ -0,0 +1,662 @@
|
||||
import { GComponent } from '../core/GComponent';
|
||||
import { GObject } from '../core/GObject';
|
||||
import { Controller } from '../core/Controller';
|
||||
import { GTextField } from './GTextField';
|
||||
import { FGUIEvents } from '../events/Events';
|
||||
import { EButtonMode, EObjectPropID } from '../core/FieldTypes';
|
||||
import type { ByteBuffer } from '../utils/ByteBuffer';
|
||||
|
||||
/**
|
||||
* GButton
|
||||
*
|
||||
* Button component with states: up, down, over, selected, disabled.
|
||||
*
|
||||
* 按钮组件,支持状态:正常、按下、悬停、选中、禁用
|
||||
*/
|
||||
export class GButton extends GComponent {
|
||||
protected _titleObject: GObject | null = null;
|
||||
protected _iconObject: GObject | null = null;
|
||||
|
||||
private _mode: EButtonMode = EButtonMode.Common;
|
||||
private _selected: boolean = false;
|
||||
private _title: string = '';
|
||||
private _selectedTitle: string = '';
|
||||
private _icon: string = '';
|
||||
private _selectedIcon: string = '';
|
||||
private _sound: string = '';
|
||||
private _soundVolumeScale: number = 1;
|
||||
private _buttonController: Controller | null = null;
|
||||
private _relatedController: Controller | null = null;
|
||||
private _relatedPageId: string = '';
|
||||
private _changeStateOnClick: boolean = true;
|
||||
private _linkedPopup: GObject | null = null;
|
||||
private _downEffect: number = 0;
|
||||
private _downEffectValue: number = 0.8;
|
||||
private _downScaled: boolean = false;
|
||||
|
||||
private _down: boolean = false;
|
||||
private _over: boolean = false;
|
||||
|
||||
public static readonly UP: string = 'up';
|
||||
public static readonly DOWN: string = 'down';
|
||||
public static readonly OVER: string = 'over';
|
||||
public static readonly SELECTED_OVER: string = 'selectedOver';
|
||||
public static readonly DISABLED: string = 'disabled';
|
||||
public static readonly SELECTED_DISABLED: string = 'selectedDisabled';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set icon URL
|
||||
* 获取/设置图标 URL
|
||||
*/
|
||||
public get icon(): string {
|
||||
return this._icon;
|
||||
}
|
||||
|
||||
public set icon(value: string) {
|
||||
this._icon = value;
|
||||
const v = this._selected && this._selectedIcon ? this._selectedIcon : this._icon;
|
||||
if (this._iconObject) {
|
||||
this._iconObject.icon = v;
|
||||
}
|
||||
this.updateGear(7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set selected icon URL
|
||||
* 获取/设置选中图标 URL
|
||||
*/
|
||||
public get selectedIcon(): string {
|
||||
return this._selectedIcon;
|
||||
}
|
||||
|
||||
public set selectedIcon(value: string) {
|
||||
this._selectedIcon = value;
|
||||
const v = this._selected && this._selectedIcon ? this._selectedIcon : this._icon;
|
||||
if (this._iconObject) {
|
||||
this._iconObject.icon = v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set title text
|
||||
* 获取/设置标题文本
|
||||
*/
|
||||
public get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
public set title(value: string) {
|
||||
this._title = value;
|
||||
if (this._titleObject) {
|
||||
this._titleObject.text =
|
||||
this._selected && this._selectedTitle ? this._selectedTitle : this._title;
|
||||
}
|
||||
this.updateGear(6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set text (alias for title)
|
||||
* 获取/设置文本(title 的别名)
|
||||
*/
|
||||
public get text(): string {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
public set text(value: string) {
|
||||
this.title = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set selected title text
|
||||
* 获取/设置选中标题文本
|
||||
*/
|
||||
public get selectedTitle(): string {
|
||||
return this._selectedTitle;
|
||||
}
|
||||
|
||||
public set selectedTitle(value: string) {
|
||||
this._selectedTitle = value;
|
||||
if (this._titleObject) {
|
||||
this._titleObject.text =
|
||||
this._selected && this._selectedTitle ? this._selectedTitle : this._title;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set title color
|
||||
* 获取/设置标题颜色
|
||||
*/
|
||||
public get titleColor(): string {
|
||||
const tf = this.getTextField();
|
||||
if (tf) {
|
||||
return tf.color;
|
||||
}
|
||||
return '#000000';
|
||||
}
|
||||
|
||||
public set titleColor(value: string) {
|
||||
const tf = this.getTextField();
|
||||
if (tf) {
|
||||
tf.color = value;
|
||||
}
|
||||
this.updateGear(4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set title font size
|
||||
* 获取/设置标题字体大小
|
||||
*/
|
||||
public get titleFontSize(): number {
|
||||
const tf = this.getTextField();
|
||||
if (tf) {
|
||||
return tf.fontSize;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public set titleFontSize(value: number) {
|
||||
const tf = this.getTextField();
|
||||
if (tf) {
|
||||
tf.fontSize = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set sound URL
|
||||
* 获取/设置声音 URL
|
||||
*/
|
||||
public get sound(): string {
|
||||
return this._sound;
|
||||
}
|
||||
|
||||
public set sound(value: string) {
|
||||
this._sound = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set sound volume scale
|
||||
* 获取/设置声音音量缩放
|
||||
*/
|
||||
public get soundVolumeScale(): number {
|
||||
return this._soundVolumeScale;
|
||||
}
|
||||
|
||||
public set soundVolumeScale(value: number) {
|
||||
this._soundVolumeScale = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set selected state
|
||||
* 获取/设置选中状态
|
||||
*/
|
||||
public get selected(): boolean {
|
||||
return this._selected;
|
||||
}
|
||||
|
||||
public set selected(value: boolean) {
|
||||
if (this._mode === EButtonMode.Common) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._selected !== value) {
|
||||
this._selected = value;
|
||||
if (
|
||||
this.grayed &&
|
||||
this._buttonController &&
|
||||
this._buttonController.hasPage(GButton.DISABLED)
|
||||
) {
|
||||
if (this._selected) {
|
||||
this.setState(GButton.SELECTED_DISABLED);
|
||||
} else {
|
||||
this.setState(GButton.DISABLED);
|
||||
}
|
||||
} else {
|
||||
if (this._selected) {
|
||||
this.setState(this._over ? GButton.SELECTED_OVER : GButton.DOWN);
|
||||
} else {
|
||||
this.setState(this._over ? GButton.OVER : GButton.UP);
|
||||
}
|
||||
}
|
||||
if (this._selectedTitle && this._titleObject) {
|
||||
this._titleObject.text = this._selected ? this._selectedTitle : this._title;
|
||||
}
|
||||
if (this._selectedIcon) {
|
||||
const str = this._selected ? this._selectedIcon : this._icon;
|
||||
if (this._iconObject) {
|
||||
this._iconObject.icon = str;
|
||||
}
|
||||
}
|
||||
if (
|
||||
this._relatedController &&
|
||||
this._parent &&
|
||||
!this._parent._buildingDisplayList
|
||||
) {
|
||||
if (this._selected) {
|
||||
this._relatedController.selectedPageId = this._relatedPageId;
|
||||
} else if (
|
||||
this._mode === EButtonMode.Check &&
|
||||
this._relatedController.selectedPageId === this._relatedPageId
|
||||
) {
|
||||
// Deselect if in check mode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set button mode
|
||||
* 获取/设置按钮模式
|
||||
*/
|
||||
public get mode(): EButtonMode {
|
||||
return this._mode;
|
||||
}
|
||||
|
||||
public set mode(value: EButtonMode) {
|
||||
if (this._mode !== value) {
|
||||
if (value === EButtonMode.Common) {
|
||||
this.selected = false;
|
||||
}
|
||||
this._mode = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set related controller
|
||||
* 获取/设置关联控制器
|
||||
*/
|
||||
public get relatedController(): Controller | null {
|
||||
return this._relatedController;
|
||||
}
|
||||
|
||||
public set relatedController(value: Controller | null) {
|
||||
if (value !== this._relatedController) {
|
||||
this._relatedController = value;
|
||||
this._relatedPageId = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set related page ID
|
||||
* 获取/设置关联页面 ID
|
||||
*/
|
||||
public get relatedPageId(): string {
|
||||
return this._relatedPageId;
|
||||
}
|
||||
|
||||
public set relatedPageId(value: string) {
|
||||
this._relatedPageId = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set change state on click
|
||||
* 获取/设置点击时是否改变状态
|
||||
*/
|
||||
public get changeStateOnClick(): boolean {
|
||||
return this._changeStateOnClick;
|
||||
}
|
||||
|
||||
public set changeStateOnClick(value: boolean) {
|
||||
this._changeStateOnClick = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set linked popup
|
||||
* 获取/设置关联弹出窗口
|
||||
*/
|
||||
public get linkedPopup(): GObject | null {
|
||||
return this._linkedPopup;
|
||||
}
|
||||
|
||||
public set linkedPopup(value: GObject | null) {
|
||||
this._linkedPopup = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get text field from title object
|
||||
* 从标题对象获取文本字段
|
||||
*/
|
||||
public getTextField(): GTextField | null {
|
||||
if (this._titleObject instanceof GTextField) {
|
||||
return this._titleObject;
|
||||
} else if (this._titleObject instanceof GButton) {
|
||||
return this._titleObject.getTextField();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a click event programmatically
|
||||
* 程序化触发点击事件
|
||||
*/
|
||||
public fireClick(bDownEffect: boolean = true): void {
|
||||
if (bDownEffect && this._mode === EButtonMode.Common) {
|
||||
this.setState(GButton.OVER);
|
||||
setTimeout(() => this.setState(GButton.DOWN), 100);
|
||||
setTimeout(() => this.setState(GButton.UP), 200);
|
||||
}
|
||||
this.handleClick();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set button state
|
||||
* 设置按钮状态
|
||||
*/
|
||||
protected setState(value: string): void {
|
||||
if (this._buttonController) {
|
||||
this._buttonController.selectedPage = value;
|
||||
}
|
||||
|
||||
if (this._downEffect === 1) {
|
||||
const cnt = this.numChildren;
|
||||
if (
|
||||
value === GButton.DOWN ||
|
||||
value === GButton.SELECTED_OVER ||
|
||||
value === GButton.SELECTED_DISABLED
|
||||
) {
|
||||
const r = Math.round(this._downEffectValue * 255);
|
||||
const color = '#' + ((r << 16) + (r << 8) + r).toString(16).padStart(6, '0');
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
const obj = this.getChildAt(i);
|
||||
if (!(obj instanceof GTextField)) {
|
||||
obj.setProp(EObjectPropID.Color, color);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
const obj = this.getChildAt(i);
|
||||
if (!(obj instanceof GTextField)) {
|
||||
obj.setProp(EObjectPropID.Color, '#FFFFFF');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (this._downEffect === 2) {
|
||||
if (
|
||||
value === GButton.DOWN ||
|
||||
value === GButton.SELECTED_OVER ||
|
||||
value === GButton.SELECTED_DISABLED
|
||||
) {
|
||||
if (!this._downScaled) {
|
||||
this.setScale(
|
||||
this.scaleX * this._downEffectValue,
|
||||
this.scaleY * this._downEffectValue
|
||||
);
|
||||
this._downScaled = true;
|
||||
}
|
||||
} else {
|
||||
if (this._downScaled) {
|
||||
this.setScale(
|
||||
this.scaleX / this._downEffectValue,
|
||||
this.scaleY / this._downEffectValue
|
||||
);
|
||||
this._downScaled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public handleControllerChanged(c: Controller): void {
|
||||
super.handleControllerChanged(c);
|
||||
|
||||
if (this._relatedController === c) {
|
||||
this.selected = this._relatedPageId === c.selectedPageId;
|
||||
}
|
||||
}
|
||||
|
||||
protected handleGrayedChanged(): void {
|
||||
if (
|
||||
this._buttonController &&
|
||||
this._buttonController.hasPage(GButton.DISABLED)
|
||||
) {
|
||||
if (this.grayed) {
|
||||
if (
|
||||
this._selected &&
|
||||
this._buttonController.hasPage(GButton.SELECTED_DISABLED)
|
||||
) {
|
||||
this.setState(GButton.SELECTED_DISABLED);
|
||||
} else {
|
||||
this.setState(GButton.DISABLED);
|
||||
}
|
||||
} else if (this._selected) {
|
||||
this.setState(GButton.DOWN);
|
||||
} else {
|
||||
this.setState(GButton.UP);
|
||||
}
|
||||
} else {
|
||||
super.handleGrayedChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public getProp(index: number): any {
|
||||
switch (index) {
|
||||
case EObjectPropID.Color:
|
||||
return this.titleColor;
|
||||
case EObjectPropID.OutlineColor:
|
||||
const tf = this.getTextField();
|
||||
if (tf) {
|
||||
return tf.strokeColor;
|
||||
}
|
||||
return '#000000';
|
||||
case EObjectPropID.FontSize:
|
||||
return this.titleFontSize;
|
||||
case EObjectPropID.Selected:
|
||||
return this.selected;
|
||||
default:
|
||||
return super.getProp(index);
|
||||
}
|
||||
}
|
||||
|
||||
public setProp(index: number, value: any): void {
|
||||
switch (index) {
|
||||
case EObjectPropID.Color:
|
||||
this.titleColor = value;
|
||||
break;
|
||||
case EObjectPropID.OutlineColor:
|
||||
const tf = this.getTextField();
|
||||
if (tf) {
|
||||
tf.strokeColor = value;
|
||||
}
|
||||
break;
|
||||
case EObjectPropID.FontSize:
|
||||
this.titleFontSize = value;
|
||||
break;
|
||||
case EObjectPropID.Selected:
|
||||
this.selected = value;
|
||||
break;
|
||||
default:
|
||||
super.setProp(index, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected constructExtension(buffer: ByteBuffer): void {
|
||||
buffer.seek(0, 6);
|
||||
|
||||
this._mode = buffer.readByte();
|
||||
const str = buffer.readS();
|
||||
if (str) {
|
||||
this._sound = str;
|
||||
}
|
||||
this._soundVolumeScale = buffer.getFloat32();
|
||||
this._downEffect = buffer.readByte();
|
||||
this._downEffectValue = buffer.getFloat32();
|
||||
if (this._downEffect === 2) {
|
||||
this.setPivot(0.5, 0.5, this.pivotAsAnchor);
|
||||
}
|
||||
|
||||
this._buttonController = this.getController('button');
|
||||
this._titleObject = this.getChild('title');
|
||||
this._iconObject = this.getChild('icon');
|
||||
if (this._titleObject) {
|
||||
this._title = this._titleObject.text || '';
|
||||
}
|
||||
if (this._iconObject) {
|
||||
this._icon = this._iconObject.icon || '';
|
||||
}
|
||||
|
||||
if (this._mode === EButtonMode.Common) {
|
||||
this.setState(GButton.UP);
|
||||
}
|
||||
|
||||
this.on(FGUIEvents.ROLL_OVER, this.handleRollOver, this);
|
||||
this.on(FGUIEvents.ROLL_OUT, this.handleRollOut, this);
|
||||
this.on(FGUIEvents.TOUCH_BEGIN, this.handleTouchBegin, this);
|
||||
this.on(FGUIEvents.CLICK, this.handleClick, this);
|
||||
}
|
||||
|
||||
public setup_afterAdd(buffer: ByteBuffer, beginPos: number): void {
|
||||
super.setup_afterAdd(buffer, beginPos);
|
||||
|
||||
if (!buffer.seek(beginPos, 6)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.readByte() !== this.packageItem?.objectType) {
|
||||
return;
|
||||
}
|
||||
|
||||
let str = buffer.readS();
|
||||
if (str) {
|
||||
this.title = str;
|
||||
}
|
||||
str = buffer.readS();
|
||||
if (str) {
|
||||
this.selectedTitle = str;
|
||||
}
|
||||
str = buffer.readS();
|
||||
if (str) {
|
||||
this.icon = str;
|
||||
}
|
||||
str = buffer.readS();
|
||||
if (str) {
|
||||
this.selectedIcon = str;
|
||||
}
|
||||
if (buffer.readBool()) {
|
||||
this.titleColor = buffer.readS();
|
||||
}
|
||||
const iv = buffer.getInt32();
|
||||
if (iv !== 0) {
|
||||
this.titleFontSize = iv;
|
||||
}
|
||||
const controllerIndex = buffer.getInt16();
|
||||
if (controllerIndex >= 0 && this.parent) {
|
||||
this._relatedController = this.parent.getControllerAt(controllerIndex);
|
||||
}
|
||||
this._relatedPageId = buffer.readS();
|
||||
|
||||
str = buffer.readS();
|
||||
if (str) {
|
||||
this._sound = str;
|
||||
}
|
||||
if (buffer.readBool()) {
|
||||
this._soundVolumeScale = buffer.getFloat32();
|
||||
}
|
||||
|
||||
this.selected = buffer.readBool();
|
||||
}
|
||||
|
||||
private handleRollOver(): void {
|
||||
if (
|
||||
!this._buttonController ||
|
||||
!this._buttonController.hasPage(GButton.OVER)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._over = true;
|
||||
if (this._down) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.grayed && this._buttonController.hasPage(GButton.DISABLED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(this._selected ? GButton.SELECTED_OVER : GButton.OVER);
|
||||
}
|
||||
|
||||
private handleRollOut(): void {
|
||||
if (
|
||||
!this._buttonController ||
|
||||
!this._buttonController.hasPage(GButton.OVER)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._over = false;
|
||||
if (this._down) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.grayed && this._buttonController.hasPage(GButton.DISABLED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(this._selected ? GButton.DOWN : GButton.UP);
|
||||
}
|
||||
|
||||
private handleTouchBegin(): void {
|
||||
this._down = true;
|
||||
|
||||
if (this._mode === EButtonMode.Common) {
|
||||
if (
|
||||
this.grayed &&
|
||||
this._buttonController &&
|
||||
this._buttonController.hasPage(GButton.DISABLED)
|
||||
) {
|
||||
this.setState(GButton.SELECTED_DISABLED);
|
||||
} else {
|
||||
this.setState(GButton.DOWN);
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for touch end globally
|
||||
this.root?.on(FGUIEvents.TOUCH_END, this.handleTouchEnd, this);
|
||||
}
|
||||
|
||||
private handleTouchEnd(): void {
|
||||
if (this._down) {
|
||||
this.root?.off(FGUIEvents.TOUCH_END, this.handleTouchEnd, this);
|
||||
this._down = false;
|
||||
|
||||
if (!this._displayObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._mode === EButtonMode.Common) {
|
||||
if (
|
||||
this.grayed &&
|
||||
this._buttonController &&
|
||||
this._buttonController.hasPage(GButton.DISABLED)
|
||||
) {
|
||||
this.setState(GButton.DISABLED);
|
||||
} else if (this._over) {
|
||||
this.setState(GButton.OVER);
|
||||
} else {
|
||||
this.setState(GButton.UP);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleClick(): void {
|
||||
if (this._mode === EButtonMode.Check) {
|
||||
if (this._changeStateOnClick) {
|
||||
this.selected = !this._selected;
|
||||
this.emit(FGUIEvents.STATE_CHANGED);
|
||||
}
|
||||
} else if (this._mode === EButtonMode.Radio) {
|
||||
if (this._changeStateOnClick && !this._selected) {
|
||||
this.selected = true;
|
||||
this.emit(FGUIEvents.STATE_CHANGED);
|
||||
}
|
||||
} else {
|
||||
if (this._relatedController) {
|
||||
this._relatedController.selectedPageId = this._relatedPageId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
330
packages/rendering/fairygui/src/widgets/GGraph.ts
Normal file
330
packages/rendering/fairygui/src/widgets/GGraph.ts
Normal file
@@ -0,0 +1,330 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
232
packages/rendering/fairygui/src/widgets/GImage.ts
Normal file
232
packages/rendering/fairygui/src/widgets/GImage.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
import { GObject } from '../core/GObject';
|
||||
import { Image } from '../display/Image';
|
||||
import { Rectangle } from '../utils/MathTypes';
|
||||
import { EFlipType, EFillMethod, EObjectPropID } from '../core/FieldTypes';
|
||||
import type { ByteBuffer } from '../utils/ByteBuffer';
|
||||
import type { PackageItem } from '../package/PackageItem';
|
||||
|
||||
/**
|
||||
* GImage
|
||||
*
|
||||
* Image display object for FairyGUI.
|
||||
*
|
||||
* FairyGUI 图像显示对象
|
||||
*/
|
||||
export class GImage extends GObject {
|
||||
private _image!: Image;
|
||||
private _flip: EFlipType = EFlipType.None;
|
||||
private _contentItem: PackageItem | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// Ensure _image is initialized - super() calls createDisplayObject() but
|
||||
// class field initializers run after super(), which may cause issues
|
||||
this.ensureImage();
|
||||
}
|
||||
|
||||
private ensureImage(): void {
|
||||
if (!this._image) {
|
||||
this.createDisplayObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internal image display object
|
||||
* 获取内部图像显示对象
|
||||
*/
|
||||
public get image(): Image {
|
||||
return this._image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set color tint
|
||||
* 获取/设置颜色着色
|
||||
*/
|
||||
public get color(): string {
|
||||
return this._image.color;
|
||||
}
|
||||
|
||||
public set color(value: string) {
|
||||
if (this._image && this._image.color !== value) {
|
||||
this._image.color = value;
|
||||
this.updateGear(4);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set flip type
|
||||
* 获取/设置翻转类型
|
||||
*/
|
||||
public get flip(): EFlipType {
|
||||
return this._flip;
|
||||
}
|
||||
|
||||
public set flip(value: EFlipType) {
|
||||
if (this._flip !== value) {
|
||||
this._flip = value;
|
||||
|
||||
let sx = 1;
|
||||
let sy = 1;
|
||||
if (this._flip === EFlipType.Horizontal || this._flip === EFlipType.Both) {
|
||||
sx = -1;
|
||||
}
|
||||
if (this._flip === EFlipType.Vertical || this._flip === EFlipType.Both) {
|
||||
sy = -1;
|
||||
}
|
||||
this.setScale(sx, sy);
|
||||
this.handleXYChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set fill method
|
||||
* 获取/设置填充方法
|
||||
*/
|
||||
public get fillMethod(): EFillMethod {
|
||||
return this._image.fillMethod;
|
||||
}
|
||||
|
||||
public set fillMethod(value: EFillMethod) {
|
||||
if (this._image) {
|
||||
this._image.fillMethod = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set fill origin
|
||||
* 获取/设置填充起点
|
||||
*/
|
||||
public get fillOrigin(): number {
|
||||
return this._image.fillOrigin;
|
||||
}
|
||||
|
||||
public set fillOrigin(value: number) {
|
||||
if (this._image) {
|
||||
this._image.fillOrigin = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set fill clockwise
|
||||
* 获取/设置填充顺时针方向
|
||||
*/
|
||||
public get fillClockwise(): boolean {
|
||||
return this._image.fillClockwise;
|
||||
}
|
||||
|
||||
public set fillClockwise(value: boolean) {
|
||||
if (this._image) {
|
||||
this._image.fillClockwise = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set fill amount (0-1)
|
||||
* 获取/设置填充量(0-1)
|
||||
*/
|
||||
public get fillAmount(): number {
|
||||
return this._image.fillAmount;
|
||||
}
|
||||
|
||||
public set fillAmount(value: number) {
|
||||
if (this._image) {
|
||||
this._image.fillAmount = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected createDisplayObject(): void {
|
||||
this._displayObject = this._image = new Image();
|
||||
this._image.touchable = false;
|
||||
this._displayObject.gOwner = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct from package resource
|
||||
* 从包资源构建
|
||||
*/
|
||||
public constructFromResource(): void {
|
||||
if (!this.packageItem) return;
|
||||
|
||||
this.ensureImage();
|
||||
|
||||
this._contentItem = this.packageItem;
|
||||
|
||||
this.sourceWidth = this._contentItem.width;
|
||||
this.sourceHeight = this._contentItem.height;
|
||||
this.initWidth = this.sourceWidth;
|
||||
this.initHeight = this.sourceHeight;
|
||||
|
||||
this._image.scale9Grid = this._contentItem.scale9Grid
|
||||
? new Rectangle(
|
||||
this._contentItem.scale9Grid.x,
|
||||
this._contentItem.scale9Grid.y,
|
||||
this._contentItem.scale9Grid.width,
|
||||
this._contentItem.scale9Grid.height
|
||||
)
|
||||
: null;
|
||||
this._image.scaleByTile = this._contentItem.scaleByTile;
|
||||
this._image.tileGridIndice = this._contentItem.tileGridIndice;
|
||||
|
||||
// Load texture from package (this decodes the sprite info)
|
||||
if (this._contentItem.owner) {
|
||||
this._contentItem.owner.getItemAsset(this._contentItem);
|
||||
}
|
||||
this._image.texture = this._contentItem.texture;
|
||||
|
||||
this.setSize(this.sourceWidth, this.sourceHeight);
|
||||
}
|
||||
|
||||
protected handleXYChanged(): void {
|
||||
super.handleXYChanged();
|
||||
|
||||
if (this._flip !== EFlipType.None) {
|
||||
if (this.scaleX === -1 && this._image) {
|
||||
this._image.x += this.width;
|
||||
}
|
||||
if (this.scaleY === -1 && this._image) {
|
||||
this._image.y += this.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public setup_beforeAdd(buffer: ByteBuffer, beginPos: number): void {
|
||||
super.setup_beforeAdd(buffer, beginPos);
|
||||
|
||||
buffer.seek(beginPos, 5);
|
||||
|
||||
if (buffer.readBool()) {
|
||||
this.color = buffer.readS();
|
||||
}
|
||||
this.flip = buffer.readByte();
|
||||
|
||||
const fillMethodValue = buffer.readByte();
|
||||
if (this._image) {
|
||||
this._image.fillMethod = fillMethodValue;
|
||||
if (this._image.fillMethod !== EFillMethod.None) {
|
||||
this._image.fillOrigin = buffer.readByte();
|
||||
this._image.fillClockwise = buffer.readBool();
|
||||
this._image.fillAmount = buffer.getFloat32();
|
||||
}
|
||||
} else if (fillMethodValue !== EFillMethod.None) {
|
||||
// Skip bytes if _image not ready
|
||||
buffer.readByte(); // fillOrigin
|
||||
buffer.readBool(); // fillClockwise
|
||||
buffer.getFloat32(); // fillAmount
|
||||
}
|
||||
}
|
||||
}
|
||||
750
packages/rendering/fairygui/src/widgets/GList.ts
Normal file
750
packages/rendering/fairygui/src/widgets/GList.ts
Normal file
@@ -0,0 +1,750 @@
|
||||
import { GComponent } from '../core/GComponent';
|
||||
import { GObject } from '../core/GObject';
|
||||
import { GObjectPool } from '../core/GObjectPool';
|
||||
import { GButton } from './GButton';
|
||||
import { Controller } from '../core/Controller';
|
||||
import { UIPackage } from '../package/UIPackage';
|
||||
import { FGUIEvents } from '../events/Events';
|
||||
import { Point, Margin } from '../utils/MathTypes';
|
||||
import {
|
||||
EListLayoutType,
|
||||
EListSelectionMode,
|
||||
EChildrenRenderOrder,
|
||||
EOverflowType,
|
||||
EAlignType,
|
||||
EVertAlignType
|
||||
} from '../core/FieldTypes';
|
||||
import type { ByteBuffer } from '../utils/ByteBuffer';
|
||||
|
||||
/**
|
||||
* Item renderer callback
|
||||
* 项渲染回调
|
||||
*/
|
||||
export type ItemRenderer = (index: number, item: GObject) => void;
|
||||
|
||||
/**
|
||||
* Item provider callback
|
||||
* 项提供者回调
|
||||
*/
|
||||
export type ItemProvider = (index: number) => string;
|
||||
|
||||
/**
|
||||
* GList
|
||||
*
|
||||
* Scrollable list component with item pooling support.
|
||||
*
|
||||
* 带有项池化支持的可滚动列表组件
|
||||
*
|
||||
* Features:
|
||||
* - Multiple layout modes (single column/row, flow, pagination)
|
||||
* - Item selection (single, multiple)
|
||||
* - Object pooling for performance
|
||||
*/
|
||||
export class GList extends GComponent {
|
||||
/** Item renderer callback | 项渲染回调 */
|
||||
public itemRenderer: ItemRenderer | null = null;
|
||||
|
||||
/** Item provider callback | 项提供者回调 */
|
||||
public itemProvider: ItemProvider | null = null;
|
||||
|
||||
/** Scroll item to view on click | 点击时滚动项到视图 */
|
||||
public scrollItemToViewOnClick: boolean = true;
|
||||
|
||||
/** Fold invisible items | 折叠不可见项 */
|
||||
public foldInvisibleItems: boolean = false;
|
||||
|
||||
private _layout: EListLayoutType = EListLayoutType.SingleColumn;
|
||||
private _lineCount: number = 0;
|
||||
private _columnCount: number = 0;
|
||||
private _lineGap: number = 0;
|
||||
private _columnGap: number = 0;
|
||||
private _defaultItem: string = '';
|
||||
private _autoResizeItem: boolean = true;
|
||||
private _selectionMode: EListSelectionMode = EListSelectionMode.Single;
|
||||
private _align: EAlignType = EAlignType.Left;
|
||||
private _verticalAlign: EVertAlignType = EVertAlignType.Top;
|
||||
private _selectionController: Controller | null = null;
|
||||
|
||||
private _lastSelectedIndex: number = -1;
|
||||
private _pool: GObjectPool;
|
||||
private _listMargin: Margin = new Margin();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._pool = new GObjectPool();
|
||||
this._trackBounds = true;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._pool.clear();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// Layout properties
|
||||
|
||||
public get layout(): EListLayoutType {
|
||||
return this._layout;
|
||||
}
|
||||
|
||||
public set layout(value: EListLayoutType) {
|
||||
if (this._layout !== value) {
|
||||
this._layout = value;
|
||||
this.setBoundsChangedFlag();
|
||||
}
|
||||
}
|
||||
|
||||
public get lineCount(): number {
|
||||
return this._lineCount;
|
||||
}
|
||||
|
||||
public set lineCount(value: number) {
|
||||
if (this._lineCount !== value) {
|
||||
this._lineCount = value;
|
||||
this.setBoundsChangedFlag();
|
||||
}
|
||||
}
|
||||
|
||||
public get columnCount(): number {
|
||||
return this._columnCount;
|
||||
}
|
||||
|
||||
public set columnCount(value: number) {
|
||||
if (this._columnCount !== value) {
|
||||
this._columnCount = value;
|
||||
this.setBoundsChangedFlag();
|
||||
}
|
||||
}
|
||||
|
||||
public get lineGap(): number {
|
||||
return this._lineGap;
|
||||
}
|
||||
|
||||
public set lineGap(value: number) {
|
||||
if (this._lineGap !== value) {
|
||||
this._lineGap = value;
|
||||
this.setBoundsChangedFlag();
|
||||
}
|
||||
}
|
||||
|
||||
public get columnGap(): number {
|
||||
return this._columnGap;
|
||||
}
|
||||
|
||||
public set columnGap(value: number) {
|
||||
if (this._columnGap !== value) {
|
||||
this._columnGap = value;
|
||||
this.setBoundsChangedFlag();
|
||||
}
|
||||
}
|
||||
|
||||
public get align(): EAlignType {
|
||||
return this._align;
|
||||
}
|
||||
|
||||
public set align(value: EAlignType) {
|
||||
if (this._align !== value) {
|
||||
this._align = value;
|
||||
this.setBoundsChangedFlag();
|
||||
}
|
||||
}
|
||||
|
||||
public get verticalAlign(): EVertAlignType {
|
||||
return this._verticalAlign;
|
||||
}
|
||||
|
||||
public set verticalAlign(value: EVertAlignType) {
|
||||
if (this._verticalAlign !== value) {
|
||||
this._verticalAlign = value;
|
||||
this.setBoundsChangedFlag();
|
||||
}
|
||||
}
|
||||
|
||||
public get defaultItem(): string {
|
||||
return this._defaultItem;
|
||||
}
|
||||
|
||||
public set defaultItem(value: string) {
|
||||
this._defaultItem = UIPackage.normalizeURL(value);
|
||||
}
|
||||
|
||||
public get autoResizeItem(): boolean {
|
||||
return this._autoResizeItem;
|
||||
}
|
||||
|
||||
public set autoResizeItem(value: boolean) {
|
||||
if (this._autoResizeItem !== value) {
|
||||
this._autoResizeItem = value;
|
||||
this.setBoundsChangedFlag();
|
||||
}
|
||||
}
|
||||
|
||||
public get selectionMode(): EListSelectionMode {
|
||||
return this._selectionMode;
|
||||
}
|
||||
|
||||
public set selectionMode(value: EListSelectionMode) {
|
||||
this._selectionMode = value;
|
||||
}
|
||||
|
||||
public get selectionController(): Controller | null {
|
||||
return this._selectionController;
|
||||
}
|
||||
|
||||
public set selectionController(value: Controller | null) {
|
||||
this._selectionController = value;
|
||||
}
|
||||
|
||||
public get itemPool(): GObjectPool {
|
||||
return this._pool;
|
||||
}
|
||||
|
||||
// Item pool operations
|
||||
|
||||
public getFromPool(url?: string): GObject | null {
|
||||
if (!url) {
|
||||
url = this._defaultItem;
|
||||
}
|
||||
const obj = this._pool.getObject(url);
|
||||
if (obj) {
|
||||
obj.visible = true;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
public returnToPool(obj: GObject): void {
|
||||
this._pool.returnObject(obj);
|
||||
}
|
||||
|
||||
// Item operations
|
||||
|
||||
public addChildAt(child: GObject, index: number): GObject {
|
||||
super.addChildAt(child, index);
|
||||
|
||||
if (child instanceof GButton) {
|
||||
child.selected = false;
|
||||
child.changeStateOnClick = false;
|
||||
}
|
||||
child.on(FGUIEvents.CLICK, this._onClickItem, this);
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
public addItem(url?: string): GObject | null {
|
||||
if (!url) {
|
||||
url = this._defaultItem;
|
||||
}
|
||||
const obj = UIPackage.createObjectFromURL(url);
|
||||
if (obj) {
|
||||
return this.addChild(obj);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public addItemFromPool(url?: string): GObject | null {
|
||||
const obj = this.getFromPool(url);
|
||||
if (obj) {
|
||||
return this.addChild(obj);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public removeChildAt(index: number, bDispose?: boolean): GObject {
|
||||
const child = super.removeChildAt(index, bDispose);
|
||||
if (!bDispose) {
|
||||
child.off(FGUIEvents.CLICK, this._onClickItem, this);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
public removeChildToPoolAt(index: number): void {
|
||||
const child = super.removeChildAt(index);
|
||||
this.returnToPool(child);
|
||||
}
|
||||
|
||||
public removeChildToPool(child: GObject): void {
|
||||
super.removeChild(child);
|
||||
this.returnToPool(child);
|
||||
}
|
||||
|
||||
public removeChildrenToPool(beginIndex: number = 0, endIndex: number = -1): void {
|
||||
if (endIndex < 0 || endIndex >= this.numChildren) {
|
||||
endIndex = this.numChildren - 1;
|
||||
}
|
||||
|
||||
for (let i = beginIndex; i <= endIndex; ++i) {
|
||||
this.removeChildToPoolAt(beginIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Selection
|
||||
|
||||
public get selectedIndex(): number {
|
||||
const cnt = this.numChildren;
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
const obj = this.getChildAt(i);
|
||||
if (obj instanceof GButton && obj.selected) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public set selectedIndex(value: number) {
|
||||
if (value >= 0 && value < this.numChildren) {
|
||||
if (this._selectionMode !== EListSelectionMode.Single) {
|
||||
this.clearSelection();
|
||||
}
|
||||
this.addSelection(value);
|
||||
} else {
|
||||
this.clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
public getSelection(result?: number[]): number[] {
|
||||
if (!result) {
|
||||
result = [];
|
||||
}
|
||||
|
||||
const cnt = this.numChildren;
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
const obj = this.getChildAt(i);
|
||||
if (obj instanceof GButton && obj.selected) {
|
||||
result.push(i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public addSelection(index: number, bScrollItToView?: boolean): void {
|
||||
if (this._selectionMode === EListSelectionMode.None) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._selectionMode === EListSelectionMode.Single) {
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
if (bScrollItToView) {
|
||||
this.scrollToView(index);
|
||||
}
|
||||
|
||||
this._lastSelectedIndex = index;
|
||||
const obj = this.getChildAt(index);
|
||||
|
||||
if (obj instanceof GButton && !obj.selected) {
|
||||
obj.selected = true;
|
||||
this.updateSelectionController(index);
|
||||
}
|
||||
}
|
||||
|
||||
public removeSelection(index: number): void {
|
||||
if (this._selectionMode === EListSelectionMode.None) {
|
||||
return;
|
||||
}
|
||||
|
||||
const obj = this.getChildAt(index);
|
||||
if (obj instanceof GButton) {
|
||||
obj.selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
public clearSelection(): void {
|
||||
const cnt = this.numChildren;
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
const obj = this.getChildAt(i);
|
||||
if (obj instanceof GButton) {
|
||||
obj.selected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public selectAll(): void {
|
||||
let last = -1;
|
||||
const cnt = this.numChildren;
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
const obj = this.getChildAt(i);
|
||||
if (obj instanceof GButton && !obj.selected) {
|
||||
obj.selected = true;
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (last !== -1) {
|
||||
this.updateSelectionController(last);
|
||||
}
|
||||
}
|
||||
|
||||
public selectNone(): void {
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
public selectReverse(): void {
|
||||
let last = -1;
|
||||
const cnt = this.numChildren;
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
const obj = this.getChildAt(i);
|
||||
if (obj instanceof GButton) {
|
||||
obj.selected = !obj.selected;
|
||||
if (obj.selected) {
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (last !== -1) {
|
||||
this.updateSelectionController(last);
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll
|
||||
|
||||
public scrollToView(index: number, bAni?: boolean, bSetFirst?: boolean): void {
|
||||
const obj = this.getChildAt(index);
|
||||
if (obj && this._scrollPane) {
|
||||
this._scrollPane.scrollToView(obj, bAni, bSetFirst);
|
||||
}
|
||||
}
|
||||
|
||||
// Item count
|
||||
|
||||
public get numItems(): number {
|
||||
return this.numChildren;
|
||||
}
|
||||
|
||||
public set numItems(value: number) {
|
||||
const cnt = this.numChildren;
|
||||
if (value > cnt) {
|
||||
for (let i = cnt; i < value; i++) {
|
||||
if (this.itemProvider) {
|
||||
this.addItemFromPool(this.itemProvider(i));
|
||||
} else {
|
||||
this.addItemFromPool();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.removeChildrenToPool(value, cnt);
|
||||
}
|
||||
|
||||
if (this.itemRenderer) {
|
||||
for (let i = 0; i < value; i++) {
|
||||
const child = this.getChildAt(i);
|
||||
if (child) {
|
||||
this.itemRenderer(i, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Size
|
||||
|
||||
public resizeToFit(itemCount?: number, minSize?: number): void {
|
||||
if (itemCount == null) itemCount = 100000;
|
||||
if (minSize == null) minSize = 0;
|
||||
|
||||
this.ensureBoundsCorrect();
|
||||
|
||||
let curCount = this.numItems;
|
||||
if (itemCount > curCount) {
|
||||
itemCount = curCount;
|
||||
}
|
||||
|
||||
if (itemCount === 0) {
|
||||
if (this._layout === EListLayoutType.SingleColumn ||
|
||||
this._layout === EListLayoutType.FlowHorizontal) {
|
||||
this.viewHeight = minSize;
|
||||
} else {
|
||||
this.viewWidth = minSize;
|
||||
}
|
||||
} else {
|
||||
let i = itemCount - 1;
|
||||
let obj: GObject | null = null;
|
||||
while (i >= 0) {
|
||||
obj = this.getChildAt(i);
|
||||
if (!this.foldInvisibleItems || obj?.visible) {
|
||||
break;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
if (i < 0 || !obj) {
|
||||
if (this._layout === EListLayoutType.SingleColumn ||
|
||||
this._layout === EListLayoutType.FlowHorizontal) {
|
||||
this.viewHeight = minSize;
|
||||
} else {
|
||||
this.viewWidth = minSize;
|
||||
}
|
||||
} else {
|
||||
let size = 0;
|
||||
if (this._layout === EListLayoutType.SingleColumn ||
|
||||
this._layout === EListLayoutType.FlowHorizontal) {
|
||||
size = obj.y + obj.height;
|
||||
if (size < minSize) size = minSize;
|
||||
this.viewHeight = size;
|
||||
} else {
|
||||
size = obj.x + obj.width;
|
||||
if (size < minSize) size = minSize;
|
||||
this.viewWidth = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getMaxItemWidth(): number {
|
||||
const cnt = this.numChildren;
|
||||
let max = 0;
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
const child = this.getChildAt(i);
|
||||
if (child && child.width > max) {
|
||||
max = child.width;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
// View size helpers
|
||||
|
||||
public get viewWidth(): number {
|
||||
if (this._scrollPane) {
|
||||
return this._scrollPane.viewWidth;
|
||||
}
|
||||
return this.width - this._listMargin.left - this._listMargin.right;
|
||||
}
|
||||
|
||||
public set viewWidth(value: number) {
|
||||
if (this._scrollPane) {
|
||||
// Adjust component width
|
||||
}
|
||||
this.width = value + this._listMargin.left + this._listMargin.right;
|
||||
}
|
||||
|
||||
public get viewHeight(): number {
|
||||
if (this._scrollPane) {
|
||||
return this._scrollPane.viewHeight;
|
||||
}
|
||||
return this.height - this._listMargin.top - this._listMargin.bottom;
|
||||
}
|
||||
|
||||
public set viewHeight(value: number) {
|
||||
if (this._scrollPane) {
|
||||
// Adjust component height
|
||||
}
|
||||
this.height = value + this._listMargin.top + this._listMargin.bottom;
|
||||
}
|
||||
|
||||
protected handleSizeChanged(): void {
|
||||
super.handleSizeChanged();
|
||||
this.setBoundsChangedFlag();
|
||||
}
|
||||
|
||||
public handleControllerChanged(c: Controller): void {
|
||||
super.handleControllerChanged(c);
|
||||
|
||||
if (this._selectionController === c) {
|
||||
this.selectedIndex = c.selectedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
private _onClickItem(item: GObject): void {
|
||||
if (this._scrollPane && this._scrollPane.isDragged) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setSelectionOnEvent(item);
|
||||
|
||||
if (this._scrollPane && this.scrollItemToViewOnClick) {
|
||||
this._scrollPane.scrollToView(item, true);
|
||||
}
|
||||
|
||||
this.emit(FGUIEvents.CLICK_ITEM, item);
|
||||
}
|
||||
|
||||
private setSelectionOnEvent(item: GObject): void {
|
||||
if (!(item instanceof GButton) || this._selectionMode === EListSelectionMode.None) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = this.getChildIndex(item);
|
||||
|
||||
if (this._selectionMode === EListSelectionMode.Single) {
|
||||
if (!item.selected) {
|
||||
this.clearSelectionExcept(item);
|
||||
item.selected = true;
|
||||
}
|
||||
} else {
|
||||
if (this._selectionMode === EListSelectionMode.MultipleSingleClick) {
|
||||
item.selected = !item.selected;
|
||||
} else {
|
||||
if (!item.selected) {
|
||||
this.clearSelectionExcept(item);
|
||||
item.selected = true;
|
||||
} else {
|
||||
this.clearSelectionExcept(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._lastSelectedIndex = index;
|
||||
|
||||
if (item.selected) {
|
||||
this.updateSelectionController(index);
|
||||
}
|
||||
}
|
||||
|
||||
private clearSelectionExcept(g: GObject): void {
|
||||
const cnt = this.numChildren;
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
const obj = this.getChildAt(i);
|
||||
if (obj instanceof GButton && obj !== g) {
|
||||
obj.selected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateSelectionController(index: number): void {
|
||||
if (this._selectionController && !this._selectionController.changing &&
|
||||
index < this._selectionController.pageCount) {
|
||||
const c = this._selectionController;
|
||||
this._selectionController = null;
|
||||
c.selectedIndex = index;
|
||||
this._selectionController = c;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup from buffer
|
||||
|
||||
public setup_beforeAdd(buffer: ByteBuffer, beginPos: number): void {
|
||||
super.setup_beforeAdd(buffer, beginPos);
|
||||
|
||||
buffer.seek(beginPos, 5);
|
||||
|
||||
this._layout = buffer.readByte();
|
||||
this._selectionMode = buffer.readByte();
|
||||
|
||||
const i1 = buffer.readByte();
|
||||
this._align = i1 === 0 ? EAlignType.Left : (i1 === 1 ? EAlignType.Center : EAlignType.Right);
|
||||
|
||||
const i2 = buffer.readByte();
|
||||
this._verticalAlign = i2 === 0 ? EVertAlignType.Top : (i2 === 1 ? EVertAlignType.Middle : EVertAlignType.Bottom);
|
||||
|
||||
this._lineGap = buffer.getInt16();
|
||||
this._columnGap = buffer.getInt16();
|
||||
this._lineCount = buffer.getInt16();
|
||||
this._columnCount = buffer.getInt16();
|
||||
this._autoResizeItem = buffer.readBool();
|
||||
this._childrenRenderOrder = buffer.readByte() as EChildrenRenderOrder;
|
||||
this._apexIndex = buffer.getInt16();
|
||||
|
||||
if (buffer.readBool()) {
|
||||
this._listMargin.top = buffer.getInt32();
|
||||
this._listMargin.bottom = buffer.getInt32();
|
||||
this._listMargin.left = buffer.getInt32();
|
||||
this._listMargin.right = buffer.getInt32();
|
||||
}
|
||||
|
||||
const overflow = buffer.readByte() as EOverflowType;
|
||||
if (overflow === EOverflowType.Scroll) {
|
||||
const savedPos = buffer.position;
|
||||
buffer.seek(beginPos, 7);
|
||||
this.setupScroll(buffer);
|
||||
buffer.position = savedPos;
|
||||
} else {
|
||||
this.setupOverflow(overflow);
|
||||
}
|
||||
|
||||
if (buffer.readBool()) {
|
||||
buffer.skip(8); // clipSoftness
|
||||
}
|
||||
|
||||
if (buffer.version >= 2) {
|
||||
this.scrollItemToViewOnClick = buffer.readBool();
|
||||
this.foldInvisibleItems = buffer.readBool();
|
||||
}
|
||||
|
||||
buffer.seek(beginPos, 8);
|
||||
this._defaultItem = buffer.readS();
|
||||
this.readItems(buffer);
|
||||
}
|
||||
|
||||
protected readItems(buffer: ByteBuffer): void {
|
||||
const cnt = buffer.getInt16();
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
const nextPos = buffer.getInt16() + buffer.position;
|
||||
|
||||
let str = buffer.readS();
|
||||
if (!str) {
|
||||
str = this._defaultItem;
|
||||
if (!str) {
|
||||
buffer.position = nextPos;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const obj = this.getFromPool(str);
|
||||
if (obj) {
|
||||
this.addChild(obj);
|
||||
this.setupItem(buffer, obj);
|
||||
}
|
||||
|
||||
buffer.position = nextPos;
|
||||
}
|
||||
}
|
||||
|
||||
protected setupItem(buffer: ByteBuffer, obj: GObject): void {
|
||||
let str = buffer.readS();
|
||||
if (str) {
|
||||
obj.text = str;
|
||||
}
|
||||
str = buffer.readS();
|
||||
if (str && obj instanceof GButton) {
|
||||
obj.selectedTitle = str;
|
||||
}
|
||||
str = buffer.readS();
|
||||
if (str) {
|
||||
obj.icon = str;
|
||||
}
|
||||
str = buffer.readS();
|
||||
if (str && obj instanceof GButton) {
|
||||
obj.selectedIcon = str;
|
||||
}
|
||||
str = buffer.readS();
|
||||
if (str) {
|
||||
obj.name = str;
|
||||
}
|
||||
|
||||
if (obj instanceof GComponent) {
|
||||
const cnt = buffer.getInt16();
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
const cc = obj.getController(buffer.readS());
|
||||
const pageId = buffer.readS();
|
||||
if (cc) {
|
||||
cc.selectedPageId = pageId;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.version >= 2) {
|
||||
const cnt2 = buffer.getInt16();
|
||||
for (let i = 0; i < cnt2; i++) {
|
||||
const target = buffer.readS();
|
||||
const propertyId = buffer.getInt16();
|
||||
const value = buffer.readS();
|
||||
const obj2 = obj.getChildByPath(target);
|
||||
if (obj2) {
|
||||
obj2.setProp(propertyId, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public setup_afterAdd(buffer: ByteBuffer, beginPos: number): void {
|
||||
super.setup_afterAdd(buffer, beginPos);
|
||||
|
||||
buffer.seek(beginPos, 6);
|
||||
|
||||
const i = buffer.getInt16();
|
||||
if (i !== -1 && this._parent) {
|
||||
this._selectionController = this._parent.getControllerAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
741
packages/rendering/fairygui/src/widgets/GLoader.ts
Normal file
741
packages/rendering/fairygui/src/widgets/GLoader.ts
Normal file
@@ -0,0 +1,741 @@
|
||||
import { GObject } from '../core/GObject';
|
||||
import { GComponent } from '../core/GComponent';
|
||||
import { GObjectPool } from '../core/GObjectPool';
|
||||
import { MovieClip, type IFrame } from '../display/MovieClip';
|
||||
import { Container } from '../display/Container';
|
||||
import type { ISpriteTexture } from '../display/Image';
|
||||
import { UIPackage } from '../package/UIPackage';
|
||||
import { getUIConfig } from '../core/UIConfig';
|
||||
import {
|
||||
ELoaderFillType,
|
||||
EAlignType,
|
||||
EVertAlignType,
|
||||
EPackageItemType,
|
||||
EObjectPropID,
|
||||
EFillMethod
|
||||
} from '../core/FieldTypes';
|
||||
import { Rectangle } from '../utils/MathTypes';
|
||||
import type { ByteBuffer } from '../utils/ByteBuffer';
|
||||
import type { PackageItem } from '../package/PackageItem';
|
||||
|
||||
/**
|
||||
* GLoader
|
||||
*
|
||||
* Content loader component for loading images, movie clips, and components.
|
||||
* Supports various fill modes, alignment, and automatic sizing.
|
||||
*
|
||||
* 内容加载器组件,用于加载图像、动画和组件
|
||||
* 支持多种填充模式、对齐方式和自动尺寸
|
||||
*
|
||||
* Features:
|
||||
* - Load images from package or external URL
|
||||
* - Load movie clips (animations)
|
||||
* - Load components as content
|
||||
* - Multiple fill modes (none, scale, fit, etc.)
|
||||
* - Alignment control
|
||||
* - Error sign display
|
||||
*/
|
||||
export class GLoader extends GObject {
|
||||
private _url: string = '';
|
||||
private _align: EAlignType = EAlignType.Left;
|
||||
private _valign: EVertAlignType = EVertAlignType.Top;
|
||||
private _autoSize: boolean = false;
|
||||
private _fill: ELoaderFillType = ELoaderFillType.None;
|
||||
private _shrinkOnly: boolean = false;
|
||||
private _useResize: boolean = false;
|
||||
private _showErrorSign: boolean = true;
|
||||
private _contentItem: PackageItem | null = null;
|
||||
private _content!: MovieClip;
|
||||
private _errorSign: GObject | null = null;
|
||||
private _content2: GComponent | null = null;
|
||||
private _updatingLayout: boolean = false;
|
||||
|
||||
private static _errorSignPool: GObjectPool = new GObjectPool();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected createDisplayObject(): void {
|
||||
this._displayObject = new Container();
|
||||
this._displayObject.gOwner = this;
|
||||
this._displayObject.touchable = true;
|
||||
|
||||
this._content = new MovieClip();
|
||||
this._displayObject.addChild(this._content);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this._contentItem && this._content?.texture) {
|
||||
this.freeExternal(this._content.texture);
|
||||
}
|
||||
|
||||
if (this._content2) {
|
||||
this._content2.dispose();
|
||||
this._content2 = null;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set resource URL
|
||||
* 获取/设置资源 URL
|
||||
*/
|
||||
public get url(): string {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
public set url(value: string) {
|
||||
if (this._url === value) return;
|
||||
|
||||
this._url = value;
|
||||
this.loadContent();
|
||||
this.updateGear(7);
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon alias for url
|
||||
* URL 的图标别名
|
||||
*/
|
||||
public get icon(): string {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
public set icon(value: string) {
|
||||
this.url = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set horizontal alignment
|
||||
* 获取/设置水平对齐
|
||||
*/
|
||||
public get align(): EAlignType {
|
||||
return this._align;
|
||||
}
|
||||
|
||||
public set align(value: EAlignType) {
|
||||
if (this._align !== value) {
|
||||
this._align = value;
|
||||
this.updateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set vertical alignment
|
||||
* 获取/设置垂直对齐
|
||||
*/
|
||||
public get verticalAlign(): EVertAlignType {
|
||||
return this._valign;
|
||||
}
|
||||
|
||||
public set verticalAlign(value: EVertAlignType) {
|
||||
if (this._valign !== value) {
|
||||
this._valign = value;
|
||||
this.updateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set fill type
|
||||
* 获取/设置填充类型
|
||||
*/
|
||||
public get fill(): ELoaderFillType {
|
||||
return this._fill;
|
||||
}
|
||||
|
||||
public set fill(value: ELoaderFillType) {
|
||||
if (this._fill !== value) {
|
||||
this._fill = value;
|
||||
this.updateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set shrink only mode
|
||||
* 获取/设置仅缩小模式
|
||||
*/
|
||||
public get shrinkOnly(): boolean {
|
||||
return this._shrinkOnly;
|
||||
}
|
||||
|
||||
public set shrinkOnly(value: boolean) {
|
||||
if (this._shrinkOnly !== value) {
|
||||
this._shrinkOnly = value;
|
||||
this.updateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set use resize mode
|
||||
* 获取/设置使用 resize 模式
|
||||
*/
|
||||
public get useResize(): boolean {
|
||||
return this._useResize;
|
||||
}
|
||||
|
||||
public set useResize(value: boolean) {
|
||||
if (this._useResize !== value) {
|
||||
this._useResize = value;
|
||||
this.updateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set auto size mode
|
||||
* 获取/设置自动尺寸模式
|
||||
*/
|
||||
public get autoSize(): boolean {
|
||||
return this._autoSize;
|
||||
}
|
||||
|
||||
public set autoSize(value: boolean) {
|
||||
if (this._autoSize !== value) {
|
||||
this._autoSize = value;
|
||||
this.updateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set playing state (for movie clips)
|
||||
* 获取/设置播放状态(用于动画)
|
||||
*/
|
||||
public get playing(): boolean {
|
||||
return this._content?.playing ?? false;
|
||||
}
|
||||
|
||||
public set playing(value: boolean) {
|
||||
if (this._content && this._content.playing !== value) {
|
||||
this._content.playing = value;
|
||||
this.updateGear(5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set current frame (for movie clips)
|
||||
* 获取/设置当前帧(用于动画)
|
||||
*/
|
||||
public get frame(): number {
|
||||
return this._content?.frame ?? 0;
|
||||
}
|
||||
|
||||
public set frame(value: number) {
|
||||
if (this._content && this._content.frame !== value) {
|
||||
this._content.frame = value;
|
||||
this.updateGear(5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set color tint
|
||||
* 获取/设置颜色着色
|
||||
*/
|
||||
public get color(): string {
|
||||
return this._content?.color ?? '#FFFFFF';
|
||||
}
|
||||
|
||||
public set color(value: string) {
|
||||
if (this._content && this._content.color !== value) {
|
||||
this._content.color = value;
|
||||
this.updateGear(4);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set fill method
|
||||
* 获取/设置填充方法
|
||||
*/
|
||||
public get fillMethod(): EFillMethod {
|
||||
return this._content?.fillMethod ?? EFillMethod.None;
|
||||
}
|
||||
|
||||
public set fillMethod(value: EFillMethod) {
|
||||
if (this._content) {
|
||||
this._content.fillMethod = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set fill origin
|
||||
* 获取/设置填充起点
|
||||
*/
|
||||
public get fillOrigin(): number {
|
||||
return this._content?.fillOrigin ?? 0;
|
||||
}
|
||||
|
||||
public set fillOrigin(value: number) {
|
||||
if (this._content) {
|
||||
this._content.fillOrigin = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set fill clockwise
|
||||
* 获取/设置顺时针填充
|
||||
*/
|
||||
public get fillClockwise(): boolean {
|
||||
return this._content?.fillClockwise ?? true;
|
||||
}
|
||||
|
||||
public set fillClockwise(value: boolean) {
|
||||
if (this._content) {
|
||||
this._content.fillClockwise = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set fill amount
|
||||
* 获取/设置填充量
|
||||
*/
|
||||
public get fillAmount(): number {
|
||||
return this._content?.fillAmount ?? 1;
|
||||
}
|
||||
|
||||
public set fillAmount(value: number) {
|
||||
if (this._content) {
|
||||
this._content.fillAmount = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set show error sign
|
||||
* 获取/设置显示错误标志
|
||||
*/
|
||||
public get showErrorSign(): boolean {
|
||||
return this._showErrorSign;
|
||||
}
|
||||
|
||||
public set showErrorSign(value: boolean) {
|
||||
this._showErrorSign = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get internal content (MovieClip)
|
||||
* 获取内部内容(MovieClip)
|
||||
*/
|
||||
public get content(): MovieClip {
|
||||
return this._content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get component content (when loading component)
|
||||
* 获取组件内容(当加载组件时)
|
||||
*/
|
||||
public get component(): GComponent | null {
|
||||
return this._content2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load content based on URL
|
||||
* 根据 URL 加载内容
|
||||
*/
|
||||
protected loadContent(): void {
|
||||
this.clearContent();
|
||||
|
||||
if (!this._url) return;
|
||||
|
||||
if (this._url.startsWith('ui://')) {
|
||||
this.loadFromPackage(this._url);
|
||||
} else {
|
||||
this.loadExternal();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load content from package
|
||||
* 从包加载内容
|
||||
*/
|
||||
protected loadFromPackage(itemURL: string): void {
|
||||
this._contentItem = UIPackage.getItemByURL(itemURL);
|
||||
|
||||
if (this._contentItem) {
|
||||
// Get branch and high resolution versions
|
||||
const branchItem = this._contentItem.getBranch();
|
||||
this.sourceWidth = branchItem.width;
|
||||
this.sourceHeight = branchItem.height;
|
||||
|
||||
const hiResItem = branchItem.getHighResolution();
|
||||
hiResItem.load();
|
||||
|
||||
if (this._autoSize) {
|
||||
this.setSize(this.sourceWidth, this.sourceHeight);
|
||||
}
|
||||
|
||||
if (hiResItem.type === EPackageItemType.Image) {
|
||||
if (!hiResItem.texture) {
|
||||
this.setErrorState();
|
||||
} else {
|
||||
this._content.texture = hiResItem.texture;
|
||||
this._content.scale9Grid = hiResItem.scale9Grid
|
||||
? new Rectangle(
|
||||
hiResItem.scale9Grid.x,
|
||||
hiResItem.scale9Grid.y,
|
||||
hiResItem.scale9Grid.width,
|
||||
hiResItem.scale9Grid.height
|
||||
)
|
||||
: null;
|
||||
this._content.scaleByTile = hiResItem.scaleByTile || false;
|
||||
this._content.tileGridIndice = hiResItem.tileGridIndice || 0;
|
||||
this.sourceWidth = hiResItem.width;
|
||||
this.sourceHeight = hiResItem.height;
|
||||
this.updateLayout();
|
||||
}
|
||||
} else if (hiResItem.type === EPackageItemType.MovieClip) {
|
||||
this.sourceWidth = hiResItem.width;
|
||||
this.sourceHeight = hiResItem.height;
|
||||
this._content.interval = hiResItem.interval || 0;
|
||||
this._content.swing = hiResItem.swing || false;
|
||||
this._content.repeatDelay = hiResItem.repeatDelay || 0;
|
||||
this._content.frames = hiResItem.frames || [];
|
||||
this.updateLayout();
|
||||
} else if (hiResItem.type === EPackageItemType.Component) {
|
||||
const obj = UIPackage.createObjectFromURL(itemURL);
|
||||
if (!obj) {
|
||||
this.setErrorState();
|
||||
} else if (!(obj instanceof GComponent)) {
|
||||
obj.dispose();
|
||||
this.setErrorState();
|
||||
} else {
|
||||
this._content2 = obj;
|
||||
if (this._displayObject && this._content2.displayObject) {
|
||||
this._displayObject.addChild(this._content2.displayObject);
|
||||
}
|
||||
this.updateLayout();
|
||||
}
|
||||
} else {
|
||||
this.setErrorState();
|
||||
}
|
||||
} else {
|
||||
this.setErrorState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load external resource (to be overridden)
|
||||
* 加载外部资源(可重写)
|
||||
*/
|
||||
protected loadExternal(): void {
|
||||
// Default implementation: load image via fetch
|
||||
this.loadExternalImage(this._url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load external image
|
||||
* 加载外部图像
|
||||
*/
|
||||
protected async loadExternalImage(url: string): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
const bitmap = await createImageBitmap(blob);
|
||||
|
||||
// Create texture ID from URL
|
||||
this.onExternalLoadSuccess(url, bitmap.width, bitmap.height);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load external image: ${url}`, error);
|
||||
this.onExternalLoadFailed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Free external resource
|
||||
* 释放外部资源
|
||||
*/
|
||||
protected freeExternal(_texture: string | number | ISpriteTexture | null): void {
|
||||
// Override in subclass if needed
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when external resource loaded successfully
|
||||
* 外部资源加载成功时调用
|
||||
*/
|
||||
protected onExternalLoadSuccess(textureId: string | number, width: number, height: number): void {
|
||||
this._content.texture = textureId;
|
||||
this._content.scale9Grid = null;
|
||||
this._content.scaleByTile = false;
|
||||
this.sourceWidth = width;
|
||||
this.sourceHeight = height;
|
||||
this.updateLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when external resource failed to load
|
||||
* 外部资源加载失败时调用
|
||||
*/
|
||||
protected onExternalLoadFailed(): void {
|
||||
this.setErrorState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set error state and show error sign
|
||||
* 设置错误状态并显示错误标志
|
||||
*/
|
||||
private setErrorState(): void {
|
||||
if (!this._showErrorSign) return;
|
||||
|
||||
if (!this._errorSign) {
|
||||
const errorSignUrl = getUIConfig('loaderErrorSign');
|
||||
if (errorSignUrl) {
|
||||
this._errorSign = GLoader._errorSignPool.getObject(errorSignUrl);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._errorSign) {
|
||||
this._errorSign.setSize(this.width, this.height);
|
||||
if (this._displayObject && this._errorSign.displayObject) {
|
||||
this._displayObject.addChild(this._errorSign.displayObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear error state
|
||||
* 清除错误状态
|
||||
*/
|
||||
private clearErrorState(): void {
|
||||
if (this._errorSign) {
|
||||
if (this._displayObject && this._errorSign.displayObject) {
|
||||
this._displayObject.removeChild(this._errorSign.displayObject);
|
||||
}
|
||||
GLoader._errorSignPool.returnObject(this._errorSign);
|
||||
this._errorSign = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update content layout
|
||||
* 更新内容布局
|
||||
*/
|
||||
protected updateLayout(): void {
|
||||
if (!this._content) return;
|
||||
|
||||
if (!this._content2 && !this._content.texture && !this._content.frames?.length) {
|
||||
if (this._autoSize) {
|
||||
this._updatingLayout = true;
|
||||
this.setSize(50, 30);
|
||||
this._updatingLayout = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let cw = this.sourceWidth;
|
||||
let ch = this.sourceHeight;
|
||||
|
||||
if (this._autoSize) {
|
||||
this._updatingLayout = true;
|
||||
if (cw === 0) cw = 50;
|
||||
if (ch === 0) ch = 30;
|
||||
this.setSize(cw, ch);
|
||||
this._updatingLayout = false;
|
||||
|
||||
if (cw === this._width && ch === this._height) {
|
||||
if (this._content2) {
|
||||
this._content2.setXY(0, 0);
|
||||
if (this._useResize) {
|
||||
this._content2.setSize(cw, ch);
|
||||
} else {
|
||||
this._content2.setScale(1, 1);
|
||||
}
|
||||
} else {
|
||||
this._content.width = cw;
|
||||
this._content.height = ch;
|
||||
this._content.x = 0;
|
||||
this._content.y = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let sx = 1;
|
||||
let sy = 1;
|
||||
|
||||
if (this._fill !== ELoaderFillType.None) {
|
||||
sx = this.width / this.sourceWidth;
|
||||
sy = this.height / this.sourceHeight;
|
||||
|
||||
if (sx !== 1 || sy !== 1) {
|
||||
if (this._fill === ELoaderFillType.ScaleMatchHeight) {
|
||||
sx = sy;
|
||||
} else if (this._fill === ELoaderFillType.ScaleMatchWidth) {
|
||||
sy = sx;
|
||||
} else if (this._fill === ELoaderFillType.Scale) {
|
||||
if (sx > sy) sx = sy;
|
||||
else sy = sx;
|
||||
} else if (this._fill === ELoaderFillType.ScaleNoBorder) {
|
||||
if (sx > sy) sy = sx;
|
||||
else sx = sy;
|
||||
}
|
||||
|
||||
if (this._shrinkOnly) {
|
||||
if (sx > 1) sx = 1;
|
||||
if (sy > 1) sy = 1;
|
||||
}
|
||||
|
||||
cw = this.sourceWidth * sx;
|
||||
ch = this.sourceHeight * sy;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._content2) {
|
||||
if (this._useResize) {
|
||||
this._content2.setSize(cw, ch);
|
||||
} else {
|
||||
this._content2.setScale(sx, sy);
|
||||
}
|
||||
} else {
|
||||
this._content.width = cw;
|
||||
this._content.height = ch;
|
||||
}
|
||||
|
||||
// Calculate position based on alignment
|
||||
let nx = 0;
|
||||
let ny = 0;
|
||||
|
||||
if (this._align === EAlignType.Center) {
|
||||
nx = Math.floor((this.width - cw) / 2);
|
||||
} else if (this._align === EAlignType.Right) {
|
||||
nx = this.width - cw;
|
||||
}
|
||||
|
||||
if (this._valign === EVertAlignType.Middle) {
|
||||
ny = Math.floor((this.height - ch) / 2);
|
||||
} else if (this._valign === EVertAlignType.Bottom) {
|
||||
ny = this.height - ch;
|
||||
}
|
||||
|
||||
if (this._content2) {
|
||||
this._content2.setXY(nx, ny);
|
||||
} else {
|
||||
this._content.x = nx;
|
||||
this._content.y = ny;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear content
|
||||
* 清除内容
|
||||
*/
|
||||
private clearContent(): void {
|
||||
this.clearErrorState();
|
||||
|
||||
if (this._content) {
|
||||
if (!this._contentItem && this._content.texture) {
|
||||
this.freeExternal(this._content.texture);
|
||||
}
|
||||
this._content.texture = null;
|
||||
this._content.frames = [];
|
||||
}
|
||||
|
||||
if (this._content2) {
|
||||
this._content2.dispose();
|
||||
this._content2 = null;
|
||||
}
|
||||
|
||||
this._contentItem = null;
|
||||
}
|
||||
|
||||
protected handleSizeChanged(): void {
|
||||
super.handleSizeChanged();
|
||||
|
||||
if (!this._updatingLayout) {
|
||||
this.updateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public getProp(index: number): any {
|
||||
switch (index) {
|
||||
case EObjectPropID.Color:
|
||||
return this.color;
|
||||
case EObjectPropID.Playing:
|
||||
return this.playing;
|
||||
case EObjectPropID.Frame:
|
||||
return this.frame;
|
||||
case EObjectPropID.TimeScale:
|
||||
return this._content?.timeScale ?? 1;
|
||||
default:
|
||||
return super.getProp(index);
|
||||
}
|
||||
}
|
||||
|
||||
public setProp(index: number, value: any): void {
|
||||
switch (index) {
|
||||
case EObjectPropID.Color:
|
||||
this.color = value;
|
||||
break;
|
||||
case EObjectPropID.Playing:
|
||||
this.playing = value;
|
||||
break;
|
||||
case EObjectPropID.Frame:
|
||||
this.frame = value;
|
||||
break;
|
||||
case EObjectPropID.TimeScale:
|
||||
if (this._content) {
|
||||
this._content.timeScale = value;
|
||||
}
|
||||
break;
|
||||
case EObjectPropID.DeltaTime:
|
||||
if (this._content) {
|
||||
this._content.advance(value);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
super.setProp(index, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public setup_beforeAdd(buffer: ByteBuffer, beginPos: number): void {
|
||||
super.setup_beforeAdd(buffer, beginPos);
|
||||
|
||||
buffer.seek(beginPos, 5);
|
||||
|
||||
this._url = buffer.readS();
|
||||
|
||||
const alignValue = buffer.readByte();
|
||||
this._align =
|
||||
alignValue === 0 ? EAlignType.Left : alignValue === 1 ? EAlignType.Center : EAlignType.Right;
|
||||
|
||||
const valignValue = buffer.readByte();
|
||||
this._valign =
|
||||
valignValue === 0 ? EVertAlignType.Top : valignValue === 1 ? EVertAlignType.Middle : EVertAlignType.Bottom;
|
||||
|
||||
this._fill = buffer.readByte();
|
||||
this._shrinkOnly = buffer.readBool();
|
||||
this._autoSize = buffer.readBool();
|
||||
this._showErrorSign = buffer.readBool();
|
||||
|
||||
const playingValue = buffer.readBool();
|
||||
const frameValue = buffer.getInt32();
|
||||
if (this._content) {
|
||||
this._content.playing = playingValue;
|
||||
this._content.frame = frameValue;
|
||||
}
|
||||
|
||||
if (buffer.readBool()) {
|
||||
this.color = buffer.readS();
|
||||
}
|
||||
|
||||
const fillMethodValue = buffer.readByte();
|
||||
if (this._content) {
|
||||
this._content.fillMethod = fillMethodValue;
|
||||
if (this._content.fillMethod !== EFillMethod.None) {
|
||||
this._content.fillOrigin = buffer.readByte();
|
||||
this._content.fillClockwise = buffer.readBool();
|
||||
this._content.fillAmount = buffer.getFloat32();
|
||||
}
|
||||
} else if (fillMethodValue !== EFillMethod.None) {
|
||||
// Skip bytes if _content not ready
|
||||
buffer.readByte(); // fillOrigin
|
||||
buffer.readBool(); // fillClockwise
|
||||
buffer.getFloat32(); // fillAmount
|
||||
}
|
||||
|
||||
if (buffer.version >= 7) {
|
||||
this._useResize = buffer.readBool();
|
||||
}
|
||||
|
||||
if (this._url) {
|
||||
this.loadContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
261
packages/rendering/fairygui/src/widgets/GMovieClip.ts
Normal file
261
packages/rendering/fairygui/src/widgets/GMovieClip.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import { GObject } from '../core/GObject';
|
||||
import { MovieClip, type IFrame } from '../display/MovieClip';
|
||||
import { EFlipType, EObjectPropID } from '../core/FieldTypes';
|
||||
import type { ByteBuffer } from '../utils/ByteBuffer';
|
||||
import type { PackageItem } from '../package/PackageItem';
|
||||
|
||||
/**
|
||||
* GMovieClip
|
||||
*
|
||||
* Movie clip display object for FairyGUI animations.
|
||||
*
|
||||
* FairyGUI 动画显示对象
|
||||
*/
|
||||
export class GMovieClip extends GObject {
|
||||
private _movieClip!: MovieClip;
|
||||
private _flip: EFlipType = EFlipType.None;
|
||||
private _contentItem: PackageItem | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.ensureMovieClip();
|
||||
}
|
||||
|
||||
private ensureMovieClip(): void {
|
||||
if (!this._movieClip) {
|
||||
this.createDisplayObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internal movie clip display object
|
||||
* 获取内部动画显示对象
|
||||
*/
|
||||
public get movieClip(): MovieClip {
|
||||
return this._movieClip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set playing state
|
||||
* 获取/设置播放状态
|
||||
*/
|
||||
public get playing(): boolean {
|
||||
return this._movieClip.playing;
|
||||
}
|
||||
|
||||
public set playing(value: boolean) {
|
||||
if (this._movieClip && this._movieClip.playing !== value) {
|
||||
this._movieClip.playing = value;
|
||||
this.updateGear(5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set current frame
|
||||
* 获取/设置当前帧
|
||||
*/
|
||||
public get frame(): number {
|
||||
return this._movieClip.frame;
|
||||
}
|
||||
|
||||
public set frame(value: number) {
|
||||
if (this._movieClip && this._movieClip.frame !== value) {
|
||||
this._movieClip.frame = value;
|
||||
this.updateGear(5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set color tint
|
||||
* 获取/设置颜色着色
|
||||
*/
|
||||
public get color(): string {
|
||||
return this._movieClip.color;
|
||||
}
|
||||
|
||||
public set color(value: string) {
|
||||
if (this._movieClip) {
|
||||
this._movieClip.color = value;
|
||||
this.updateGear(4);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set flip type
|
||||
* 获取/设置翻转类型
|
||||
*/
|
||||
public get flip(): EFlipType {
|
||||
return this._flip;
|
||||
}
|
||||
|
||||
public set flip(value: EFlipType) {
|
||||
if (this._flip !== value) {
|
||||
this._flip = value;
|
||||
|
||||
let sx = 1;
|
||||
let sy = 1;
|
||||
if (this._flip === EFlipType.Horizontal || this._flip === EFlipType.Both) {
|
||||
sx = -1;
|
||||
}
|
||||
if (this._flip === EFlipType.Vertical || this._flip === EFlipType.Both) {
|
||||
sy = -1;
|
||||
}
|
||||
this.setScale(sx, sy);
|
||||
this.handleXYChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set time scale
|
||||
* 获取/设置时间缩放
|
||||
*/
|
||||
public get timeScale(): number {
|
||||
return this._movieClip.timeScale;
|
||||
}
|
||||
|
||||
public set timeScale(value: number) {
|
||||
if (this._movieClip) {
|
||||
this._movieClip.timeScale = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewind to beginning
|
||||
* 回到开始
|
||||
*/
|
||||
public rewind(): void {
|
||||
this._movieClip.rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync status with another movie clip
|
||||
* 同步状态
|
||||
*/
|
||||
public syncStatus(anotherMc: GMovieClip): void {
|
||||
this._movieClip.syncStatus(anotherMc._movieClip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance by time
|
||||
* 按时间前进
|
||||
*/
|
||||
public advance(time: number): void {
|
||||
this._movieClip.advance(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set play settings
|
||||
* 设置播放设置
|
||||
*/
|
||||
public setPlaySettings(
|
||||
start: number = 0,
|
||||
end: number = -1,
|
||||
times: number = 0,
|
||||
endAt: number = -1,
|
||||
endCallback?: () => void
|
||||
): void {
|
||||
this._movieClip.setPlaySettings(start, end, times, endAt, endCallback);
|
||||
}
|
||||
|
||||
protected createDisplayObject(): void {
|
||||
this._displayObject = this._movieClip = new MovieClip();
|
||||
this._displayObject.gOwner = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct from package resource
|
||||
* 从包资源构建
|
||||
*/
|
||||
public constructFromResource(): void {
|
||||
if (!this.packageItem) return;
|
||||
|
||||
this.ensureMovieClip();
|
||||
|
||||
this._contentItem = this.packageItem;
|
||||
|
||||
this.sourceWidth = this._contentItem.width;
|
||||
this.sourceHeight = this._contentItem.height;
|
||||
this.initWidth = this.sourceWidth;
|
||||
this.initHeight = this.sourceHeight;
|
||||
|
||||
// Load frames from package
|
||||
if (this._contentItem.owner) {
|
||||
this._contentItem.owner.getItemAsset(this._contentItem);
|
||||
}
|
||||
|
||||
if (this._contentItem.frames) {
|
||||
this._movieClip.interval = this._contentItem.interval;
|
||||
this._movieClip.swing = this._contentItem.swing;
|
||||
this._movieClip.repeatDelay = this._contentItem.repeatDelay;
|
||||
this._movieClip.frames = this._contentItem.frames as IFrame[];
|
||||
}
|
||||
|
||||
this.setSize(this.sourceWidth, this.sourceHeight);
|
||||
}
|
||||
|
||||
protected handleXYChanged(): void {
|
||||
super.handleXYChanged();
|
||||
|
||||
if (this._flip !== EFlipType.None) {
|
||||
if (this.scaleX === -1 && this._movieClip) {
|
||||
this._movieClip.x += this.width;
|
||||
}
|
||||
if (this.scaleY === -1 && this._movieClip) {
|
||||
this._movieClip.y += this.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getProp(index: number): any {
|
||||
switch (index) {
|
||||
case EObjectPropID.Color:
|
||||
return this.color;
|
||||
case EObjectPropID.Playing:
|
||||
return this.playing;
|
||||
case EObjectPropID.Frame:
|
||||
return this.frame;
|
||||
case EObjectPropID.TimeScale:
|
||||
return this.timeScale;
|
||||
default:
|
||||
return super.getProp(index);
|
||||
}
|
||||
}
|
||||
|
||||
public setProp(index: number, value: any): void {
|
||||
switch (index) {
|
||||
case EObjectPropID.Color:
|
||||
this.color = value;
|
||||
break;
|
||||
case EObjectPropID.Playing:
|
||||
this.playing = value;
|
||||
break;
|
||||
case EObjectPropID.Frame:
|
||||
this.frame = value;
|
||||
break;
|
||||
case EObjectPropID.TimeScale:
|
||||
this.timeScale = value;
|
||||
break;
|
||||
default:
|
||||
super.setProp(index, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public setup_beforeAdd(buffer: ByteBuffer, beginPos: number): void {
|
||||
super.setup_beforeAdd(buffer, beginPos);
|
||||
|
||||
buffer.seek(beginPos, 5);
|
||||
|
||||
if (buffer.readBool()) {
|
||||
this.color = buffer.readS();
|
||||
}
|
||||
this.flip = buffer.readByte();
|
||||
|
||||
const frameValue = buffer.getInt32();
|
||||
const playingValue = buffer.readBool();
|
||||
if (this._movieClip) {
|
||||
this._movieClip.frame = frameValue;
|
||||
this._movieClip.playing = playingValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
228
packages/rendering/fairygui/src/widgets/GProgressBar.ts
Normal file
228
packages/rendering/fairygui/src/widgets/GProgressBar.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
import { GComponent } from '../core/GComponent';
|
||||
import { GObject } from '../core/GObject';
|
||||
import { GImage } from './GImage';
|
||||
import { EProgressTitleType, EObjectPropID, EFillMethod } from '../core/FieldTypes';
|
||||
import type { ByteBuffer } from '../utils/ByteBuffer';
|
||||
|
||||
/**
|
||||
* GProgressBar
|
||||
*
|
||||
* Progress bar component.
|
||||
*
|
||||
* 进度条组件
|
||||
*/
|
||||
export class GProgressBar extends GComponent {
|
||||
private _min: number = 0;
|
||||
private _max: number = 100;
|
||||
private _value: number = 50;
|
||||
private _titleType: EProgressTitleType = EProgressTitleType.Percent;
|
||||
private _reverse: boolean = false;
|
||||
|
||||
private _titleObject: GObject | null = null;
|
||||
private _aniObject: GObject | null = null;
|
||||
private _barObjectH: GObject | null = null;
|
||||
private _barObjectV: GObject | null = null;
|
||||
private _barMaxWidth: number = 0;
|
||||
private _barMaxHeight: number = 0;
|
||||
private _barMaxWidthDelta: number = 0;
|
||||
private _barMaxHeightDelta: number = 0;
|
||||
private _barStartX: number = 0;
|
||||
private _barStartY: number = 0;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set title type
|
||||
* 获取/设置标题类型
|
||||
*/
|
||||
public get titleType(): EProgressTitleType {
|
||||
return this._titleType;
|
||||
}
|
||||
|
||||
public set titleType(value: EProgressTitleType) {
|
||||
if (this._titleType !== value) {
|
||||
this._titleType = value;
|
||||
this.update(this._value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set minimum value
|
||||
* 获取/设置最小值
|
||||
*/
|
||||
public get min(): number {
|
||||
return this._min;
|
||||
}
|
||||
|
||||
public set min(value: number) {
|
||||
if (this._min !== value) {
|
||||
this._min = value;
|
||||
this.update(this._value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set maximum value
|
||||
* 获取/设置最大值
|
||||
*/
|
||||
public get max(): number {
|
||||
return this._max;
|
||||
}
|
||||
|
||||
public set max(value: number) {
|
||||
if (this._max !== value) {
|
||||
this._max = value;
|
||||
this.update(this._value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set current value
|
||||
* 获取/设置当前值
|
||||
*/
|
||||
public get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public set value(value: number) {
|
||||
if (this._value !== value) {
|
||||
this._value = value;
|
||||
this.update(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update progress bar display
|
||||
* 更新进度条显示
|
||||
*/
|
||||
public update(newValue: number): void {
|
||||
let percent = this.clamp01((newValue - this._min) / (this._max - this._min));
|
||||
|
||||
if (this._titleObject) {
|
||||
switch (this._titleType) {
|
||||
case EProgressTitleType.Percent:
|
||||
this._titleObject.text = Math.floor(percent * 100) + '%';
|
||||
break;
|
||||
case EProgressTitleType.ValueAndMax:
|
||||
this._titleObject.text =
|
||||
Math.floor(newValue) + '/' + Math.floor(this._max);
|
||||
break;
|
||||
case EProgressTitleType.Value:
|
||||
this._titleObject.text = '' + Math.floor(newValue);
|
||||
break;
|
||||
case EProgressTitleType.Max:
|
||||
this._titleObject.text = '' + Math.floor(this._max);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const fullWidth = this.width - this._barMaxWidthDelta;
|
||||
const fullHeight = this.height - this._barMaxHeightDelta;
|
||||
|
||||
if (!this._reverse) {
|
||||
if (this._barObjectH) {
|
||||
if (!this.setFillAmount(this._barObjectH, percent)) {
|
||||
this._barObjectH.width = Math.round(fullWidth * percent);
|
||||
}
|
||||
}
|
||||
if (this._barObjectV) {
|
||||
if (!this.setFillAmount(this._barObjectV, percent)) {
|
||||
this._barObjectV.height = Math.round(fullHeight * percent);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this._barObjectH) {
|
||||
if (!this.setFillAmount(this._barObjectH, 1 - percent)) {
|
||||
this._barObjectH.width = Math.round(fullWidth * percent);
|
||||
this._barObjectH.x = this._barStartX + (fullWidth - this._barObjectH.width);
|
||||
}
|
||||
}
|
||||
if (this._barObjectV) {
|
||||
if (!this.setFillAmount(this._barObjectV, 1 - percent)) {
|
||||
this._barObjectV.height = Math.round(fullHeight * percent);
|
||||
this._barObjectV.y =
|
||||
this._barStartY + (fullHeight - this._barObjectV.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this._aniObject) {
|
||||
this._aniObject.setProp(EObjectPropID.Frame, Math.floor(percent * 100));
|
||||
}
|
||||
}
|
||||
|
||||
private setFillAmount(bar: GObject, percent: number): boolean {
|
||||
if (bar instanceof GImage && bar.fillMethod !== EFillMethod.None) {
|
||||
bar.fillAmount = percent;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private clamp01(value: number): number {
|
||||
if (value < 0) return 0;
|
||||
if (value > 1) return 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
protected constructExtension(buffer: ByteBuffer): void {
|
||||
buffer.seek(0, 6);
|
||||
|
||||
this._titleType = buffer.readByte();
|
||||
this._reverse = buffer.readBool();
|
||||
|
||||
this._titleObject = this.getChild('title');
|
||||
this._barObjectH = this.getChild('bar');
|
||||
this._barObjectV = this.getChild('bar_v');
|
||||
this._aniObject = this.getChild('ani');
|
||||
|
||||
if (this._barObjectH) {
|
||||
this._barMaxWidth = this._barObjectH.width;
|
||||
this._barMaxWidthDelta = this.width - this._barMaxWidth;
|
||||
this._barStartX = this._barObjectH.x;
|
||||
}
|
||||
if (this._barObjectV) {
|
||||
this._barMaxHeight = this._barObjectV.height;
|
||||
this._barMaxHeightDelta = this.height - this._barMaxHeight;
|
||||
this._barStartY = this._barObjectV.y;
|
||||
}
|
||||
}
|
||||
|
||||
protected handleSizeChanged(): void {
|
||||
super.handleSizeChanged();
|
||||
|
||||
if (this._barObjectH) {
|
||||
this._barMaxWidth = this.width - this._barMaxWidthDelta;
|
||||
}
|
||||
if (this._barObjectV) {
|
||||
this._barMaxHeight = this.height - this._barMaxHeightDelta;
|
||||
}
|
||||
if (!this._underConstruct) {
|
||||
this.update(this._value);
|
||||
}
|
||||
}
|
||||
|
||||
public setup_afterAdd(buffer: ByteBuffer, beginPos: number): void {
|
||||
super.setup_afterAdd(buffer, beginPos);
|
||||
|
||||
if (!buffer.seek(beginPos, 6)) {
|
||||
this.update(this._value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.readByte() !== this.packageItem?.objectType) {
|
||||
this.update(this._value);
|
||||
return;
|
||||
}
|
||||
|
||||
this._value = buffer.getInt32();
|
||||
this._max = buffer.getInt32();
|
||||
if (buffer.version >= 2) {
|
||||
this._min = buffer.getInt32();
|
||||
}
|
||||
|
||||
this.update(this._value);
|
||||
}
|
||||
}
|
||||
332
packages/rendering/fairygui/src/widgets/GSlider.ts
Normal file
332
packages/rendering/fairygui/src/widgets/GSlider.ts
Normal file
@@ -0,0 +1,332 @@
|
||||
import { GComponent } from '../core/GComponent';
|
||||
import { GObject } from '../core/GObject';
|
||||
import { FGUIEvents } from '../events/Events';
|
||||
import { EProgressTitleType } from '../core/FieldTypes';
|
||||
import { Point } from '../utils/MathTypes';
|
||||
import type { ByteBuffer } from '../utils/ByteBuffer';
|
||||
|
||||
/**
|
||||
* GSlider
|
||||
*
|
||||
* Slider component with draggable grip.
|
||||
*
|
||||
* 滑动条组件,支持拖动手柄
|
||||
*/
|
||||
export class GSlider extends GComponent {
|
||||
private _min: number = 0;
|
||||
private _max: number = 100;
|
||||
private _value: number = 50;
|
||||
private _titleType: EProgressTitleType = EProgressTitleType.Percent;
|
||||
private _reverse: boolean = false;
|
||||
private _wholeNumbers: boolean = false;
|
||||
|
||||
private _titleObject: GObject | null = null;
|
||||
private _barObjectH: GObject | null = null;
|
||||
private _barObjectV: GObject | null = null;
|
||||
private _barMaxWidth: number = 0;
|
||||
private _barMaxHeight: number = 0;
|
||||
private _barMaxWidthDelta: number = 0;
|
||||
private _barMaxHeightDelta: number = 0;
|
||||
private _gripObject: GObject | null = null;
|
||||
private _clickPos: Point = new Point();
|
||||
private _clickPercent: number = 0;
|
||||
private _barStartX: number = 0;
|
||||
private _barStartY: number = 0;
|
||||
|
||||
/** Allow click on bar to change value | 允许点击条改变值 */
|
||||
public changeOnClick: boolean = true;
|
||||
|
||||
/** Allow dragging | 允许拖动 */
|
||||
public canDrag: boolean = true;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set title type
|
||||
* 获取/设置标题类型
|
||||
*/
|
||||
public get titleType(): EProgressTitleType {
|
||||
return this._titleType;
|
||||
}
|
||||
|
||||
public set titleType(value: EProgressTitleType) {
|
||||
this._titleType = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set whole numbers mode
|
||||
* 获取/设置整数模式
|
||||
*/
|
||||
public get wholeNumbers(): boolean {
|
||||
return this._wholeNumbers;
|
||||
}
|
||||
|
||||
public set wholeNumbers(value: boolean) {
|
||||
if (this._wholeNumbers !== value) {
|
||||
this._wholeNumbers = value;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set minimum value
|
||||
* 获取/设置最小值
|
||||
*/
|
||||
public get min(): number {
|
||||
return this._min;
|
||||
}
|
||||
|
||||
public set min(value: number) {
|
||||
if (this._min !== value) {
|
||||
this._min = value;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set maximum value
|
||||
* 获取/设置最大值
|
||||
*/
|
||||
public get max(): number {
|
||||
return this._max;
|
||||
}
|
||||
|
||||
public set max(value: number) {
|
||||
if (this._max !== value) {
|
||||
this._max = value;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set current value
|
||||
* 获取/设置当前值
|
||||
*/
|
||||
public get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public set value(value: number) {
|
||||
if (this._value !== value) {
|
||||
this._value = value;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update slider display
|
||||
* 更新滑动条显示
|
||||
*/
|
||||
public update(): void {
|
||||
this.updateWithPercent(
|
||||
(this._value - this._min) / (this._max - this._min),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
private updateWithPercent(percent: number, bEmitEvent: boolean): void {
|
||||
percent = this.clamp01(percent);
|
||||
|
||||
if (bEmitEvent) {
|
||||
let newValue = this.clamp(
|
||||
this._min + (this._max - this._min) * percent,
|
||||
this._min,
|
||||
this._max
|
||||
);
|
||||
if (this._wholeNumbers) {
|
||||
newValue = Math.round(newValue);
|
||||
percent = this.clamp01((newValue - this._min) / (this._max - this._min));
|
||||
}
|
||||
|
||||
if (newValue !== this._value) {
|
||||
this._value = newValue;
|
||||
this.emit(FGUIEvents.STATE_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._titleObject) {
|
||||
switch (this._titleType) {
|
||||
case EProgressTitleType.Percent:
|
||||
this._titleObject.text = Math.floor(percent * 100) + '%';
|
||||
break;
|
||||
case EProgressTitleType.ValueAndMax:
|
||||
this._titleObject.text = this._value + '/' + this._max;
|
||||
break;
|
||||
case EProgressTitleType.Value:
|
||||
this._titleObject.text = '' + this._value;
|
||||
break;
|
||||
case EProgressTitleType.Max:
|
||||
this._titleObject.text = '' + this._max;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const fullWidth = this.width - this._barMaxWidthDelta;
|
||||
const fullHeight = this.height - this._barMaxHeightDelta;
|
||||
|
||||
if (!this._reverse) {
|
||||
if (this._barObjectH) {
|
||||
this._barObjectH.width = Math.round(fullWidth * percent);
|
||||
}
|
||||
if (this._barObjectV) {
|
||||
this._barObjectV.height = Math.round(fullHeight * percent);
|
||||
}
|
||||
} else {
|
||||
if (this._barObjectH) {
|
||||
this._barObjectH.width = Math.round(fullWidth * percent);
|
||||
this._barObjectH.x = this._barStartX + (fullWidth - this._barObjectH.width);
|
||||
}
|
||||
if (this._barObjectV) {
|
||||
this._barObjectV.height = Math.round(fullHeight * percent);
|
||||
this._barObjectV.y =
|
||||
this._barStartY + (fullHeight - this._barObjectV.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private clamp01(value: number): number {
|
||||
if (value < 0) return 0;
|
||||
if (value > 1) return 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
private clamp(value: number, min: number, max: number): number {
|
||||
if (value < min) return min;
|
||||
if (value > max) return max;
|
||||
return value;
|
||||
}
|
||||
|
||||
protected constructExtension(buffer: ByteBuffer): void {
|
||||
buffer.seek(0, 6);
|
||||
|
||||
this._titleType = buffer.readByte();
|
||||
this._reverse = buffer.readBool();
|
||||
if (buffer.version >= 2) {
|
||||
this._wholeNumbers = buffer.readBool();
|
||||
this.changeOnClick = buffer.readBool();
|
||||
}
|
||||
|
||||
this._titleObject = this.getChild('title');
|
||||
this._barObjectH = this.getChild('bar');
|
||||
this._barObjectV = this.getChild('bar_v');
|
||||
this._gripObject = this.getChild('grip');
|
||||
|
||||
if (this._barObjectH) {
|
||||
this._barMaxWidth = this._barObjectH.width;
|
||||
this._barMaxWidthDelta = this.width - this._barMaxWidth;
|
||||
this._barStartX = this._barObjectH.x;
|
||||
}
|
||||
if (this._barObjectV) {
|
||||
this._barMaxHeight = this._barObjectV.height;
|
||||
this._barMaxHeightDelta = this.height - this._barMaxHeight;
|
||||
this._barStartY = this._barObjectV.y;
|
||||
}
|
||||
if (this._gripObject) {
|
||||
this._gripObject.on(FGUIEvents.TOUCH_BEGIN, this.handleGripTouchBegin, this);
|
||||
}
|
||||
|
||||
this.on(FGUIEvents.TOUCH_BEGIN, this.handleBarTouchBegin, this);
|
||||
}
|
||||
|
||||
protected handleSizeChanged(): void {
|
||||
super.handleSizeChanged();
|
||||
|
||||
if (this._barObjectH) {
|
||||
this._barMaxWidth = this.width - this._barMaxWidthDelta;
|
||||
}
|
||||
if (this._barObjectV) {
|
||||
this._barMaxHeight = this.height - this._barMaxHeightDelta;
|
||||
}
|
||||
if (!this._underConstruct) {
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
public setup_afterAdd(buffer: ByteBuffer, beginPos: number): void {
|
||||
super.setup_afterAdd(buffer, beginPos);
|
||||
|
||||
if (!buffer.seek(beginPos, 6)) {
|
||||
this.update();
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.readByte() !== this.packageItem?.objectType) {
|
||||
this.update();
|
||||
return;
|
||||
}
|
||||
|
||||
this._value = buffer.getInt32();
|
||||
this._max = buffer.getInt32();
|
||||
if (buffer.version >= 2) {
|
||||
this._min = buffer.getInt32();
|
||||
}
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
private handleGripTouchBegin(evt: any): void {
|
||||
this.canDrag = true;
|
||||
if (evt.stopPropagation) {
|
||||
evt.stopPropagation();
|
||||
}
|
||||
|
||||
this._clickPos = this.globalToLocal(evt.stageX || 0, evt.stageY || 0);
|
||||
this._clickPercent = this.clamp01(
|
||||
(this._value - this._min) / (this._max - this._min)
|
||||
);
|
||||
|
||||
this.root?.on(FGUIEvents.TOUCH_MOVE, this.handleGripTouchMove, this);
|
||||
this.root?.on(FGUIEvents.TOUCH_END, this.handleGripTouchEnd, this);
|
||||
}
|
||||
|
||||
private handleGripTouchMove(evt: any): void {
|
||||
if (!this.canDrag) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pt = this.globalToLocal(evt.stageX || 0, evt.stageY || 0);
|
||||
let deltaX = pt.x - this._clickPos.x;
|
||||
let deltaY = pt.y - this._clickPos.y;
|
||||
if (this._reverse) {
|
||||
deltaX = -deltaX;
|
||||
deltaY = -deltaY;
|
||||
}
|
||||
|
||||
let percent: number;
|
||||
if (this._barObjectH) {
|
||||
percent = this._clickPercent + deltaX / this._barMaxWidth;
|
||||
} else {
|
||||
percent = this._clickPercent + deltaY / this._barMaxHeight;
|
||||
}
|
||||
this.updateWithPercent(percent, true);
|
||||
}
|
||||
|
||||
private handleGripTouchEnd(): void {
|
||||
this.root?.off(FGUIEvents.TOUCH_MOVE, this.handleGripTouchMove, this);
|
||||
this.root?.off(FGUIEvents.TOUCH_END, this.handleGripTouchEnd, this);
|
||||
}
|
||||
|
||||
private handleBarTouchBegin(evt: any): void {
|
||||
if (!this.changeOnClick || !this._gripObject) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pt = this._gripObject.globalToLocal(evt.stageX || 0, evt.stageY || 0);
|
||||
let percent = this.clamp01((this._value - this._min) / (this._max - this._min));
|
||||
let delta: number = 0;
|
||||
if (this._barObjectH) {
|
||||
delta = pt.x / this._barMaxWidth;
|
||||
}
|
||||
if (this._barObjectV) {
|
||||
delta = pt.y / this._barMaxHeight;
|
||||
}
|
||||
if (this._reverse) {
|
||||
percent -= delta;
|
||||
} else {
|
||||
percent += delta;
|
||||
}
|
||||
this.updateWithPercent(percent, true);
|
||||
}
|
||||
}
|
||||
470
packages/rendering/fairygui/src/widgets/GTextField.ts
Normal file
470
packages/rendering/fairygui/src/widgets/GTextField.ts
Normal file
@@ -0,0 +1,470 @@
|
||||
import { GObject } from '../core/GObject';
|
||||
import { TextField } from '../display/TextField';
|
||||
import {
|
||||
EAutoSizeType,
|
||||
EAlignType,
|
||||
EVertAlignType,
|
||||
EObjectPropID
|
||||
} from '../core/FieldTypes';
|
||||
import type { ByteBuffer } from '../utils/ByteBuffer';
|
||||
|
||||
/**
|
||||
* GTextField
|
||||
*
|
||||
* Text field display object for FairyGUI.
|
||||
*
|
||||
* FairyGUI 文本字段显示对象
|
||||
*/
|
||||
export class GTextField extends GObject {
|
||||
protected _textField!: TextField;
|
||||
protected _text: string = '';
|
||||
protected _autoSize: EAutoSizeType = EAutoSizeType.Both;
|
||||
protected _widthAutoSize: boolean = true;
|
||||
protected _heightAutoSize: boolean = true;
|
||||
protected _color: string = '#000000';
|
||||
protected _singleLine: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// Ensure _textField is initialized - super() calls createDisplayObject() but
|
||||
// class field initializers run after super(), which may cause issues
|
||||
this.ensureTextField();
|
||||
}
|
||||
|
||||
private ensureTextField(): void {
|
||||
if (!this._textField) {
|
||||
this.createDisplayObject();
|
||||
}
|
||||
}
|
||||
|
||||
protected createDisplayObject(): void {
|
||||
this._displayObject = this._textField = new TextField();
|
||||
this._textField.touchable = false;
|
||||
this._displayObject.gOwner = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the internal text field display object
|
||||
* 获取内部文本字段显示对象
|
||||
*/
|
||||
public get textField(): TextField {
|
||||
return this._textField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set text content
|
||||
* 获取/设置文本内容
|
||||
*/
|
||||
public get text(): string {
|
||||
return this._text;
|
||||
}
|
||||
|
||||
public set text(value: string) {
|
||||
this._text = value;
|
||||
if (this._textField) {
|
||||
this._textField.text = value;
|
||||
this.updateSize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set font
|
||||
* 获取/设置字体
|
||||
*/
|
||||
public get font(): string {
|
||||
return this._textField.font;
|
||||
}
|
||||
|
||||
public set font(value: string) {
|
||||
if (this._textField) {
|
||||
this._textField.font = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set font size
|
||||
* 获取/设置字体大小
|
||||
*/
|
||||
public get fontSize(): number {
|
||||
return this._textField.fontSize;
|
||||
}
|
||||
|
||||
public set fontSize(value: number) {
|
||||
if (this._textField) {
|
||||
this._textField.fontSize = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set text color
|
||||
* 获取/设置文本颜色
|
||||
*/
|
||||
public get color(): string {
|
||||
return this._color;
|
||||
}
|
||||
|
||||
public set color(value: string) {
|
||||
if (this._color !== value) {
|
||||
this._color = value;
|
||||
this.updateGear(4);
|
||||
|
||||
if (this.grayed) {
|
||||
this._textField.color = '#AAAAAA';
|
||||
} else {
|
||||
this._textField.color = this._color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set horizontal alignment
|
||||
* 获取/设置水平对齐
|
||||
*/
|
||||
public get align(): EAlignType {
|
||||
return this._textField.align;
|
||||
}
|
||||
|
||||
public set align(value: EAlignType) {
|
||||
if (this._textField) {
|
||||
this._textField.align = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set vertical alignment
|
||||
* 获取/设置垂直对齐
|
||||
*/
|
||||
public get valign(): EVertAlignType {
|
||||
return this._textField.valign;
|
||||
}
|
||||
|
||||
public set valign(value: EVertAlignType) {
|
||||
if (this._textField) {
|
||||
this._textField.valign = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set leading (line spacing)
|
||||
* 获取/设置行间距
|
||||
*/
|
||||
public get leading(): number {
|
||||
return this._textField.leading;
|
||||
}
|
||||
|
||||
public set leading(value: number) {
|
||||
if (this._textField) {
|
||||
this._textField.leading = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set letter spacing
|
||||
* 获取/设置字间距
|
||||
*/
|
||||
public get letterSpacing(): number {
|
||||
return this._textField.letterSpacing;
|
||||
}
|
||||
|
||||
public set letterSpacing(value: number) {
|
||||
if (this._textField) {
|
||||
this._textField.letterSpacing = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set bold
|
||||
* 获取/设置粗体
|
||||
*/
|
||||
public get bold(): boolean {
|
||||
return this._textField.bold;
|
||||
}
|
||||
|
||||
public set bold(value: boolean) {
|
||||
if (this._textField) {
|
||||
this._textField.bold = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set italic
|
||||
* 获取/设置斜体
|
||||
*/
|
||||
public get italic(): boolean {
|
||||
return this._textField.italic;
|
||||
}
|
||||
|
||||
public set italic(value: boolean) {
|
||||
if (this._textField) {
|
||||
this._textField.italic = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set underline
|
||||
* 获取/设置下划线
|
||||
*/
|
||||
public get underline(): boolean {
|
||||
return this._textField.underline;
|
||||
}
|
||||
|
||||
public set underline(value: boolean) {
|
||||
if (this._textField) {
|
||||
this._textField.underline = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set single line mode
|
||||
* 获取/设置单行模式
|
||||
*/
|
||||
public get singleLine(): boolean {
|
||||
return this._singleLine;
|
||||
}
|
||||
|
||||
public set singleLine(value: boolean) {
|
||||
this._singleLine = value;
|
||||
if (this._textField) {
|
||||
this._textField.singleLine = value;
|
||||
this._textField.wordWrap = !this._widthAutoSize && !this._singleLine;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set stroke width
|
||||
* 获取/设置描边宽度
|
||||
*/
|
||||
public get stroke(): number {
|
||||
return this._textField.stroke;
|
||||
}
|
||||
|
||||
public set stroke(value: number) {
|
||||
if (this._textField) {
|
||||
this._textField.stroke = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set stroke color
|
||||
* 获取/设置描边颜色
|
||||
*/
|
||||
public get strokeColor(): string {
|
||||
return this._textField.strokeColor;
|
||||
}
|
||||
|
||||
public set strokeColor(value: string) {
|
||||
if (this._textField && this._textField.strokeColor !== value) {
|
||||
this._textField.strokeColor = value;
|
||||
this.updateGear(4);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set UBB enabled
|
||||
* 获取/设置 UBB 标签启用
|
||||
*/
|
||||
public get ubbEnabled(): boolean {
|
||||
return this._textField.ubbEnabled;
|
||||
}
|
||||
|
||||
public set ubbEnabled(value: boolean) {
|
||||
if (this._textField) {
|
||||
this._textField.ubbEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set auto size type
|
||||
* 获取/设置自动尺寸类型
|
||||
*/
|
||||
public get autoSize(): EAutoSizeType {
|
||||
return this._autoSize;
|
||||
}
|
||||
|
||||
public set autoSize(value: EAutoSizeType) {
|
||||
if (this._autoSize !== value) {
|
||||
this._autoSize = value;
|
||||
this._widthAutoSize = this._autoSize === EAutoSizeType.Both;
|
||||
this._heightAutoSize =
|
||||
this._autoSize === EAutoSizeType.Both ||
|
||||
this._autoSize === EAutoSizeType.Height;
|
||||
this.updateAutoSize();
|
||||
}
|
||||
}
|
||||
|
||||
protected updateAutoSize(): void {
|
||||
if (!this._textField) return;
|
||||
|
||||
this._textField.wordWrap = !this._widthAutoSize && !this._singleLine;
|
||||
this._textField.autoSize = this._autoSize;
|
||||
if (!this._underConstruct) {
|
||||
if (!this._heightAutoSize) {
|
||||
this._textField.width = this._width;
|
||||
this._textField.height = this._height;
|
||||
} else if (!this._widthAutoSize) {
|
||||
this._textField.width = this._width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get text width
|
||||
* 获取文本宽度
|
||||
*/
|
||||
public get textWidth(): number {
|
||||
return this._textField.textWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set template variables
|
||||
* 获取/设置模板变量
|
||||
*/
|
||||
public get templateVars(): Record<string, string> | null {
|
||||
return this._textField.templateVars;
|
||||
}
|
||||
|
||||
public set templateVars(value: Record<string, string> | null) {
|
||||
if (this._textField) {
|
||||
this._textField.templateVars = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a template variable
|
||||
* 设置模板变量
|
||||
*/
|
||||
public setVar(name: string, value: string): GTextField {
|
||||
if (this._textField) {
|
||||
this._textField.setVar(name, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush template variables
|
||||
* 刷新模板变量
|
||||
*/
|
||||
public flushVars(): void {
|
||||
// Auto flush, nothing needed
|
||||
}
|
||||
|
||||
public ensureSizeCorrect(): void {
|
||||
// Force layout if needed
|
||||
}
|
||||
|
||||
private updateSize(): void {
|
||||
if (!this._textField) return;
|
||||
|
||||
if (this._widthAutoSize) {
|
||||
this.setSize(this._textField.textWidth, this._textField.textHeight);
|
||||
} else if (this._heightAutoSize) {
|
||||
this.height = this._textField.textHeight;
|
||||
}
|
||||
}
|
||||
|
||||
protected handleSizeChanged(): void {
|
||||
super.handleSizeChanged();
|
||||
if (this._textField) {
|
||||
this._textField.width = this._width;
|
||||
this._textField.height = this._height;
|
||||
}
|
||||
}
|
||||
|
||||
protected handleGrayedChanged(): void {
|
||||
super.handleGrayedChanged();
|
||||
|
||||
if (this._textField) {
|
||||
if (this.grayed) {
|
||||
this._textField.color = '#AAAAAA';
|
||||
} else {
|
||||
this._textField.color = this._color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public getProp(index: number): any {
|
||||
switch (index) {
|
||||
case EObjectPropID.Color:
|
||||
return this.color;
|
||||
case EObjectPropID.OutlineColor:
|
||||
return this.strokeColor;
|
||||
case EObjectPropID.FontSize:
|
||||
return this.fontSize;
|
||||
default:
|
||||
return super.getProp(index);
|
||||
}
|
||||
}
|
||||
|
||||
public setProp(index: number, value: any): void {
|
||||
switch (index) {
|
||||
case EObjectPropID.Color:
|
||||
this.color = value;
|
||||
break;
|
||||
case EObjectPropID.OutlineColor:
|
||||
this.strokeColor = value;
|
||||
break;
|
||||
case EObjectPropID.FontSize:
|
||||
this.fontSize = value;
|
||||
break;
|
||||
default:
|
||||
super.setProp(index, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public setup_beforeAdd(buffer: ByteBuffer, beginPos: number): void {
|
||||
super.setup_beforeAdd(buffer, beginPos);
|
||||
|
||||
buffer.seek(beginPos, 5);
|
||||
|
||||
this.font = buffer.readS();
|
||||
this.fontSize = buffer.getInt16();
|
||||
this.color = buffer.readColorS();
|
||||
const alignValue = buffer.readByte();
|
||||
this.align =
|
||||
alignValue === 0
|
||||
? EAlignType.Left
|
||||
: alignValue === 1
|
||||
? EAlignType.Center
|
||||
: EAlignType.Right;
|
||||
const valignValue = buffer.readByte();
|
||||
this.valign =
|
||||
valignValue === 0
|
||||
? EVertAlignType.Top
|
||||
: valignValue === 1
|
||||
? EVertAlignType.Middle
|
||||
: EVertAlignType.Bottom;
|
||||
this.leading = buffer.getInt16();
|
||||
this.letterSpacing = buffer.getInt16();
|
||||
this.ubbEnabled = buffer.readBool();
|
||||
this.autoSize = buffer.readByte();
|
||||
this.underline = buffer.readBool();
|
||||
this.italic = buffer.readBool();
|
||||
this.bold = buffer.readBool();
|
||||
this.singleLine = buffer.readBool();
|
||||
if (buffer.readBool()) {
|
||||
this.strokeColor = buffer.readColorS();
|
||||
this.stroke = buffer.getFloat32() + 1;
|
||||
}
|
||||
|
||||
if (buffer.readBool()) {
|
||||
// Shadow - skip for now
|
||||
buffer.skip(12);
|
||||
}
|
||||
|
||||
if (buffer.readBool()) {
|
||||
this._textField.templateVars = {};
|
||||
}
|
||||
}
|
||||
|
||||
public setup_afterAdd(buffer: ByteBuffer, beginPos: number): void {
|
||||
super.setup_afterAdd(buffer, beginPos);
|
||||
|
||||
buffer.seek(beginPos, 6);
|
||||
|
||||
const str = buffer.readS();
|
||||
if (str) {
|
||||
this.text = str;
|
||||
}
|
||||
}
|
||||
}
|
||||
235
packages/rendering/fairygui/src/widgets/GTextInput.ts
Normal file
235
packages/rendering/fairygui/src/widgets/GTextInput.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import { GTextField } from './GTextField';
|
||||
import { InputTextField } from '../display/InputTextField';
|
||||
import { FGUIEvents } from '../events/Events';
|
||||
import type { ByteBuffer } from '../utils/ByteBuffer';
|
||||
|
||||
/**
|
||||
* Keyboard type constants
|
||||
* 键盘类型常量
|
||||
*/
|
||||
export const enum EKeyboardType {
|
||||
Default = 'text',
|
||||
Number = 'number',
|
||||
Url = 'url',
|
||||
Email = 'email',
|
||||
Tel = 'tel',
|
||||
Password = 'password'
|
||||
}
|
||||
|
||||
/**
|
||||
* GTextInput
|
||||
*
|
||||
* Editable text input component.
|
||||
*
|
||||
* 可编辑的文本输入组件
|
||||
*
|
||||
* Features:
|
||||
* - Text input with IME support
|
||||
* - Password mode
|
||||
* - Character restriction
|
||||
* - Max length
|
||||
* - Placeholder text
|
||||
*/
|
||||
export class GTextInput extends GTextField {
|
||||
protected declare _displayObject: InputTextField;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected createDisplayObject(): void {
|
||||
const inputField = new InputTextField();
|
||||
// Set both _displayObject and _textField since parent class uses _textField for color etc.
|
||||
this._displayObject = inputField;
|
||||
this._textField = inputField;
|
||||
this._displayObject.gOwner = this;
|
||||
|
||||
// Forward events
|
||||
inputField.on('input', () => {
|
||||
this.emit(FGUIEvents.TEXT_CHANGED);
|
||||
});
|
||||
inputField.on('submit', () => {
|
||||
this.emit(FGUIEvents.TEXT_SUBMIT);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get native input element
|
||||
* 获取原生输入元素
|
||||
*/
|
||||
public get nativeInput(): InputTextField {
|
||||
return this._displayObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set password mode
|
||||
* 获取/设置密码模式
|
||||
*/
|
||||
public get password(): boolean {
|
||||
return this._displayObject.password;
|
||||
}
|
||||
|
||||
public set password(value: boolean) {
|
||||
if (this._displayObject) {
|
||||
this._displayObject.password = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set keyboard type
|
||||
* 获取/设置键盘类型
|
||||
*/
|
||||
public get keyboardType(): string {
|
||||
return this._displayObject.keyboardType;
|
||||
}
|
||||
|
||||
public set keyboardType(value: string) {
|
||||
if (this._displayObject) {
|
||||
this._displayObject.keyboardType = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set editable state
|
||||
* 获取/设置可编辑状态
|
||||
*/
|
||||
public get editable(): boolean {
|
||||
return this._displayObject.editable;
|
||||
}
|
||||
|
||||
public set editable(value: boolean) {
|
||||
if (this._displayObject) {
|
||||
this._displayObject.editable = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set max length
|
||||
* 获取/设置最大长度
|
||||
*/
|
||||
public get maxLength(): number {
|
||||
return this._displayObject.maxLength;
|
||||
}
|
||||
|
||||
public set maxLength(value: number) {
|
||||
if (this._displayObject) {
|
||||
this._displayObject.maxLength = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set placeholder text
|
||||
* 获取/设置占位符文本
|
||||
*/
|
||||
public get promptText(): string {
|
||||
return this._displayObject.promptText;
|
||||
}
|
||||
|
||||
public set promptText(value: string) {
|
||||
if (this._displayObject) {
|
||||
this._displayObject.promptText = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set placeholder color
|
||||
* 获取/设置占位符颜色
|
||||
*/
|
||||
public get promptColor(): string {
|
||||
return this._displayObject.promptColor;
|
||||
}
|
||||
|
||||
public set promptColor(value: string) {
|
||||
if (this._displayObject) {
|
||||
this._displayObject.promptColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set character restriction pattern
|
||||
* 获取/设置字符限制模式
|
||||
*/
|
||||
public get restrict(): string {
|
||||
return this._displayObject.restrict;
|
||||
}
|
||||
|
||||
public set restrict(value: string) {
|
||||
if (this._displayObject) {
|
||||
this._displayObject.restrict = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set single line mode
|
||||
* 获取/设置单行模式
|
||||
*/
|
||||
public get singleLine(): boolean {
|
||||
return this._singleLine;
|
||||
}
|
||||
|
||||
public set singleLine(value: boolean) {
|
||||
this._singleLine = value;
|
||||
if (this._displayObject) {
|
||||
this._displayObject.multiline = !value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request focus
|
||||
* 请求焦点
|
||||
*/
|
||||
public requestFocus(): void {
|
||||
this._displayObject.focus();
|
||||
super.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear focus
|
||||
* 清除焦点
|
||||
*/
|
||||
public clearFocus(): void {
|
||||
this._displayObject.blur();
|
||||
}
|
||||
|
||||
/**
|
||||
* Select all text
|
||||
* 选择所有文本
|
||||
*/
|
||||
public selectAll(): void {
|
||||
this._displayObject.selectAll();
|
||||
}
|
||||
|
||||
public setup_beforeAdd(buffer: ByteBuffer, beginPos: number): void {
|
||||
super.setup_beforeAdd(buffer, beginPos);
|
||||
|
||||
buffer.seek(beginPos, 4);
|
||||
|
||||
let str = buffer.readS();
|
||||
if (str) {
|
||||
this.promptText = str;
|
||||
}
|
||||
|
||||
str = buffer.readS();
|
||||
if (str) {
|
||||
this.restrict = str;
|
||||
}
|
||||
|
||||
const iv = buffer.getInt32();
|
||||
if (iv !== 0) {
|
||||
this.maxLength = iv;
|
||||
}
|
||||
|
||||
const keyboardTypeValue = buffer.getInt32();
|
||||
if (keyboardTypeValue !== 0) {
|
||||
if (keyboardTypeValue === 4) {
|
||||
this.keyboardType = EKeyboardType.Number;
|
||||
} else if (keyboardTypeValue === 3) {
|
||||
this.keyboardType = EKeyboardType.Url;
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.readBool()) {
|
||||
this.password = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
325
packages/rendering/fairygui/src/widgets/PopupMenu.ts
Normal file
325
packages/rendering/fairygui/src/widgets/PopupMenu.ts
Normal file
@@ -0,0 +1,325 @@
|
||||
import { GComponent } from '../core/GComponent';
|
||||
import { GObject } from '../core/GObject';
|
||||
import { GRoot } from '../core/GRoot';
|
||||
import { Controller } from '../core/Controller';
|
||||
import { getUIConfig } from '../core/UIConfig';
|
||||
import { UIPackage } from '../package/UIPackage';
|
||||
import { ERelationType } from '../core/FieldTypes';
|
||||
import { FGUIEvents } from '../events/Events';
|
||||
import { Timer } from '../core/Timer';
|
||||
import { GList } from './GList';
|
||||
import { GButton } from './GButton';
|
||||
import type { SimpleHandler } from '../display/MovieClip';
|
||||
|
||||
/**
|
||||
* PopupMenu
|
||||
*
|
||||
* Context menu component with item management.
|
||||
*
|
||||
* 上下文菜单组件,支持菜单项管理
|
||||
*
|
||||
* Features:
|
||||
* - Add/remove menu items
|
||||
* - Checkable items
|
||||
* - Separators
|
||||
* - Grayed out items
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const menu = new PopupMenu();
|
||||
* menu.addItem('Open', () => console.log('Open clicked'));
|
||||
* menu.addItem('Save', () => console.log('Save clicked'));
|
||||
* menu.addSeperator();
|
||||
* menu.addItem('Exit', () => console.log('Exit clicked'));
|
||||
* menu.show(targetButton);
|
||||
* ```
|
||||
*/
|
||||
export class PopupMenu {
|
||||
protected _contentPane: GComponent;
|
||||
protected _list: GList;
|
||||
|
||||
constructor(resourceURL?: string) {
|
||||
if (!resourceURL) {
|
||||
resourceURL = getUIConfig('popupMenu');
|
||||
if (!resourceURL) {
|
||||
throw new Error('UIConfig.popupMenu not defined');
|
||||
}
|
||||
}
|
||||
|
||||
const obj = UIPackage.createObjectFromURL(resourceURL);
|
||||
if (!obj || !(obj instanceof GComponent)) {
|
||||
throw new Error(`Failed to create popup menu from: ${resourceURL}`);
|
||||
}
|
||||
|
||||
this._contentPane = obj;
|
||||
this._contentPane.on(FGUIEvents.DISPLAY, this.onAddedToStage, this);
|
||||
|
||||
const list = this._contentPane.getChild('list');
|
||||
if (!list || !(list instanceof GList)) {
|
||||
throw new Error('PopupMenu content pane must have a child named "list" of type GList');
|
||||
}
|
||||
|
||||
this._list = list;
|
||||
this._list.removeChildrenToPool();
|
||||
this._list.relations.add(this._contentPane, ERelationType.Width);
|
||||
this._list.relations.remove(this._contentPane, ERelationType.Height);
|
||||
this._contentPane.relations.add(this._list, ERelationType.Height);
|
||||
this._list.on(FGUIEvents.CLICK_ITEM, this.onClickItem, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose the menu
|
||||
* 销毁菜单
|
||||
*/
|
||||
public dispose(): void {
|
||||
this._contentPane.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a menu item
|
||||
* 添加菜单项
|
||||
*/
|
||||
public addItem(caption: string, handler?: SimpleHandler): GButton {
|
||||
const item = this._list.addItemFromPool();
|
||||
if (!item || !(item instanceof GButton)) {
|
||||
throw new Error('Failed to create menu item');
|
||||
}
|
||||
|
||||
item.title = caption;
|
||||
item.data = handler;
|
||||
item.grayed = false;
|
||||
|
||||
const c = item.getController('checked');
|
||||
if (c) {
|
||||
c.selectedIndex = 0;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a menu item at specified index
|
||||
* 在指定索引处添加菜单项
|
||||
*/
|
||||
public addItemAt(caption: string, index: number, handler?: SimpleHandler): GButton {
|
||||
const item = this._list.getFromPool();
|
||||
if (!item || !(item instanceof GButton)) {
|
||||
throw new Error('Failed to create menu item');
|
||||
}
|
||||
|
||||
this._list.addChildAt(item, index);
|
||||
item.title = caption;
|
||||
item.data = handler;
|
||||
item.grayed = false;
|
||||
|
||||
const c = item.getController('checked');
|
||||
if (c) {
|
||||
c.selectedIndex = 0;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a separator
|
||||
* 添加分隔符
|
||||
*/
|
||||
public addSeperator(): void {
|
||||
const seperatorUrl = getUIConfig('popupMenuSeperator');
|
||||
if (!seperatorUrl) {
|
||||
throw new Error('UIConfig.popupMenuSeperator not defined');
|
||||
}
|
||||
this._list.addItemFromPool(seperatorUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item name at index
|
||||
* 获取指定索引处的菜单项名称
|
||||
*/
|
||||
public getItemName(index: number): string {
|
||||
const item = this._list.getChildAt(index);
|
||||
return item ? item.name : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item text by name
|
||||
* 通过名称设置菜单项文本
|
||||
*/
|
||||
public setItemText(name: string, caption: string): void {
|
||||
const item = this._list.getChild(name);
|
||||
if (item && item instanceof GButton) {
|
||||
item.title = caption;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item visibility by name
|
||||
* 通过名称设置菜单项可见性
|
||||
*/
|
||||
public setItemVisible(name: string, bVisible: boolean): void {
|
||||
const item = this._list.getChild(name);
|
||||
if (item && item.visible !== bVisible) {
|
||||
item.visible = bVisible;
|
||||
this._list.setBoundsChangedFlag();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item grayed state by name
|
||||
* 通过名称设置菜单项灰色状态
|
||||
*/
|
||||
public setItemGrayed(name: string, bGrayed: boolean): void {
|
||||
const item = this._list.getChild(name);
|
||||
if (item && item instanceof GButton) {
|
||||
item.grayed = bGrayed;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item checkable state by name
|
||||
* 通过名称设置菜单项可选中状态
|
||||
*/
|
||||
public setItemCheckable(name: string, bCheckable: boolean): void {
|
||||
const item = this._list.getChild(name);
|
||||
if (item && item instanceof GButton) {
|
||||
const c = item.getController('checked');
|
||||
if (c) {
|
||||
if (bCheckable) {
|
||||
if (c.selectedIndex === 0) {
|
||||
c.selectedIndex = 1;
|
||||
}
|
||||
} else {
|
||||
c.selectedIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item checked state by name
|
||||
* 通过名称设置菜单项选中状态
|
||||
*/
|
||||
public setItemChecked(name: string, bChecked: boolean): void {
|
||||
const item = this._list.getChild(name);
|
||||
if (item && item instanceof GButton) {
|
||||
const c = item.getController('checked');
|
||||
if (c) {
|
||||
c.selectedIndex = bChecked ? 2 : 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if item is checked by name
|
||||
* 通过名称检查菜单项是否选中
|
||||
*/
|
||||
public isItemChecked(name: string): boolean {
|
||||
const item = this._list.getChild(name);
|
||||
if (item && item instanceof GButton) {
|
||||
const c = item.getController('checked');
|
||||
if (c) {
|
||||
return c.selectedIndex === 2;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove item by name
|
||||
* 通过名称移除菜单项
|
||||
*/
|
||||
public removeItem(name: string): boolean {
|
||||
const item = this._list.getChild(name);
|
||||
if (item) {
|
||||
const index = this._list.getChildIndex(item);
|
||||
this._list.removeChildToPoolAt(index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all items
|
||||
* 清除所有菜单项
|
||||
*/
|
||||
public clearItems(): void {
|
||||
this._list.removeChildrenToPool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item count
|
||||
* 获取菜单项数量
|
||||
*/
|
||||
public get itemCount(): number {
|
||||
return this._list.numChildren;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content pane
|
||||
* 获取内容面板
|
||||
*/
|
||||
public get contentPane(): GComponent {
|
||||
return this._contentPane;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list component
|
||||
* 获取列表组件
|
||||
*/
|
||||
public get list(): GList {
|
||||
return this._list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show menu
|
||||
* 显示菜单
|
||||
*/
|
||||
public show(target?: GObject, dir?: number): void {
|
||||
const r = target?.root ?? GRoot.inst;
|
||||
const popupTarget = target instanceof GRoot ? undefined : target;
|
||||
r.showPopup(this._contentPane, popupTarget, dir);
|
||||
}
|
||||
|
||||
private onClickItem(itemObject: GObject): void {
|
||||
Timer.inst.callLater(this, () => this.handleItemClick(itemObject));
|
||||
}
|
||||
|
||||
private handleItemClick(itemObject: GObject): void {
|
||||
if (!(itemObject instanceof GButton)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (itemObject.grayed) {
|
||||
this._list.selectedIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
const c = itemObject.getController('checked');
|
||||
if (c && c.selectedIndex !== 0) {
|
||||
if (c.selectedIndex === 1) {
|
||||
c.selectedIndex = 2;
|
||||
} else {
|
||||
c.selectedIndex = 1;
|
||||
}
|
||||
}
|
||||
|
||||
const r = this._contentPane.parent as GRoot | null;
|
||||
if (r) {
|
||||
r.hidePopup(this._contentPane);
|
||||
}
|
||||
|
||||
const handler = itemObject.data as SimpleHandler | null;
|
||||
if (handler) {
|
||||
if (typeof handler === 'function') {
|
||||
handler();
|
||||
} else if (typeof handler.run === 'function') {
|
||||
handler.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onAddedToStage(): void {
|
||||
this._list.selectedIndex = -1;
|
||||
this._list.resizeToFit(100000, 10);
|
||||
}
|
||||
}
|
||||
522
packages/rendering/fairygui/src/widgets/Window.ts
Normal file
522
packages/rendering/fairygui/src/widgets/Window.ts
Normal file
@@ -0,0 +1,522 @@
|
||||
import { GComponent } from '../core/GComponent';
|
||||
import { GObject } from '../core/GObject';
|
||||
import { GRoot } from '../core/GRoot';
|
||||
import { GGraph } from './GGraph';
|
||||
import { getUIConfig } from '../core/UIConfig';
|
||||
import { UIPackage } from '../package/UIPackage';
|
||||
import { ERelationType } from '../core/FieldTypes';
|
||||
import { FGUIEvents } from '../events/Events';
|
||||
import { Point } from '../utils/MathTypes';
|
||||
|
||||
/**
|
||||
* IUISource
|
||||
*
|
||||
* Interface for dynamic UI loading sources
|
||||
* 动态 UI 加载源接口
|
||||
*/
|
||||
export interface IUISource {
|
||||
/** Source file name | 源文件名 */
|
||||
fileName: string;
|
||||
|
||||
/** Whether the source is loaded | 是否已加载 */
|
||||
loaded: boolean;
|
||||
|
||||
/**
|
||||
* Load the source
|
||||
* 加载源
|
||||
*/
|
||||
load(callback: () => void, thisObj: any): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Window
|
||||
*
|
||||
* Base class for popup windows with modal support.
|
||||
*
|
||||
* 弹窗基类,支持模态窗口
|
||||
*
|
||||
* Features:
|
||||
* - Content pane management
|
||||
* - Modal wait indicator
|
||||
* - Draggable title bar
|
||||
* - Close button binding
|
||||
* - Bring to front on click
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class MyWindow extends Window {
|
||||
* constructor() {
|
||||
* super();
|
||||
* this.contentPane = UIPackage.createObject('pkg', 'MyWindowContent') as GComponent;
|
||||
* }
|
||||
*
|
||||
* protected onInit(): void {
|
||||
* // Initialize window
|
||||
* }
|
||||
*
|
||||
* protected onShown(): void {
|
||||
* // Window is shown
|
||||
* }
|
||||
*
|
||||
* protected onHide(): void {
|
||||
* // Window is hidden
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* const win = new MyWindow();
|
||||
* win.show();
|
||||
* ```
|
||||
*/
|
||||
export class Window extends GComponent {
|
||||
/** Bring window to front when clicked | 点击时将窗口置顶 */
|
||||
public bringToFrontOnClick: boolean;
|
||||
|
||||
protected _requestingCmd: number = 0;
|
||||
|
||||
private _contentPane: GComponent | null = null;
|
||||
private _modalWaitPane: GObject | null = null;
|
||||
private _closeButton: GObject | null = null;
|
||||
private _dragArea: GObject | null = null;
|
||||
private _contentArea: GObject | null = null;
|
||||
private _frame: GComponent | null = null;
|
||||
private _modal: boolean = false;
|
||||
|
||||
private _uiSources: IUISource[] = [];
|
||||
private _inited: boolean = false;
|
||||
private _loading: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.bringToFrontOnClick = getUIConfig('bringWindowToFrontOnClick');
|
||||
|
||||
this.on(FGUIEvents.DISPLAY, this.onWindowShown, this);
|
||||
this.on(FGUIEvents.REMOVED_FROM_STAGE, this.onWindowHidden, this);
|
||||
this.on(FGUIEvents.TOUCH_BEGIN, this.onMouseDown, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add UI source for lazy loading
|
||||
* 添加用于懒加载的 UI 源
|
||||
*/
|
||||
public addUISource(source: IUISource): void {
|
||||
this._uiSources.push(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content pane
|
||||
* 获取内容面板
|
||||
*/
|
||||
public get contentPane(): GComponent | null {
|
||||
return this._contentPane;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content pane
|
||||
* 设置内容面板
|
||||
*/
|
||||
public set contentPane(value: GComponent | null) {
|
||||
if (this._contentPane !== value) {
|
||||
if (this._contentPane) {
|
||||
this.removeChild(this._contentPane);
|
||||
}
|
||||
|
||||
this._contentPane = value;
|
||||
|
||||
if (this._contentPane) {
|
||||
this.addChild(this._contentPane);
|
||||
this.setSize(this._contentPane.width, this._contentPane.height);
|
||||
this._contentPane.relations.add(this, ERelationType.Size);
|
||||
|
||||
this._frame = this._contentPane.getChild('frame') as GComponent | null;
|
||||
if (this._frame) {
|
||||
this.closeButton = this._frame.getChild('closeButton');
|
||||
this.dragArea = this._frame.getChild('dragArea');
|
||||
this.contentArea = this._frame.getChild('contentArea');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get frame component
|
||||
* 获取框架组件
|
||||
*/
|
||||
public get frame(): GComponent | null {
|
||||
return this._frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get close button
|
||||
* 获取关闭按钮
|
||||
*/
|
||||
public get closeButton(): GObject | null {
|
||||
return this._closeButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set close button
|
||||
* 设置关闭按钮
|
||||
*/
|
||||
public set closeButton(value: GObject | null) {
|
||||
if (this._closeButton) {
|
||||
this._closeButton.off(FGUIEvents.CLICK, this.closeEventHandler, this);
|
||||
}
|
||||
this._closeButton = value;
|
||||
if (this._closeButton) {
|
||||
this._closeButton.on(FGUIEvents.CLICK, this.closeEventHandler, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get drag area
|
||||
* 获取拖拽区域
|
||||
*/
|
||||
public get dragArea(): GObject | null {
|
||||
return this._dragArea;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set drag area
|
||||
* 设置拖拽区域
|
||||
*/
|
||||
public set dragArea(value: GObject | null) {
|
||||
if (this._dragArea !== value) {
|
||||
if (this._dragArea) {
|
||||
this._dragArea.draggable = false;
|
||||
this._dragArea.off(FGUIEvents.DRAG_START, this.onDragStart, this);
|
||||
}
|
||||
|
||||
this._dragArea = value;
|
||||
|
||||
if (this._dragArea) {
|
||||
if (this._dragArea instanceof GGraph) {
|
||||
this._dragArea.drawRect(0, 'transparent', 'transparent');
|
||||
}
|
||||
this._dragArea.draggable = true;
|
||||
this._dragArea.on(FGUIEvents.DRAG_START, this.onDragStart, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content area
|
||||
* 获取内容区域
|
||||
*/
|
||||
public get contentArea(): GObject | null {
|
||||
return this._contentArea;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content area
|
||||
* 设置内容区域
|
||||
*/
|
||||
public set contentArea(value: GObject | null) {
|
||||
this._contentArea = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show window on default GRoot
|
||||
* 在默认 GRoot 上显示窗口
|
||||
*/
|
||||
public show(): void {
|
||||
GRoot.inst.showWindow(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show window on specified GRoot
|
||||
* 在指定 GRoot 上显示窗口
|
||||
*/
|
||||
public showOn(root: GRoot): void {
|
||||
root.showWindow(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide window with animation
|
||||
* 隐藏窗口(带动画)
|
||||
*/
|
||||
public hide(): void {
|
||||
if (this.isShowing) {
|
||||
this.doHideAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide window immediately
|
||||
* 立即隐藏窗口
|
||||
*/
|
||||
public hideImmediately(): void {
|
||||
const r = this.parent instanceof GRoot ? this.parent : GRoot.inst;
|
||||
r.hideWindowImmediately(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Center window on GRoot
|
||||
* 在 GRoot 上居中窗口
|
||||
*/
|
||||
public centerOn(r: GRoot, bRestraint?: boolean): void {
|
||||
this.setXY(
|
||||
Math.round((r.width - this.width) / 2),
|
||||
Math.round((r.height - this.height) / 2)
|
||||
);
|
||||
|
||||
if (bRestraint) {
|
||||
this.relations.add(r, ERelationType.CenterCenter);
|
||||
this.relations.add(r, ERelationType.MiddleMiddle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle window visibility
|
||||
* 切换窗口可见性
|
||||
*/
|
||||
public toggleStatus(): void {
|
||||
if (this.isTop) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if window is showing
|
||||
* 检查窗口是否正在显示
|
||||
*/
|
||||
public get isShowing(): boolean {
|
||||
return this.parent !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if window is on top
|
||||
* 检查窗口是否在最上层
|
||||
*/
|
||||
public get isTop(): boolean {
|
||||
return (
|
||||
this.parent !== null &&
|
||||
this.parent.getChildIndex(this) === this.parent.numChildren - 1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get modal state
|
||||
* 获取模态状态
|
||||
*/
|
||||
public get modal(): boolean {
|
||||
return this._modal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set modal state
|
||||
* 设置模态状态
|
||||
*/
|
||||
public set modal(value: boolean) {
|
||||
this._modal = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bring window to front
|
||||
* 将窗口置于最前
|
||||
*/
|
||||
public bringToFront(): void {
|
||||
this.root?.bringToFront(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show modal wait indicator
|
||||
* 显示模态等待指示器
|
||||
*/
|
||||
public showModalWait(requestingCmd?: number): void {
|
||||
if (requestingCmd !== undefined) {
|
||||
this._requestingCmd = requestingCmd;
|
||||
}
|
||||
|
||||
const modalWaitingUrl = getUIConfig('windowModalWaiting');
|
||||
if (modalWaitingUrl) {
|
||||
if (!this._modalWaitPane) {
|
||||
this._modalWaitPane = UIPackage.createObjectFromURL(modalWaitingUrl);
|
||||
}
|
||||
|
||||
if (this._modalWaitPane) {
|
||||
this.layoutModalWaitPane();
|
||||
this.addChild(this._modalWaitPane);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout modal wait pane
|
||||
* 布局模态等待面板
|
||||
*/
|
||||
protected layoutModalWaitPane(): void {
|
||||
if (!this._modalWaitPane) return;
|
||||
|
||||
if (this._contentArea && this._frame) {
|
||||
const pt = this._frame.localToGlobal(0, 0);
|
||||
const localPt = this.globalToLocal(pt.x, pt.y);
|
||||
this._modalWaitPane.setXY(
|
||||
localPt.x + this._contentArea.x,
|
||||
localPt.y + this._contentArea.y
|
||||
);
|
||||
this._modalWaitPane.setSize(this._contentArea.width, this._contentArea.height);
|
||||
} else {
|
||||
this._modalWaitPane.setSize(this.width, this.height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close modal wait indicator
|
||||
* 关闭模态等待指示器
|
||||
*/
|
||||
public closeModalWait(requestingCmd?: number): boolean {
|
||||
if (requestingCmd !== undefined) {
|
||||
if (this._requestingCmd !== requestingCmd) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this._requestingCmd = 0;
|
||||
|
||||
if (this._modalWaitPane?.parent) {
|
||||
this.removeChild(this._modalWaitPane);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if modal waiting
|
||||
* 检查是否正在模态等待
|
||||
*/
|
||||
public get modalWaiting(): boolean {
|
||||
return this._modalWaitPane?.parent !== null && this._modalWaitPane?.parent !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize window
|
||||
* 初始化窗口
|
||||
*/
|
||||
public init(): void {
|
||||
if (this._inited || this._loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._uiSources.length > 0) {
|
||||
this._loading = false;
|
||||
|
||||
for (const source of this._uiSources) {
|
||||
if (!source.loaded) {
|
||||
source.load(this.onUILoadComplete.bind(this), this);
|
||||
this._loading = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this._loading) {
|
||||
this.doInit();
|
||||
}
|
||||
} else {
|
||||
this.doInit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when window is initialized
|
||||
* 窗口初始化时调用
|
||||
*/
|
||||
protected onInit(): void {
|
||||
// Override in subclass
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when window is shown
|
||||
* 窗口显示时调用
|
||||
*/
|
||||
protected onShown(): void {
|
||||
// Override in subclass
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when window is hidden
|
||||
* 窗口隐藏时调用
|
||||
*/
|
||||
protected onHide(): void {
|
||||
// Override in subclass
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform show animation
|
||||
* 执行显示动画
|
||||
*/
|
||||
protected doShowAnimation(): void {
|
||||
this.onShown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform hide animation
|
||||
* 执行隐藏动画
|
||||
*/
|
||||
protected doHideAnimation(): void {
|
||||
this.hideImmediately();
|
||||
}
|
||||
|
||||
private onUILoadComplete(): void {
|
||||
for (const source of this._uiSources) {
|
||||
if (!source.loaded) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._loading = false;
|
||||
this.doInit();
|
||||
}
|
||||
|
||||
private doInit(): void {
|
||||
this._inited = true;
|
||||
this.onInit();
|
||||
|
||||
if (this.isShowing) {
|
||||
this.doShowAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this.parent) {
|
||||
this.hideImmediately();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close button event handler
|
||||
* 关闭按钮事件处理
|
||||
*/
|
||||
protected closeEventHandler(): void {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
private onWindowShown(): void {
|
||||
if (!this._inited) {
|
||||
this.init();
|
||||
} else {
|
||||
this.doShowAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
private onWindowHidden(): void {
|
||||
this.closeModalWait();
|
||||
this.onHide();
|
||||
}
|
||||
|
||||
private onMouseDown(): void {
|
||||
if (this.isShowing && this.bringToFrontOnClick) {
|
||||
this.bringToFront();
|
||||
}
|
||||
}
|
||||
|
||||
private onDragStart(): void {
|
||||
if (this._dragArea) {
|
||||
this._dragArea.stopDrag();
|
||||
}
|
||||
this.startDrag();
|
||||
}
|
||||
}
|
||||
10
packages/rendering/fairygui/src/widgets/index.ts
Normal file
10
packages/rendering/fairygui/src/widgets/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export { GImage } from './GImage';
|
||||
export { GTextField } from './GTextField';
|
||||
export { GGraph } from './GGraph';
|
||||
export { GButton } from './GButton';
|
||||
export { GProgressBar } from './GProgressBar';
|
||||
export { GSlider } from './GSlider';
|
||||
export { GLoader } from './GLoader';
|
||||
export { GList } from './GList';
|
||||
export type { ItemRenderer, ItemProvider } from './GList';
|
||||
export { GTextInput, EKeyboardType } from './GTextInput';
|
||||
Reference in New Issue
Block a user