2025-11-26 11:08:10 +08:00
/ * *
2025-11-27 20:42:46 +08:00
* UI 编 辑 器 模 块 入 口
* UI Editor Module Entry
2025-11-26 11:08:10 +08:00
* /
import React from 'react' ;
import { LayoutGrid , Square , Type , MousePointer2 , Sliders , BarChart3 , ScrollText , PanelTop } from 'lucide-react' ;
import type { ServiceContainer , Entity } from '@esengine/ecs-framework' ;
import { Core } from '@esengine/ecs-framework' ;
import type {
2025-11-27 20:42:46 +08:00
IEditorModuleLoader ,
PanelDescriptor ,
2025-11-26 11:08:10 +08:00
EntityCreationTemplate ,
2025-11-27 20:42:46 +08:00
ComponentAction ,
ComponentInspectorProviderDef
2025-11-26 11:08:10 +08:00
} from '@esengine/editor-core' ;
import {
EntityStoreService ,
MessageHub ,
ComponentRegistry ,
ComponentInspectorRegistry
} from '@esengine/editor-core' ;
2025-11-27 20:42:46 +08:00
// Local imports
2025-11-26 11:08:10 +08:00
import {
UITransformComponent ,
UIRenderComponent ,
UIInteractableComponent ,
UITextComponent ,
UILayoutComponent ,
UILayoutType ,
UIJustifyContent ,
UIAlignItems ,
UIButtonComponent ,
UIProgressBarComponent ,
UISliderComponent ,
UIScrollViewComponent
2025-11-27 20:42:46 +08:00
} from '../components' ;
import { UITransformInspector } from './inspectors' ;
import { registerUITransformGizmo , unregisterUITransformGizmo } from './gizmos' ;
// Re-exports
export { UITransformInspector } from './inspectors' ;
export { registerUITransformGizmo , unregisterUITransformGizmo } from './gizmos' ;
2025-11-26 11:08:10 +08:00
/ * *
2025-11-27 20:42:46 +08:00
* UI 编 辑 器 模 块
* UI Editor Module
2025-11-26 11:08:10 +08:00
* /
2025-11-27 20:42:46 +08:00
export class UIEditorModule implements IEditorModuleLoader {
async install ( services : ServiceContainer ) : Promise < void > {
// 注册 UI 组件到编辑器组件注册表 | Register UI components to editor component registry
2025-11-26 11:08:10 +08:00
const componentRegistry = services . resolve ( ComponentRegistry ) ;
if ( componentRegistry ) {
2025-11-27 20:42:46 +08:00
const uiComponents = [
{ name : 'UITransform' , type : UITransformComponent , category : 'components.category.ui' , description : 'UI element positioning and sizing' , icon : 'Move' } ,
{ name : 'UIRender' , type : UIRenderComponent , category : 'components.category.ui' , description : 'UI element visual appearance' , icon : 'Palette' } ,
{ name : 'UIInteractable' , type : UIInteractableComponent , category : 'components.category.ui' , description : 'UI element interaction handling' , icon : 'MousePointer2' } ,
{ name : 'UIText' , type : UITextComponent , category : 'components.category.ui' , description : 'Text rendering component' , icon : 'Type' } ,
{ name : 'UILayout' , type : UILayoutComponent , category : 'components.category.ui' , description : 'Automatic child layout (Flexbox-like)' , icon : 'LayoutGrid' } ,
{ name : 'UIButton' , type : UIButtonComponent , category : 'components.category.ui.widgets' , description : 'Interactive button component' , icon : 'RectangleHorizontal' } ,
{ name : 'UIProgressBar' , type : UIProgressBarComponent , category : 'components.category.ui.widgets' , description : 'Progress indicator component' , icon : 'BarChart3' } ,
{ name : 'UISlider' , type : UISliderComponent , category : 'components.category.ui.widgets' , description : 'Value slider component' , icon : 'Sliders' } ,
{ name : 'UIScrollView' , type : UIScrollViewComponent , category : 'components.category.ui.widgets' , description : 'Scrollable container component' , icon : 'ScrollText' } ,
] ;
for ( const comp of uiComponents ) {
componentRegistry . register ( {
name : comp.name ,
type : comp . type ,
category : comp.category ,
description : comp.description ,
icon : comp.icon
} ) ;
}
2025-11-26 11:08:10 +08:00
}
2025-11-27 20:42:46 +08:00
// 注册自定义组件检视器 | Register custom component inspectors
2025-11-26 11:08:10 +08:00
const componentInspectorRegistry = services . tryResolve ( ComponentInspectorRegistry ) ;
if ( componentInspectorRegistry ) {
componentInspectorRegistry . register ( new UITransformInspector ( ) ) ;
}
2025-11-27 20:42:46 +08:00
// 注册 Gizmo | Register gizmo
2025-11-26 11:08:10 +08:00
registerUITransformGizmo ( ) ;
}
async uninstall ( ) : Promise < void > {
unregisterUITransformGizmo ( ) ;
}
2025-11-27 20:42:46 +08:00
getEntityCreationTemplates ( ) : EntityCreationTemplate [ ] {
2025-11-26 11:08:10 +08:00
return [
// UI Canvas (Root container)
{
id : 'create-ui-canvas' ,
label : 'UI Canvas' ,
2025-11-27 20:42:46 +08:00
icon : 'PanelTop' ,
2025-11-26 11:08:10 +08:00
category : 'ui' ,
order : 200 ,
2025-11-27 20:42:46 +08:00
create : ( ) : number = > {
2025-11-26 11:08:10 +08:00
return this . createUIEntity ( 'UI Canvas' , ( entity ) = > {
const transform = entity . getComponent ( UITransformComponent ) ! ;
transform . width = 1920 ;
transform . height = 1080 ;
transform . anchorMinX = 0 ;
transform . anchorMinY = 0 ;
transform . anchorMaxX = 1 ;
transform . anchorMaxY = 1 ;
} ) ;
}
} ,
// UI Panel
{
id : 'create-ui-panel' ,
label : 'Panel' ,
2025-11-27 20:42:46 +08:00
icon : 'Square' ,
2025-11-26 11:08:10 +08:00
category : 'ui' ,
order : 201 ,
2025-11-27 20:42:46 +08:00
create : ( ) : number = > {
2025-11-26 11:08:10 +08:00
return this . createUIEntity ( 'Panel' , ( entity ) = > {
const render = entity . getComponent ( UIRenderComponent ) ! ;
render . backgroundColor = 0x2D2D2D ;
render . backgroundAlpha = 0.9 ;
render . setCornerRadius ( 8 ) ;
} ) ;
}
} ,
// UI Text
{
id : 'create-ui-text' ,
label : 'Text' ,
2025-11-27 20:42:46 +08:00
icon : 'Type' ,
2025-11-26 11:08:10 +08:00
category : 'ui' ,
order : 202 ,
2025-11-27 20:42:46 +08:00
create : ( ) : number = > {
2025-11-26 11:08:10 +08:00
return this . createUIEntity ( 'Text' , ( entity ) = > {
const transform = entity . getComponent ( UITransformComponent ) ! ;
transform . width = 200 ;
transform . height = 30 ;
const render = entity . getComponent ( UIRenderComponent ) ! ;
render . backgroundAlpha = 0 ;
const text = new UITextComponent ( ) ;
text . text = 'Hello World' ;
text . fontSize = 16 ;
text . color = 0xFFFFFF ;
entity . addComponent ( text ) ;
} ) ;
}
} ,
// UI Button
{
id : 'create-ui-button' ,
label : 'Button' ,
2025-11-27 20:42:46 +08:00
icon : 'MousePointer2' ,
2025-11-26 11:08:10 +08:00
category : 'ui' ,
order : 203 ,
2025-11-27 20:42:46 +08:00
create : ( ) : number = > {
2025-11-26 11:08:10 +08:00
return this . createUIEntity ( 'Button' , ( entity ) = > {
const transform = entity . getComponent ( UITransformComponent ) ! ;
transform . width = 120 ;
transform . height = 40 ;
const render = entity . getComponent ( UIRenderComponent ) ! ;
render . setCornerRadius ( 4 ) ;
const button = new UIButtonComponent ( ) ;
button . label = 'Button' ;
entity . addComponent ( button ) ;
const interactable = entity . getComponent ( UIInteractableComponent ) ! ;
interactable . enabled = true ;
interactable . cursor = 'pointer' ;
const text = new UITextComponent ( ) ;
text . text = 'Button' ;
text . fontSize = 14 ;
text . color = 0xFFFFFF ;
text . align = 'center' ;
text . verticalAlign = 'middle' ;
entity . addComponent ( text ) ;
} ) ;
}
} ,
// UI Slider
{
id : 'create-ui-slider' ,
label : 'Slider' ,
2025-11-27 20:42:46 +08:00
icon : 'Sliders' ,
2025-11-26 11:08:10 +08:00
category : 'ui' ,
order : 204 ,
2025-11-27 20:42:46 +08:00
create : ( ) : number = > {
2025-11-26 11:08:10 +08:00
return this . createUIEntity ( 'Slider' , ( entity ) = > {
const transform = entity . getComponent ( UITransformComponent ) ! ;
transform . width = 200 ;
transform . height = 20 ;
const render = entity . getComponent ( UIRenderComponent ) ;
if ( render ) {
entity . removeComponent ( render ) ;
}
const slider = new UISliderComponent ( ) ;
slider . value = 50 ;
slider . minValue = 0 ;
slider . maxValue = 100 ;
entity . addComponent ( slider ) ;
const interactable = entity . getComponent ( UIInteractableComponent ) ! ;
interactable . enabled = true ;
interactable . cursor = 'pointer' ;
} ) ;
}
} ,
// UI Progress Bar
{
id : 'create-ui-progressbar' ,
label : 'ProgressBar' ,
2025-11-27 20:42:46 +08:00
icon : 'BarChart3' ,
2025-11-26 11:08:10 +08:00
category : 'ui' ,
order : 205 ,
2025-11-27 20:42:46 +08:00
create : ( ) : number = > {
2025-11-26 11:08:10 +08:00
return this . createUIEntity ( 'ProgressBar' , ( entity ) = > {
const transform = entity . getComponent ( UITransformComponent ) ! ;
transform . width = 200 ;
transform . height = 20 ;
const render = entity . getComponent ( UIRenderComponent ) ;
if ( render ) {
entity . removeComponent ( render ) ;
}
const progress = new UIProgressBarComponent ( ) ;
progress . value = 50 ;
progress . minValue = 0 ;
progress . maxValue = 100 ;
progress . cornerRadius = 4 ;
entity . addComponent ( progress ) ;
} ) ;
}
} ,
// UI ScrollView
{
id : 'create-ui-scrollview' ,
label : 'ScrollView' ,
2025-11-27 20:42:46 +08:00
icon : 'ScrollText' ,
2025-11-26 11:08:10 +08:00
category : 'ui' ,
order : 206 ,
2025-11-27 20:42:46 +08:00
create : ( ) : number = > {
2025-11-26 11:08:10 +08:00
return this . createUIEntity ( 'ScrollView' , ( entity ) = > {
const transform = entity . getComponent ( UITransformComponent ) ! ;
transform . width = 300 ;
transform . height = 400 ;
const render = entity . getComponent ( UIRenderComponent ) ! ;
render . backgroundColor = 0x1A1A1A ;
render . setCornerRadius ( 4 ) ;
const scrollView = new UIScrollViewComponent ( ) ;
scrollView . verticalScroll = true ;
scrollView . horizontalScroll = false ;
scrollView . contentHeight = 800 ;
entity . addComponent ( scrollView ) ;
const interactable = entity . getComponent ( UIInteractableComponent ) ! ;
interactable . enabled = true ;
} ) ;
}
} ,
// UI Layout Container (Horizontal)
{
id : 'create-ui-hlayout' ,
label : 'HLayout' ,
2025-11-27 20:42:46 +08:00
icon : 'LayoutGrid' ,
2025-11-26 11:08:10 +08:00
category : 'ui' ,
order : 207 ,
2025-11-27 20:42:46 +08:00
create : ( ) : number = > {
2025-11-26 11:08:10 +08:00
return this . createUIEntity ( 'HLayout' , ( entity ) = > {
const transform = entity . getComponent ( UITransformComponent ) ! ;
transform . width = 400 ;
transform . height = 100 ;
const layout = new UILayoutComponent ( ) ;
layout . type = UILayoutType . Horizontal ;
layout . gap = 10 ;
layout . justifyContent = UIJustifyContent . Start ;
layout . alignItems = UIAlignItems . Center ;
entity . addComponent ( layout ) ;
const render = entity . getComponent ( UIRenderComponent ) ! ;
render . backgroundAlpha = 0 ;
} ) ;
}
} ,
// UI Layout Container (Vertical)
{
id : 'create-ui-vlayout' ,
label : 'VLayout' ,
2025-11-27 20:42:46 +08:00
icon : 'LayoutGrid' ,
2025-11-26 11:08:10 +08:00
category : 'ui' ,
order : 208 ,
2025-11-27 20:42:46 +08:00
create : ( ) : number = > {
2025-11-26 11:08:10 +08:00
return this . createUIEntity ( 'VLayout' , ( entity ) = > {
const transform = entity . getComponent ( UITransformComponent ) ! ;
transform . width = 200 ;
transform . height = 400 ;
const layout = new UILayoutComponent ( ) ;
layout . type = UILayoutType . Vertical ;
layout . gap = 10 ;
layout . justifyContent = UIJustifyContent . Start ;
layout . alignItems = UIAlignItems . Stretch ;
entity . addComponent ( layout ) ;
const render = entity . getComponent ( UIRenderComponent ) ! ;
render . backgroundAlpha = 0 ;
} ) ;
}
} ,
// UI Grid Layout
{
id : 'create-ui-grid' ,
label : 'Grid' ,
2025-11-27 20:42:46 +08:00
icon : 'LayoutGrid' ,
2025-11-26 11:08:10 +08:00
category : 'ui' ,
order : 209 ,
2025-11-27 20:42:46 +08:00
create : ( ) : number = > {
2025-11-26 11:08:10 +08:00
return this . createUIEntity ( 'Grid' , ( entity ) = > {
const transform = entity . getComponent ( UITransformComponent ) ! ;
transform . width = 400 ;
transform . height = 400 ;
const layout = new UILayoutComponent ( ) ;
layout . type = UILayoutType . Grid ;
layout . columns = 3 ;
layout . gap = 10 ;
entity . addComponent ( layout ) ;
const render = entity . getComponent ( UIRenderComponent ) ! ;
render . backgroundAlpha = 0 ;
} ) ;
}
} ,
] ;
}
/ * *
* 创 建 UI 实 体 的 辅 助 方 法
2025-11-27 20:42:46 +08:00
* Helper method to create UI entity
2025-11-26 11:08:10 +08:00
* /
private createUIEntity ( baseName : string , configure ? : ( entity : Entity ) = > void ) : number {
const scene = Core . scene ;
if ( ! scene ) {
throw new Error ( 'Scene not available' ) ;
}
const entityStore = Core . services . resolve ( EntityStoreService ) ;
const messageHub = Core . services . resolve ( MessageHub ) ;
if ( ! entityStore || ! messageHub ) {
throw new Error ( 'EntityStoreService or MessageHub not available' ) ;
}
const existingCount = entityStore . getAllEntities ( )
. filter ( ( e : Entity ) = > e . name . startsWith ( baseName ) ) . length ;
const entityName = existingCount > 0 ? ` ${ baseName } ${ existingCount + 1 } ` : baseName ;
const entity = scene . createEntity ( entityName ) ;
const transform = new UITransformComponent ( ) ;
transform . width = 100 ;
transform . height = 100 ;
entity . addComponent ( transform ) ;
const render = new UIRenderComponent ( ) ;
render . backgroundColor = 0x4A90D9 ;
entity . addComponent ( render ) ;
const interactable = new UIInteractableComponent ( ) ;
entity . addComponent ( interactable ) ;
if ( configure ) {
configure ( entity ) ;
}
entityStore . addEntity ( entity ) ;
messageHub . publish ( 'entity:added' , { entity } ) ;
messageHub . publish ( 'scene:modified' , { } ) ;
entityStore . selectEntity ( entity ) ;
return entity . id ;
}
}
2025-11-27 20:42:46 +08:00
export const uiEditorModule = new UIEditorModule ( ) ;
// Plugin exports
export { UIPlugin , UIRuntimeModule } from './UIPlugin' ;
export default uiEditorModule ;