feat(editor-app): 添加渲染调试面板
- 新增 RenderDebugService 和调试面板 UI - App/ContentBrowser 添加调试日志 - TitleBar/Viewport 优化 - DialogManager 改进
This commit is contained in:
@@ -1026,13 +1026,16 @@ export class ${className} {
|
||||
|
||||
// Handle asset double click
|
||||
const handleAssetDoubleClick = useCallback(async (asset: AssetItem) => {
|
||||
console.log('[ContentBrowser] Double click:', asset.name, 'type:', asset.type, 'ext:', asset.extension);
|
||||
if (asset.type === 'folder') {
|
||||
setCurrentPath(asset.path);
|
||||
loadAssets(asset.path);
|
||||
setExpandedFolders(prev => new Set([...prev, asset.path]));
|
||||
} else {
|
||||
const ext = asset.extension?.toLowerCase();
|
||||
console.log('[ContentBrowser] File ext:', ext, 'onOpenScene:', !!onOpenScene);
|
||||
if (ext === 'ecs' && onOpenScene) {
|
||||
console.log('[ContentBrowser] Opening scene:', asset.path);
|
||||
onOpenScene(asset.path);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ interface TitleBarProps {
|
||||
onCreatePlugin?: () => void;
|
||||
onReloadPlugins?: () => void;
|
||||
onOpenBuildSettings?: () => void;
|
||||
onOpenRenderDebug?: () => void;
|
||||
}
|
||||
|
||||
export function TitleBar({
|
||||
@@ -61,7 +62,8 @@ export function TitleBar({
|
||||
onOpenAbout,
|
||||
onCreatePlugin,
|
||||
onReloadPlugins,
|
||||
onOpenBuildSettings
|
||||
onOpenBuildSettings,
|
||||
onOpenRenderDebug
|
||||
}: TitleBarProps) {
|
||||
const { t } = useLocale();
|
||||
const [openMenu, setOpenMenu] = useState<string | null>(null);
|
||||
@@ -197,6 +199,7 @@ export function TitleBar({
|
||||
{ label: t('menu.tools.reloadPlugins'), shortcut: 'Ctrl+R', onClick: onReloadPlugins },
|
||||
{ separator: true },
|
||||
{ label: t('menu.tools.portManager'), onClick: onOpenPortManager },
|
||||
{ label: t('menu.tools.renderDebug'), onClick: onOpenRenderDebug },
|
||||
{ separator: true },
|
||||
{ label: t('menu.tools.settings'), onClick: onOpenSettings }
|
||||
],
|
||||
|
||||
@@ -8,9 +8,9 @@ import '../styles/Viewport.css';
|
||||
import { useEngine } from '../hooks/useEngine';
|
||||
import { useLocale } from '../hooks/useLocale';
|
||||
import { EngineService } from '../services/EngineService';
|
||||
import { Core, Entity, SceneSerializer, PrefabSerializer, ComponentRegistry } from '@esengine/ecs-framework';
|
||||
import { Core, Entity, SceneSerializer, PrefabSerializer, GlobalComponentRegistry } from '@esengine/ecs-framework';
|
||||
import type { PrefabData, ComponentType } from '@esengine/ecs-framework';
|
||||
import { MessageHub, ProjectService, AssetRegistryService, EntityStoreService, CommandManager, SceneManagerService } from '@esengine/editor-core';
|
||||
import { MessageHub, ProjectService, AssetRegistryService, EntityStoreService, CommandManager, SceneManagerService, UserCodeService, UserCodeTarget } from '@esengine/editor-core';
|
||||
import { InstantiatePrefabCommand } from '../application/commands/prefab/InstantiatePrefabCommand';
|
||||
import { TransformCommand, type TransformState, type TransformOperationType } from '../application/commands';
|
||||
import { TransformComponent } from '@esengine/engine-core';
|
||||
@@ -21,6 +21,8 @@ import { open } from '@tauri-apps/plugin-shell';
|
||||
import { RuntimeResolver } from '../services/RuntimeResolver';
|
||||
import { QRCodeDialog } from './QRCodeDialog';
|
||||
import { collectAssetReferences } from '@esengine/asset-system';
|
||||
import { RuntimeSceneManager, type IRuntimeSceneManager } from '@esengine/runtime-core';
|
||||
import { ParticleSystemComponent } from '@esengine/particle';
|
||||
|
||||
import type { ModuleManifest } from '../services/RuntimeResolver';
|
||||
|
||||
@@ -264,6 +266,9 @@ export function Viewport({ locale = 'en', messageHub, commandManager }: Viewport
|
||||
const editorCameraRef = useRef({ x: 0, y: 0, zoom: 1 });
|
||||
const playStateRef = useRef<PlayState>('stopped');
|
||||
|
||||
// Runtime scene manager for play mode scene switching | Play 模式场景切换管理器
|
||||
const runtimeSceneManagerRef = useRef<IRuntimeSceneManager | null>(null);
|
||||
|
||||
// Live transform display state | 实时变换显示状态
|
||||
const [liveTransform, setLiveTransform] = useState<{
|
||||
type: 'move' | 'rotate' | 'scale';
|
||||
@@ -811,7 +816,22 @@ export function Viewport({ locale = 'en', messageHub, commandManager }: Viewport
|
||||
return;
|
||||
}
|
||||
// Save scene snapshot before playing
|
||||
// saveSceneSnapshot clears all textures, so we need to reset particle textureIds after
|
||||
// saveSceneSnapshot 会清除所有纹理,所以之后需要重置粒子的 textureId
|
||||
EngineService.getInstance().saveSceneSnapshot();
|
||||
|
||||
// Reset particle component textureIds after snapshot (textures were cleared)
|
||||
// 快照后重置粒子组件的 textureId(纹理已被清除)
|
||||
const scene = Core.scene;
|
||||
if (scene) {
|
||||
for (const entity of scene.entities.buffer) {
|
||||
const particleComponent = entity.getComponent(ParticleSystemComponent);
|
||||
if (particleComponent) {
|
||||
particleComponent.textureId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save editor camera state
|
||||
editorCameraRef.current = { x: camera2DOffset.x, y: camera2DOffset.y, zoom: camera2DZoom };
|
||||
setPlayState('playing');
|
||||
@@ -820,6 +840,132 @@ export function Viewport({ locale = 'en', messageHub, commandManager }: Viewport
|
||||
EngineService.getInstance().setEditorMode(false);
|
||||
// Switch to player camera
|
||||
syncPlayerCamera();
|
||||
|
||||
// Register RuntimeSceneManager for scene switching in play mode
|
||||
// 注册 RuntimeSceneManager 以支持 Play 模式下的场景切换
|
||||
const projectService = Core.services.tryResolve(ProjectService);
|
||||
const projectPath = projectService?.getCurrentProject()?.path;
|
||||
if (projectPath) {
|
||||
// Create scene loader function that reads scene files using Tauri API
|
||||
// 创建使用 Tauri API 读取场景文件的场景加载器函数
|
||||
const editorSceneLoader = async (scenePath: string): Promise<void> => {
|
||||
try {
|
||||
// Normalize path: handle both relative and absolute paths
|
||||
// 标准化路径:处理相对路径和绝对路径
|
||||
let fullPath = scenePath;
|
||||
if (!scenePath.includes(':') && !scenePath.startsWith('/')) {
|
||||
// Relative path - construct full path
|
||||
// 相对路径 - 构建完整路径
|
||||
const normalizedPath = scenePath.replace(/^\.\//, '').replace(/\//g, '\\');
|
||||
fullPath = `${projectPath}\\${normalizedPath}`;
|
||||
} else {
|
||||
// Absolute path - normalize separators for Windows
|
||||
// 绝对路径 - 为 Windows 规范化分隔符
|
||||
fullPath = scenePath.replace(/\//g, '\\');
|
||||
}
|
||||
|
||||
// Read scene file content
|
||||
// 读取场景文件内容
|
||||
const sceneJson = await TauriAPI.readFileContent(fullPath);
|
||||
|
||||
// Validate scene data
|
||||
// 验证场景数据
|
||||
const validation = SceneSerializer.validate(sceneJson);
|
||||
if (!validation.valid) {
|
||||
throw new Error(`Invalid scene: ${validation.errors?.join(', ')}`);
|
||||
}
|
||||
|
||||
// Save current scene snapshot (so we can go back)
|
||||
// 保存当前场景快照(以便返回)
|
||||
EngineService.getInstance().saveSceneSnapshot();
|
||||
|
||||
// Load new scene by deserializing into current scene
|
||||
// 通过反序列化加载新场景到当前场景
|
||||
const scene = Core.scene;
|
||||
if (scene) {
|
||||
scene.deserialize(sceneJson, { strategy: 'replace' });
|
||||
|
||||
// Reset particle component textureIds after scene switch
|
||||
// 场景切换后重置粒子组件的 textureId
|
||||
// This ensures ParticleUpdateSystem will reload textures
|
||||
// 这确保 ParticleUpdateSystem 会重新加载纹理
|
||||
for (const entity of scene.entities.buffer) {
|
||||
const particleComponent = entity.getComponent(ParticleSystemComponent);
|
||||
if (particleComponent) {
|
||||
particleComponent.textureId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-register user code components and systems after scene switch
|
||||
// 场景切换后重新注册用户代码组件和系统
|
||||
const userCodeService = Core.services.tryResolve(UserCodeService);
|
||||
if (userCodeService) {
|
||||
const runtimeModule = userCodeService.getModule(UserCodeTarget.Runtime);
|
||||
if (runtimeModule) {
|
||||
// Re-register components (ensures GlobalComponentRegistry has correct references)
|
||||
// 重新注册组件(确保 GlobalComponentRegistry 有正确的引用)
|
||||
userCodeService.registerComponents(runtimeModule, GlobalComponentRegistry);
|
||||
|
||||
// Re-register systems (recreates systems with correct component references)
|
||||
// 重新注册系统(使用正确的组件引用重建系统)
|
||||
userCodeService.registerSystems(runtimeModule, scene);
|
||||
}
|
||||
}
|
||||
|
||||
// Load scene resources (textures, etc.)
|
||||
// 加载场景资源(纹理等)
|
||||
await EngineService.getInstance().loadSceneResources();
|
||||
|
||||
// Sync entity store
|
||||
// 同步实体存储
|
||||
const entityStore = Core.services.tryResolve(EntityStoreService);
|
||||
entityStore?.syncFromScene();
|
||||
}
|
||||
|
||||
console.log(`[Viewport] Scene loaded in play mode: ${scenePath}`);
|
||||
} catch (error) {
|
||||
console.error(`[Viewport] Failed to load scene: ${scenePath}`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Create and register RuntimeSceneManager
|
||||
// 创建并注册 RuntimeSceneManager
|
||||
const sceneManager = new RuntimeSceneManager(
|
||||
editorSceneLoader,
|
||||
`${projectPath}\\scenes`
|
||||
);
|
||||
runtimeSceneManagerRef.current = sceneManager;
|
||||
|
||||
// Register to Core.services with the global key
|
||||
// 使用全局 key 注册到 Core.services
|
||||
const GlobalSceneManagerKey = Symbol.for('@esengine/service:runtimeSceneManager');
|
||||
if (!Core.services.isRegistered(GlobalSceneManagerKey)) {
|
||||
Core.services.registerInstance(GlobalSceneManagerKey, sceneManager);
|
||||
}
|
||||
|
||||
console.log('[Viewport] RuntimeSceneManager registered for play mode');
|
||||
}
|
||||
|
||||
// Register user code components and systems before starting engine
|
||||
// 在启动引擎前注册用户代码组件和系统
|
||||
const userCodeService = Core.services.tryResolve(UserCodeService);
|
||||
if (userCodeService) {
|
||||
const runtimeModule = userCodeService.getModule(UserCodeTarget.Runtime);
|
||||
if (runtimeModule) {
|
||||
// Register components first (ensures GlobalComponentRegistry has correct references)
|
||||
// 先注册组件(确保 GlobalComponentRegistry 有正确的引用)
|
||||
userCodeService.registerComponents(runtimeModule, GlobalComponentRegistry);
|
||||
|
||||
// Then register systems (uses registered component references)
|
||||
// 然后注册系统(使用已注册的组件引用)
|
||||
const scene = Core.scene;
|
||||
if (scene) {
|
||||
userCodeService.registerSystems(runtimeModule, scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
engine.start();
|
||||
} else if (playState === 'paused') {
|
||||
setPlayState('playing');
|
||||
@@ -837,6 +983,19 @@ export function Viewport({ locale = 'en', messageHub, commandManager }: Viewport
|
||||
const handleStop = async () => {
|
||||
setPlayState('stopped');
|
||||
engine.stop();
|
||||
|
||||
// Unregister RuntimeSceneManager
|
||||
// 注销 RuntimeSceneManager
|
||||
if (runtimeSceneManagerRef.current) {
|
||||
const GlobalSceneManagerKey = Symbol.for('@esengine/service:runtimeSceneManager');
|
||||
if (Core.services.isRegistered(GlobalSceneManagerKey)) {
|
||||
Core.services.unregister(GlobalSceneManagerKey);
|
||||
}
|
||||
runtimeSceneManagerRef.current.dispose();
|
||||
runtimeSceneManagerRef.current = null;
|
||||
console.log('[Viewport] RuntimeSceneManager unregistered');
|
||||
}
|
||||
|
||||
// Restore scene snapshot
|
||||
await EngineService.getInstance().restoreSceneSnapshot();
|
||||
// Restore editor camera state
|
||||
|
||||
633
packages/editor-app/src/components/debug/RenderDebugPanel.css
Normal file
633
packages/editor-app/src/components/debug/RenderDebugPanel.css
Normal file
@@ -0,0 +1,633 @@
|
||||
/**
|
||||
* 渲染调试面板样式 (浮动窗口)
|
||||
* Render Debug Panel Styles (Floating Window)
|
||||
*/
|
||||
|
||||
/* ==================== Floating Window ==================== */
|
||||
.render-debug-window {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #3c3c3c;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
overflow: hidden;
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
font-size: 11px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.render-debug-window.dragging {
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* 独立窗口模式 | Standalone mode */
|
||||
.render-debug-window.standalone {
|
||||
position: relative;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.render-debug-window.standalone .window-header {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* ==================== Window Header ==================== */
|
||||
.render-debug-window .window-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: #2d2d2d;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
cursor: move;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.render-debug-window .window-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.render-debug-window .window-title svg {
|
||||
color: #4a9eff;
|
||||
}
|
||||
|
||||
.render-debug-window .paused-badge {
|
||||
padding: 2px 6px;
|
||||
background: #f59e0b;
|
||||
color: #000;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
border-radius: 3px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.render-debug-window .window-controls {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.render-debug-window .window-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.render-debug-window .window-btn:hover {
|
||||
background: #3a3a3a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ==================== Toolbar ==================== */
|
||||
.render-debug-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 6px 12px;
|
||||
background: #262626;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.render-debug-toolbar .toolbar-left,
|
||||
.render-debug-toolbar .toolbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.render-debug-toolbar .toolbar-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
background: #3a3a3a;
|
||||
border: 1px solid #4a4a4a;
|
||||
border-radius: 3px;
|
||||
color: #ccc;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.render-debug-toolbar .toolbar-btn:hover {
|
||||
background: #4a4a4a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.render-debug-toolbar .toolbar-btn.active {
|
||||
background: #4a9eff;
|
||||
border-color: #4a9eff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.render-debug-toolbar .toolbar-btn.icon-only {
|
||||
padding: 4px 6px;
|
||||
}
|
||||
|
||||
.render-debug-toolbar .toolbar-btn.recording {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
border-color: #ef4444;
|
||||
}
|
||||
|
||||
.render-debug-toolbar .toolbar-btn .record-dot {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #ef4444;
|
||||
border-radius: 50%;
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.render-debug-toolbar .history-badge {
|
||||
padding: 2px 6px;
|
||||
background: #8b5cf6;
|
||||
color: #fff;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
border-radius: 3px;
|
||||
letter-spacing: 0.5px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.render-debug-toolbar .toolbar-btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.render-debug-toolbar .toolbar-btn:disabled:hover {
|
||||
background: #3a3a3a;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
/* ==================== Timeline ==================== */
|
||||
.render-debug-timeline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
background: #222;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.render-debug-timeline .timeline-slider {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: #333;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.render-debug-timeline .timeline-slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: #4a9eff;
|
||||
border-radius: 50%;
|
||||
cursor: grab;
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
|
||||
.render-debug-timeline .timeline-slider::-webkit-slider-thumb:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.render-debug-timeline .timeline-slider::-webkit-slider-thumb:active {
|
||||
cursor: grabbing;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.render-debug-timeline .timeline-slider::-moz-range-thumb {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background: #4a9eff;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.render-debug-timeline .timeline-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 9px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.render-debug-toolbar .toolbar-separator {
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
background: #3a3a3a;
|
||||
}
|
||||
|
||||
.render-debug-toolbar .frame-counter {
|
||||
font-family: 'Consolas', monospace;
|
||||
font-size: 10px;
|
||||
color: #888;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
/* ==================== Main Layout ==================== */
|
||||
.render-debug-main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ==================== Left Panel (Event List) ==================== */
|
||||
.render-debug-left {
|
||||
width: 260px;
|
||||
min-width: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #222;
|
||||
border-right: 1px solid #1a1a1a;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.event-list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 6px 10px;
|
||||
background: #262626;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.event-list-header .event-count {
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.event-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.event-list::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
.event-list::-webkit-scrollbar-track {
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
.event-list::-webkit-scrollbar-thumb {
|
||||
background: #3a3a3a;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.event-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: #555;
|
||||
font-size: 10px;
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Event Items */
|
||||
.event-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 3px 6px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 10px;
|
||||
color: #bbb;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.event-item:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.event-item.selected {
|
||||
background: rgba(74, 158, 255, 0.2);
|
||||
border-left: 2px solid #4a9eff;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.event-item .expand-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.event-item .expand-icon:not(.placeholder):hover {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.event-item .expand-icon.placeholder {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.event-item .event-icon {
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.event-item .event-icon.sprite {
|
||||
color: #4fc3f7;
|
||||
}
|
||||
|
||||
.event-item .event-icon.particle {
|
||||
color: #ffb74d;
|
||||
}
|
||||
|
||||
.event-item .event-icon.ui {
|
||||
color: #81c784;
|
||||
}
|
||||
|
||||
.event-item .event-icon.batch {
|
||||
color: #81c784;
|
||||
}
|
||||
|
||||
.event-item .event-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.event-item .event-draws {
|
||||
font-family: 'Consolas', monospace;
|
||||
font-size: 9px;
|
||||
color: #666;
|
||||
padding: 1px 3px;
|
||||
background: #1a1a1a;
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ==================== Right Panel ==================== */
|
||||
.render-debug-right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Preview Section */
|
||||
.render-debug-preview {
|
||||
height: 40%;
|
||||
min-height: 120px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
}
|
||||
|
||||
.preview-header {
|
||||
padding: 6px 10px;
|
||||
background: #262626;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.preview-canvas-container {
|
||||
flex: 1;
|
||||
background: #1a1a1a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview-canvas-container canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Details Section */
|
||||
.render-debug-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.details-header {
|
||||
padding: 6px 10px;
|
||||
background: #262626;
|
||||
border-bottom: 1px solid #1a1a1a;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.details-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
background: #1e1e1e;
|
||||
}
|
||||
|
||||
.details-content::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
.details-content::-webkit-scrollbar-track {
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
.details-content::-webkit-scrollbar-thumb {
|
||||
background: #3a3a3a;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.details-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: #555;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* Details Grid */
|
||||
.details-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.details-section {
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 8px 0 3px 0;
|
||||
margin-top: 6px;
|
||||
border-top: 1px solid #333;
|
||||
}
|
||||
|
||||
.details-section:first-child {
|
||||
margin-top: 0;
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 3px 0;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.detail-row .detail-label {
|
||||
width: 100px;
|
||||
color: #888;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-row .detail-value {
|
||||
flex: 1;
|
||||
color: #ccc;
|
||||
font-family: 'Consolas', monospace;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.detail-row.highlight .detail-value {
|
||||
color: #4fc3f7;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ==================== Stats Bar ==================== */
|
||||
.render-debug-stats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 6px 12px;
|
||||
background: #262626;
|
||||
border-top: 1px solid #1a1a1a;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.render-debug-stats .stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 10px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.render-debug-stats .stat-item svg {
|
||||
color: #4a9eff;
|
||||
}
|
||||
|
||||
/* ==================== Resize Handle ==================== */
|
||||
.resize-handle {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: se-resize;
|
||||
background: linear-gradient(135deg, transparent 50%, #3a3a3a 50%);
|
||||
border-radius: 0 0 6px 0;
|
||||
}
|
||||
|
||||
.resize-handle:hover {
|
||||
background: linear-gradient(135deg, transparent 50%, #4a9eff 50%);
|
||||
}
|
||||
|
||||
/* ==================== TextureSheet Preview ==================== */
|
||||
.texture-sheet-preview {
|
||||
margin-top: 8px;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.texture-sheet-preview canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* ==================== Texture Preview ==================== */
|
||||
.texture-preview-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 3px 0;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.texture-preview-row .detail-label {
|
||||
width: 100px;
|
||||
color: #888;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.texture-preview-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.texture-thumbnail-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.texture-thumbnail {
|
||||
max-width: 100%;
|
||||
max-height: 80px;
|
||||
object-fit: contain;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #333;
|
||||
background: repeating-conic-gradient(#2a2a2a 0% 25%, #1a1a1a 0% 50%) 50% / 8px 8px;
|
||||
}
|
||||
|
||||
.texture-path {
|
||||
font-family: 'Consolas', monospace;
|
||||
font-size: 9px;
|
||||
color: #666;
|
||||
word-break: break-all;
|
||||
line-height: 1.3;
|
||||
}
|
||||
1059
packages/editor-app/src/components/debug/RenderDebugPanel.tsx
Normal file
1059
packages/editor-app/src/components/debug/RenderDebugPanel.tsx
Normal file
File diff suppressed because it is too large
Load Diff
7
packages/editor-app/src/components/debug/index.ts
Normal file
7
packages/editor-app/src/components/debug/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 调试组件导出
|
||||
* Debug components export
|
||||
*/
|
||||
|
||||
export { RenderDebugPanel } from './RenderDebugPanel';
|
||||
export type { default as RenderDebugPanelProps } from './RenderDebugPanel';
|
||||
Reference in New Issue
Block a user