Skip to content

Commit 2094afc

Browse files
FIX: [Port] [UUM-154808] Fixing glitch on the Input Actions Editor window when closing popup window (#2488)
1 parent 2c451ed commit 2094afc

2 files changed

Lines changed: 56 additions & 104 deletions

File tree

‎Packages/com.unity.inputsystem/CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
99

1010
### Fixed
1111

12+
- Fixed the Input Actions editor window's unsaved-changes handling on close: it now uses the Editor's built-in save prompt (including on Editor quit), auto-saves pending changes when the window is closed with auto-save enabled, and prompts instead of silently dropping changes if an auto-save fails (e.g. version control refused the checkout). [UUM-154808](https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-154808)
1213
- Fixed misaligned labels in the Input Actions editor properties pane. [UUM-154536](https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-154536)
1314
- Fixed a single click producing two simultaneous touches when the Device Simulator view is open and "Simulate Touch Input From Mouse or Pen" is enabled in the Input Debugger; Touch Simulation now suppresses its events while a Device Simulator view is open [UUM-153420]
1415
- Fixed the search field in the "Add supported device" popup (Input System Package Settings > Supported Devices > "+") drawing flush against the popup's left edge with no left margin, unlike its right-side spacing; it is now inset to match [UUM-150217](https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-150217)

‎Packages/com.unity.inputsystem/InputSystem/Editor/UITKAssetEditor/InputActionsEditorWindow.cs‎

Lines changed: 55 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -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\nYour 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

Comments
 (0)