// Animancer // Copyright 2018-2024 Kybernetik //
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Animancer.Editor
{
/// [Editor-Only]
/// A window for managing a copy of some serialized data and applying or reverting it.
///
///
/// This system assumes the implementation of
/// compares the values of all fields in .
///
/// https://kybernetik.com.au/animancer/api/Animancer.Editor/SerializedDataEditorWindow_2
public abstract class SerializedDataEditorWindow : EditorWindow
where TObject : Object
where TData : class, ICopyable, IEquatable, new()
{
/************************************************************************************************************************/
[SerializeField]
private TObject _SourceObject;
/// The object which contains the data this class manages.
/// should generally be used instead of setting this property directly.
public virtual TObject SourceObject
{
get => _SourceObject;
protected set => _SourceObject = value;
}
/************************************************************************************************************************/
/// The field of the .
public abstract TData SourceData { get; set; }
/************************************************************************************************************************/
[SerializeField]
private TData _Data;
/// A copy of the being managed by this window.
public ref TData Data
=> ref _Data;
/************************************************************************************************************************/
/// Is the managed by this window different to the .
public bool HasDataChanged
{
get
{
try
{
return _Data != null && !_Data.Equals(SourceData);
}
catch (Exception exception)
{
Debug.LogException(exception);
return false;
}
}
}
/************************************************************************************************************************/
/// Initializes this window.
protected virtual void OnEnable()
{
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
EditorApplication.wantsToQuit += OnTryCloseEditor;
Undo.undoRedoPerformed += Repaint;
}
/// Cleans up this window.
protected virtual void OnDisable()
{
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
EditorApplication.wantsToQuit -= OnTryCloseEditor;
Undo.undoRedoPerformed -= Repaint;
}
/************************************************************************************************************************/
///
/// Prompts the user to or
/// if there are changes in the when this window is closed.
///
protected virtual void OnDestroy()
{
var sourceObject = SourceObject;
if (sourceObject == null ||
!HasDataChanged ||
titleContent == null)
return;
if (EditorUtility.DisplayDialog(
titleContent.text,
$"Apply unsaved changes to '{sourceObject.name}'?",
"Apply",
"Revert"))
{
Apply();
}
}
/************************************************************************************************************************/
/// Called before closing the Unity Editor to confirm that un-saved data is applied.
private bool OnTryCloseEditor()
{
var sourceObject = SourceObject;
if (sourceObject == null ||
!HasDataChanged ||
titleContent == null)
return true;
var option = EditorUtility.DisplayDialogComplex(
titleContent.text,
$"Apply unsaved changes to '{sourceObject.name}'?",
"Apply",
"Cancel",
"Revert");
switch (option)
{
case 0:// Apply.
Apply();
return true;
case 2:// Revert.
Revert();
return true;
case 1:// Cancel.
default:
return false;
}
}
/************************************************************************************************************************/
///
/// Sets the and captures the
/// as a copy of its .
///
protected void SetAndCaptureSource(TObject sourceObject)
{
_SourceObject = sourceObject;
CaptureData();
Repaint();
}
/************************************************************************************************************************/
///
/// Override this to return true if the could be part of a prefab
/// to ensure that modifications are serialized properly.
///
public virtual bool SourceObjectMightBePrefab
=> false;
/************************************************************************************************************************/
/// Saves the edited into the .
public virtual void Apply()
{
var sourceObject = SourceObject;
if (sourceObject == null)
return;
using (new ModifySerializedField(sourceObject, name, SourceObjectMightBePrefab))
{
SourceData = _Data.CopyableClone();
if (EditorUtility.IsPersistent(SourceObject))
{
var objects = SetPool.Acquire