diff --git a/packages/editor-app/src/App.tsx b/packages/editor-app/src/App.tsx index 2369afaa..318c52f5 100644 --- a/packages/editor-app/src/App.tsx +++ b/packages/editor-app/src/App.tsx @@ -8,11 +8,12 @@ import { EntityInspector } from './components/EntityInspector'; import { AssetBrowser } from './components/AssetBrowser'; import { ConsolePanel } from './components/ConsolePanel'; import { Viewport } from './components/Viewport'; +import { MenuBar } from './components/MenuBar'; import { DockContainer, DockablePanel } from './components/DockContainer'; import { TauriAPI } from './api/tauri'; import { useLocale } from './hooks/useLocale'; import { en, zh } from './locales'; -import { Plus, Trash2, Loader2, Globe } from 'lucide-react'; +import { Loader2, Globe } from 'lucide-react'; import './styles/App.css'; const coreInstance = Core.create({ debug: true }); @@ -146,22 +147,30 @@ function App() { console.log('Create project not implemented yet'); }; - const handleCreateEntity = () => { - if (!initialized || !entityStore) return; - const scene = Core.scene; - if (!scene) return; - - const entity = scene.createEntity('Entity'); - entityStore.addEntity(entity); + const handleNewScene = () => { + console.log('New scene not implemented yet'); }; - const handleDeleteEntity = () => { - if (!entityStore) return; - const selected = entityStore.getSelectedEntity(); - if (selected) { - selected.destroy(); - entityStore.removeEntity(selected); - } + const handleOpenScene = () => { + console.log('Open scene not implemented yet'); + }; + + const handleSaveScene = () => { + console.log('Save scene not implemented yet'); + }; + + const handleSaveSceneAs = () => { + console.log('Save scene as not implemented yet'); + }; + + const handleCloseProject = () => { + setProjectLoaded(false); + setCurrentProjectPath(null); + setStatus(t('header.status.ready')); + }; + + const handleExit = () => { + window.close(); }; const handleLocaleChange = () => { @@ -242,21 +251,22 @@ function App() { return (
-

{t('app.title')}

-
- - + +
+ {status}
- {status}
diff --git a/packages/editor-app/src/components/MenuBar.tsx b/packages/editor-app/src/components/MenuBar.tsx new file mode 100644 index 00000000..3bc3195e --- /dev/null +++ b/packages/editor-app/src/components/MenuBar.tsx @@ -0,0 +1,193 @@ +import { useState, useRef, useEffect } from 'react'; +import '../styles/MenuBar.css'; + +interface MenuItem { + label: string; + shortcut?: string; + disabled?: boolean; + separator?: boolean; + submenu?: MenuItem[]; + onClick?: () => void; +} + +interface MenuBarProps { + locale?: string; + onNewScene?: () => void; + onOpenScene?: () => void; + onSaveScene?: () => void; + onSaveSceneAs?: () => void; + onOpenProject?: () => void; + onCloseProject?: () => void; + onExit?: () => void; +} + +export function MenuBar({ + locale = 'en', + onNewScene, + onOpenScene, + onSaveScene, + onSaveSceneAs, + onOpenProject, + onCloseProject, + onExit +}: MenuBarProps) { + const [openMenu, setOpenMenu] = useState(null); + const menuRef = useRef(null); + + const t = (key: string) => { + const translations: Record> = { + en: { + file: 'File', + newScene: 'New Scene', + openScene: 'Open Scene', + saveScene: 'Save Scene', + saveSceneAs: 'Save Scene As...', + openProject: 'Open Project', + closeProject: 'Close Project', + exit: 'Exit', + edit: 'Edit', + undo: 'Undo', + redo: 'Redo', + cut: 'Cut', + copy: 'Copy', + paste: 'Paste', + delete: 'Delete', + selectAll: 'Select All', + window: 'Window', + sceneHierarchy: 'Scene Hierarchy', + inspector: 'Inspector', + assets: 'Assets', + console: 'Console', + viewport: 'Viewport', + help: 'Help', + documentation: 'Documentation', + about: 'About' + }, + zh: { + file: '文件', + newScene: '新建场景', + openScene: '打开场景', + saveScene: '保存场景', + saveSceneAs: '场景另存为...', + openProject: '打开项目', + closeProject: '关闭项目', + exit: '退出', + edit: '编辑', + undo: '撤销', + redo: '重做', + cut: '剪切', + copy: '复制', + paste: '粘贴', + delete: '删除', + selectAll: '全选', + window: '窗口', + sceneHierarchy: '场景层级', + inspector: '检视器', + assets: '资产', + console: '控制台', + viewport: '视口', + help: '帮助', + documentation: '文档', + about: '关于' + } + }; + return translations[locale]?.[key] || key; + }; + + const menus: Record = { + file: [ + { label: t('newScene'), shortcut: 'Ctrl+N', onClick: onNewScene }, + { label: t('openScene'), shortcut: 'Ctrl+O', onClick: onOpenScene }, + { separator: true }, + { label: t('saveScene'), shortcut: 'Ctrl+S', onClick: onSaveScene }, + { label: t('saveSceneAs'), shortcut: 'Ctrl+Shift+S', onClick: onSaveSceneAs }, + { separator: true }, + { label: t('openProject'), onClick: onOpenProject }, + { label: t('closeProject'), onClick: onCloseProject }, + { separator: true }, + { label: t('exit'), onClick: onExit } + ], + edit: [ + { label: t('undo'), shortcut: 'Ctrl+Z', disabled: true }, + { label: t('redo'), shortcut: 'Ctrl+Y', disabled: true }, + { separator: true }, + { label: t('cut'), shortcut: 'Ctrl+X', disabled: true }, + { label: t('copy'), shortcut: 'Ctrl+C', disabled: true }, + { label: t('paste'), shortcut: 'Ctrl+V', disabled: true }, + { label: t('delete'), shortcut: 'Delete', disabled: true }, + { separator: true }, + { label: t('selectAll'), shortcut: 'Ctrl+A', disabled: true } + ], + window: [ + { label: t('sceneHierarchy'), disabled: true }, + { label: t('inspector'), disabled: true }, + { label: t('assets'), disabled: true }, + { label: t('console'), disabled: true }, + { label: t('viewport'), disabled: true } + ], + help: [ + { label: t('documentation'), disabled: true }, + { separator: true }, + { label: t('about'), disabled: true } + ] + }; + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + setOpenMenu(null); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + const handleMenuClick = (menuKey: string) => { + setOpenMenu(openMenu === menuKey ? null : menuKey); + }; + + const handleMenuItemClick = (item: MenuItem) => { + if (!item.disabled && !item.separator && item.onClick) { + item.onClick(); + setOpenMenu(null); + } + }; + + return ( +
+ {Object.keys(menus).map(menuKey => ( +
+ + {openMenu === menuKey && ( +
+ {menus[menuKey].map((item, index) => { + if (item.separator) { + return
; + } + return ( + + ); + })} +
+ )} +
+ ))} +
+ ); +} diff --git a/packages/editor-app/src/styles/App.css b/packages/editor-app/src/styles/App.css index ad4415e2..cfebb5d2 100644 --- a/packages/editor-app/src/styles/App.css +++ b/packages/editor-app/src/styles/App.css @@ -27,7 +27,7 @@ .editor-header { display: flex; align-items: center; - gap: var(--spacing-lg); + justify-content: space-between; height: var(--layout-header-height); padding: 0 var(--spacing-lg); background-color: var(--color-bg-elevated); @@ -35,19 +35,11 @@ flex-shrink: 0; } -.editor-header h1 { - font-size: var(--font-size-base); - font-weight: var(--font-weight-semibold); - color: var(--color-text-primary); - margin: 0; - letter-spacing: -0.01em; -} - -.header-toolbar { +.header-right { display: flex; align-items: center; - gap: var(--spacing-sm); - flex: 1; + gap: var(--spacing-md); + margin-left: auto; } .toolbar-btn { @@ -99,15 +91,17 @@ .locale-btn { width: var(--size-button-sm); + height: var(--size-button-sm); padding: 0; - background-color: var(--color-bg-overlay); + background-color: transparent; color: var(--color-text-primary); - font-weight: var(--font-weight-semibold); + border: 1px solid var(--color-border-default); } .locale-btn:hover:not(:disabled) { background-color: var(--color-bg-hover); color: var(--color-primary); + border-color: var(--color-primary); } .editor-header .status { diff --git a/packages/editor-app/src/styles/MenuBar.css b/packages/editor-app/src/styles/MenuBar.css new file mode 100644 index 00000000..50523af9 --- /dev/null +++ b/packages/editor-app/src/styles/MenuBar.css @@ -0,0 +1,82 @@ +.menu-bar { + display: flex; + align-items: center; + gap: 2px; + height: 100%; + user-select: none; +} + +.menu-item { + position: relative; +} + +.menu-button { + height: 32px; + padding: 0 12px; + background: transparent; + border: none; + color: var(--color-text-primary, #cccccc); + font-size: 13px; + cursor: pointer; + transition: background-color 0.15s; + border-radius: 3px; +} + +.menu-button:hover { + background-color: var(--color-bg-hover, rgba(255, 255, 255, 0.1)); +} + +.menu-button.active { + background-color: var(--color-bg-active, rgba(255, 255, 255, 0.15)); +} + +.menu-dropdown { + position: absolute; + top: 100%; + left: 0; + min-width: 200px; + background: var(--color-bg-elevated, #252526); + border: 1px solid var(--color-border-default, #3e3e42); + border-radius: 4px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); + padding: 4px 0; + z-index: 1000; + margin-top: 2px; +} + +.menu-dropdown-item { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 16px; + background: transparent; + border: none; + color: var(--color-text-primary, #cccccc); + font-size: 13px; + cursor: pointer; + text-align: left; + transition: background-color 0.15s; +} + +.menu-dropdown-item:hover:not(.disabled) { + background-color: var(--color-bg-hover, rgba(255, 255, 255, 0.1)); +} + +.menu-dropdown-item.disabled { + color: var(--color-text-disabled, #6e6e6e); + cursor: not-allowed; + opacity: 0.5; +} + +.menu-shortcut { + margin-left: 24px; + color: var(--color-text-secondary, #858585); + font-size: 12px; +} + +.menu-separator { + height: 1px; + background-color: var(--color-border-default, #3e3e42); + margin: 4px 8px; +} diff --git a/packages/editor-app/src/styles/StartupPage.css b/packages/editor-app/src/styles/StartupPage.css index f4fe870a..8c408065 100644 --- a/packages/editor-app/src/styles/StartupPage.css +++ b/packages/editor-app/src/styles/StartupPage.css @@ -1,138 +1,131 @@ .startup-page { display: flex; flex-direction: column; + width: 100%; height: 100vh; - background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); - color: #e0e0e0; - overflow: hidden; + background-color: #1e1e1e; + color: #cccccc; } .startup-header { - text-align: center; - padding: 60px 20px 40px; - background: linear-gradient(180deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0) 100%); + padding: 60px 40px 40px; } .startup-title { - font-size: 3rem; - font-weight: 700; - margin: 0; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; + font-size: 32px; + font-weight: 300; + color: #ffffff; + margin: 0 0 8px 0; + letter-spacing: -0.5px; } .startup-subtitle { - font-size: 1.2rem; - color: #a0a0a0; - margin: 10px 0 0; + font-size: 14px; + font-weight: 400; + color: #858585; + margin: 0; } .startup-content { flex: 1; display: flex; - flex-direction: column; - align-items: center; - padding: 20px; - overflow-y: auto; + gap: 60px; + padding: 0 40px; + overflow: auto; } .startup-actions { display: flex; - gap: 20px; - margin-bottom: 60px; + flex-direction: column; + gap: 12px; + min-width: 280px; } .startup-action-btn { display: flex; - flex-direction: column; align-items: center; - gap: 15px; - padding: 40px 60px; - background: rgba(255, 255, 255, 0.05); - border: 2px solid rgba(255, 255, 255, 0.1); - border-radius: 12px; - color: #e0e0e0; - font-size: 1.1rem; - font-weight: 500; + gap: 12px; + padding: 12px 16px; + background-color: #2d2d30; + border: 1px solid #3e3e42; + border-radius: 2px; + color: #cccccc; + font-size: 13px; cursor: pointer; - transition: all 0.3s ease; - min-width: 200px; + transition: all 0.15s; + text-align: left; } .startup-action-btn:hover { - background: rgba(255, 255, 255, 0.1); - border-color: rgba(255, 255, 255, 0.3); - transform: translateY(-2px); - box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3); + background-color: #37373d; + border-color: #007acc; } .startup-action-btn.primary { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - border-color: transparent; + background-color: #0e639c; + border-color: #0e639c; + color: #ffffff; } .startup-action-btn.primary:hover { - background: linear-gradient(135deg, #7a8ef0 0%, #8a5cb2 100%); - box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4); + background-color: #1177bb; + border-color: #1177bb; } .btn-icon { - width: 48px; - height: 48px; - stroke-width: 2; + width: 20px; + height: 20px; + flex-shrink: 0; } .startup-recent { - width: 100%; - max-width: 800px; + flex: 1; + max-width: 600px; } .recent-title { - font-size: 1.5rem; + font-size: 13px; font-weight: 600; - margin: 0 0 20px; - color: #f0f0f0; + color: #cccccc; + margin: 0 0 16px 0; + text-transform: uppercase; + letter-spacing: 0.5px; } .recent-empty { - text-align: center; - color: #808080; - padding: 40px; - font-size: 1.1rem; + font-size: 13px; + color: #858585; + margin: 0; } .recent-list { list-style: none; padding: 0; margin: 0; + display: flex; + flex-direction: column; + gap: 1px; } .recent-item { display: flex; align-items: center; - gap: 15px; - padding: 15px 20px; - margin-bottom: 10px; - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 8px; + gap: 12px; + padding: 10px 12px; + background-color: transparent; + border-radius: 2px; cursor: pointer; - transition: all 0.2s ease; + transition: background-color 0.15s; } -.recent-item:hover, -.recent-item.hovered { - background: rgba(255, 255, 255, 0.08); - border-color: rgba(102, 126, 234, 0.5); - transform: translateX(4px); +.recent-item:hover { + background-color: #2a2d2e; } .recent-icon { - width: 32px; - height: 32px; - color: #667eea; + width: 16px; + height: 16px; + color: #858585; flex-shrink: 0; } @@ -142,42 +135,28 @@ } .recent-name { - font-size: 1.1rem; - font-weight: 500; - color: #f0f0f0; - margin-bottom: 4px; + font-size: 13px; + color: #cccccc; + margin-bottom: 2px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .recent-path { - font-size: 0.9rem; - color: #808080; + font-size: 11px; + color: #6e6e6e; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .startup-footer { - padding: 20px; - text-align: center; - background: rgba(0, 0, 0, 0.2); - border-top: 1px solid rgba(255, 255, 255, 0.1); + padding: 20px 40px; + border-top: 1px solid #2d2d30; } .startup-version { - color: #606060; - font-size: 0.9rem; -} - -@media (max-width: 768px) { - .startup-actions { - flex-direction: column; - } - - .startup-action-btn { - width: 100%; - } - - .startup-title { - font-size: 2rem; - } + font-size: 11px; + color: #6e6e6e; }