@@ -59,6 +59,7 @@ const state = reactive({
showOpenSceneDialog : false ,
pendingScenePath : null as string | null ,
pendingAction : null as 'close' | 'openScene' | null ,
hasOpenedFile : false , // 标记是否已打开过文件或添加过内容
} ) ;
// Computed title
@@ -71,6 +72,12 @@ const hasContent = computed(() => {
return sceneManager . elements . value . length > 0 ;
} ) ;
// Check if should show welcome overlay
// Only show when: no content AND no file has been opened/used
const showWelcome = computed ( ( ) => {
return ! hasContent . value && ! state . hasOpenedFile ;
} ) ;
// Selection box state
const selectionBox = reactive ( {
visible : false ,
@@ -125,7 +132,9 @@ function getContextMenuItems(): MenuItem[] {
items . push ( { label : '' , action : ( ) => { } , separator : true } ) ;
// File operations
const canSave = hasContent . value ;
// 允许保存:有内容 或 已打开文件(可以保存空文件)
const canSave = hasContent . value || state . hasOpenedFile ;
const canCreateNew = hasContent . value || state . hasOpenedFile ;
items . push (
{
@@ -148,7 +157,7 @@ function getContextMenuItems(): MenuItem[] {
action : ( ) => handleNew ( ) ,
shortcut : 'Ctrl+N' ,
checked : false ,
disabled : ! canSave ,
disabled : ! canCreateNew ,
}
) ;
@@ -212,6 +221,9 @@ async function handleCanvasInitialized() {
try {
const loaded = await sceneService . loadOnStartup ( ) ;
if ( loaded ) {
// 标记已打开文件
state . hasOpenedFile = true ;
// Sync renderer and view transform after loading
canvasViewRef . value ? . syncRendererElements ( ) ;
canvasViewRef . value ? . syncViewTransform ( ) ;
@@ -305,6 +317,9 @@ async function handleTauriFileDrop(paths: string[], dropX: number, dropY: number
// Handle image files
if ( imageFiles . length === 0 ) return ;
// 标记已开始使用场景,防止删除元素后显示欢迎界面
state . hasOpenedFile = true ;
// Record state for undo
const currentState = sceneManager . getState ( ) ;
historyManager . record ( currentState . elements , currentState . viewTransform ) ;
@@ -335,6 +350,9 @@ async function loadSceneFromPath(path: string) {
try {
const success = await sceneService . loadFromPath ( path ) ;
if ( success ) {
// 标记已打开文件,防止删除元素后显示欢迎界面
state . hasOpenedFile = true ;
canvasViewRef . value ? . syncRendererElements ( ) ;
canvasViewRef . value ? . syncViewTransform ( ) ;
canvasViewRef . value ? . getRenderer ( ) ? . requestRender ( ) ;
@@ -355,8 +373,12 @@ async function loadSceneFromPath(path: string) {
async function handleConfirmOpenScene ( ) {
state . showOpenSceneDialog = false ;
if ( state . pendingScenePath ) {
// 从指定路径加载(拖放文件)
await loadSceneFromPath ( state . pendingScenePath ) ;
state . pendingScenePath = null ;
} else {
// 通过对话框选择文件( Ctrl+L)
await performLoad ( ) ;
}
}
@@ -553,8 +575,8 @@ function handleGlobalAction(action: string, data: unknown) {
// ============================================
async function handleSave ( ) {
// Don't save if no content
if ( ! hasContent . value ) return ;
// 允许保存:有内容 或 已打开文件(可以保存空文件)
if ( ! hasContent . value && ! state . hasOpenedFile ) return ;
try {
const success = await sceneService . save ( ) ;
@@ -567,8 +589,8 @@ async function handleSave() {
}
async function handleSaveAs ( ) {
// Don't save if no content
if ( ! hasContent . value ) return ;
// 允许另存为:有内容 或 已打开文件(可以保存空文件)
if ( ! hasContent . value && ! state . hasOpenedFile ) return ;
try {
const success = await sceneService . saveAs ( ) ;
@@ -581,6 +603,29 @@ async function handleSaveAs() {
}
async function handleLoad ( ) {
// 如果有未保存的更改,显示提示对话框
if ( state . isDirty && ( hasContent . value || state . hasOpenedFile ) ) {
state . pendingAction = 'openScene' ;
state . pendingScenePath = null ; // null 表示通过对话框选择文件
state . showUnsavedDialog = true ;
return ;
}
// 如果有内容但没有未保存更改,显示确认对话框
if ( hasContent . value || state . hasOpenedFile ) {
state . pendingScenePath = null ;
state . showOpenSceneDialog = true ;
return ;
}
// 没有内容,直接打开
await performLoad ( ) ;
}
/**
* 实际执行加载操作
*/
async function performLoad ( ) {
try {
// Record state for undo before loading
const currentState = sceneManager . getState ( ) ;
@@ -588,6 +633,9 @@ async function handleLoad() {
const success = await sceneService . load ( ) ;
if ( success ) {
// 标记已打开文件,防止显示欢迎界面
state . hasOpenedFile = true ;
// Sync renderer and view transform after loading
canvasViewRef . value ? . syncRendererElements ( ) ;
canvasViewRef . value ? . syncViewTransform ( ) ;
@@ -619,14 +667,18 @@ async function handleBrowseFiles() {
}
function handleNew ( ) {
// Don't create new scene if already empty
if ( ! hasContent . value ) return ;
// Don't create new scene if already empty and no file opened
if ( ! hasContent . value && ! state . hasOpenedFile ) return ;
// Record state for undo
const currentState = sceneManager . getState ( ) ;
historyManager . record ( currentState . elements , currentState . viewTransform ) ;
// Record state for undo if there's content
if ( hasContent . value ) {
const currentState = sceneManager . getState ( ) ;
historyManager . record ( currentState . elements , currentState . viewTransform ) ;
}
sceneService . newScene ( ) ;
// 重置标志,允许显示欢迎界面
state . hasOpenedFile = false ;
canvasViewRef . value ? . syncRendererElements ( ) ;
}
@@ -1005,8 +1057,14 @@ async function executePendingAction() {
if ( action === 'close' ) {
await forceCloseWindow ( ) ;
} else if ( action === 'openScene' && scenePath ) {
await loadSceneFromPath ( scenePath ) ;
} else if ( action === 'openScene' ) {
if ( scenePath ) {
// 从指定路径加载(拖放文件)
await loadSceneFromPath ( scenePath ) ;
} else {
// 通过对话框选择文件( Ctrl+L)
await performLoad ( ) ;
}
}
}
@@ -1140,6 +1198,7 @@ onUnmounted(() => {
:always-on-top = "state.alwaysOnTop"
:is-dirty = "state.isDirty"
:has-content = "hasContent"
:has-opened-file = "state.hasOpenedFile"
@minimize ="minimizeWindow"
@maximize ="toggleFullscreen"
@close ="closeWindow"
@@ -1156,9 +1215,10 @@ onUnmounted(() => {
@ file -drop = " handleFileDrop "
/ >
<!-- Welcome Overlay ( when scene is empty ) -- >
<!-- Welcome Overlay ( when scene is empty and no file opened ) -- >
< WelcomeOverlay
v-if = "state.isInitialized && !hasContent "
v-if = "state.isInitialized && showWelcome "
: key = "'welcome-' + hasContent + '-' + state.hasOpenedFile"
@browse ="handleBrowseFiles"
@help ="state.showHelp = true"
/ >