@@ -24,7 +24,7 @@ static InputActionsEditorWindow()
2424
2525 private string m_AssetJson ;
2626 private bool m_IsDirty ;
27- private bool m_IsEditorQuitting ;
27+ private bool m_AutoSaveFailed ;
2828
2929 private StateContainer m_StateContainer ;
3030 private InputActionsEditorView m_View ;
@@ -105,7 +105,7 @@ private static InputActionsEditorWindow OpenWindow(InputActionAsset asset, strin
105105 }
106106
107107 var window = GetWindow < InputActionsEditorWindow > ( ) ;
108- if ( window . m_IsDirty )
108+ if ( window . isDirty )
109109 {
110110 var assetPath = AssetDatabase . GUIDToAssetPath ( window . m_AssetGUID ) ;
111111 if ( ! string . IsNullOrEmpty ( assetPath ) )
@@ -127,7 +127,7 @@ private static InputActionsEditorWindow OpenWindow(InputActionAsset asset, strin
127127 }
128128 }
129129
130- window . m_IsDirty = false ;
130+ window . isDirty = false ;
131131 window . minSize = k_MinWindowSize ;
132132 window . SetAsset ( asset , actionToSelect , actionMapToSelect ) ;
133133 window . Show ( ) ;
@@ -146,12 +146,9 @@ public static InputActionsEditorWindow OpenEditor(InputActionAsset asset)
146146 return OpenWindow ( asset , null , null ) ;
147147 }
148148
149- private static GUIContent GetEditorTitle ( InputActionAsset asset , bool isDirty )
149+ private static GUIContent GetEditorTitle ( InputActionAsset asset )
150150 {
151- var text = asset . name + " (Input Actions Editor)" ;
152- if ( isDirty )
153- text = "(*) " + text ;
154- return new GUIContent ( text ) ;
151+ return new GUIContent ( asset . name + " (Input Actions Editor)" ) ;
155152 }
156153
157154 private void SetAsset ( InputActionAsset asset , string actionToSelect = null , string actionMapToSelect = null )
@@ -227,7 +224,10 @@ private void CreateGUI() // Only domain reload
227224 }
228225 else
229226 m_State = new InputActionsEditorState ( m_State , new SerializedObject ( m_AssetObjectForEditing ) ) ;
230- m_IsDirty = HasContentChanged ( ) ;
227+ isDirty = HasContentChanged ( ) ;
228+
229+ // saveChangesMessage is not serialized, so the prompt would be blank after a domain reload.
230+ UpdateWindowTitle ( ) ;
231231 }
232232 catch ( Exception e )
233233 {
@@ -293,7 +293,9 @@ private void OnStateChanged(InputActionsEditorState newState, UIRebuildMode edit
293293
294294 private void UpdateWindowTitle ( )
295295 {
296- titleContent = GetEditorTitle ( GetEditedAsset ( ) , m_IsDirty ) ;
296+ titleContent = GetEditorTitle ( GetEditedAsset ( ) ) ;
297+ saveChangesMessage = "Do you want to save the changes you made in:\n " +
298+ AssetDatabase . GUIDToAssetPath ( m_AssetGUID ) + "\n \n Your changes will be lost if you don't save them." ;
297299 }
298300
299301 private InputActionAsset GetEditedAsset ( )
@@ -312,6 +314,14 @@ private void Save(bool isAutoSave)
312314 if ( InputActionAssetManager . SaveAsset ( path , GetEditedAsset ( ) . ToJson ( ) ) )
313315 TryUpdateFromAsset ( ) ;
314316
317+ // If an auto-save did not go through (e.g. version control refused the checkout), stop relying on
318+ // auto-save so that closing the window prompts instead of silently dropping the changes.
319+ if ( isAutoSave && isDirty )
320+ {
321+ m_AutoSaveFailed = true ;
322+ UpdateUnsavedChangesState ( ) ;
323+ }
324+
315325 if ( isAutoSave )
316326 analytics . RegisterAutoSave ( ) ;
317327 else
@@ -327,40 +337,17 @@ private bool HasContentChanged()
327337
328338 private void DirtyInputActionsEditorWindow ( InputActionsEditorState newState )
329339 {
330- var isWindowDirty = HasContentChanged ( ) ;
331-
332- if ( m_IsDirty == isWindowDirty )
333- return ;
334-
335- m_IsDirty = isWindowDirty ;
336- UpdateWindowTitle ( ) ;
340+ isDirty = HasContentChanged ( ) ;
337341 }
338342
339343 private void OnEnable ( )
340344 {
341345 analytics . Begin ( ) ;
342- EditorApplication . wantsToQuit += OnWantsToQuit ;
343346 }
344347
345348 private void OnDisable ( )
346349 {
347350 analytics . End ( ) ;
348- EditorApplication . wantsToQuit -= OnWantsToQuit ;
349- }
350-
351- private bool OnWantsToQuit ( )
352- {
353- // Here the user will be prompted
354- bool isAllowedToQuit = CheckCanCloseAndPromptIfDirty ( false ) ;
355- m_IsEditorQuitting = isAllowedToQuit ;
356-
357- if ( m_IsEditorQuitting )
358- {
359- // Reset flag in case another wantsToQuit listener aborts the quit.
360- EditorApplication . delayCall += ( ) => m_IsEditorQuitting = false ;
361- }
362-
363- return m_IsEditorQuitting ;
364351 }
365352
366353 private void OnFocus ( )
@@ -370,7 +357,7 @@ private void OnFocus()
370357
371358 private void OnLostFocus ( )
372359 {
373- if ( InputEditorUserSettings . autoSaveInputActionAssets && m_IsDirty )
360+ if ( InputEditorUserSettings . autoSaveInputActionAssets && isDirty )
374361 {
375362 // We'd like to avoid saving in case the focus was lost due to the drop-down window being spawned.
376363 // This code should be cleaned up once we migrate the InputControl stuff from ImGUI completely.
@@ -385,84 +372,31 @@ private void OnLostFocus()
385372 analytics . RegisterEditorFocusOut ( ) ;
386373 }
387374
388- /// <summary>
389- /// Shows a dialog when trying to close an input asset without saving changes.
390- /// </summary>
391- /// <param name="rebuildUIOnCancel">If true, reopens the editor window when user cancels.</param>
392- /// <returns> Returns true if you should allow the Unity Editor to close. </returns>
393- private bool CheckCanCloseAndPromptIfDirty ( bool rebuildUIOnCancel )
375+ public override void SaveChanges ( )
394376 {
395- // Do we have unsaved changes that we need to ask the user to save or discard?
396- // Early out if asset up to date or editor closing.
397- if ( ! m_IsDirty || m_IsEditorQuitting )
398- return true ;
399-
400- // Get target asset path from GUID, if this fails file no longer exists and we need to abort.
401- var assetPath = AssetDatabase . GUIDToAssetPath ( m_AssetGUID ) ;
402- if ( string . IsNullOrEmpty ( assetPath ) )
403- return true ;
404-
405- // Prompt user with a dialog
406- var result = Dialog . InputActionAsset . ShowSaveChanges ( assetPath ) ;
407- switch ( result )
408- {
409- case Dialog . Result . Save :
410- Save ( isAutoSave : false ) ;
411- return true ;
412- case Dialog . Result . Cancel :
413- if ( rebuildUIOnCancel )
414- {
415- // Cancel editor quit. (open new editor window with the edited asset)
416- ReshowEditorWindowWithUnsavedChanges ( ) ;
417- }
377+ Save ( isAutoSave : false ) ;
378+ }
418379
419- return false ;
420- case Dialog . Result . Discard :
421- // Don't save, quit - reload the old asset from the json to prevent the asset from being dirtied
422- return true ;
423- default :
424- throw new ArgumentOutOfRangeException ( nameof ( result ) ) ;
425- }
380+ public override void DiscardChanges ( )
381+ {
382+ // Clear the dirty state so OnDestroy does not auto-save the changes the user chose to discard.
383+ isDirty = false ;
384+ base . DiscardChanges ( ) ;
426385 }
427386
428387 private void OnDestroy ( )
429388 {
430- CheckCanCloseAndPromptIfDirty ( true ) ;
389+ // Closing the tab does not always take focus away from the window, so OnLostFocus may not have run.
390+ if ( InputEditorUserSettings . autoSaveInputActionAssets && isDirty && m_AssetObjectForEditing != null )
391+ Save ( isAutoSave : true ) ;
431392
432- // Clean-up
433393 CleanupStateContainer ( ) ;
434394 if ( m_AssetObjectForEditing != null )
435395 DestroyImmediate ( m_AssetObjectForEditing ) ;
436396
437397 m_View ? . DestroyView ( ) ;
438398 }
439399
440- private void ReshowEditorWindowWithUnsavedChanges ( )
441- {
442- var window = CreateWindow < InputActionsEditorWindow > ( ) ;
443-
444- // Move/transfer ownership of m_AssetObjectForEditing to new window
445- window . m_AssetObjectForEditing = m_AssetObjectForEditing ;
446- m_AssetObjectForEditing = null ;
447-
448- // Move/transfer ownership of m_State to new window (struct)
449- window . m_State = m_State ;
450- m_State = new InputActionsEditorState ( ) ;
451-
452- // Just copy trivial arguments
453- window . m_AssetGUID = m_AssetGUID ;
454- window . m_AssetJson = m_AssetJson ;
455- window . m_IsDirty = m_IsDirty ;
456-
457- // Note that view and state container will get destroyed with this window instance
458- // and recreated for this window below
459- window . BuildUI ( ) ;
460- window . Show ( ) ;
461-
462- // Make sure window title is up to date
463- window . UpdateWindowTitle ( ) ;
464- }
465-
466400 private bool TryUpdateFromAsset ( )
467401 {
468402 Debug . Assert ( ! string . IsNullOrEmpty ( m_AssetGUID ) , "Asset GUID is empty" ) ;
@@ -480,7 +414,7 @@ private bool TryUpdateFromAsset()
480414 workingCopy = InputActionAssetManager . CreateWorkingCopy ( asset ) ;
481415 m_AssetJson = InputActionsEditorWindowUtils . ToJsonWithoutName ( asset ) ;
482416 m_State = new InputActionsEditorState ( m_State , new SerializedObject ( workingCopy ) ) ;
483- m_IsDirty = false ;
417+ isDirty = false ;
484418 }
485419 catch ( Exception e )
486420 {
@@ -500,7 +434,24 @@ private bool TryUpdateFromAsset()
500434 #region IInputActionEditorWindow
501435
502436 public string assetGUID => m_AssetGUID ;
503- public bool isDirty => m_IsDirty ;
437+ public bool isDirty
438+ {
439+ get { return m_IsDirty ; }
440+ private set
441+ {
442+ m_IsDirty = value ;
443+ if ( ! value )
444+ m_AutoSaveFailed = false ;
445+ UpdateUnsavedChangesState ( ) ;
446+ }
447+ }
448+
449+ private void UpdateUnsavedChangesState ( )
450+ {
451+ // With auto-save enabled, changes are saved on focus loss or when the window is destroyed, so there is
452+ // nothing to prompt for on close, unless a previous auto-save attempt failed.
453+ hasUnsavedChanges = m_IsDirty && ( ! InputEditorUserSettings . autoSaveInputActionAssets || m_AutoSaveFailed ) ;
454+ }
504455
505456 public void OnAssetMoved ( )
506457 {
@@ -512,7 +463,7 @@ public void OnAssetDeleted()
512463 {
513464 // When associated asset is deleted on disk, just close the editor, but also mark the editor
514465 // as not being dirty to avoid prompting the user to save changes.
515- m_IsDirty = false ;
466+ isDirty = false ;
516467 Close ( ) ;
517468 }
518469
@@ -521,14 +472,14 @@ public void OnAssetImported()
521472 // If the editor has pending changes done by the user and the contents changes on disc, there
522473 // is not much we can do about it but to ignore loading the changes. If the editors asset is
523474 // unmodified, we can refresh the editor with the latest content from disc.
524- if ( m_IsDirty )
475+ if ( isDirty )
525476 return ;
526477
527478 // If our asset has disappeared from disk, just close the window.
528479 var assetPath = AssetDatabase . GUIDToAssetPath ( assetGUID ) ;
529480 if ( string . IsNullOrEmpty ( assetPath ) )
530481 {
531- m_IsDirty = false ; // Avoid checks
482+ isDirty = false ; // Avoid checks
532483 Close ( ) ;
533484 return ;
534485 }
0 commit comments