From 3cbfa1e4cb7870c03d5970242c3bbdc12efa7abc Mon Sep 17 00:00:00 2001 From: YHH <359807859@qq.com> Date: Sat, 6 Dec 2025 11:56:25 +0800 Subject: [PATCH] =?UTF-8?q?fix(editor):=20=E4=BF=AE=E5=A4=8D=E5=8F=B3?= =?UTF-8?q?=E9=94=AE=E8=8F=9C=E5=8D=95=E5=92=8C=E7=B2=92=E5=AD=90=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E9=97=AE=E9=A2=98=20(#286)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复右键菜单被状态栏遮挡的问题 - 修复右键菜单边界检测,考虑标题栏和状态栏高度 - 调整右键菜单结构:新建文件夹 → 资源类型 → 工具操作 - 修复 Particle 插件默认未启用的问题(defaultEnabled 的新插件不被旧配置禁用) - 修复 SizeOverLifetime 模块在预览中无效果的问题 - 移除 MaterialEditorModule 中的重复模板注册 --- .../src/components/ContentBrowser.tsx | 95 +++++++++++++++---- .../editor-app/src/components/ContextMenu.tsx | 46 ++++++--- .../editor-app/src/styles/ContextMenu.css | 4 +- .../editor-core/src/Plugin/PluginManager.ts | 9 +- packages/material-editor/src/index.ts | 12 +-- .../src/panels/ParticleEditorPanel.tsx | 40 ++++++-- 6 files changed, 153 insertions(+), 53 deletions(-) diff --git a/packages/editor-app/src/components/ContentBrowser.tsx b/packages/editor-app/src/components/ContentBrowser.tsx index efd346e8..5883ac41 100644 --- a/packages/editor-app/src/components/ContentBrowser.tsx +++ b/packages/editor-app/src/components/ContentBrowser.tsx @@ -164,6 +164,34 @@ export function ContentBrowser({ template: FileCreationTemplate; } | null>(null); + // 文件创建模板列表(需要状态跟踪以便插件安装后刷新) + // File creation templates list (need state tracking to refresh after plugin installation) + const [fileCreationTemplates, setFileCreationTemplates] = useState([]); + + // 初始化和监听插件安装事件以更新模板列表 + // Initialize and listen for plugin installation events to update template list + useEffect(() => { + const updateTemplates = () => { + if (fileActionRegistry) { + const templates = fileActionRegistry.getCreationTemplates(); + setFileCreationTemplates([...templates]); + } + }; + + // 初始加载 + updateTemplates(); + + // 监听插件安装/卸载事件 + if (messageHub) { + const unsubInstall = messageHub.subscribe('plugin:installed', updateTemplates); + const unsubUninstall = messageHub.subscribe('plugin:uninstalled', updateTemplates); + return () => { + unsubInstall(); + unsubUninstall(); + }; + } + }, [fileActionRegistry, messageHub]); + const t = { en: { favorites: 'Favorites', @@ -844,7 +872,6 @@ export class ${className} { const items: ContextMenuItem[] = []; if (!asset) { - // Background context menu items.push({ label: t.newFolder, icon: , @@ -861,29 +888,55 @@ export class ${className} { } }); - if (fileActionRegistry) { - const templates = fileActionRegistry.getCreationTemplates(); - if (templates.length > 0) { - items.push({ label: '', separator: true, onClick: () => {} }); - for (const template of templates) { - const localizedLabel = getTemplateLabel(template.label); - items.push({ - label: `${t.newPrefix} ${localizedLabel}`, - icon: getIconComponent(template.icon, 16), - onClick: () => { - setContextMenu(null); - if (currentPath) { - setCreateFileDialog({ - parentPath: currentPath, - template - }); - } + if (fileCreationTemplates.length > 0) { + items.push({ label: '', separator: true, onClick: () => {} }); + + for (const template of fileCreationTemplates) { + const localizedLabel = getTemplateLabel(template.label); + items.push({ + label: localizedLabel, + icon: getIconComponent(template.icon, 16), + onClick: () => { + setContextMenu(null); + if (currentPath) { + setCreateFileDialog({ + parentPath: currentPath, + template + }); } - }); - } + } + }); } } + items.push({ label: '', separator: true, onClick: () => {} }); + + items.push({ + label: locale === 'zh' ? '在资源管理器中显示' : 'Show in Explorer', + icon: , + onClick: async () => { + if (currentPath) { + try { + await TauriAPI.showInFolder(currentPath); + } catch (error) { + console.error('Failed to show in folder:', error); + } + } + setContextMenu(null); + } + }); + + items.push({ + label: locale === 'zh' ? '刷新' : 'Refresh', + icon: , + onClick: async () => { + if (currentPath) { + await loadAssets(currentPath); + } + setContextMenu(null); + } + }); + return items; } @@ -1093,7 +1146,7 @@ export class ${className} { }); return items; - }, [currentPath, fileActionRegistry, handleAssetDoubleClick, loadAssets, locale, t.newFolder, setRenameDialog, setDeleteConfirmDialog, setContextMenu, setCreateFileDialog]); + }, [currentPath, fileCreationTemplates, handleAssetDoubleClick, loadAssets, locale, t.newFolder, t.newPrefix, setRenameDialog, setDeleteConfirmDialog, setContextMenu, setCreateFileDialog]); // Render folder tree node const renderFolderNode = useCallback((node: FolderNode, depth: number = 0) => { diff --git a/packages/editor-app/src/components/ContextMenu.tsx b/packages/editor-app/src/components/ContextMenu.tsx index 31ee18b7..1e40f832 100644 --- a/packages/editor-app/src/components/ContextMenu.tsx +++ b/packages/editor-app/src/components/ContextMenu.tsx @@ -129,27 +129,43 @@ export function ContextMenu({ items, position, onClose }: ContextMenuProps) { const [submenuRect, setSubmenuRect] = useState(null); useEffect(() => { - if (menuRef.current) { - const menu = menuRef.current; - const rect = menu.getBoundingClientRect(); - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; + const adjustPosition = () => { + if (menuRef.current) { + const menu = menuRef.current; + const rect = menu.getBoundingClientRect(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; - let x = position.x; - let y = position.y; + const STATUS_BAR_HEIGHT = 28; + const TITLE_BAR_HEIGHT = 32; - if (x + rect.width > viewportWidth) { - x = Math.max(0, viewportWidth - rect.width - 10); - } + let x = position.x; + let y = position.y; - if (y + rect.height > viewportHeight) { - y = Math.max(0, viewportHeight - rect.height - 10); - } + if (x + rect.width > viewportWidth - 10) { + x = Math.max(10, viewportWidth - rect.width - 10); + } + + if (y + rect.height > viewportHeight - STATUS_BAR_HEIGHT - 10) { + y = Math.max(TITLE_BAR_HEIGHT + 10, viewportHeight - STATUS_BAR_HEIGHT - rect.height - 10); + } + + if (x < 10) { + x = 10; + } + + if (y < TITLE_BAR_HEIGHT + 10) { + y = TITLE_BAR_HEIGHT + 10; + } - if (x !== position.x || y !== position.y) { setAdjustedPosition({ x, y }); } - } + }; + + adjustPosition(); + const rafId = requestAnimationFrame(adjustPosition); + + return () => cancelAnimationFrame(rafId); }, [position]); useEffect(() => { diff --git a/packages/editor-app/src/styles/ContextMenu.css b/packages/editor-app/src/styles/ContextMenu.css index 3ec496aa..819f8b61 100644 --- a/packages/editor-app/src/styles/ContextMenu.css +++ b/packages/editor-app/src/styles/ContextMenu.css @@ -6,7 +6,9 @@ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); padding: 4px 0; min-width: 200px; - z-index: var(--z-index-popover); + max-height: calc(100vh - 80px); + overflow-y: auto; + z-index: 10001; font-size: 13px; } diff --git a/packages/editor-core/src/Plugin/PluginManager.ts b/packages/editor-core/src/Plugin/PluginManager.ts index 7d316ae7..afef63a5 100644 --- a/packages/editor-core/src/Plugin/PluginManager.ts +++ b/packages/editor-core/src/Plugin/PluginManager.ts @@ -1076,8 +1076,15 @@ export class PluginManager implements IService { continue; // 核心插件始终启用 } - const shouldBeEnabled = enabledPlugins.includes(id); + const inConfig = enabledPlugins.includes(id); const wasEnabled = plugin.enabled; + const isDefaultEnabled = plugin.plugin.manifest.defaultEnabled; + + // 如果插件在配置中明确列出,按配置来 + // 如果插件不在配置中但 defaultEnabled=true,保持启用(新插件不应被旧配置禁用) + // If plugin is explicitly in config, follow config + // If plugin is not in config but defaultEnabled=true, keep enabled (new plugins should not be disabled by old config) + const shouldBeEnabled = inConfig || (isDefaultEnabled && !enabledPlugins.some(p => p === id)); if (shouldBeEnabled && !wasEnabled) { toEnable.push(id); diff --git a/packages/material-editor/src/index.ts b/packages/material-editor/src/index.ts index 91f9ef73..321fba4b 100644 --- a/packages/material-editor/src/index.ts +++ b/packages/material-editor/src/index.ts @@ -62,14 +62,14 @@ export class MaterialEditorModule implements IEditorModuleLoader { private inspectorProvider?: MaterialAssetInspectorProvider; async install(services: ServiceContainer): Promise { - // Register file creation templates + // 注意:文件创建模板由 PluginManager.activatePluginEditor() 自动注册 + // 不要在这里手动注册,否则会重复 + // NOTE: File creation templates are auto-registered by PluginManager.activatePluginEditor() + // Do not manually register here to avoid duplicates + + // Register asset creation mapping for .mat files const fileActionRegistry = services.resolve(FileActionRegistry); if (fileActionRegistry) { - for (const template of this.getFileCreationTemplates()) { - fileActionRegistry.registerCreationTemplate(template); - } - - // Register asset creation mapping for .mat files fileActionRegistry.registerAssetCreationMapping({ extension: '.mat', createMessage: 'material:create', diff --git a/packages/particle-editor/src/panels/ParticleEditorPanel.tsx b/packages/particle-editor/src/panels/ParticleEditorPanel.tsx index b1c7fa44..92577702 100644 --- a/packages/particle-editor/src/panels/ParticleEditorPanel.tsx +++ b/packages/particle-editor/src/panels/ParticleEditorPanel.tsx @@ -140,6 +140,28 @@ function evaluateScaleCurve(t: number, curveType: string): number { } } +/** 评估缩放关键帧 | Evaluate scale keys */ +function evaluateScaleKeys(keys: ScaleKey[], normalizedAge: number): number { + if (keys.length === 0) return 1; + if (keys.length === 1) return keys[0].scale; + + // 找到当前时间所在的两个关键帧 + let startKey = keys[0]; + let endKey = keys[keys.length - 1]; + + for (let i = 0; i < keys.length - 1; i++) { + if (normalizedAge >= keys[i].time && normalizedAge <= keys[i + 1].time) { + startKey = keys[i]; + endKey = keys[i + 1]; + break; + } + } + + const range = endKey.time - startKey.time; + const t = range > 0 ? (normalizedAge - startKey.time) / range : 0; + return lerp(startKey.scale, endKey.scale, t); +} + /** 评估颜色渐变 | Evaluate color gradient */ function evaluateColorGradient(gradient: ColorKey[], normalizedAge: number): ColorKey { if (gradient.length === 0) { @@ -235,11 +257,10 @@ function useParticlePreview( ]; // 缩放曲线 | Scale curve - const scaleCurveType: string = sizeModule?.enabled && sizeModule.params?.curveType - ? sizeModule.params.curveType as string - : 'linear'; - const scaleStartMultiplier: number = (sizeModule?.params?.startMultiplier as number) ?? 1; - const scaleEndMultiplier: number = (sizeModule?.params?.endMultiplier as number) ?? data.endScale; + const sizeEnabled = sizeModule?.enabled ?? false; + const scaleKeys: ScaleKey[] = sizeModule?.params?.keys + ? sizeModule.params.keys as ScaleKey[] + : [{ time: 0, scale: 1 }, { time: 1, scale: data.endScale }]; // 噪声参数 | Noise parameters const noiseEnabled = noiseModule?.enabled ?? false; @@ -532,10 +553,11 @@ function useParticlePreview( p.alpha = p.startAlpha * color.a; // 缩放曲线 | Scale curve - const scaleT = evaluateScaleCurve(normalizedAge, scaleCurveType); - const scaleMult = lerp(scaleStartMultiplier, scaleEndMultiplier, scaleT); - p.scaleX = p.startScaleX * scaleMult; - p.scaleY = p.startScaleY * scaleMult; + if (sizeEnabled) { + const scaleMult = evaluateScaleKeys(scaleKeys, normalizedAge); + p.scaleX = p.startScaleX * scaleMult; + p.scaleY = p.startScaleY * scaleMult; + } // 噪声模块 | Noise module if (noiseEnabled) {