2021-09-09 20:42:29 -04:00

522 lines
21 KiB
C#

using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using UnityEditor.Experimental;
namespace UnityEditor.Rendering.HighDefinition
{
partial class HDWizard : EditorWindow
{
#region OBJECT_SELECTOR
//utility class to show only non scene object selection
static class ObjectSelector
{
static Action<UnityEngine.Object, Type, Action<UnityEngine.Object>> ShowObjectSelector;
static Func<UnityEngine.Object> GetCurrentObject;
static Func<int> GetSelectorID;
static Action<int> SetSelectorID;
const string ObjectSelectorUpdatedCommand = "ObjectSelectorUpdated";
static int id;
static int selectorID { get => GetSelectorID(); set => SetSelectorID(value); }
public static bool opened
=> Resources.FindObjectsOfTypeAll(typeof(PlayerSettings).Assembly.GetType("UnityEditor.ObjectSelector")).Length > 0;
// Action to be called with the window is closed
static Action s_OnClose;
static ObjectSelector()
{
Type playerSettingsType = typeof(PlayerSettings);
Type objectSelectorType = playerSettingsType.Assembly.GetType("UnityEditor.ObjectSelector");
var instanceObjectSelectorInfo = objectSelectorType.GetProperty("get", BindingFlags.Static | BindingFlags.Public);
#if UNITY_2020_1_OR_NEWER
var showInfo = objectSelectorType.GetMethod("Show", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(UnityEngine.Object), typeof(Type), typeof(UnityEngine.Object), typeof(bool), typeof(List<int>), typeof(Action<UnityEngine.Object>), typeof(Action<UnityEngine.Object>) }, null);
#else
var showInfo = objectSelectorType.GetMethod("Show", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(UnityEngine.Object), typeof(Type), typeof(SerializedProperty), typeof(bool), typeof(List<int>), typeof(Action<UnityEngine.Object>), typeof(Action<UnityEngine.Object>) }, null);
#endif
var objectSelectorVariable = Expression.Variable(objectSelectorType, "objectSelector");
var objectParameter = Expression.Parameter(typeof(UnityEngine.Object), "unityObject");
var typeParameter = Expression.Parameter(typeof(Type), "type");
var onClosedParameter = Expression.Parameter(typeof(Action<UnityEngine.Object>), "onClosed");
var onChangedObjectParameter = Expression.Parameter(typeof(Action<UnityEngine.Object>), "onChangedObject");
var showObjectSelectorBlock = Expression.Block(
new[] { objectSelectorVariable },
Expression.Assign(objectSelectorVariable, Expression.Call(null, instanceObjectSelectorInfo.GetGetMethod())),
#if UNITY_2020_1_OR_NEWER
Expression.Call(objectSelectorVariable, showInfo, objectParameter, typeParameter, Expression.Constant(null, typeof(UnityEngine.Object)), Expression.Constant(false), Expression.Constant(null, typeof(List<int>)), Expression.Constant(null, typeof(Action<UnityEngine.Object>)), onChangedObjectParameter)
#else
Expression.Call(objectSelectorVariable, showInfo, objectParameter, typeParameter, Expression.Constant(null, typeof(SerializedProperty)), Expression.Constant(false), Expression.Constant(null, typeof(List<int>)), Expression.Constant(null, typeof(Action<UnityEngine.Object>)), onChangedObjectParameter)
#endif
);
var showObjectSelectorLambda = Expression.Lambda<Action<UnityEngine.Object, Type, Action<UnityEngine.Object>>>(showObjectSelectorBlock, objectParameter, typeParameter, onChangedObjectParameter);
ShowObjectSelector = showObjectSelectorLambda.Compile();
var instanceCall = Expression.Call(null, instanceObjectSelectorInfo.GetGetMethod());
var objectSelectorIDField = Expression.Field(instanceCall, "objectSelectorID");
var getSelectorIDLambda = Expression.Lambda<Func<int>>(objectSelectorIDField);
GetSelectorID = getSelectorIDLambda.Compile();
var inSelectorIDParam = Expression.Parameter(typeof(int), "value");
var setSelectorIDLambda = Expression.Lambda<Action<int>>(Expression.Assign(objectSelectorIDField, inSelectorIDParam), inSelectorIDParam);
SetSelectorID = setSelectorIDLambda.Compile();
var getCurrentObjectInfo = objectSelectorType.GetMethod("GetCurrentObject");
var getCurrentObjectLambda = Expression.Lambda<Func<UnityEngine.Object>>(Expression.Call(null, getCurrentObjectInfo));
GetCurrentObject = getCurrentObjectLambda.Compile();
}
public static void Show(UnityEngine.Object obj, Type type, Action<UnityEngine.Object> onChangedObject, Action onClose)
{
id = GUIUtility.GetControlID("s_ObjectFieldHash".GetHashCode(), FocusType.Keyboard);
GUIUtility.keyboardControl = id;
ShowObjectSelector(obj, type, onChangedObject);
selectorID = id;
ObjectSelector.s_OnClose = onClose;
EditorApplication.update += CheckClose;
}
static void CheckClose()
{
if (!opened)
{
ObjectSelector.s_OnClose?.Invoke();
EditorApplication.update -= CheckClose;
}
}
public static void CheckAssignationEvent<T>(Action<T> assignator)
where T : UnityEngine.Object
{
Event evt = Event.current;
if (evt.type != EventType.ExecuteCommand)
return;
string commandName = evt.commandName;
if (commandName != ObjectSelectorUpdatedCommand || selectorID != id)
return;
T current = GetCurrentObject() as T;
if (current == null)
return;
assignator(current);
GUI.changed = true;
evt.Use();
}
}
void CreateOrLoad<T>(Action onCancel, Action<T> onObjectChanged)
where T : ScriptableObject
{
string title;
string content;
UnityEngine.Object target;
if (typeof(T) == typeof(HDRenderPipelineAsset))
{
title = Style.hdrpAssetDisplayDialogTitle;
content = Style.hdrpAssetDisplayDialogContent;
target = GraphicsSettings.renderPipelineAsset as HDRenderPipelineAsset;
}
else
throw new ArgumentException("Unknown type used");
switch (EditorUtility.DisplayDialogComplex(title, content, Style.displayDialogCreate, "Cancel", Style.displayDialogLoad))
{
case 0: //create
if (!AssetDatabase.IsValidFolder("Assets/" + HDProjectSettings.projectSettingsFolderPath))
AssetDatabase.CreateFolder("Assets", HDProjectSettings.projectSettingsFolderPath);
var asset = ScriptableObject.CreateInstance<T>();
asset.name = typeof(T).Name;
AssetDatabase.CreateAsset(asset, "Assets/" + HDProjectSettings.projectSettingsFolderPath + "/" + asset.name + ".asset");
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
if (typeof(T) == typeof(HDRenderPipelineAsset))
GraphicsSettings.renderPipelineAsset = asset as HDRenderPipelineAsset;
break;
case 1: //cancel
onCancel?.Invoke();
break;
case 2: //Load
{
m_Fixer.Pause();
ObjectSelector.Show(target, typeof(T), o => onObjectChanged?.Invoke((T)o), m_Fixer.Unpause);
break;
}
default:
throw new ArgumentException("Unrecognized option");
}
}
#endregion
#region UIELEMENT
class ToolbarRadio : UIElements.Toolbar, INotifyValueChanged<int>
{
public new class UxmlFactory : UxmlFactory<ToolbarRadio, UxmlTraits> {}
public new class UxmlTraits : Button.UxmlTraits {}
List<ToolbarToggle> radios = new List<ToolbarToggle>();
public new static readonly string ussClassName = "unity-toolbar-radio";
public int radioLength => radios.Count;
int m_Value;
public int value
{
get => m_Value;
set
{
if (value == m_Value)
return;
if (panel != null)
{
using (ChangeEvent<int> evt = ChangeEvent<int>.GetPooled(m_Value, value))
{
evt.target = this;
SetValueWithoutNotify(value);
SendEvent(evt);
}
}
else
{
SetValueWithoutNotify(value);
}
}
}
public ToolbarRadio()
{
AddToClassList(ussClassName);
}
void AddRadio(string label = null, string tooltip = null)
{
var toggle = new ToolbarToggle()
{
text = label,
tooltip = tooltip
};
toggle.RegisterValueChangedCallback(InnerValueChanged(radioLength));
toggle.SetValueWithoutNotify(radioLength == 0);
if (radioLength == 0)
toggle.AddToClassList("SelectedRadio");
radios.Add(toggle);
Add(toggle);
toggle.AddToClassList("Radio");
}
public void AddRadios((string label, string tooltip)[] tabs)
{
if (tabs.Length == 0)
return;
if (radioLength > 0)
{
radios[radioLength - 1].RemoveFromClassList("LastRadio");
}
foreach (var(label, tooltip) in tabs)
AddRadio(label, tooltip);
radios[radioLength - 1].AddToClassList("LastRadio");
}
EventCallback<ChangeEvent<bool>> InnerValueChanged(int radioIndex)
{
return (ChangeEvent<bool> evt) =>
{
if (radioIndex == m_Value)
{
if (!evt.newValue)
{
//cannot deselect in a radio
radios[m_Value].RemoveFromClassList("SelectedRadio");
radios[radioIndex].AddToClassList("SelectedRadio");
radios[radioIndex].SetValueWithoutNotify(true);
}
else
value = -1;
}
else
value = radioIndex;
};
}
public void SetValueWithoutNotify(int newValue)
{
if (m_Value != newValue)
{
if (newValue < 0 || newValue >= radioLength)
throw new System.IndexOutOfRangeException();
if (m_Value != newValue)
{
radios[m_Value].RemoveFromClassList("SelectedRadio");
radios[newValue].AddToClassList("SelectedRadio");
radios[newValue].SetValueWithoutNotify(true);
m_Value = newValue;
}
}
}
}
abstract class VisualElementUpdatable : VisualElement
{
protected Func<bool> m_Tester;
bool m_HaveFixer;
public bool currentStatus { get; private set; }
protected VisualElementUpdatable(Func<bool> tester, bool haveFixer)
{
m_Tester = tester;
m_HaveFixer = haveFixer;
}
public virtual void CheckUpdate()
{
bool wellConfigured = m_Tester();
if (wellConfigured ^ currentStatus)
{
UpdateDisplay(wellConfigured, m_HaveFixer);
currentStatus = wellConfigured;
}
}
protected void Init() => UpdateDisplay(currentStatus, m_HaveFixer);
protected abstract void UpdateDisplay(bool statusOK, bool haveFixer);
}
class HiddableUpdatableContainer : VisualElementUpdatable
{
public HiddableUpdatableContainer(Func<bool> tester, bool haveFixer = false) : base(tester, haveFixer) {}
public override void CheckUpdate()
{
base.CheckUpdate();
if (currentStatus)
{
foreach (VisualElementUpdatable updatable in Children().Where(e => e is VisualElementUpdatable))
updatable.CheckUpdate();
}
}
new public void Init() => base.Init();
protected override void UpdateDisplay(bool visible, bool haveFixer)
=> style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
}
class ConfigInfoLine : VisualElementUpdatable
{
static class Style
{
const string k_IconFolder = @"Packages/com.unity.render-pipelines.high-definition/Editor/Wizard/WizardResources/";
public static readonly Texture ok = CoreEditorUtils.LoadIcon(k_IconFolder, "OK");
public static readonly Texture error = CoreEditorUtils.LoadIcon(k_IconFolder, "Error");
public static readonly Texture warning = CoreEditorUtils.LoadIcon(k_IconFolder, "Warning");
public const int k_IndentStepSize = 15;
}
readonly bool m_VisibleStatus;
readonly bool m_SkipErrorIcon;
public ConfigInfoLine(string label, string error, MessageType messageType, string resolverButtonLabel, Func<bool> tester, Action resolver, int indent = 0, bool visibleStatus = true, bool skipErrorIcon = false)
: base(tester, resolver != null)
{
m_VisibleStatus = visibleStatus;
m_SkipErrorIcon = skipErrorIcon;
var testLabel = new Label(label)
{
name = "TestLabel"
};
var fixer = new Button(resolver)
{
text = resolverButtonLabel,
name = "Resolver"
};
var testRow = new VisualElement() { name = "TestRow" };
testRow.Add(testLabel);
if (m_VisibleStatus)
{
var statusOK = new Image()
{
image = Style.ok,
name = "StatusOK"
};
var statusKO = new Image()
{
image = Style.error,
name = "StatusError"
};
testRow.Add(statusOK);
testRow.Add(statusKO);
}
testRow.Add(fixer);
Add(testRow);
HelpBox.Kind kind;
switch (messageType)
{
default:
case MessageType.None: kind = HelpBox.Kind.None; break;
case MessageType.Error: kind = HelpBox.Kind.Error; break;
case MessageType.Warning: kind = HelpBox.Kind.Warning; break;
case MessageType.Info: kind = HelpBox.Kind.Info; break;
}
Add(new HelpBox(kind, error));
testLabel.style.paddingLeft = style.paddingLeft.value.value + indent * Style.k_IndentStepSize;
Init();
}
protected override void UpdateDisplay(bool statusOK, bool haveFixer)
{
if (!((hierarchy.parent as HiddableUpdatableContainer)?.currentStatus ?? true))
{
if (m_VisibleStatus)
{
this.Q(name: "StatusOK").style.display = DisplayStyle.None;
this.Q(name: "StatusError").style.display = DisplayStyle.None;
}
this.Q(name: "Resolver").style.display = DisplayStyle.None;
this.Q(className: "HelpBox").style.display = DisplayStyle.None;
}
else
{
if (m_VisibleStatus)
{
this.Q(name: "StatusOK").style.display = statusOK ? DisplayStyle.Flex : DisplayStyle.None;
this.Q(name: "StatusError").style.display = !statusOK ? (m_SkipErrorIcon ? DisplayStyle.None : DisplayStyle.Flex) : DisplayStyle.None;
}
this.Q(name: "Resolver").style.display = statusOK || !haveFixer ? DisplayStyle.None : DisplayStyle.Flex;
this.Q(className: "HelpBox").style.display = statusOK ? DisplayStyle.None : DisplayStyle.Flex;
}
}
}
class HelpBox : VisualElement
{
public enum Kind
{
None,
Info,
Warning,
Error
}
readonly Label label;
readonly Image icon;
public string text
{
get => label.text;
set => label.text = value;
}
Kind m_Kind = Kind.None;
public Kind kind
{
get => m_Kind;
set
{
if (m_Kind != value)
{
m_Kind = value;
string iconName;
switch (kind)
{
default:
case Kind.None:
icon.style.display = DisplayStyle.None;
return;
case Kind.Info:
iconName = "console.infoicon";
break;
case Kind.Warning:
iconName = "console.warnicon";
break;
case Kind.Error:
iconName = "console.erroricon";
break;
}
icon.image = EditorGUIUtility.IconContent(iconName).image;
icon.style.display = DisplayStyle.Flex;
}
}
}
public HelpBox(Kind kind, string message)
{
this.label = new Label(message);
icon = new Image();
AddToClassList("HelpBox");
Add(icon);
Add(this.label);
this.kind = kind;
}
}
class FixAllButton : VisualElementUpdatable
{
public FixAllButton(string label, Func<bool> tester, Action resolver)
: base(tester, resolver != null)
{
Add(new Button(resolver)
{
text = label,
name = "FixAll"
});
Init();
}
protected override void UpdateDisplay(bool statusOK, bool haveFixer)
=> this.Q(name: "FixAll").style.display = statusOK ? DisplayStyle.None : DisplayStyle.Flex;
}
class ScopeBox : VisualElementUpdatable
{
readonly Label label;
bool initTitleBackground;
public ScopeBox(string title) : base(null, false)
{
label = new Label(title);
label.name = "Title";
AddToClassList("ScopeBox");
Add(label);
}
public override void CheckUpdate()
{
foreach (VisualElementUpdatable updatable in Children().Where(e => e is VisualElementUpdatable))
updatable.CheckUpdate();
}
protected override void UpdateDisplay(bool statusOK, bool haveFixer)
{}
}
#endregion
}
}