This commit is contained in:
2024-09-20 20:30:10 +02:00
commit 4fabf1a6fd
29169 changed files with 1706941 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEditorInternal;
namespace UnityEditor.Rendering
{
/// <summary>
/// Callback method that will be called when the Global Preferences for Additional Properties is changed
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class SetAdditionalPropertiesVisibilityAttribute : Attribute
{
}
class AdditionalPropertiesPreferences : ICoreRenderPipelinePreferencesProvider
{
class Styles
{
public static readonly GUIContent additionalPropertiesLabel = EditorGUIUtility.TrTextContent("Visibility", "Toggle all additional properties to either visible or hidden.");
public static readonly GUIContent[] additionalPropertiesNames = { EditorGUIUtility.TrTextContent("All Visible"), EditorGUIUtility.TrTextContent("All Hidden") };
public static readonly int[] additionalPropertiesValues = { 1, 0 };
}
static List<Type> s_VolumeComponentEditorTypes;
static TypeCache.MethodCollection s_AdditionalPropertiesVisibilityMethods;
static bool s_ShowAllAdditionalProperties = false;
static AdditionalPropertiesPreferences()
{
s_ShowAllAdditionalProperties = EditorPrefs.GetBool(Keys.showAllAdditionalProperties);
}
static void InitializeIfNeeded()
{
if (s_VolumeComponentEditorTypes == null)
{
s_AdditionalPropertiesVisibilityMethods = TypeCache.GetMethodsWithAttribute<SetAdditionalPropertiesVisibilityAttribute>();
s_VolumeComponentEditorTypes = TypeCache.GetTypesDerivedFrom<VolumeComponentEditor>()
.Where(
t => !t.IsAbstract
).ToList();
}
}
static bool showAllAdditionalProperties
{
get => s_ShowAllAdditionalProperties;
set
{
s_ShowAllAdditionalProperties = value;
EditorPrefs.SetBool(Keys.showAllAdditionalProperties, s_ShowAllAdditionalProperties);
ShowAllAdditionalProperties(showAllAdditionalProperties);
}
}
static List<string> s_SearchKeywords = new() { "Additional", "Properties" };
public List<string> keywords => s_SearchKeywords;
public GUIContent header { get; } = EditorGUIUtility.TrTextContent("Additional Properties");
static class Keys
{
internal const string showAllAdditionalProperties = "General.ShowAllAdditionalProperties";
}
public void PreferenceGUI()
{
EditorGUI.BeginChangeCheck();
int newValue = EditorGUILayout.IntPopup(Styles.additionalPropertiesLabel, showAllAdditionalProperties ? 1 : 0, Styles.additionalPropertiesNames, Styles.additionalPropertiesValues);
if (EditorGUI.EndChangeCheck())
{
showAllAdditionalProperties = newValue == 1;
}
}
static void ShowAllAdditionalProperties(bool value)
{
// The way we do this here is to gather all types of either VolumeComponentEditor or IAdditionalPropertiesBoolFlagsHandler (for regular components)
// then we instantiate those classes in order to be able to call the relevant function to update the "ShowAdditionalProperties" flags.
// The instance on which we call is not important because in the end it will only change a global editor preference.
InitializeIfNeeded();
// Volume components
foreach (var editorType in s_VolumeComponentEditorTypes)
{
var key = VolumeComponentEditor.GetAdditionalPropertiesPreferenceKey(editorType);
var showAdditionalProperties = new EditorPrefBool(key);
showAdditionalProperties.value = value;
}
// Regular components
foreach (var method in s_AdditionalPropertiesVisibilityMethods)
{
method.Invoke(null, new object[1] { value });
}
// Force repaint in case some editors are already open.
InternalEditorUtility.RepaintAllViews();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 120ef01d4680b044f8402eb244cbe45d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,119 @@
using System;
using UnityEditor.AnimatedValues;
using System.Collections.Generic;
namespace UnityEditor.Rendering
{
/// <summary>Used in editor drawer part to store the state of additional properties areas.</summary>
/// <typeparam name="TState">An enum to use to describe the state.</typeparam>
/// <typeparam name="TTarget">A type given to automatically compute the key.</typeparam>
public class AdditionalPropertiesState<TState, TTarget>
where TState : struct, IConvertible
{
EditorPrefBoolFlags<TState> m_State;
HashSet<Editor> m_Editors = new HashSet<Editor>();
Dictionary<TState, AnimFloat> m_AnimFloats = new Dictionary<TState, AnimFloat>();
void RepaintAll()
{
foreach (var editor in m_Editors)
{
editor.Repaint();
}
}
/// <summary>Constructor will create the key to store in the EditorPref the state given generic type passed.</summary>
/// <param name="defaultValue">If key did not exist, it will be created with this value for initialization.</param>
/// <param name="prefix">[Optional] Prefix scope of the key (Default is CoreRP)</param>
public AdditionalPropertiesState(TState defaultValue, string prefix = "CoreRP")
{
string key = $"{prefix}:{typeof(TTarget).Name}:{typeof(TState).Name}:UI_AP_State";
m_State = new EditorPrefBoolFlags<TState>(key);
//register key if not already there
if (!EditorPrefs.HasKey(key))
{
EditorPrefs.SetInt(key, (int)(object)defaultValue);
}
}
/// <summary>Get or set the state given the mask.</summary>
/// <param name="mask">The filtering mask</param>
/// <returns>True: All flagged area are expended</returns>
public bool this[TState mask]
{
get => GetAdditionalPropertiesState(mask);
set => SetAdditionalPropertiesState(mask, value);
}
/// <summary>Accessor to the expended state of this specific mask.</summary>
/// <param name="mask">The filtering mask</param>
/// <returns>True: All flagged area are expended</returns>
public bool GetAdditionalPropertiesState(TState mask)
{
return m_State.HasFlag(mask);
}
/// <summary>Setter to the expended state.</summary>
/// <param name="mask">The filtering mask</param>
/// <param name="value">True to show the additional properties.</param>
public void SetAdditionalPropertiesState(TState mask, bool value)
{
m_State.SetFlag(mask, value);
if (value)
ResetAnimation(mask);
}
/// <summary> Utility to set all states to true </summary>
public void ShowAll()
{
m_State.rawValue = 0xFFFFFFFF;
}
/// <summary> Utility to set all states to false </summary>
public void HideAll()
{
m_State.rawValue = 0;
}
internal AnimFloat GetAnimation(TState mask)
{
AnimFloat anim = null;
if (!m_AnimFloats.TryGetValue(mask, out anim))
{
anim = new AnimFloat(0, RepaintAll);
anim.speed = CoreEditorConstants.additionalPropertiesHightLightSpeed;
m_AnimFloats.Add(mask, anim);
}
return anim;
}
void ResetAnimation(TState mask)
{
AnimFloat anim = GetAnimation(mask);
anim.value = 1.0f;
anim.target = 0.0f;
}
/// <summary>
/// Register an editor for this set of additional properties.
/// </summary>
/// <param name="editor">Editor to register.</param>
public void RegisterEditor(Editor editor)
{
m_Editors.Add(editor);
}
/// <summary>
/// Unregister an editor for this set of additional properties.
/// </summary>
/// <param name="editor">Editor to unregister.</param>
public void UnregisterEditor(Editor editor)
{
m_Editors.Remove(editor);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0e0256e1ff0335046ac199efc4d09b69
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 24da0d1c6b55a9143bba19dbb26d6880
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,282 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using UnityEngine;
using UnityEngine.Analytics;
using UnityEngine.Pool;
namespace UnityEditor.Rendering
{
/// <summary>
/// Set of utilities for analytics
/// </summary>
public static class AnalyticsUtils
{
const string k_VendorKey = "unity.srp";
internal static bool TryRegisterEvent(string eventName, int version = 1, int maxEventPerHour = 100, int maxNumberOfElements = 1000)
{
if (!EditorAnalytics.enabled) return false;
if (EditorAnalytics.RegisterEventWithLimit(eventName, maxEventPerHour, maxNumberOfElements, k_VendorKey, version) != AnalyticsResult.Ok) return false;
return true;
}
internal static void SendData<T>(T data, string eventName, int version)
{
EditorAnalytics.SendEventWithLimit(eventName, data, version);
}
internal static IEnumerable<FieldInfo> GetSerializableFields(this Type type, bool removeObsolete = false)
{
var members = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
if (type.BaseType != null && type.BaseType != typeof(object))
{
foreach (FieldInfo field in type.BaseType.GetSerializableFields())
{
yield return field;
}
}
foreach (var member in members)
{
if (member.MemberType != MemberTypes.Field && member.MemberType != MemberTypes.Property)
{
continue;
}
if (member.DeclaringType != type || member is not FieldInfo field)
{
continue;
}
if (removeObsolete && member.GetCustomAttribute<ObsoleteAttribute>() != null)
continue;
if (field.IsPublic)
{
if (member.GetCustomAttribute<NonSerializedAttribute>() != null)
continue;
yield return field;
}
else
{
if (member.GetCustomAttribute<SerializeField>() != null)
yield return field;
}
}
}
static bool AreArraysDifferent(IList a, IList b)
{
if ((a == null) && (b == null))
return false;
if ((a == null) ^ (b == null))
return true;
if (a.Count != b.Count)
return true;
for (int i = 0; i < a.Count; i++)
{
if (!a[i].Equals(b[i]))
return true;
}
return false;
}
static string DumpValues(this IList list)
{
using (ListPool<string>.Get(out var tempList))
{
for (int i = 0; i < list.Count; i++)
{
tempList.Add(list[i].ToString());
}
var arrayValues = string.Join(",", tempList);
return $"[{arrayValues}]";
}
}
static Dictionary<string, string> DumpValues(Type type, object current)
{
var diff = new Dictionary<string, string>();
foreach (var field in type.GetSerializableFields(removeObsolete: true))
{
var t = field.FieldType;
try
{
if (typeof(ScriptableObject).IsAssignableFrom(t))
continue;
var valueCurrent = current != null ? field.GetValue(current) : null;
if (t == typeof(string))
{
var stringCurrent = (string)valueCurrent;
diff[field.Name] = stringCurrent;
}
else if (t.IsPrimitive || t.IsEnum)
{
diff[field.Name] = valueCurrent.ToString();
}
else if (t.IsArray && valueCurrent is IList valueCurrentList)
{
diff[field.Name] = valueCurrentList.DumpValues();
}
else if (t.IsClass || t.IsValueType)
{
if (valueCurrent is IEnumerable ea)
continue; // List<T> not supported
var subDiff = DumpValues(t, valueCurrent);
foreach (var d in subDiff)
{
diff[field.Name + "." + d.Key] = d.Value;
}
}
}
catch (Exception ex)
{
Debug.LogError($"Exception found while parsing {field}, {ex}");
}
}
return diff;
}
static Dictionary<string, string> GetDiffAsDictionary(Type type, object current, object defaults)
{
var diff = new Dictionary<string, string>();
foreach (var field in type.GetSerializableFields())
{
var t = field.FieldType;
try
{
if (t.GetCustomAttribute<ObsoleteAttribute>() != null || typeof(ScriptableObject).IsAssignableFrom(t))
continue;
var valueCurrent = current != null ? field.GetValue(current) : null;
var valueDefault = defaults != null ? field.GetValue(defaults) : null;
if (t == typeof(string))
{
var stringCurrent = (string)valueCurrent;
var stringDefault = (string)valueDefault;
if (stringCurrent != stringDefault)
{
diff[field.Name] = stringCurrent;
}
}
else if (t.IsPrimitive || t.IsEnum)
{
if (!valueCurrent.Equals(valueDefault))
diff[field.Name] = valueCurrent.ToString();
}
else if (t.IsArray && valueCurrent is IList valueCurrentList)
{
if (AreArraysDifferent(valueCurrentList, valueDefault as IList))
diff[field.Name] = valueCurrentList.DumpValues();
}
else if (t.IsClass || t.IsValueType)
{
if (valueCurrent is IEnumerable ea)
continue; // List<T> not supported
var subDiff = GetDiffAsDictionary(t, valueCurrent, valueDefault);
foreach (var d in subDiff)
{
diff[field.Name + "." + d.Key] = d.Value;
}
}
}
catch (Exception ex)
{
Debug.LogError($"Exception found while parsing {field}, {ex}");
}
}
return diff;
}
static string[] ToStringArray(Dictionary<string, string> diff)
{
var changedSettings = new string[diff.Count];
int i = 0;
foreach (var d in diff)
changedSettings[i++] = $@"{{""{d.Key}"":""{d.Value}""}}";
return changedSettings;
}
/// <summary>
/// Obtains the Serialized fields and values in form of nested columns for BigQuery
/// https://cloud.google.com/bigquery/docs/nested-repeated
/// </summary>
/// <typeparam name="T">The given type</typeparam>
/// <param name="current">The current object to obtain the fields and values.</param>
/// <param name="compareAndSimplifyWithDefault">If a comparison against the default value must be done.</param>
/// <returns>The nested columns in form of {key.nestedKey : value} </returns>
/// <exception cref="ArgumentNullException"></exception>
public static string[] ToNestedColumn<T>([DisallowNull] this T current, bool compareAndSimplifyWithDefault = false)
where T : new()
{
if (current == null)
throw new ArgumentNullException(nameof(current));
var type = current.GetType();
Dictionary<string, string> diff;
if (compareAndSimplifyWithDefault)
{
if (typeof(UnityEngine.Object).IsAssignableFrom(typeof(T)))
{
var instance = ScriptableObject.CreateInstance(type);
diff = GetDiffAsDictionary(type, current, instance);
ScriptableObject.DestroyImmediate(instance);
}
else
{
diff = GetDiffAsDictionary(type, current, new T());
}
}
else
{
diff = DumpValues(type, current);
}
return ToStringArray(diff);
}
/// <summary>
/// Obtains the Serialized fields and values in form of nested columns for BigQuery
/// https://cloud.google.com/bigquery/docs/nested-repeated
/// </summary>
/// <typeparam name="T">The given type</typeparam>
/// <param name="current">The current object to obtain the fields and values.</param>
/// <param name="defaultObject">The default object</param>
/// <param name="compareAndSimplifyWithDefault">If a comparison against the default value must be done.</param>
/// <returns>The nested columns in form of {key.nestedKey : value} </returns>
/// <exception cref="ArgumentNullException"></exception>
public static string[] ToNestedColumnWithDefault<T>([DisallowNull] this T current, [DisallowNull] T defaultObject, bool compareAndSimplifyWithDefault = false)
{
if (current == null)
throw new ArgumentNullException(nameof(current));
var type = current.GetType();
Dictionary<string, string> diff = (compareAndSimplifyWithDefault) ?
GetDiffAsDictionary(type, current, defaultObject) : DumpValues(type, current);
return ToStringArray(diff);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0bbdae02753ac26469071b3d5c0fe9d9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,72 @@
using JetBrains.Annotations;
using System.Diagnostics.CodeAnalysis;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
using UnityEngine.Analytics;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering.Analytics
{
internal class BuildTargetAnalytic : IPostprocessBuildWithReport
{
public int callbackOrder => int.MaxValue;
const int k_MaxEventsPerHour = 10;
const int k_MaxNumberOfElements = 1000;
const string k_VendorKey = "unity.srp";
[System.Diagnostics.DebuggerDisplay("{render_pipeline_asset_type} - {quality_levels}/{total_quality_levels_on_project}")]
internal struct BuildTargetAnalyticData
{
internal const string k_EventName = "uBuildTargetAnalytic";
// Naming convention for analytics data
public string build_target;
public string render_pipeline_asset_type;
public int quality_levels;
public int total_quality_levels_on_project;
};
void IPostprocessBuildWithReport.OnPostprocessBuild(BuildReport _)
{
if (!EditorAnalytics.enabled || EditorAnalytics.RegisterEventWithLimit(BuildTargetAnalyticData.k_EventName, k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey) != AnalyticsResult.Ok)
return;
if (!TryGatherData(out var data, out var warning))
Debug.Log(warning);
EditorAnalytics.SendEventWithLimit(BuildTargetAnalyticData.k_EventName, data);
}
[MustUseReturnValue]
static bool TryGatherData([NotNullWhen(true)] out BuildTargetAnalyticData data, [NotNullWhen(false)] out string warning)
{
var activeBuildTarget = EditorUserBuildSettings.activeBuildTarget;
var activeBuildTargetGroup = BuildPipeline.GetBuildTargetGroup(activeBuildTarget);
var activeBuildTargetGroupName = activeBuildTargetGroup.ToString();
warning = string.Empty;
var assetType = GraphicsSettings.currentRenderPipeline == null ? "Built-In Render Pipeline" : GraphicsSettings.currentRenderPipeline.GetType().ToString();
data = new BuildTargetAnalyticData()
{
build_target = activeBuildTarget.ToString(),
quality_levels = QualitySettings.GetActiveQualityLevelsForPlatformCount(activeBuildTargetGroupName),
render_pipeline_asset_type = assetType,
total_quality_levels_on_project = QualitySettings.count
};
return true;
}
[MenuItem("internal:Edit/Rendering/Analytics/Send BuildTargetAnalytic ", priority = 0)]
static void SendAnalyitic()
{
if (!TryGatherData(out var data, out var warning))
Debug.Log(warning);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9f249a7110f8dd14ca061023fb8aa6e5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
using System.Diagnostics;
using UnityEngine.Analytics;
namespace UnityEditor.Rendering.Analytics
{
// schema = com.unity3d.data.schemas.editor.analytics.uDebugManagerWidgetUsedAnalytic_v1
// taxonomy = editor.analytics.uDebugManagerWidgetUsedAnalytic.v1
internal class DebugManagerWidgetUsedAnalytic
{
const int k_MaxEventsPerHour = 1000;
const int k_MaxNumberOfElements = 1000;
const string k_VendorKey = "unity.srp";
[DebuggerDisplay("{query_path} - {value}")]
class Data
{
internal const string k_EventName = "uDebugManagerWidgetUsedAnalytic";
// Naming convention for analytics data
public string query_path;
public string value;
}
public static void Send(string path, object value)
{
if (EditorAnalytics.enabled &&
EditorAnalytics.RegisterEventWithLimit(Data.k_EventName, k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey) == AnalyticsResult.Ok)
{
using (UnityEngine.Pool.GenericPool<Data>.Get(out var data))
{
data.query_path = path;
data.value = value.ToString();
EditorAnalytics.SendEventWithLimit(Data.k_EventName, data);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f3400ad1efb6cc346b10e6ff7586b933
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,78 @@
using System;
using System.Diagnostics;
using System.Linq;
using UnityEngine.Analytics;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering.Analytics
{
// schema = com.unity3d.data.schemas.editor.analytics.uDebugManagerWindowLifetimeAnalytic_v1
// taxonomy = editor.analytics.uDebugManagerWindowLifetimeAnalytic.v1
internal class DebugManagerWindowLifetimeAnalytic
{
const int k_MaxEventsPerHour = 10;
const int k_MaxNumberOfElements = 1000;
const string k_VendorKey = "unity.srp";
private static DateTime?[] timeStamps = new DateTime?[2] { null, null};
[InitializeOnLoadMethod]
static void SubscribeToDebugManagerOpenCloseWindows()
{
DebugManager.windowStateChanged += OnWindowStateChanged;
AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload;
}
private static void OnBeforeAssemblyReload()
{
AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload;
DebugManager.windowStateChanged -= OnWindowStateChanged;
}
private static void OnWindowStateChanged(DebugManager.UIMode windowMode, bool open)
{
try
{
if (!timeStamps[(int)windowMode].HasValue)
{
timeStamps[(int)windowMode] = DateTime.Now;
}
else
{
Send(windowMode, timeStamps[(int)windowMode].Value);
timeStamps[(int)windowMode] = null;
}
}
catch
{
// ignored, do not let analytics throw an error
}
}
[DebuggerDisplay("{window_mode} - {seconds_opened}")]
class Data
{
internal const string k_EventName = "uDebugManagerWindowLifetimeAnalytic";
// Naming convention for analytics data
public string window_mode;
public int seconds_opened;
}
static void Send(DebugManager.UIMode windowMode, DateTime start)
{
var elapsed = DateTime.Now - start;
if (EditorAnalytics.enabled &&
EditorAnalytics.RegisterEventWithLimit(Data.k_EventName, k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey) == AnalyticsResult.Ok)
{
using (UnityEngine.Pool.GenericPool<Data>.Get(out var data))
{
data.window_mode = windowMode.ToString();
data.seconds_opened = elapsed.Seconds;
EditorAnalytics.SendEventWithLimit(Data.k_EventName, data);
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9b3c4db4ffda4ec996c83a20adf76676
timeCreated: 1666684497

View File

@@ -0,0 +1,42 @@
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Rendering;
using Scene = UnityEditor.SearchService.SceneSearch;
namespace UnityEditor.Rendering.Analytics
{
// schema = com.unity3d.data.schemas.editor.analytics.uVolumePriorityUsageAnalyticData_v2
// taxonomy = editor.analytics.uVolumePriorityUsageAnalyticData.v2
internal class VolumePriorityUsageAnalytic
{
[System.Diagnostics.DebuggerDisplay("{volume_name} - {scene_name} - {priority}")]
class Data
{
internal const string k_EventName = "uVolumePriorityUsageAnalyticData";
internal const int k_Version = 2;
// Naming convention for analytics data
public string volume_name;
public string scene_name;
public float priority;
}
public static void Send(Volume volume)
{
if (volume == null || !AnalyticsUtils.TryRegisterEvent(Data.k_EventName, Data.k_Version))
return;
var scene = EditorSceneManager.GetActiveScene();
if (!scene.IsValid())
return;
using (GenericPool<Data>.Get(out var data))
{
data.volume_name = Hash128.Compute(volume.name).ToString();
data.scene_name = AssetDatabase.AssetPathToGUID(scene.path);
data.priority = volume.priority;
AnalyticsUtils.SendData<Data>(data, Data.k_EventName, Data.k_Version);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b29e0569eca4bc54da7a07d64b33e1b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,83 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using System.Diagnostics.CodeAnalysis;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering.Analytics
{
// schema = com.unity3d.data.schemas.editor.analytics.uVolumeProfileOverridesAnalytic_v2
// taxonomy = editor.analytics.uVolumeProfileOverridesAnalytic.v2
internal class VolumeProfileOverridesAnalytic : IPostprocessBuildWithReport
{
public int callbackOrder => int.MaxValue;
[System.Diagnostics.DebuggerDisplay("{volume_profile_asset_guid} - {component_type} - {overrided_parameters.Length}")]
struct Data
{
internal const string k_EventName = "uVolumeProfileOverridesAnalytic";
internal const int k_Version = 2;
// Naming convention for analytics data
public string volume_profile_asset_guid;
public string component_type;
public string[] overrided_parameters;
}
void IPostprocessBuildWithReport.OnPostprocessBuild(BuildReport _)
{
SendAnalyitic();
}
private static readonly string[] k_SearchFolders = new[] { "Assets" };
[MustUseReturnValue]
static bool TryGatherData([NotNullWhen(true)] out List<Data> datas, [NotNullWhen(false)] out string warning)
{
warning = string.Empty;
datas = new List<Data>();
var volumeProfileGUIDs = AssetDatabase.FindAssets($"t:{nameof(VolumeProfile)} glob:\"**/*.asset\"", k_SearchFolders);
foreach (var guid in volumeProfileGUIDs)
{
var volumeProfile = AssetDatabase.LoadAssetAtPath<VolumeProfile>(AssetDatabase.GUIDToAssetPath(guid));
if (volumeProfile == null)
continue;
foreach (var volumeComponent in volumeProfile.components)
{
var volumeComponentType = volumeComponent.GetType();
var overrideParameters =
volumeComponent.ToNestedColumnWithDefault(VolumeManager.instance.GetDefaultVolumeComponent(volumeComponentType),
true);
if (overrideParameters.Length == 0)
continue;
datas.Add(new Data()
{
volume_profile_asset_guid = guid,
component_type = volumeComponent.GetType().Name,
overrided_parameters = overrideParameters
});
}
}
return true;
}
[MenuItem("internal:Edit/Rendering/Analytics/Send VolumeProfileOverridesAnalytic ", priority = 1)]
static void SendAnalyitic()
{
if(!AnalyticsUtils.TryRegisterEvent(Data.k_EventName, Data.k_Version, maxEventPerHour: 1000))
return;
if (!TryGatherData(out var data, out var warning))
Debug.Log(warning);
data.ForEach(d => AnalyticsUtils.SendData(d, Data.k_EventName, Data.k_Version));
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3e30261210b9c0040baf9d684f0b3e8d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,41 @@
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering.Analytics
{
// schema = com.unity3d.data.schemas.editor.analytics.uVolumeProfileUsageAnalytic_v4
// taxonomy = editor.analytics.uVolumeProfileUsageAnalytic.v4
internal class VolumeProfileUsageAnalytic
{
[System.Diagnostics.DebuggerDisplay("{volume_name} - {scene_name}- {volume_profile_asset_guid}")]
class Data
{
internal const string k_EventName = "uVolumeProfileUsageAnalytic";
internal const int k_Version = 4;
// Naming convention for analytics data
public string volume_name;
public string scene_name;
public string volume_profile_asset_guid;
}
public static void Send(Volume volume, VolumeProfile volumeProfile)
{
if (volume == null || volumeProfile == null || !AnalyticsUtils.TryRegisterEvent(Data.k_EventName, Data.k_Version))
return;
var scene = EditorSceneManager.GetActiveScene();
if (!scene.IsValid())
return;
using (GenericPool<Data>.Get(out var data))
{
data.volume_name = Hash128.Compute(volume.name).ToString();
data.scene_name = AssetDatabase.AssetPathToGUID(scene.path);
data.volume_profile_asset_guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(volumeProfile.GetInstanceID()));
AnalyticsUtils.SendData<Data>(data, Data.k_EventName, Data.k_Version);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e7e0182f8c41fea4aaebcffa2915a381
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.RenderPipelines.Core.Editor.Tests")]

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bfa722e189cafe743b7884ff1953f0b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,46 @@
using JetBrains.Annotations;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
/// <summary>
/// Extensions for <see cref="BuildTarget"/>
/// </summary>
public static class BuildTargetExtensions
{
/// <summary>
/// Obtains a list of the <see cref="RenderPipelineAsset"/> that are references into the settings either on <see cref="QualitySettings"/> or in <see cref="GraphicsSettings"/>
/// </summary>
/// <typeparam name="T">The type of <see cref="RenderPipelineAsset"/></typeparam>
/// <param name="buildTarget">The <see cref="BuildTarget"/> to obtain the assets.</param>
/// <param name="srpAssets">The output list of <see cref="RenderPipelineAsset"/> that are referenced by the platform.</param>
/// <returns>false if there was an error fetching the <see cref="RenderPipelineAsset"/> for this <see cref="BuildTarget"/></returns>
[MustUseReturnValue]
public static bool TryGetRenderPipelineAssets<T>([DisallowNull] this BuildTarget buildTarget, List<T> srpAssets)
where T : RenderPipelineAsset
{
if (srpAssets == null)
return false;
var activeBuildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget);
var activeBuildTargetGroupName = activeBuildTargetGroup.ToString();
QualitySettings.GetRenderPipelineAssetsForPlatform<T>(activeBuildTargetGroupName, out var buildPipelineAssets);
srpAssets.AddRange(buildPipelineAssets);
int count = QualitySettings.GetActiveQualityLevelsForPlatformCount(activeBuildTargetGroupName);
var allQualityLevelsAreOverriden = buildPipelineAssets.Count == count;
if (count == 0 || !allQualityLevelsAreOverriden)
{
// We need to check the fallback cases
if (GraphicsSettings.defaultRenderPipeline is T srpAsset)
srpAssets.Add(srpAsset);
}
return true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2e78621c9df5d0b4d884d48b1ded36c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 234fa6c8645c73d4e8857414e1823942
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,171 @@
using System.Linq;
using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary> Camera UI Shared Properties among SRP</summary>
public static partial class CameraUI
{
/// <summary>Camera Projection type</summary>
public enum ProjectionType
{
/// <summary> Perspective</summary>
Perspective,
/// <summary> Orthographic</summary>
Orthographic
}
/// <summary>Camera Projection matrix mode</summary>
public enum ProjectionMatrixMode
{
/// <summary> Explicit</summary>
Explicit,
/// <summary> Implicit</summary>
Implicit,
/// <summary> PhysicalPropertiesBased</summary>
PhysicalPropertiesBased,
}
static bool s_FovChanged;
static float s_FovLastValue;
static ProjectionType DrawerProjectionType(ISerializedCamera p, Editor owner)
{
var cam = p.baseCameraSettings;
ProjectionType projectionType;
Rect perspectiveRect = EditorGUILayout.GetControlRect();
EditorGUI.BeginProperty(perspectiveRect, Styles.projectionContent, cam.orthographic);
{
projectionType = cam.orthographic.boolValue ? ProjectionType.Orthographic : ProjectionType.Perspective;
EditorGUI.BeginChangeCheck();
projectionType = (ProjectionType)EditorGUI.EnumPopup(perspectiveRect, Styles.projectionContent, projectionType);
if (EditorGUI.EndChangeCheck())
cam.orthographic.boolValue = (projectionType == ProjectionType.Orthographic);
}
EditorGUI.EndProperty();
return projectionType;
}
static void DrawerOrthographicType(ISerializedCamera p, Editor owner)
{
EditorGUILayout.PropertyField(p.baseCameraSettings.orthographicSize, Styles.sizeContent);
Drawer_FieldClippingPlanes(p, owner);
}
static void DrawerPerspectiveType(ISerializedCamera p, Editor owner)
{
var cam = p.baseCameraSettings;
var targets = p.serializedObject.targetObjects;
var camera0 = targets[0] as Camera;
float fovCurrentValue;
bool multipleDifferentFovValues = false;
bool isPhysicalCamera = p.projectionMatrixMode.intValue == (int)ProjectionMatrixMode.PhysicalPropertiesBased;
var rect = EditorGUILayout.GetControlRect();
var guiContent = EditorGUI.BeginProperty(rect, Styles.FOVAxisModeContent, cam.fovAxisMode);
EditorGUI.showMixedValue = cam.fovAxisMode.hasMultipleDifferentValues;
CoreEditorUtils.DrawEnumPopup<Camera.FieldOfViewAxis>(rect, guiContent, cam.fovAxisMode);
bool fovAxisVertical = cam.fovAxisMode.intValue == 0;
if (!fovAxisVertical && !cam.fovAxisMode.hasMultipleDifferentValues)
{
float aspectRatio = isPhysicalCamera ? cam.sensorSize.vector2Value.x / cam.sensorSize.vector2Value.y : camera0.aspect;
// camera.aspect is not serialized so we have to check all targets.
fovCurrentValue = Camera.VerticalToHorizontalFieldOfView(camera0.fieldOfView, aspectRatio);
if (targets.Cast<Camera>().Any(camera => camera.fieldOfView != fovCurrentValue))
multipleDifferentFovValues = true;
}
else
{
fovCurrentValue = cam.verticalFOV.floatValue;
multipleDifferentFovValues = cam.fovAxisMode.hasMultipleDifferentValues;
}
EditorGUI.showMixedValue = multipleDifferentFovValues;
var content = EditorGUI.BeginProperty(EditorGUILayout.BeginHorizontal(), Styles.fieldOfViewContent, cam.verticalFOV);
EditorGUI.BeginDisabledGroup(p.projectionMatrixMode.hasMultipleDifferentValues || isPhysicalCamera && (cam.sensorSize.hasMultipleDifferentValues || cam.fovAxisMode.hasMultipleDifferentValues));
EditorGUI.BeginChangeCheck();
s_FovLastValue = EditorGUILayout.Slider(content, fovCurrentValue, 0.00001f, 179f);
s_FovChanged = EditorGUI.EndChangeCheck();
EditorGUI.EndDisabledGroup();
EditorGUILayout.EndHorizontal();
EditorGUI.EndProperty();
EditorGUI.showMixedValue = false;
Drawer_FieldClippingPlanes(p, owner);
content = EditorGUI.BeginProperty(EditorGUILayout.BeginHorizontal(), Styles.physicalCameraContent, p.projectionMatrixMode);
EditorGUI.showMixedValue = p.projectionMatrixMode.hasMultipleDifferentValues;
EditorGUI.BeginChangeCheck();
isPhysicalCamera = EditorGUILayout.Toggle(content, isPhysicalCamera);
if (EditorGUI.EndChangeCheck())
{
p.projectionMatrixMode.intValue = isPhysicalCamera ? (int)ProjectionMatrixMode.PhysicalPropertiesBased : (int)ProjectionMatrixMode.Implicit;
s_FovChanged = true;
}
EditorGUILayout.EndHorizontal();
EditorGUI.EndProperty();
EditorGUI.showMixedValue = false;
if (s_FovChanged)
{
if (!isPhysicalCamera || p.projectionMatrixMode.hasMultipleDifferentValues)
{
cam.verticalFOV.floatValue = fovAxisVertical
? s_FovLastValue
: Camera.HorizontalToVerticalFieldOfView(s_FovLastValue, camera0.aspect);
}
else if (!p.projectionMatrixMode.hasMultipleDifferentValues)
{
cam.verticalFOV.floatValue = fovAxisVertical
? s_FovLastValue
: Camera.HorizontalToVerticalFieldOfView(s_FovLastValue, camera0.aspect);
}
}
}
/// <summary>Draws projection related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_Projection(ISerializedCamera p, Editor owner)
{
// Most of this is replicated from CameraEditor.DrawProjection as we don't want to draw
// it the same way it's done in non-SRP cameras. Unfortunately, because a lot of the
// code is internal, we have to copy/paste some stuff from the editor code :(
var projectionType = DrawerProjectionType(p, owner);
if (p.baseCameraSettings.orthographic.hasMultipleDifferentValues)
return;
using (new EditorGUI.IndentLevelScope())
{
if (projectionType == ProjectionType.Orthographic)
{
DrawerOrthographicType(p, owner);
}
else
{
DrawerPerspectiveType(p, owner);
}
}
}
static void Drawer_FieldClippingPlanes(ISerializedCamera p, Editor owner)
{
CoreEditorUtils.DrawMultipleFields(
Styles.clippingPlaneMultiFieldTitle,
new[] { p.baseCameraSettings.nearClippingPlane, p.baseCameraSettings.farClippingPlane },
new[] { Styles.nearPlaneContent, Styles.farPlaneContent });
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a0d2633fc585aec4a8a89a6014bd7640
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
namespace UnityEditor.Rendering
{
/// <summary> Camera UI Shared Properties among SRP</summary>
public static partial class CameraUI
{
/// <summary>
/// Environment Section
/// </summary>
public static partial class Environment
{
/// <summary>Draws layer mask planes related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_Environment_VolumeLayerMask(ISerializedCamera p, Editor owner)
{
EditorGUILayout.PropertyField(p.volumeLayerMask, Styles.volumeLayerMask);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 40b2b7ce6c436184b91d2d7ea4ef35ce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary> Camera UI Shared Properties among SRP</summary>
public static partial class CameraUI
{
/// <summary>
/// Environment section
/// </summary>
public static partial class Environment
{
/// <summary>
/// Styles
/// </summary>
public static class Styles
{
/// <summary>
/// Header of the section
/// </summary>
public static readonly GUIContent header = EditorGUIUtility.TrTextContent("Environment", "These settings control what the camera background looks like.");
/// <summary>
/// Volume layer mask content
/// </summary>
public static readonly GUIContent volumeLayerMask = EditorGUIUtility.TrTextContent("Volume Mask", "This camera will only be affected by volumes in the selected scene-layers.");
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c94bc25276e67ea429b40e3228a39b05
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
namespace UnityEditor.Rendering
{
/// <summary> Camera UI Shared Properties among SRP</summary>
public static partial class CameraUI
{
/// <summary>
/// Output Section
/// </summary>
public static partial class Output
{
/// <summary>Draws Allow Dynamic Resolution related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_Output_AllowDynamicResolution(ISerializedCamera p, Editor owner)
{
EditorGUILayout.PropertyField(p.allowDynamicResolution, Styles.allowDynamicResolution);
p.baseCameraSettings.allowDynamicResolution.boolValue = p.allowDynamicResolution.boolValue;
}
/// <summary>Draws Normalized ViewPort related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_Output_NormalizedViewPort(ISerializedCamera p, Editor owner)
{
EditorGUILayout.PropertyField(p.baseCameraSettings.normalizedViewPortRect, Styles.viewport);
}
/// <summary>Draws Depth related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_Output_Depth(ISerializedCamera p, Editor owner)
{
EditorGUILayout.PropertyField(p.baseCameraSettings.depth, Styles.depth);
}
/// <summary>Draws Render Target related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_Output_RenderTarget(ISerializedCamera p, Editor owner)
{
EditorGUILayout.PropertyField(p.baseCameraSettings.targetTexture);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c77d1122a4e2ed448966b1535f9f1130
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary> Camera UI Shared Properties among SRP</summary>
public static partial class CameraUI
{
/// <summary>
/// Output section
/// </summary>
public static partial class Output
{
/// <summary>
/// Styles
/// </summary>
public static class Styles
{
/// <summary>
/// Header of the section
/// </summary>
public static readonly GUIContent header = EditorGUIUtility.TrTextContent("Output", "These settings control how the camera output is formatted.");
#if ENABLE_MULTIPLE_DISPLAYS
/// <summary>
/// Target display content
/// </summary>
public static readonly GUIContent targetDisplay = EditorGUIUtility.TrTextContent("Target Display");
#endif
/// <summary>
/// Viewport
/// </summary>
public static readonly GUIContent viewport = EditorGUIUtility.TrTextContent("Viewport Rect", "Four values that indicate where on the screen HDRP draws this Camera view. Measured in Viewport Coordinates (values in the range of [0, 1]).");
/// <summary>
/// Allow dynamic resolution content
/// </summary>
public static readonly GUIContent allowDynamicResolution = EditorGUIUtility.TrTextContent("Allow Dynamic Resolution", "Whether to support dynamic resolution.");
/// <summary>
/// Depth content
/// </summary>
public static readonly GUIContent depth = EditorGUIUtility.TrTextContent("Depth");
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f82f0aee4a6afa04f86310b233ea3d66
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,290 @@
using System;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace UnityEditor.Rendering
{
using CED = CoreEditorDrawer<ISerializedCamera>;
/// <summary> Camera UI Shared Properties among SRP</summary>
public static partial class CameraUI
{
/// <summary>
/// Physical camera related drawers
/// </summary>
public static partial class PhysicalCamera
{
// Saves the value of the sensor size when the user switches from "custom" size to a preset per camera.
// We use a ConditionalWeakTable instead of a Dictionary to avoid keeping alive (with strong references) deleted cameras
static ConditionalWeakTable<Camera, object> s_PerCameraSensorSizeHistory = new ConditionalWeakTable<Camera, object>();
/// <summary>Draws Body Sensor related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_PhysicalCamera_CameraBody_Sensor(ISerializedCamera p, Editor owner)
{
var cam = p.baseCameraSettings;
EditorGUI.BeginChangeCheck();
int oldFilmGateIndex = Array.IndexOf(Styles.apertureFormatValues, new Vector2((float)Math.Round(cam.sensorSize.vector2Value.x, 3), (float)Math.Round(cam.sensorSize.vector2Value.y, 3)));
// If it is not one of the preset sizes, set it to custom
oldFilmGateIndex = (oldFilmGateIndex == -1) ? Styles.customPresetIndex : oldFilmGateIndex;
// Get the new user selection
int newFilmGateIndex = EditorGUILayout.Popup(Styles.sensorType, oldFilmGateIndex, Styles.apertureFormatNames);
if (EditorGUI.EndChangeCheck())
{
// Retrieve the previous custom size value, if one exists for this camera
object previousCustomValue;
s_PerCameraSensorSizeHistory.TryGetValue((Camera)p.serializedObject.targetObject, out previousCustomValue);
// When switching from custom to a preset, update the last custom value (to display again, in case the user switches back to custom)
if (oldFilmGateIndex == Styles.customPresetIndex)
{
if (previousCustomValue == null)
{
s_PerCameraSensorSizeHistory.Add((Camera)p.serializedObject.targetObject, cam.sensorSize.vector2Value);
}
else
{
previousCustomValue = cam.sensorSize.vector2Value;
}
}
if (newFilmGateIndex < Styles.customPresetIndex)
{
cam.sensorSize.vector2Value = Styles.apertureFormatValues[newFilmGateIndex];
}
else
{
// The user switched back to custom, so display by deafulr the previous custom value
if (previousCustomValue != null)
{
cam.sensorSize.vector2Value = (Vector2)previousCustomValue;
}
else
{
cam.sensorSize.vector2Value = new Vector2(36.0f, 24.0f); // this is the value new cameras are created with
}
}
}
EditorGUILayout.PropertyField(cam.sensorSize, Styles.sensorSize);
}
/// <summary>Draws Gate fit related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_PhysicalCamera_CameraBody_GateFit(ISerializedCamera p, Editor owner)
{
var cam = p.baseCameraSettings;
using (var horizontal = new EditorGUILayout.HorizontalScope())
using (var propertyScope = new EditorGUI.PropertyScope(horizontal.rect, Styles.gateFit, cam.gateFit))
using (var checkScope = new EditorGUI.ChangeCheckScope())
{
int gateValue = (int)(Camera.GateFitMode)EditorGUILayout.EnumPopup(propertyScope.content, (Camera.GateFitMode)cam.gateFit.intValue);
if (checkScope.changed)
cam.gateFit.intValue = gateValue;
}
}
/// <summary>Draws Focal Length related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_PhysicalCamera_Lens_FocalLength(ISerializedCamera p, Editor owner)
{
var cam = p.baseCameraSettings;
using (var horizontal = new EditorGUILayout.HorizontalScope())
using (new EditorGUI.PropertyScope(horizontal.rect, Styles.focalLength, cam.focalLength))
using (var checkScope = new EditorGUI.ChangeCheckScope())
{
bool isPhysical = p.projectionMatrixMode.intValue == (int)CameraUI.ProjectionMatrixMode.PhysicalPropertiesBased;
// We need to update the focal length if the camera is physical and the FoV has changed.
bool focalLengthIsDirty = (s_FovChanged && isPhysical);
float sensorLength = cam.fovAxisMode.intValue == 0 ? cam.sensorSize.vector2Value.y : cam.sensorSize.vector2Value.x;
float focalLengthVal = focalLengthIsDirty ? Camera.FieldOfViewToFocalLength(s_FovLastValue, sensorLength) : cam.focalLength.floatValue;
focalLengthVal = EditorGUILayout.FloatField(Styles.focalLength, focalLengthVal);
if (checkScope.changed || focalLengthIsDirty)
cam.focalLength.floatValue = focalLengthVal;
}
}
/// <summary>Draws Lens Shift related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_PhysicalCamera_Lens_Shift(ISerializedCamera p, Editor owner)
{
EditorGUILayout.PropertyField(p.baseCameraSettings.lensShift, Styles.shift);
}
/// <summary>Draws Focus Distance related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_PhysicalCamera_FocusDistance(ISerializedCamera p, Editor owner)
{
var cam = p.baseCameraSettings;
EditorGUILayout.PropertyField(cam.focusDistance, Styles.focusDistance);
}
/// <summary>Draws ISO related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_PhysicalCamera_CameraBody_ISO(ISerializedCamera p, Editor owner)
{
var cam = p.baseCameraSettings;
EditorGUILayout.PropertyField(cam.iso, Styles.ISO);
}
static EditorPrefBoolFlags<ShutterSpeedUnit> m_ShutterSpeedState = new EditorPrefBoolFlags<ShutterSpeedUnit>($"HDRP:{nameof(CameraUI)}:ShutterSpeedState");
enum ShutterSpeedUnit
{
[InspectorName("Second")]
Second,
[InspectorName("1 \u2215 Second")] // Don't use a slash here else Unity will auto-create a submenu...
OneOverSecond
}
/// <summary>Draws Shutter Speed related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_PhysicalCamera_CameraBody_ShutterSpeed(ISerializedCamera p, Editor owner)
{
var cam = p.baseCameraSettings;
// Custom layout for shutter speed
const int k_UnitMenuWidth = 90;
const int k_OffsetPerIndent = 15;
const int k_LabelFieldSeparator = 2;
const int k_Offset = 1;
int oldIndentLevel = EditorGUI.indentLevel;
// Don't take into account the indentLevel when rendering the units field
EditorGUI.indentLevel = 0;
var lineRect = EditorGUILayout.GetControlRect();
var fieldRect = new Rect(k_OffsetPerIndent + k_LabelFieldSeparator + k_Offset, lineRect.y, lineRect.width - k_UnitMenuWidth, lineRect.height);
var unitMenu = new Rect(fieldRect.xMax + k_LabelFieldSeparator, lineRect.y, k_UnitMenuWidth - k_LabelFieldSeparator, lineRect.height);
// We cannot had the shutterSpeedState as this is not a serialized property but a global edition mode.
// This imply that it will never go bold nor can be reverted in prefab overrides
m_ShutterSpeedState.value = (ShutterSpeedUnit)EditorGUI.EnumPopup(unitMenu, m_ShutterSpeedState.value);
// Reset the indent level
EditorGUI.indentLevel = oldIndentLevel;
EditorGUI.BeginProperty(fieldRect, Styles.shutterSpeed, cam.shutterSpeed);
{
// if we we use (1 / second) units, then change the value for the display and then revert it back
if (m_ShutterSpeedState.value == ShutterSpeedUnit.OneOverSecond && cam.shutterSpeed.floatValue > 0)
cam.shutterSpeed.floatValue = 1.0f / cam.shutterSpeed.floatValue;
EditorGUI.PropertyField(fieldRect, cam.shutterSpeed, Styles.shutterSpeed);
if (m_ShutterSpeedState.value == ShutterSpeedUnit.OneOverSecond && cam.shutterSpeed.floatValue > 0)
cam.shutterSpeed.floatValue = 1.0f / cam.shutterSpeed.floatValue;
}
EditorGUI.EndProperty();
}
/// <summary>Draws Lens Aperture related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_PhysicalCamera_Lens_Aperture(ISerializedCamera p, Editor owner)
{
var cam = p.baseCameraSettings;
// Custom layout for aperture
var rect = EditorGUILayout.BeginHorizontal();
{
// Magic values/offsets to get the UI look consistent
const float textRectSize = 80;
const float textRectPaddingRight = 62;
const float unitRectPaddingRight = 97;
const float sliderPaddingLeft = 2;
const float sliderPaddingRight = 77;
var labelRect = rect;
labelRect.width = EditorGUIUtility.labelWidth;
labelRect.height = EditorGUIUtility.singleLineHeight;
EditorGUI.LabelField(labelRect, Styles.aperture);
GUI.SetNextControlName("ApertureSlider");
var sliderRect = rect;
sliderRect.x += labelRect.width + sliderPaddingLeft;
sliderRect.width = rect.width - labelRect.width - sliderPaddingRight;
float newVal = GUI.HorizontalSlider(sliderRect, cam.aperture.floatValue, Camera.kMinAperture, Camera.kMaxAperture);
// keep only 2 digits of precision, like the otehr editor fields
newVal = Mathf.Floor(100 * newVal) / 100.0f;
if (cam.aperture.floatValue != newVal)
{
cam.aperture.floatValue = newVal;
// Note: We need to move the focus when the slider changes, otherwise the textField will not update
GUI.FocusControl("ApertureSlider");
}
var unitRect = rect;
unitRect.x += rect.width - unitRectPaddingRight;
unitRect.width = textRectSize;
unitRect.height = EditorGUIUtility.singleLineHeight;
EditorGUI.LabelField(unitRect, "f /", EditorStyles.label);
var textRect = rect;
textRect.x = rect.width - textRectPaddingRight;
textRect.width = textRectSize;
textRect.height = EditorGUIUtility.singleLineHeight;
string newAperture = EditorGUI.TextField(textRect, cam.aperture.floatValue.ToString());
if (float.TryParse(newAperture, out float parsedValue))
cam.aperture.floatValue = Mathf.Clamp(parsedValue, Camera.kMinAperture, Camera.kMaxAperture);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(EditorGUIUtility.singleLineHeight);
}
/// <summary>Draws Aperture Shape related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_PhysicalCamera_ApertureShape(ISerializedCamera p, Editor owner)
{
var cam = p.baseCameraSettings;
EditorGUILayout.PropertyField(cam.bladeCount, Styles.bladeCount);
using (var horizontal = new EditorGUILayout.HorizontalScope())
using (var propertyScope = new EditorGUI.PropertyScope(horizontal.rect, Styles.curvature, cam.curvature))
{
var v = cam.curvature.vector2Value;
// The layout system breaks alignment when mixing inspector fields with custom layout'd
// fields as soon as a scrollbar is needed in the inspector, so we'll do the layout
// manually instead
const int kFloatFieldWidth = 50;
const int kSeparatorWidth = 5;
float indentOffset = EditorGUI.indentLevel * 15f;
var lineRect = EditorGUILayout.GetControlRect();
var labelRect = new Rect(lineRect.x, lineRect.y, EditorGUIUtility.labelWidth - indentOffset, lineRect.height);
var floatFieldLeft = new Rect(labelRect.xMax, lineRect.y, kFloatFieldWidth + indentOffset, lineRect.height);
var sliderRect = new Rect(floatFieldLeft.xMax + kSeparatorWidth - indentOffset, lineRect.y, lineRect.width - labelRect.width - kFloatFieldWidth * 2 - kSeparatorWidth * 2, lineRect.height);
var floatFieldRight = new Rect(sliderRect.xMax + kSeparatorWidth - indentOffset, lineRect.y, kFloatFieldWidth + indentOffset, lineRect.height);
EditorGUI.PrefixLabel(labelRect, propertyScope.content);
v.x = EditorGUI.FloatField(floatFieldLeft, v.x);
EditorGUI.MinMaxSlider(sliderRect, ref v.x, ref v.y, Camera.kMinAperture, Camera.kMaxAperture);
v.y = EditorGUI.FloatField(floatFieldRight, v.y);
cam.curvature.vector2Value = v;
}
EditorGUILayout.PropertyField(cam.barrelClipping, Styles.barrelClipping);
EditorGUILayout.PropertyField(cam.anamorphism, Styles.anamorphism);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b7fbad490dc4ecc46b2936be42e9c71f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,120 @@
using System.Linq;
using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary> Camera UI Shared Properties among SRP</summary>
public static partial class CameraUI
{
/// <summary>
/// Physical camera content content
/// </summary>
public static partial class PhysicalCamera
{
/// <summary>
/// Styles
/// </summary>
public static class Styles
{
// Camera Body
/// <summary>
/// Camera Body content
/// </summary>
public static readonly GUIContent cameraBody = EditorGUIUtility.TrTextContent("Camera Body");
/// <summary>
/// Sensor type content
/// </summary>
public static readonly GUIContent sensorType = EditorGUIUtility.TrTextContent("Sensor Type", "Common sensor sizes. Choose an item to set Sensor Size, or edit Sensor Size for your custom settings.");
/// <summary>
/// Aperture format names
/// </summary>
public static readonly string[] apertureFormatNames = CameraEditor.Settings.ApertureFormatNames.ToArray();
/// <summary>
/// Aperture format values
/// </summary>
public static readonly Vector2[] apertureFormatValues = CameraEditor.Settings.ApertureFormatValues.ToArray();
/// <summary>
/// Custom preset index
/// </summary>
public static readonly int customPresetIndex = apertureFormatNames.Length - 1;
/// <summary>
/// Sensor size
/// </summary>
public static readonly GUIContent sensorSize = EditorGUIUtility.TrTextContent("Sensor Size", "The size of the camera sensor in millimeters.");
/// <summary>
/// Gate Fit
/// </summary>
public static readonly GUIContent gateFit = EditorGUIUtility.TrTextContent("Gate Fit", "Determines how the rendered area (resolution gate) fits into the sensor area (film gate).");
// Lens
/// <summary>
/// Lens content
/// </summary>
public static readonly GUIContent lens = EditorGUIUtility.TrTextContent("Lens");
/// <summary>
/// Focal Length content
/// </summary>
public static readonly GUIContent focalLength = EditorGUIUtility.TrTextContent("Focal Length", "The simulated distance between the lens and the sensor of the physical camera. Larger values give a narrower field of view.");
/// <summary>
/// Shift content
/// </summary>
public static readonly GUIContent shift = EditorGUIUtility.TrTextContent("Shift", "Offset from the camera sensor. Use these properties to simulate a shift lens. Measured as a multiple of the sensor size.");
/// <summary>
/// ISO content
/// </summary>
public static readonly GUIContent ISO = EditorGUIUtility.TrTextContent("ISO", "Sets the light sensitivity of the Camera sensor. This property affects Exposure if you set its Mode to Use Physical Camera.");
/// <summary>
/// Shutter Speed content
/// </summary>
public static readonly GUIContent shutterSpeed = EditorGUIUtility.TrTextContent("Shutter Speed", "The amount of time the Camera sensor is capturing light.");
/// <summary>
/// Aperture content
/// </summary>
public static readonly GUIContent aperture = EditorGUIUtility.TrTextContent("Aperture", "The f-stop (f-number) of the lens. Lower values give a wider lens aperture.");
/// <summary>
/// Focus Distance content
/// </summary>
public static readonly GUIContent focusDistance = EditorGUIUtility.TrTextContent("Focus Distance", "The distance from the camera where objects appear sharp when Depth Of Field is enabled.");
// Aperture Shape
/// <summary>
/// Aperture Shape content
/// </summary>
public static readonly GUIContent apertureShape = EditorGUIUtility.TrTextContent("Aperture Shape", "Common sensor sizes. Choose an item to set Sensor Size, or edit Sensor Size for your custom settings.");
/// <summary>
/// Blade Count content
/// </summary>
public static readonly GUIContent bladeCount = EditorGUIUtility.TrTextContent("Blade Count", "The number of blades in the lens aperture. Higher values give a rounder aperture shape.");
/// <summary>
/// Curvature content
/// </summary>
public static readonly GUIContent curvature = EditorGUIUtility.TrTextContent("Curvature", "Controls the curvature of the lens aperture blades. The minimum value results in fully-curved, perfectly-circular bokeh, and the maximum value results in visible aperture blades.");
/// <summary>
/// Barrel Clipping content
/// </summary>
public static readonly GUIContent barrelClipping = EditorGUIUtility.TrTextContent("Barrel Clipping", "Controls the self-occlusion of the lens, creating a cat's eye effect.");
/// <summary>
/// Anamorphism content
/// </summary>
public static readonly GUIContent anamorphism = EditorGUIUtility.TrTextContent("Anamorphism", "Use the slider to stretch the sensor to simulate an anamorphic look.");
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7e978521b37d51146b2cc9750ebfa16c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,41 @@
namespace UnityEditor.Rendering
{
/// <summary> Camera UI Shared Properties among SRP</summary>
public static partial class CameraUI
{
public static partial class Rendering
{
/// <summary>Draws Stop NaNs related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_Rendering_StopNaNs(ISerializedCamera p, Editor owner)
{
EditorGUILayout.PropertyField(p.stopNaNs, Styles.stopNaNs);
}
/// <summary>Draws Dithering related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_Rendering_Dithering(ISerializedCamera p, Editor owner)
{
EditorGUILayout.PropertyField(p.dithering, Styles.dithering);
}
/// <summary>Draws Culling mask related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_Rendering_CullingMask(ISerializedCamera p, Editor owner)
{
EditorGUILayout.PropertyField(p.baseCameraSettings.cullingMask, Styles.cullingMask);
}
/// <summary>Draws occlusion Culling related fields on the inspector</summary>
/// <param name="p"><see cref="ISerializedCamera"/> The serialized camera</param>
/// <param name="owner"><see cref="Editor"/> The editor owner calling this drawer</param>
public static void Drawer_Rendering_OcclusionCulling(ISerializedCamera p, Editor owner)
{
EditorGUILayout.PropertyField(p.baseCameraSettings.occlusionCulling, Styles.occlusionCulling);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6cf20b5e989565749903509e13e351f6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,60 @@
using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary> Camera UI Shared Properties among SRP</summary>
public static partial class CameraUI
{
/// <summary>
/// Rendering section
/// </summary>
public static partial class Rendering
{
/// <summary>
/// Styles
/// </summary>
public static class Styles
{
/// <summary>
/// Header of the section
/// </summary>
public static readonly GUIContent header = EditorGUIUtility.TrTextContent("Rendering", "These settings control for the specific rendering features for this camera.");
/// <summary>
/// antialiasing content
/// </summary>
public static readonly GUIContent antialiasing = EditorGUIUtility.TrTextContent("Post Anti-aliasing", "The postprocess anti-aliasing method to use.");
/// <summary>
/// dithering content
/// </summary>
public static readonly GUIContent dithering = EditorGUIUtility.TrTextContent("Dithering", "Applies 8-bit dithering to the final render to reduce color banding.");
/// <summary>
/// stopNaNs content
/// </summary>
public static readonly GUIContent stopNaNs = EditorGUIUtility.TrTextContent("Stop NaNs", "Automatically replaces NaN/Inf in shaders by a black pixel to avoid breaking some effects. This will slightly affect performances and should only be used if you experience NaN issues that you can't fix.");
/// <summary>
/// cullingMask content
/// </summary>
public static readonly GUIContent cullingMask = EditorGUIUtility.TrTextContent("Culling Mask");
/// <summary>
/// occlusionCulling content
/// </summary>
public static readonly GUIContent occlusionCulling = EditorGUIUtility.TrTextContent("Occlusion Culling");
/// <summary>
/// renderingPath content
/// </summary>
public static readonly GUIContent renderingPath = EditorGUIUtility.TrTextContent("Custom Frame Settings", "Define the custom Frame Settings for this Camera to use.");
/// <summary>
/// exposureTarget content
/// </summary>
public static readonly GUIContent exposureTarget = EditorGUIUtility.TrTextContent("Exposure Target", "The object used as a target for centering the Exposure's Procedural Mask metering mode when target object option is set (See Exposure Volume Component).");
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 96acda4e859ca224986250b52266e0c9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,64 @@
using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary> Camera UI Shared Properties among SRP</summary>
public static partial class CameraUI
{
/// <summary>
/// Styles
/// </summary>
public static class Styles
{
/// <summary>
/// Projection section header
/// </summary>
public static GUIContent projectionSettingsHeaderContent { get; } = EditorGUIUtility.TrTextContent("Projection");
/// <summary>
/// Clipping planes content
/// </summary>
public static GUIContent clippingPlaneMultiFieldTitle = EditorGUIUtility.TrTextContent("Clipping Planes");
/// <summary>
/// Projection Content
/// </summary>
public static readonly GUIContent projectionContent = EditorGUIUtility.TrTextContent("Projection", "How the Camera renders perspective.\n\nChoose Perspective to render objects with perspective.\n\nChoose Orthographic to render objects uniformly, with no sense of perspective.");
/// <summary>
/// Size content
/// </summary>
public static readonly GUIContent sizeContent = EditorGUIUtility.TrTextContent("Size");
/// <summary>
/// FOV content
/// </summary>
public static readonly GUIContent fieldOfViewContent = EditorGUIUtility.TrTextContent("Field of View", "The height of the Camera's view angle, measured in degrees along the specified axis.");
/// <summary>
/// FOV Axis content
/// </summary>
public static readonly GUIContent FOVAxisModeContent = EditorGUIUtility.TrTextContent("Field of View Axis", "The axis the Camera's view angle is measured along.");
/// <summary>
/// Physical camera content
/// </summary>
public static readonly GUIContent physicalCameraContent = EditorGUIUtility.TrTextContent("Physical Camera", "Enables Physical camera mode for FOV calculation. When checked, the field of view is calculated from properties for simulating physical attributes (focal length, sensor size, and lens shift).");
/// <summary>
/// Near plane content
/// </summary>
public static readonly GUIContent nearPlaneContent = EditorGUIUtility.TrTextContent("Near", "The closest point relative to the camera that drawing occurs.");
/// <summary>
/// Far plane content
/// </summary>
public static readonly GUIContent farPlaneContent = EditorGUIUtility.TrTextContent("Far", "The furthest point relative to the camera that drawing occurs.");
/// <summary>
/// Message displayed about unsupported fields for Camera Presets
/// </summary>
public static readonly string unsupportedPresetPropertiesMessage = L10n.Tr("When using Preset of Camera Component, only a subset of properties are supported. Unsupported properties are hidden.");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e26c221569d1d284a9d21434b3a4b277
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,42 @@
namespace UnityEditor.Rendering
{
/// <summary>
/// Interface to be implemented by each pipeline to hold the <see cref="SerializedObject"/> for a Camera Editor
/// </summary>
public interface ISerializedCamera
{
/// <summary>The camera serialized</summary>
SerializedObject serializedObject { get; }
/// <summary>The additional camera data serialized</summary>
SerializedObject serializedAdditionalDataObject { get; }
/// <summary>The base camera settings</summary>
CameraEditor.Settings baseCameraSettings { get; }
// This one is internal in UnityEditor for whatever reason...
/// <summary>The projection matrix mode</summary>
SerializedProperty projectionMatrixMode { get; }
// Common properties
/// <summary>Dithering property</summary>
SerializedProperty dithering { get; }
/// <summary>Stop NaNs property</summary>
SerializedProperty stopNaNs { get; }
/// <summary>Allow Dynamic resolution property</summary>
SerializedProperty allowDynamicResolution { get; }
/// <summary>Volume layer mask property</summary>
SerializedProperty volumeLayerMask { get; }
/// <summary>Clear Depth property property</summary>
SerializedProperty clearDepth { get; }
/// <summary>Anti aliasing property</summary>
SerializedProperty antialiasing { get; }
/// <summary>Method that updates the <see cref="SerializedObject"/> of the Camera and the Additional Camera Data</summary>
void Update();
/// <summary>Applies the modified properties to the <see cref="SerializedObject"/> of the Camera and the Additional Camera Data</summary>
void Apply();
/// <summary>Refreshes the <see cref="SerializedProperty"/> of the <see cref="SerializedObject"/> of the Camera and the Additional Camera Data</summary>
void Refresh();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 55ef358f023224145a079dd70d4692b5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,123 @@
using UnityEngine;
using Object = UnityEngine.Object;
namespace UnityEditor.Rendering
{
/// <summary>
/// Utility functions for cameras in the editor.
/// </summary>
public static class CameraEditorUtils
{
/// <summary>Delegate that must give an initialized preview camera</summary>
/// <param name="sourceCamera">The initial Camera we want a preview from</param>
/// <param name="previewSize">The size of the preview</param>
/// <returns>The Preview camera, initialized</returns>
public delegate Camera GetPreviewCamera(Camera sourceCamera, Vector2 previewSize);
const float k_PreviewNormalizedSize = 0.2f;
internal static Material s_GUITextureBlit2SRGBMaterial;
/// <summary>
/// The material used to display a texture into SRGB
/// </summary>
public static Material GUITextureBlit2SRGBMaterial
{
get
{
if (!s_GUITextureBlit2SRGBMaterial)
{
Shader shader = EditorGUIUtility.LoadRequired("SceneView/GUITextureBlit2SRGB.shader") as Shader;
s_GUITextureBlit2SRGBMaterial = new Material(shader);
s_GUITextureBlit2SRGBMaterial.hideFlags = HideFlags.HideAndDontSave;
}
s_GUITextureBlit2SRGBMaterial.SetFloat("_ManualTex2SRGB", QualitySettings.activeColorSpace == ColorSpace.Linear ? 1.0f : 0.0f);
return s_GUITextureBlit2SRGBMaterial;
}
}
/// <summary>
/// Draw the overlay of a Camera
/// </summary>
/// <param name="target">The Camera that we want a preview</param>
/// <param name="sceneView">The scene view where to draw it</param>
/// <param name="previewCameraGetter">The way to get the preview camera corresponding to the target</param>
public static void DrawCameraSceneViewOverlay(Object target, SceneView sceneView, GetPreviewCamera previewCameraGetter)
{
if (target == null) return;
// cache some deep values
var c = (Camera)target;
var previewSize = Handles.GetMainGameViewSize();
if (previewSize.x < 0f)
{
// Fallback to Scene View of not a valid game view size
previewSize.x = sceneView.position.width;
previewSize.y = sceneView.position.height;
}
// Apply normalizedviewport rect of camera
var normalizedViewPortRect = c.rect;
previewSize.x *= Mathf.Max(normalizedViewPortRect.width, 0f);
previewSize.y *= Mathf.Max(normalizedViewPortRect.height, 0f);
// Prevent using invalid previewSize
if (previewSize.x <= 0f || previewSize.y <= 0f)
return;
var aspect = previewSize.x / previewSize.y;
// Scale down (fit to scene view)
previewSize.y = k_PreviewNormalizedSize * sceneView.position.height;
previewSize.x = previewSize.y * aspect;
if (previewSize.y > sceneView.position.height * 0.5f)
{
previewSize.y = sceneView.position.height * 0.5f;
previewSize.x = previewSize.y * aspect;
}
if (previewSize.x > sceneView.position.width * 0.5f)
{
previewSize.x = sceneView.position.width * 0.5f;
previewSize.y = previewSize.x / aspect;
}
// Get and reserve rect
Rect cameraRect = GUILayoutUtility.GetRect(previewSize.x, previewSize.y);
if (Event.current.type == EventType.Repaint)
{
var previewCamera = previewCameraGetter(c, previewSize);
if (previewCamera.targetTexture == null)
{
Debug.LogError("The preview camera must render in a render target");
return;
}
bool drawGizmo = sceneView.drawGizmos;
sceneView.drawGizmos = false;
previewCamera.Render();
sceneView.drawGizmos = drawGizmo;
Graphics.DrawTexture(cameraRect, previewCamera.targetTexture, new Rect(0, 0, 1, 1), 0, 0, 0, 0, GUI.color, GUITextureBlit2SRGBMaterial);
// We set target texture to null after this call otherwise if both sceneview and gameview are visible and we have a preview camera wwe
// get this error: "Releasing render texture that is set as Camera.targetTexture!"
previewCamera.targetTexture = null;
}
}
/// <summary>
/// Check if the view port rect have a positive size
/// </summary>
/// <param name="normalizedViewPortRect">The rect to check</param>
/// <returns>True: the rect have positive size</returns>
public static bool IsViewPortRectValidToRender(Rect normalizedViewPortRect)
{
if (normalizedViewPortRect.width <= 0f || normalizedViewPortRect.height <= 0f)
return false;
if (normalizedViewPortRect.x >= 1f || normalizedViewPortRect.xMax <= 0f)
return false;
if (normalizedViewPortRect.y >= 1f || normalizedViewPortRect.yMax <= 0f)
return false;
return true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9fabbcdc8ab50c44a8112c70c67735c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary>
/// Helper methods for overriding contextual menus
/// </summary>
static class ContextualMenuDispatcher
{
[MenuItem("CONTEXT/ReflectionProbe/Remove Component")]
[MenuItem("CONTEXT/Light/Remove Component")]
[MenuItem("CONTEXT/Camera/Remove Component")]
static void RemoveComponentWithAdditionalData(MenuCommand command)
{
RemoveComponentUtils.RemoveComponent(command.context as Component);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b55ea781ffec78245a85c7b70fefee48
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,885 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEditor.AnimatedValues;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
/// <summary>display options added to the Foldout</summary>
[Flags]
public enum FoldoutOption
{
/// <summary>No Option</summary>
None = 0,
/// <summary>Foldout will be indented</summary>
Indent = 1 << 0,
/// <summary>Foldout will be boxed</summary>
Boxed = 1 << 2,
/// <summary>Foldout will be inside another foldout</summary>
SubFoldout = 1 << 3,
/// <summary>Remove the space at end of the foldout</summary>
NoSpaceAtEnd = 1 << 4
}
/// <summary>display options added to the Group</summary>
[Flags]
public enum GroupOption
{
/// <summary>No Option</summary>
None = 0,
/// <summary>Group will be indented</summary>
Indent = 1 << 0
}
/// <summary>
/// Utility class to draw inspectors
/// </summary>
/// <typeparam name="TData">Type of class containing data needed to draw inspector</typeparam>
public static class CoreEditorDrawer<TData>
{
/// <summary> Abstraction that have the Draw hability </summary>
public interface IDrawer
{
/// <summary>
/// The draw function
/// </summary>
/// <param name="serializedProperty">The SerializedProperty to draw</param>
/// <param name="owner">The editor handling this draw call</param>
void Draw(TData serializedProperty, Editor owner);
/// <summary>
/// Expands all children that use a given mask
/// </summary>
/// <param name="mask">The mask to expand</param>
/// <returns>If the drawer is expanded</returns>
bool Expand(int mask);
}
/// <summary>Delegate that must say if this is enabled for drawing</summary>
/// <param name="data">The data used</param>
/// <param name="owner">The editor rendering</param>
/// <returns>True if this should be drawn</returns>
public delegate bool Enabler(TData data, Editor owner);
/// <summary>Delegate is called when the foldout state is switched</summary>
/// <param name="data">The data used</param>
/// <param name="owner">The editor rendering</param>
public delegate void SwitchEnabler(TData data, Editor owner);
/// <summary>Delegate that must be used to select sub object for data for drawing</summary>
/// <typeparam name="T2Data">The type of the sub object used for data</typeparam>
/// <param name="data">The data used</param>
/// <param name="owner">The editor rendering</param>
/// <returns>Embeded object that will be used as data source for later draw in this Select</returns>
public delegate T2Data DataSelect<T2Data>(TData data, Editor owner);
/// <summary>Delegate type alternative to IDrawer</summary>
/// <param name="data">The data used</param>
/// <param name="owner">The editor rendering</param>
public delegate void ActionDrawer(TData data, Editor owner);
/// <summary> Equivalent to EditorGUILayout.Space that can be put in a drawer group </summary>
public static readonly IDrawer space = Group((data, owner) => EditorGUILayout.Space());
/// <summary> Use it when IDrawer required but no operation should be done </summary>
public static readonly IDrawer noop = Group((data, owner) => { });
internal static bool DefaultExpand(ActionDrawer[] actionDrawers, int mask)
{
for (var i = 0; i < actionDrawers.Length; i++)
{
if (actionDrawers[i] == null)
continue;
var targets = (actionDrawers[i].Target as IDrawer[]);
if (targets == null)
continue;
foreach (var target in targets)
{
if (target.Expand(mask))
return true;
}
}
return false;
}
/// <summary>
/// Conditioned drawer that will only be drawn if its enabler function is null or return true
/// </summary>
/// <param name="enabler">Enable the drawing if null or return true</param>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Conditional(Enabler enabler, params IDrawer[] contentDrawers)
{
return new ConditionalDrawerInternal(enabler, contentDrawers.Draw);
}
/// <summary>
/// Conditioned drawer that will only be drawn if its enabler function is null or return true
/// </summary>
/// <param name="enabler">Enable the drawing if null or return true</param>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Conditional(Enabler enabler, params ActionDrawer[] contentDrawers)
{
return new ConditionalDrawerInternal(enabler, contentDrawers);
}
class ConditionalDrawerInternal : IDrawer
{
ActionDrawer[] actionDrawers { get; set; }
Enabler m_Enabler;
public ConditionalDrawerInternal(Enabler enabler = null, params ActionDrawer[] actionDrawers)
{
this.actionDrawers = actionDrawers;
m_Enabler = enabler;
}
void IDrawer.Draw(TData data, Editor owner)
{
if (m_Enabler != null && !m_Enabler(data, owner))
return;
for (var i = 0; i < actionDrawers.Length; i++)
actionDrawers[i](data, owner);
}
bool IDrawer.Expand(int mask) => DefaultExpand(actionDrawers, mask);
}
internal static IDrawer ConditionalWithAdditionalProperties(Enabler enabler, AnimFloat animation, params IDrawer[] contentDrawers)
{
return new ConditionalDrawerWithAdditionalPropertiesInternal(enabler, animation, contentDrawers.Draw);
}
internal static IDrawer ConditionalWithAdditionalProperties(Enabler enabler, AnimFloat animation, params ActionDrawer[] contentDrawers)
{
return new ConditionalDrawerWithAdditionalPropertiesInternal(enabler, animation, contentDrawers);
}
class ConditionalDrawerWithAdditionalPropertiesInternal : IDrawer
{
ActionDrawer[] m_ActionDrawers { get; set; }
Enabler m_Enabler;
AnimFloat m_Anim;
public ConditionalDrawerWithAdditionalPropertiesInternal(Enabler enabler = null, AnimFloat anim = null, params ActionDrawer[] actionDrawers)
{
m_ActionDrawers = actionDrawers;
m_Enabler = enabler;
m_Anim = anim;
}
void IDrawer.Draw(TData data, Editor owner)
{
if (m_Enabler != null && !m_Enabler(data, owner))
return;
if (m_Anim != null)
CoreEditorUtils.BeginAdditionalPropertiesHighlight(m_Anim);
for (var i = 0; i < m_ActionDrawers.Length; i++)
m_ActionDrawers[i](data, owner);
if (m_Anim != null)
{
CoreEditorUtils.EndAdditionalPropertiesHighlight();
// While the highlight is being changed, force the Repaint of the editor
if (m_Anim.value > 0.0f)
owner.Repaint();
}
}
bool IDrawer.Expand(int mask) => DefaultExpand(m_ActionDrawers, mask);
}
/// <summary>
/// Conditioned drawer that will draw something depending of the return of the switch
/// </summary>
/// <param name="switch">Chose witch drawing to use</param>
/// <param name="drawIfTrue">This will be draw if the <see cref="switch"/> is true</param>
/// <param name="drawIfFalse">This will be draw if the <see cref="switch"/> is false</param>
/// <returns>A IDrawer object</returns>
public static IDrawer TernaryConditional(Enabler @switch, IDrawer drawIfTrue, IDrawer drawIfFalse)
=> new TernaryConditionalDrawerInternal(@switch, drawIfTrue.Draw, drawIfFalse.Draw);
/// <summary>
/// Conditioned drawer that will draw something depending of the return of the switch
/// </summary>
/// <param name="switch">Chose witch drawing to use</param>
/// <param name="drawIfTrue">This will be draw if the <see cref="switch"/> is true</param>
/// <param name="drawIfFalse">This will be draw if the <see cref="switch"/> is false</param>
/// <returns>A IDrawer object</returns>
public static IDrawer TernaryConditional(Enabler @switch, ActionDrawer drawIfTrue, ActionDrawer drawIfFalse)
=> new TernaryConditionalDrawerInternal(@switch, drawIfTrue, drawIfFalse);
class TernaryConditionalDrawerInternal : IDrawer
{
ActionDrawer drawIfTrue;
ActionDrawer drawIfFalse;
Enabler m_Switch;
public TernaryConditionalDrawerInternal(Enabler @switch, ActionDrawer drawIfTrue, ActionDrawer drawIfFalse)
{
this.drawIfTrue = drawIfTrue;
this.drawIfFalse = drawIfFalse;
m_Switch = @switch;
}
void IDrawer.Draw(TData data, Editor owner)
{
if (m_Switch != null && !m_Switch(data, owner))
drawIfFalse?.Invoke(data, owner);
else
drawIfTrue?.Invoke(data, owner);
}
bool IDrawer.Expand(int mask) => DefaultExpand(new ActionDrawer[] { drawIfTrue, drawIfFalse }, mask);
}
/// <summary>
/// Group of drawing function for inspector.
/// They will be drawn one after the other.
/// </summary>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Group(params IDrawer[] contentDrawers)
{
return new GroupDrawerInternal(-1f, null, GroupOption.None, contentDrawers.Draw);
}
/// <summary>
/// Group of drawing function for inspector.
/// They will be drawn one after the other.
/// </summary>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Group(params ActionDrawer[] contentDrawers)
{
return new GroupDrawerInternal(-1f, null, GroupOption.None, contentDrawers);
}
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
/// <param name="labelWidth">Width used for all labels in the group</param>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Group(float labelWidth, params IDrawer[] contentDrawers)
{
return new GroupDrawerInternal(labelWidth, null, GroupOption.None, contentDrawers.Draw);
}
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
/// <param name="header">Adds a header on top <see cref="GUIContent"/></param>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Group(GUIContent header, params IDrawer[] contentDrawers)
{
return new GroupDrawerInternal(-1f, header, GroupOption.None, contentDrawers.Draw);
}
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
/// <param name="labelWidth">Width used for all labels in the group</param>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Group(float labelWidth, params ActionDrawer[] contentDrawers)
{
return new GroupDrawerInternal(labelWidth, null, GroupOption.None, contentDrawers);
}
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
/// <param name="header">Adds a header on top <see cref="GUIContent"/></param>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Group(GUIContent header, params ActionDrawer[] contentDrawers)
{
return new GroupDrawerInternal(-1f, header, GroupOption.None, contentDrawers);
}
/// <summary>
/// Group of drawing function for inspector.
/// They will be drawn one after the other.
/// </summary>
/// <param name="options">Allow to add indentation on this group</param>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Group(GroupOption options, params IDrawer[] contentDrawers)
{
return new GroupDrawerInternal(-1f, null, options, contentDrawers.Draw);
}
/// <summary>
/// Group of drawing function for inspector.
/// They will be drawn one after the other.
/// </summary>
/// <param name="options">Allow to add indentation on this group</param>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Group(GroupOption options, params ActionDrawer[] contentDrawers)
{
return new GroupDrawerInternal(-1f, null, options, contentDrawers);
}
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
/// <param name="labelWidth">Width used for all labels in the group</param>
/// <param name="options">Allow to add indentation on this group</param>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Group(float labelWidth, GroupOption options, params IDrawer[] contentDrawers)
{
return new GroupDrawerInternal(labelWidth, null, options, contentDrawers.Draw);
}
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
/// <param name="header">Adds a header on top <see cref="GUIContent"/></param>
/// <param name="options">Allow to add indentation on this group</param>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Group(GUIContent header, GroupOption options, params IDrawer[] contentDrawers)
{
return new GroupDrawerInternal(-1f, header, options, contentDrawers.Draw);
}
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
/// <param name="labelWidth">Width used for all labels in the group</param>
/// <param name="options">Allow to add indentation on this group</param>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Group(float labelWidth, GroupOption options, params ActionDrawer[] contentDrawers)
{
return new GroupDrawerInternal(labelWidth, null, options, contentDrawers);
}
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
/// <param name="header">Adds a header on top <see cref="GUIContent"/></param>
/// <param name="options">Allow to add indentation on this group</param>
/// <param name="contentDrawers">The content of the group</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Group(GUIContent header, GroupOption options, params ActionDrawer[] contentDrawers)
{
return new GroupDrawerInternal(-1, header, options, contentDrawers);
}
class GroupDrawerInternal : IDrawer
{
ActionDrawer[] actionDrawers { get; set; }
GUIContent header { get; }
float m_LabelWidth;
bool isIndented;
public GroupDrawerInternal(float labelWidth = -1f, GUIContent header = null, GroupOption options = GroupOption.None, params ActionDrawer[] actionDrawers)
{
this.actionDrawers = actionDrawers;
this.header = header;
m_LabelWidth = labelWidth;
isIndented = (options & GroupOption.Indent) != 0;
}
void IDrawer.Draw(TData data, Editor owner)
{
if (isIndented)
++EditorGUI.indentLevel;
var currentLabelWidth = EditorGUIUtility.labelWidth;
if (m_LabelWidth >= 0f)
{
EditorGUIUtility.labelWidth = m_LabelWidth;
}
if (header != null)
EditorGUILayout.LabelField(header, EditorStyles.boldLabel);
for (var i = 0; i < actionDrawers.Length; i++)
actionDrawers[i](data, owner);
if (m_LabelWidth >= 0f)
{
EditorGUIUtility.labelWidth = currentLabelWidth;
}
if (isIndented)
--EditorGUI.indentLevel;
}
bool IDrawer.Expand(int mask) => DefaultExpand(actionDrawers, mask);
}
class FoldoutGroupDrawerInternal<TEnum, TState> : IDrawer
where TEnum : struct, IConvertible
{
readonly ActionDrawer[] m_ActionDrawers;
readonly bool m_IsBoxed;
readonly bool m_IsSubFoldout;
readonly bool m_NoSpaceAtEnd;
readonly bool m_IsIndented;
readonly GUIContent m_Title;
readonly string m_HelpUrl;
ExpandedState<TEnum, TState> m_State;
readonly TEnum m_Mask;
readonly Enabler m_Enabler;
readonly SwitchEnabler m_SwitchEnabler;
public FoldoutGroupDrawerInternal(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state,
Enabler enabler, SwitchEnabler switchEnabler, FoldoutOption options = FoldoutOption.None, params ActionDrawer[] actionDrawers)
{
m_IsBoxed = (options & FoldoutOption.Boxed) != 0;
m_IsIndented = (options & FoldoutOption.Indent) != 0;
m_IsSubFoldout = (options & FoldoutOption.SubFoldout) != 0;
m_NoSpaceAtEnd = (options & FoldoutOption.NoSpaceAtEnd) != 0;
m_ActionDrawers = actionDrawers;
m_Title = title;
m_State = state;
m_Mask = mask;
m_HelpUrl = DocumentationUtils.GetHelpURL<TEnum>(mask);
m_Enabler = enabler;
m_SwitchEnabler = switchEnabler;
}
void IDrawer.Draw(TData data, Editor owner)
{
bool expended = m_State[m_Mask];
bool newExpended;
if (m_IsSubFoldout)
{
newExpended = CoreEditorUtils.DrawSubHeaderFoldout(m_Title, expended, m_IsBoxed);
}
else
{
CoreEditorUtils.DrawSplitter(m_IsBoxed);
newExpended = CoreEditorUtils.DrawHeaderFoldout(m_Title,
expended,
m_IsBoxed,
m_Enabler == null ? (Func<bool>)null : () => m_Enabler(data, owner),
m_SwitchEnabler == null ? (Action)null : () => m_SwitchEnabler(data, owner),
m_HelpUrl);
}
if (newExpended ^ expended)
m_State[m_Mask] = newExpended;
if (!newExpended)
return;
if (m_IsIndented)
++EditorGUI.indentLevel;
for (var i = 0; i < m_ActionDrawers.Length; i++)
m_ActionDrawers[i](data, owner);
if (m_IsIndented)
--EditorGUI.indentLevel;
if (!m_NoSpaceAtEnd)
EditorGUILayout.Space();
}
bool IDrawer.Expand(int mask)
{
bool expand = (mask == (int)(m_Mask as object));
if (!expand)
expand = DefaultExpand(m_ActionDrawers, mask);
if (expand)
m_State[m_Mask] = true;
return expand;
}
}
/// <summary> Create an IDrawer based on an other data container </summary>
/// <typeparam name="T2Data">Type of selected object containing in the given data containing data needed to draw inspector</typeparam>
/// <param name="dataSelect">The data new source for the inner drawers</param>
/// <param name="otherDrawers">Inner drawers drawed with given data sources</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Select<T2Data>(
DataSelect<T2Data> dataSelect,
params CoreEditorDrawer<T2Data>.IDrawer[] otherDrawers)
{
return new SelectDrawerInternal<T2Data>(dataSelect, otherDrawers.Draw);
}
/// <summary> Create an IDrawer based on an other data container </summary>
/// <typeparam name="T2Data">Type of selected object containing in the given data containing data needed to draw inspector</typeparam>
/// <param name="dataSelect">The data new source for the inner drawers</param>
/// <param name="otherDrawers">Inner drawers drawed with given data sources</param>
/// <returns>A IDrawer object</returns>
public static IDrawer Select<T2Data>(
DataSelect<T2Data> dataSelect,
params CoreEditorDrawer<T2Data>.ActionDrawer[] otherDrawers)
{
return new SelectDrawerInternal<T2Data>(dataSelect, otherDrawers);
}
class SelectDrawerInternal<T2Data> : IDrawer
{
DataSelect<T2Data> m_DataSelect;
CoreEditorDrawer<T2Data>.ActionDrawer[] m_SourceDrawers;
public SelectDrawerInternal(DataSelect<T2Data> dataSelect,
params CoreEditorDrawer<T2Data>.ActionDrawer[] otherDrawers)
{
m_SourceDrawers = otherDrawers;
m_DataSelect = dataSelect;
}
void IDrawer.Draw(TData data, Editor o)
{
var p2 = m_DataSelect(data, o);
for (var i = 0; i < m_SourceDrawers.Length; i++)
m_SourceDrawers[i](p2, o);
}
bool IDrawer.Expand(int mask) => false;
}
/// <summary>
/// Create an IDrawer foldout header using an ExpandedState.
/// The default option is Indent in this version.
/// </summary>
/// <typeparam name="TEnum">Type of the mask used</typeparam>
/// <typeparam name="TState">Type of the persistent state</typeparam>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="contentDrawers">The content of the foldout header</param>
/// <returns>A IDrawer object</returns>
public static IDrawer FoldoutGroup<TEnum, TState>(string title, TEnum mask, ExpandedState<TEnum, TState> state, params IDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(title, mask, state, contentDrawers.Draw);
}
/// <summary>
/// Create an IDrawer foldout header using an ExpandedState.
/// The default option is Indent in this version.
/// </summary>
/// <typeparam name="TEnum">Type of the mask used</typeparam>
/// <typeparam name="TState">Type of the persistent state</typeparam>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="contentDrawers">The content of the foldout header</param>
/// <returns>A IDrawer object</returns>
public static IDrawer FoldoutGroup<TEnum, TState>(string title, TEnum mask, ExpandedState<TEnum, TState> state, params ActionDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(EditorGUIUtility.TrTextContent(title), mask, state, contentDrawers);
}
/// <summary> Create an IDrawer foldout header using an ExpandedState </summary>
/// <typeparam name="TEnum">Type of the mask used</typeparam>
/// <typeparam name="TState">Type of the persistent state</typeparam>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="options">Drawing options</param>
/// <param name="contentDrawers">The content of the foldout header</param>
/// <returns>A IDrawer object</returns>
public static IDrawer FoldoutGroup<TEnum, TState>(string title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, params IDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(title, mask, state, options, contentDrawers.Draw);
}
/// <summary> Create an IDrawer foldout header using an ExpandedState </summary>
/// <typeparam name="TEnum">Type of the mask used</typeparam>
/// <typeparam name="TState">Type of the persistent state</typeparam>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="options">Drawing options</param>
/// <param name="contentDrawers">The content of the foldout header</param>
/// <returns>A IDrawer object</returns>
public static IDrawer FoldoutGroup<TEnum, TState>(string title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, params ActionDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(EditorGUIUtility.TrTextContent(title), mask, state, options, contentDrawers);
}
/// <summary>
/// Create an IDrawer foldout header using an ExpandedState.
/// The default option is Indent in this version.
/// </summary>
/// <typeparam name="TEnum">Type of the mask used</typeparam>
/// <typeparam name="TState">Type of the persistent state</typeparam>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="contentDrawers">The content of the foldout header</param>
/// <returns>A IDrawer object</returns>
public static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, params IDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(title, mask, state, contentDrawers.Draw);
}
/// <summary>
/// Create an IDrawer foldout header using an ExpandedState.
/// The default option is Indent in this version.
/// </summary>
/// <typeparam name="TEnum">Type of the mask used</typeparam>
/// <typeparam name="TState">Type of the persistent state</typeparam>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="contentDrawers">The content of the foldout header</param>
/// <returns>A IDrawer object</returns>
public static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, params ActionDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(title, mask, state, FoldoutOption.Indent, contentDrawers);
}
/// <summary> Create an IDrawer foldout header using an ExpandedState </summary>
/// <typeparam name="TEnum">Type of the mask used</typeparam>
/// <typeparam name="TState">Type of the persistent state</typeparam>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="options">Drawing options</param>
/// <param name="contentDrawers">The content of the foldout header</param>
/// <returns>A IDrawer object</returns>
public static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, params IDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(title, mask, state, options, contentDrawers.Draw);
}
/// <summary> Create an IDrawer foldout header using an ExpandedState </summary>
/// <typeparam name="TEnum">Type of the mask used</typeparam>
/// <typeparam name="TState">Type of the persistent state</typeparam>
/// <param name="title">Title wanted for this foldout header</param>
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="state">The ExpandedState describing the component</param>
/// <param name="options">Drawing options</param>
/// <param name="contentDrawers">The content of the foldout header</param>
/// <returns>A IDrawer object</returns>
public static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, params ActionDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return FoldoutGroup(title, mask, state, options, null, null, contentDrawers);
}
// This one is private as we do not want to have unhandled advanced switch. Change it if necessary.
static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, Enabler showAdditionalProperties, SwitchEnabler switchAdditionalProperties, params ActionDrawer[] contentDrawers)
where TEnum : struct, IConvertible
{
return new FoldoutGroupDrawerInternal<TEnum, TState>(title, mask, state, showAdditionalProperties, switchAdditionalProperties, options, contentDrawers);
}
/// <summary> Helper to draw a foldout with an advanced switch on it. </summary>
/// <typeparam name="TEnum">Type of the mask used</typeparam>
/// <typeparam name="TState">Type of the persistent state</typeparam>
/// <param name="foldoutTitle">Title wanted for this foldout header</param>
/// <param name="foldoutMask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="foldoutState">The ExpandedState describing the component</param>
/// <param name="isAdvanced"> Delegate allowing to check if advanced mode is active. </param>
/// <param name="switchAdvanced"> Delegate to know what to do when advance is switched. </param>
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
/// <param name="advancedContent"> The content of the foldout header only visible if advanced mode is active and if foldout is expended. </param>
/// <param name="options">Drawing options</param>
/// <returns>A IDrawer object</returns>
[Obsolete("Use AdditionalPropertiesFoldoutGroup instead.")]
public static IDrawer AdvancedFoldoutGroup<TEnum, TState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, IDrawer normalContent, IDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent)
where TEnum : struct, IConvertible
{
return AdvancedFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, isAdvanced, switchAdvanced, normalContent.Draw, advancedContent.Draw, options);
}
/// <summary> Helper to draw a foldout with an advanced switch on it. </summary>
/// <typeparam name="TEnum">Type of the mask used</typeparam>
/// <typeparam name="TState">Type of the persistent state</typeparam>
/// <param name="foldoutTitle">Title wanted for this foldout header</param>
/// <param name="foldoutMask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="foldoutState">The ExpandedState describing the component</param>
/// <param name="isAdvanced"> Delegate allowing to check if advanced mode is active. </param>
/// <param name="switchAdvanced"> Delegate to know what to do when advance is switched. </param>
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
/// <param name="advancedContent"> The content of the foldout header only visible if advanced mode is active and if foldout is expended. </param>
/// <param name="options">Drawing options</param>
/// <returns>A IDrawer object</returns>
[Obsolete("Use AdditionalPropertiesFoldoutGroup instead.")]
public static IDrawer AdvancedFoldoutGroup<TEnum, TState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, ActionDrawer normalContent, IDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent)
where TEnum : struct, IConvertible
{
return AdvancedFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, isAdvanced, switchAdvanced, normalContent, advancedContent.Draw, options);
}
/// <summary> Helper to draw a foldout with an advanced switch on it. </summary>
/// <typeparam name="TEnum">Type of the mask used</typeparam>
/// <typeparam name="TState">Type of the persistent state</typeparam>
/// <param name="foldoutTitle">Title wanted for this foldout header</param>
/// <param name="foldoutMask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="foldoutState">The ExpandedState describing the component</param>
/// <param name="isAdvanced"> Delegate allowing to check if advanced mode is active. </param>
/// <param name="switchAdvanced"> Delegate to know what to do when advance is switched. </param>
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
/// <param name="advancedContent"> The content of the foldout header only visible if advanced mode is active and if foldout is expended. </param>
/// <param name="options">Drawing options</param>
/// <returns>A IDrawer object</returns>
[Obsolete("Use AdditionalPropertiesFoldoutGroup instead.")]
public static IDrawer AdvancedFoldoutGroup<TEnum, TState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, IDrawer normalContent, ActionDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent)
where TEnum : struct, IConvertible
{
return AdvancedFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, isAdvanced, switchAdvanced, normalContent.Draw, advancedContent, options);
}
/// <summary> Helper to draw a foldout with an advanced switch on it. </summary>
/// <typeparam name="TEnum">Type of the mask used</typeparam>
/// <typeparam name="TState">Type of the persistent state</typeparam>
/// <param name="foldoutTitle">Title wanted for this foldout header</param>
/// <param name="foldoutMask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="foldoutState">The ExpandedState describing the component</param>
/// <param name="isAdvanced"> Delegate allowing to check if advanced mode is active. </param>
/// <param name="switchAdvanced"> Delegate to know what to do when advance is switched. </param>
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
/// <param name="advancedContent"> The content of the foldout header only visible if advanced mode is active and if foldout is expended. </param>
/// <param name="options">Drawing options</param>
/// <returns>A IDrawer object</returns>
[Obsolete("Use AdditionalPropertiesFoldoutGroup instead.")]
public static IDrawer AdvancedFoldoutGroup<TEnum, TState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, ActionDrawer normalContent, ActionDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent)
where TEnum : struct, IConvertible
{
return FoldoutGroup(foldoutTitle, foldoutMask, foldoutState, options, isAdvanced, switchAdvanced, normalContent,
Conditional((serialized, owner) => isAdvanced(serialized, owner) && foldoutState[foldoutMask], advancedContent).Draw);
}
/// <summary>
/// Helper to draw a foldout with additional properties.
/// </summary>
/// <typeparam name="TEnum">Type of the foldout mask used.</typeparam>
/// <typeparam name="TState">Type of the persistent foldout state.</typeparam>
/// <typeparam name="TAPEnum">Type of the additional properties mask used.</typeparam>
/// <typeparam name="TAPState">Type of the persistent additional properties state.</typeparam>
/// <param name="foldoutTitle">Title wanted for this foldout header</param>
/// <param name="foldoutMask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="foldoutState">The ExpandedState describing the component</param>
/// <param name="additionalPropertiesMask">Bit mask (enum) used to define the boolean saving the state in AdditionalPropertiesState</param>
/// <param name="additionalPropertiesState">The AdditionalPropertiesState describing the component</param>
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
/// <param name="additionalContent">The content of the foldout header only visible if additional properties are shown and if foldout is expanded.</param>
/// <param name="options">Drawing options</param>
/// <returns>A IDrawer object</returns>
public static IDrawer AdditionalPropertiesFoldoutGroup<TEnum, TState, TAPEnum, TAPState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState,
TAPEnum additionalPropertiesMask, AdditionalPropertiesState<TAPEnum, TAPState> additionalPropertiesState, IDrawer normalContent, IDrawer additionalContent, FoldoutOption options = FoldoutOption.Indent)
where TEnum : struct, IConvertible
where TAPEnum : struct, IConvertible
{
return AdditionalPropertiesFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, additionalPropertiesMask, additionalPropertiesState, normalContent.Draw, additionalContent.Draw, options);
}
/// <summary>
/// Helper to draw a foldout with additional properties.
/// </summary>
/// <typeparam name="TEnum">Type of the foldout mask used.</typeparam>
/// <typeparam name="TState">Type of the persistent foldout state.</typeparam>
/// <typeparam name="TAPEnum">Type of the additional properties mask used.</typeparam>
/// <typeparam name="TAPState">Type of the persistent additional properties state.</typeparam>
/// <param name="foldoutTitle">Title wanted for this foldout header</param>
/// <param name="foldoutMask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="foldoutState">The ExpandedState describing the component</param>
/// <param name="additionalPropertiesMask">Bit mask (enum) used to define the boolean saving the state in AdditionalPropertiesState</param>
/// <param name="additionalPropertiesState">The AdditionalPropertiesState describing the component</param>
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
/// <param name="additionalContent">The content of the foldout header only visible if additional properties are shown and if foldout is expanded.</param>
/// <param name="options">Drawing options</param>
/// <returns>A IDrawer object</returns>
public static IDrawer AdditionalPropertiesFoldoutGroup<TEnum, TState, TAPEnum, TAPState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState,
TAPEnum additionalPropertiesMask, AdditionalPropertiesState<TAPEnum, TAPState> additionalPropertiesState, ActionDrawer normalContent, IDrawer additionalContent, FoldoutOption options = FoldoutOption.Indent)
where TEnum : struct, IConvertible
where TAPEnum : struct, IConvertible
{
return AdditionalPropertiesFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, additionalPropertiesMask, additionalPropertiesState, normalContent, additionalContent.Draw, options);
}
/// <summary>
/// Helper to draw a foldout with additional properties.
/// </summary>
/// <typeparam name="TEnum">Type of the foldout mask used.</typeparam>
/// <typeparam name="TState">Type of the persistent foldout state.</typeparam>
/// <typeparam name="TAPEnum">Type of the additional properties mask used.</typeparam>
/// <typeparam name="TAPState">Type of the persistent additional properties state.</typeparam>
/// <param name="foldoutTitle">Title wanted for this foldout header</param>
/// <param name="foldoutMask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="foldoutState">The ExpandedState describing the component</param>
/// <param name="additionalPropertiesMask">Bit mask (enum) used to define the boolean saving the state in AdditionalPropertiesState</param>
/// <param name="additionalPropertiesState">The AdditionalPropertiesState describing the component</param>
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
/// <param name="additionalContent">The content of the foldout header only visible if additional properties are shown and if foldout is expanded.</param>
/// <param name="options">Drawing options</param>
/// <returns>A IDrawer object</returns>
public static IDrawer AdditionalPropertiesFoldoutGroup<TEnum, TState, TAPEnum, TAPState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState,
TAPEnum additionalPropertiesMask, AdditionalPropertiesState<TAPEnum, TAPState> additionalPropertiesState, IDrawer normalContent, ActionDrawer additionalContent, FoldoutOption options = FoldoutOption.Indent)
where TEnum : struct, IConvertible
where TAPEnum : struct, IConvertible
{
return AdditionalPropertiesFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, additionalPropertiesMask, additionalPropertiesState, normalContent.Draw, additionalContent, options);
}
/// <summary>
/// Helper to draw a foldout with additional properties.
/// </summary>
/// <typeparam name="TEnum">Type of the foldout mask used.</typeparam>
/// <typeparam name="TState">Type of the persistent foldout state.</typeparam>
/// <typeparam name="TAPEnum">Type of the additional properties mask used.</typeparam>
/// <typeparam name="TAPState">Type of the persistent additional properties state.</typeparam>
/// <param name="foldoutTitle">Title wanted for this foldout header</param>
/// <param name="foldoutMask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
/// <param name="foldoutState">The ExpandedState describing the component</param>
/// <param name="additionalPropertiesMask">Bit mask (enum) used to define the boolean saving the state in AdditionalPropertiesState</param>
/// <param name="additionalPropertiesState">The AdditionalPropertiesState describing the component</param>
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
/// <param name="additionalContent">The content of the foldout header only visible if additional properties are shown and if foldout is expanded.</param>
/// <param name="options">Drawing options</param>
/// <returns>A IDrawer object</returns>
public static IDrawer AdditionalPropertiesFoldoutGroup<TEnum, TState, TAPEnum, TAPState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState,
TAPEnum additionalPropertiesMask, AdditionalPropertiesState<TAPEnum, TAPState> additionalPropertiesState, ActionDrawer normalContent, ActionDrawer additionalContent, FoldoutOption options = FoldoutOption.Indent)
where TEnum : struct, IConvertible
where TAPEnum : struct, IConvertible
{
bool Enabler(TData data, Editor owner)
{
return additionalPropertiesState[additionalPropertiesMask];
}
void SwitchEnabler(TData data, Editor owner)
{
additionalPropertiesState[additionalPropertiesMask] = !additionalPropertiesState[additionalPropertiesMask];
}
return FoldoutGroup(foldoutTitle, foldoutMask, foldoutState, options, Enabler, SwitchEnabler,
normalContent,
ConditionalWithAdditionalProperties((serialized, owner) => additionalPropertiesState[additionalPropertiesMask] && foldoutState[foldoutMask], additionalPropertiesState.GetAnimation(additionalPropertiesMask), additionalContent).Draw
);
}
}
/// <summary>CoreEditorDrawer extensions</summary>
public static class CoreEditorDrawersExtensions
{
/// <summary> Concatenate a collection of IDrawer as a unique IDrawer </summary>
/// <typeparam name="TData">Type of class containing data needed to draw inspector</typeparam>
/// <param name="drawers">A collection of IDrawers</param>
/// <param name="data">The data source for the inner drawers</param>
/// <param name="owner">The editor drawing</param>
public static void Draw<TData>(this IEnumerable<CoreEditorDrawer<TData>.IDrawer> drawers, TData data, Editor owner)
{
EditorGUILayout.BeginVertical();
foreach (var drawer in drawers)
drawer.Draw(data, owner);
EditorGUILayout.EndVertical();
}
internal static bool Expand<TData>(this IEnumerable<CoreEditorDrawer<TData>.IDrawer> drawers, int mask)
{
foreach (var drawer in drawers)
{
if (drawer.Expand(mask))
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7e9fe0ff34cd022408422f6b92ad7df6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,234 @@
using System;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using Object = UnityEngine.Object;
namespace UnityEditor.Rendering
{
/// <summary>Class containing constants</summary>
public static class CoreEditorConstants
{
/// <summary>Speed of additional properties highlight.</summary>
public static readonly float additionalPropertiesHightLightSpeed = 0.3f;
/// <summary>Standard UI spacing</summary>
public static float standardHorizontalSpacing => 5f;
}
/// <summary>Class containing style definition</summary>
public static class CoreEditorStyles
{
#region Styles
static System.Lazy<GUIStyle> m_SmallTickbox = new(() => new GUIStyle("ShurikenToggle"));
/// <summary>Style for a small checkbox</summary>
public static GUIStyle smallTickbox => m_SmallTickbox.Value;
static System.Lazy<GUIStyle> m_SmallMixedTickbox = new(() => new GUIStyle("ShurikenToggleMixed"));
/// <summary>Style for a small checkbox in mixed state</summary>
public static GUIStyle smallMixedTickbox => m_SmallMixedTickbox.Value;
static GUIStyle m_MiniLabelButton;
/// <summary>Style for a minilabel button</summary>
public static GUIStyle miniLabelButton
{
get
{
if (m_MiniLabelButton == null)
{
m_MiniLabelButton = new GUIStyle(EditorStyles.miniLabel)
{
normal = new GUIStyleState
{
background = m_TransparentTexture,
scaledBackgrounds = null,
textColor = Color.grey
}
};
var activeState = new GUIStyleState
{
background = m_TransparentTexture,
scaledBackgrounds = null,
textColor = Color.white
};
m_MiniLabelButton.active = activeState;
m_MiniLabelButton.onNormal = activeState;
m_MiniLabelButton.onActive = activeState;
return m_MiniLabelButton;
}
return m_MiniLabelButton;
}
}
static System.Lazy<GUIStyle> m_ContextMenuStyle = new(() => new GUIStyle("IconButton"));
/// <summary>Context Menu button style</summary>
public static GUIStyle contextMenuStyle => m_ContextMenuStyle.Value;
static System.Lazy<GUIStyle> m_AdditionalPropertiesHighlightStyle = new(() => new GUIStyle { normal = { background = Texture2D.whiteTexture } });
/// <summary>Style of a additional properties highlighted background.</summary>
public static GUIStyle additionalPropertiesHighlightStyle => m_AdditionalPropertiesHighlightStyle.Value;
/// <summary>Help icon style</summary>
public static GUIStyle iconHelpStyle => GUI.skin.FindStyle("IconButton") ?? EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector).FindStyle("IconButton");
static System.Lazy<GUIStyle> m_SectionHeaderStyle = new(() => new GUIStyle(EditorStyles.largeLabel) { richText = true, fontSize = 18, fixedHeight = 42 });
/// <summary>Style of Section Headers.</summary>
public static GUIStyle sectionHeaderStyle => m_SectionHeaderStyle.Value;
static System.Lazy<GUIStyle> m_SubSectionHeaderStyle = new(() => new GUIStyle(EditorStyles.boldLabel));
/// <summary>Style of Sub-Section Headers.</summary>
public static GUIStyle subSectionHeaderStyle => m_SubSectionHeaderStyle.Value;
static System.Lazy<GUIStyle> m_HelpBox = new(() =>
{
var style = new GUIStyle() { imagePosition = ImagePosition.ImageLeft, fontSize = 10, wordWrap = true };
style.normal.textColor = EditorStyles.helpBox.normal.textColor;
return style;
});
internal static GUIStyle helpBox => m_HelpBox.Value;
#endregion
#region Textures 2D
static Texture2D m_TransparentTexture;
/// <summary><see cref="Texture2D"/> 1x1 pixel with red color</summary>
public static readonly Texture2D redTexture;
/// <summary><see cref="Texture2D"/> 1x1 pixel with green color</summary>
public static readonly Texture2D greenTexture;
/// <summary><see cref="Texture2D"/> 1x1 pixel with blue color</summary>
public static readonly Texture2D blueTexture;
/// <summary> PaneOption icon for dark skin</summary>
static readonly Texture2D paneOptionsIconDark;
/// <summary> PaneOption icon for light skin</summary>
static readonly Texture2D paneOptionsIconLight;
/// <summary> PaneOption icon </summary>
public static Texture2D paneOptionsIcon => EditorGUIUtility.isProSkin ? paneOptionsIconDark : paneOptionsIconLight;
/// <summary> Warning icon </summary>
public static readonly Texture2D iconWarn;
/// <summary> Help icon </summary>
public static readonly Texture2D iconHelp;
/// <summary> Fail icon </summary>
public static readonly Texture2D iconFail;
/// <summary> Success icon </summary>
public static readonly Texture2D iconSuccess;
/// <summary> Complete icon </summary>
public static readonly Texture2D iconComplete;
/// <summary> Pending icon </summary>
public static readonly Texture2D iconPending;
/// <summary>RenderPipeline Global Settings icon</summary>
public static readonly Texture2D globalSettingsIcon;
/// <summary>
/// Gets the icon that describes the <see cref="MessageType"/>
/// </summary>
/// <param name="messageType">The <see cref="MessageType"/> to obtain the icon from</param>
/// <returns>a <see cref="Texture2D"/> with the icon for the <see cref="MessageType"/></returns>
internal static Texture2D GetMessageTypeIcon(MessageType messageType)
{
switch (messageType)
{
case MessageType.None:
return null;
case MessageType.Info:
return iconHelp;
case MessageType.Warning:
return iconWarn;
case MessageType.Error:
return iconFail;
default:
throw new ArgumentOutOfRangeException(nameof(messageType), messageType, null);
}
}
#endregion
#region Colors
static readonly Color m_LightThemeBackgroundColor;
static readonly Color m_LightThemeBackgroundHighlightColor;
static readonly Color m_DarkThemeBackgroundColor;
static readonly Color m_DarkThemeBackgroundHighlightColor;
/// <summary>Regular background color.</summary>
public static Color backgroundColor => EditorGUIUtility.isProSkin ? m_DarkThemeBackgroundColor : m_LightThemeBackgroundColor;
/// <summary>Hightlited background color.</summary>
public static Color backgroundHighlightColor => EditorGUIUtility.isProSkin ? m_DarkThemeBackgroundHighlightColor : m_LightThemeBackgroundHighlightColor;
#endregion
#region GUIContents
/// <summary>Context Menu button icon</summary>
public static readonly GUIContent contextMenuIcon;
/// <summary>Reset Content</summary>
public static readonly GUIContent resetButtonLabel = EditorGUIUtility.TrTextContent("Reset");
/// <summary>Reset All content</summary>
public static readonly GUIContent resetAllButtonLabel = EditorGUIUtility.TrTextContent("Reset All");
/// <summary>
/// Empty space content in case that you want to keep the indentation but have nothing to write
/// </summary>
public static readonly GUIContent empty = EditorGUIUtility.TrTextContent(" ");
#endregion
static CoreEditorStyles()
{
m_TransparentTexture = new Texture2D(1, 1, GraphicsFormat.R8G8B8A8_SRGB, TextureCreationFlags.None)
{
name = "transparent"
};
m_TransparentTexture.SetPixel(0, 0, Color.clear);
m_TransparentTexture.Apply();
paneOptionsIconDark = CoreEditorUtils.LoadIcon("Builtin Skins/DarkSkin/Images", "pane options", ".png");
paneOptionsIconDark.name = "pane options dark skin";
paneOptionsIconLight = CoreEditorUtils.LoadIcon("Builtin Skins/LightSkin/Images", "pane options", ".png");
paneOptionsIconLight.name = "pane options light skin";
m_LightThemeBackgroundColor = new Color(0.7843138f, 0.7843138f, 0.7843138f, 1.0f);
m_LightThemeBackgroundHighlightColor = new Color32(174, 174, 174, 255);
m_DarkThemeBackgroundColor = new Color(0.2196079f, 0.2196079f, 0.2196079f, 1.0f);
m_DarkThemeBackgroundHighlightColor = new Color32(77, 77, 77, 255);
const string contextTooltip = ""; // To be defined (see with UX)
contextMenuIcon = new GUIContent(paneOptionsIcon, contextTooltip);
redTexture = CoreEditorUtils.CreateColoredTexture2D(Color.red, "Red 1x1");
greenTexture = CoreEditorUtils.CreateColoredTexture2D(Color.green, "Green 1x1");
blueTexture = CoreEditorUtils.CreateColoredTexture2D(Color.blue, "Blue 1x1");
iconHelp = CoreEditorUtils.FindTexture("_Help");
iconWarn = CoreEditorUtils.LoadIcon("icons", "console.warnicon", ".png");
iconFail = CoreEditorUtils.LoadIcon("icons", "console.erroricon", ".png");
iconSuccess = EditorGUIUtility.FindTexture("TestPassed");
iconComplete = CoreEditorUtils.LoadIcon("icons", "GreenCheckmark", ".png");
iconPending = EditorGUIUtility.FindTexture("Toolbar Minus");
globalSettingsIcon = EditorGUIUtility.FindTexture("ScriptableObject Icon");
// Make sure that textures are unloaded on domain reloads.
void OnBeforeAssemblyReload()
{
Object.DestroyImmediate(redTexture);
Object.DestroyImmediate(greenTexture);
Object.DestroyImmediate(blueTexture);
Object.DestroyImmediate(m_TransparentTexture);
AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeAssemblyReload;
}
AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 106fd77ef6b30234597e56c849578bad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 744ceabda269e6c469964dda8c490d0d
timeCreated: 1507109827
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace UnityEditor.Rendering
{
/// <summary>
/// Core Render Pipeline preferences.
/// </summary>
public static class CoreRenderPipelinePreferences
{
/// <summary>
/// Path to the Render Pipeline Preferences
/// </summary>
public static readonly string corePreferencePath = "Preferences/Core Render Pipeline";
private static readonly List<ICoreRenderPipelinePreferencesProvider> s_Providers = new();
[InitializeOnLoadMethod]
static void InitPreferenceProviders()
{
foreach (var provider in TypeCache.GetTypesDerivedFrom<ICoreRenderPipelinePreferencesProvider>())
{
if (provider.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null) == null)
continue;
s_Providers.Add(Activator.CreateInstance(provider) as ICoreRenderPipelinePreferencesProvider);
}
}
[SettingsProvider]
static SettingsProvider PreferenceGUI()
{
var provider = new SettingsProvider(corePreferencePath, SettingsScope.User)
{
guiHandler = searchContext =>
{
using (new SettingsProviderGUIScope())
{
foreach (var providers in s_Providers)
{
EditorGUILayout.LabelField(providers.header, EditorStyles.boldLabel);
providers.PreferenceGUI();
}
}
}
};
FillKeywords(provider);
return provider;
}
private static void FillKeywords(SettingsProvider provider)
{
List<string> keywords = new List<string>();
foreach (var providers in s_Providers)
keywords.AddRange(providers.keywords);
provider.keywords = keywords;
}
/// <summary>
/// Open the Core Rendering Pipeline preference window.
/// </summary>
public static void Open()
{
SettingsService.OpenUserPreferences(corePreferencePath);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1c13e3f9217a6854ebb4d5d716b60103
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f5415ef8821c020469ee7e810da8b3ff
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,14 @@
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
static class CoreMenuItems
{
[MenuItem("Assets/Create/Shader/Custom Render Texture", priority = CoreUtils.Priorities.assetsCreateShaderMenuPriority + 2)]
static void MenuCreateCustomRenderTextureShader()
{
string templatePath = $"{CoreUtils.GetCorePath()}/Editor/CustomRenderTexture/CustomRenderTextureShader.template";
ProjectWindowUtil.CreateScriptAssetFromTemplateFile(templatePath, "New Custom Render Texture.shader");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7e2d77127041cd24faf2847015043c6f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
Shader "CustomRenderTexture/#NAME#"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_MainTex("InputTex", 2D) = "white" {}
}
SubShader
{
Blend One Zero
Pass
{
Name "#NAME#"
CGPROGRAM
#include "UnityCustomRenderTexture.cginc"
#pragma vertex CustomRenderTextureVertexShader
#pragma fragment frag
#pragma target 3.0
float4 _Color;
sampler2D _MainTex;
float4 frag(v2f_customrendertexture IN) : SV_Target
{
float2 uv = IN.localTexcoord.xy;
float4 color = tex2D(_MainTex, uv) * _Color;
// TODO: Replace this by actual code!
uint2 p = uv.xy * 256;
return countbits(~(p.x & p.y) + 1) % 2 * float4(uv, 1, 1) * color;
}
ENDCG
}
}
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6e1b69fb677bd95439f31bff8ca1c431
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dca094a63ee8e4df7b043d29c2c100e4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,251 @@
using System;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
/// <summary>
/// Serialized state of a Debug Item.
/// </summary>
[Serializable]
public abstract class DebugState : ScriptableObject
{
/// <summary>
/// Path of the Debug Item.
/// </summary>
[SerializeField]
protected string m_QueryPath;
// We need this to keep track of the state modified in the current frame.
// This helps reduces the cost of re-applying states to original widgets and is also needed
// when two states point to the same value (e.g. when using split enums like HDRP does for
// the `fullscreenDebugMode`.
internal static DebugState m_CurrentDirtyState;
/// <summary>
/// Path of the Debug Item.
/// </summary>
public string queryPath
{
get { return m_QueryPath; }
internal set { m_QueryPath = value; }
}
/// <summary>
/// Returns the value of the Debug Item.
/// </summary>
/// <returns>Value of the Debug Item.</returns>
public abstract object GetValue();
/// <summary>
/// Set the value of the Debug Item.
/// </summary>
/// <param name="value">Input value.</param>
/// <param name="field">Debug Item field.</param>
public abstract void SetValue(object value, DebugUI.IValueField field);
/// <summary>
/// OnEnable implementation.
/// </summary>
public virtual void OnEnable()
{
hideFlags = HideFlags.HideAndDontSave;
}
}
/// <summary>
/// Generic serialized state of a Debug Item.
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
public class DebugState<T> : DebugState
{
/// <summary>
/// Value of the Debug Item.
/// </summary>
[SerializeField]
protected T m_Value;
/// <summary>
/// Value of the Debug Item
/// </summary>
public virtual T value
{
get { return m_Value; }
set { m_Value = value; }
}
/// <summary>
/// Returns the value of the Debug Item.
/// </summary>
/// <returns>Value of the Debug Item.</returns>
public override object GetValue()
{
return value;
}
/// <summary>
/// Set the value of the Debug Item.
/// </summary>
/// <param name="value">Input value.</param>
/// <param name="field">Debug Item field.</param>
public override void SetValue(object value, DebugUI.IValueField field)
{
this.value = (T)field.ValidateValue(value);
}
/// <summary>
/// Returns the hash code of the Debug Item.
/// </summary>
/// <returns>Hash code of the Debug Item</returns>
public override int GetHashCode()
{
unchecked
{
int hash = 13;
hash = hash * 23 + m_QueryPath.GetHashCode();
if (value != null)
hash = hash * 23 + value.GetHashCode();
return hash;
}
}
}
/// <summary>
/// Attribute specifying which types should be save as this Debug State.
/// </summary>
public sealed class DebugStateAttribute : Attribute
{
internal readonly Type[] types;
/// <summary>
/// Debug State Attribute constructor
/// </summary>
/// <param name="types">List of types of the Debug State.</param>
public DebugStateAttribute(params Type[] types)
{
this.types = types;
}
}
// Builtins
/// <summary>
/// Boolean Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.BoolField), typeof(DebugUI.Foldout), typeof(DebugUI.HistoryBoolField))]
public sealed class DebugStateBool : DebugState<bool> { }
/// <summary>
/// Enums Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.EnumField), typeof(DebugUI.HistoryEnumField))]
public sealed class DebugStateEnum : DebugState<int>
{
DebugUI.EnumField m_EnumField;
/// <summary>
/// Set the value of the Debug Item.
/// </summary>
/// <param name="value">Input value.</param>
/// <param name="field">Debug Item field.</param>
public override void SetValue(object value, DebugUI.IValueField field)
{
m_EnumField = field as DebugUI.EnumField;
base.SetValue(value, field);
}
/// <summary>
/// On Enable method from <see cref="ScriptableObject"/>
/// </summary>
public override void OnEnable()
{
base.OnEnable();
if (m_EnumField == null)
return;
m_EnumField.SetValue(value);
base.SetValue(value, m_EnumField);
}
}
/// <summary>
/// Integer Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.IntField))]
public sealed class DebugStateInt : DebugState<int> { }
/// <summary>
/// Object Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.ObjectPopupField))]
public sealed class DebugStateObject : DebugState<UnityEngine.Object> { }
/// <summary>
/// Flags Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.BitField))]
public sealed class DebugStateFlags : DebugState<Enum>
{
[SerializeField]
private SerializableEnum m_SerializableEnum;
/// <summary>
/// Value of the Debug Item
/// </summary>
public override Enum value
{
get => m_SerializableEnum?.value ?? default;
set => m_SerializableEnum.value = value;
}
/// <summary>
/// Set the value of the Debug Item.
/// </summary>
/// <param name="value">Input value.</param>
/// <param name="field">Debug Item field.</param>
public override void SetValue(object value, DebugUI.IValueField field)
{
if (m_SerializableEnum == null)
m_SerializableEnum = new SerializableEnum((field as DebugUI.BitField).enumType);
base.SetValue(value, field);
}
}
/// <summary>
/// Unsigned Integer Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.UIntField))]
public sealed class DebugStateUInt : DebugState<uint> { }
/// <summary>
/// Float Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.FloatField))]
public sealed class DebugStateFloat : DebugState<float> { }
/// <summary>
/// Color Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.ColorField))]
public sealed class DebugStateColor : DebugState<Color> { }
/// <summary>
/// Vector2 Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.Vector2Field))]
public sealed class DebugStateVector2 : DebugState<Vector2> { }
/// <summary>
/// Vector3 Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.Vector3Field))]
public sealed class DebugStateVector3 : DebugState<Vector3> { }
/// <summary>
/// Vector4 Debug State.
/// </summary>
[Serializable, DebugState(typeof(DebugUI.Vector4Field))]
public sealed class DebugStateVector4 : DebugState<Vector4> { }
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7270f3dffc138834da5642e5943f5072
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,897 @@
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
/// <summary>
/// Builtin Drawer for Value Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Value))]
public sealed class DebugUIDrawerValue : DebugUIWidgetDrawer<DebugUI.Value>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The widget</param>
protected override void DoGUI(Rect rect, GUIContent label, DebugUI.Value field)
{
EditorGUI.LabelField(rect, label, EditorGUIUtility.TrTextContent(field.FormatString(field.GetValue())));
}
}
/// <summary>
/// Builtin Drawer for ValueTuple Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.ValueTuple))]
public sealed class DebugUIDrawerValueTuple : DebugUIWidgetDrawer<DebugUI.ValueTuple>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The widget</param>
protected override void DoGUI(Rect rect, GUIContent label, DebugUI.ValueTuple field)
{
var labelRect = PrepareControlRect();
EditorGUI.PrefixLabel(labelRect, label);
// Following layout should match DebugUIDrawerFoldout to make column labels align
Rect drawRect = GUILayoutUtility.GetLastRect();
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0; //be at left of rects
for (int i = 0; i < field.numElements; i++)
{
var columnRect = drawRect;
columnRect.x += EditorGUIUtility.labelWidth + i * DebugWindow.Styles.foldoutColumnWidth;
columnRect.width = DebugWindow.Styles.foldoutColumnWidth;
var value = field.values[i].GetValue();
EditorGUI.LabelField(columnRect, field.values[i].FormatString(value));
}
EditorGUI.indentLevel = indent;
}
}
/// <summary>
/// Builtin Drawer for ProgressBarValue Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.ProgressBarValue))]
public sealed class DebugUIDrawerProgressBarValue : DebugUIWidgetDrawer<DebugUI.ProgressBarValue>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The widget</param>
protected override void DoGUI(Rect rect, GUIContent label, DebugUI.ProgressBarValue field)
{
var labelRect = PrepareControlRect();
var progressBarRect = EditorGUI.PrefixLabel(labelRect, label);
float value = (float)field.GetValue();
EditorGUI.ProgressBar(progressBarRect, value, field.FormatString(value));
}
}
/// <summary>
/// Builtin Drawer for Button Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Button))]
public sealed class DebugUIDrawerButton : DebugUIWidgetDrawer<DebugUI.Button>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The widget</param>
protected override void DoGUI(Rect rect, GUIContent label, DebugUI.Button field)
{
rect = EditorGUI.IndentedRect(rect);
if (GUI.Button(rect, label, EditorStyles.miniButton))
{
if (field.action != null)
field.action();
}
}
}
/// <summary>
/// Builtin Drawer for Boolean Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.BoolField))]
public sealed class DebugUIDrawerBoolField : DebugUIFieldDrawer<bool, DebugUI.BoolField, DebugStateBool>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The value</returns>
protected override bool DoGUI(Rect rect, GUIContent label, DebugUI.BoolField field, DebugStateBool state)
{
return EditorGUI.Toggle(rect, label, field.GetValue());
}
}
/// <summary>
/// Builtin Drawer for History Boolean Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.HistoryBoolField))]
public sealed class DebugUIDrawerHistoryBoolField : DebugUIFieldDrawer<bool, DebugUI.HistoryBoolField, DebugStateBool>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override bool DoGUI(Rect rect, GUIContent label, DebugUI.HistoryBoolField field, DebugStateBool state)
{
var labelRect = rect;
labelRect.width = EditorGUIUtility.labelWidth;
const int oneValueWidth = 70;
var valueRects = new Rect[field.historyDepth + 1];
for (int i = 0; i < field.historyDepth + 1; i++)
{
valueRects[i] = rect;
valueRects[i].x += EditorGUIUtility.labelWidth + i * oneValueWidth;
valueRects[i].width = oneValueWidth;
}
EditorGUI.LabelField(labelRect, label);
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0; //be at left of rects
bool value = EditorGUI.Toggle(valueRects[0], field.GetValue());
using (new EditorGUI.DisabledScope(true))
{
for (int i = 0; i < field.historyDepth; i++)
EditorGUI.Toggle(valueRects[i + 1], field.GetHistoryValue(i));
}
EditorGUI.indentLevel = indent;
return value;
}
}
/// <summary>
/// Builtin Drawer for Integer Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.IntField))]
public sealed class DebugUIDrawerIntField : DebugUIFieldDrawer<int, DebugUI.IntField, DebugStateInt>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override int DoGUI(Rect rect, GUIContent label, DebugUI.IntField field, DebugStateInt state)
{
return field.min != null && field.max != null
? EditorGUI.IntSlider(rect, label, field.GetValue(), field.min(), field.max())
: EditorGUI.IntField(rect, label, field.GetValue());
}
}
/// <summary>
/// Builtin Drawer for Unsigned Integer Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.UIntField))]
public sealed class DebugUIDrawerUIntField : DebugUIFieldDrawer<uint, DebugUI.UIntField, DebugStateUInt>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override uint DoGUI(Rect rect, GUIContent label, DebugUI.UIntField field, DebugStateUInt state)
{
// No UIntField so we need to max to 0 ourselves or the value will wrap around
int tmp = field.min != null && field.max != null
? EditorGUI.IntSlider(rect, label, Mathf.Max(0, (int)field.GetValue()), Mathf.Max(0, (int)field.min()), Mathf.Max(0, (int)field.max()))
: EditorGUI.IntField(rect, label, Mathf.Max(0, (int)field.GetValue()));
return (uint)Mathf.Max(0, tmp);
}
}
/// <summary>
/// Builtin Drawer for Float Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.FloatField))]
public sealed class DebugUIDrawerFloatField : DebugUIFieldDrawer<float, DebugUI.FloatField, DebugStateFloat>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override float DoGUI(Rect rect, GUIContent label, DebugUI.FloatField field, DebugStateFloat state)
{
return field.min != null && field.max != null
? EditorGUI.Slider(rect, label, field.GetValue(), field.min(), field.max())
: EditorGUI.FloatField(rect, label, field.GetValue());
}
}
/// <summary>
/// Builtin Drawer for Enum Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.EnumField))]
public sealed class DebugUIDrawerEnumField : DebugUIFieldDrawer<int, DebugUI.EnumField, DebugStateEnum>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override int DoGUI(Rect rect, GUIContent label, DebugUI.EnumField field, DebugStateEnum state)
{
int index = Mathf.Max(0, field.currentIndex); // Fallback just in case, we may be handling sub/sectioned enums here
int value = field.GetValue();
if (field.enumNames == null || field.enumValues == null)
{
EditorGUI.LabelField(rect, label, "Can't draw an empty enumeration.");
}
else if (field.enumNames.Length != field.enumValues.Length)
{
EditorGUI.LabelField(rect, label, "Invalid data");
}
else
{
index = EditorGUI.IntPopup(rect, label, index, field.enumNames, field.indexes);
value = field.enumValues[index];
}
return value;
}
}
/// <summary>
/// Builtin Drawer for Object Popup Fields Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.ObjectPopupField))]
public sealed class DebugUIDrawerObjectPopupField : DebugUIFieldDrawer<UnityEngine.Object, DebugUI.ObjectPopupField, DebugStateObject>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override UnityEngine.Object DoGUI(Rect rect, GUIContent label, DebugUI.ObjectPopupField field, DebugStateObject state)
{
var selectedValue = field.GetValue();
rect = EditorGUI.PrefixLabel(rect, label);
var elements = field.getObjects();
if (elements?.Any() ?? false)
{
var elementsArrayNames = elements.Select(e => e.name).ToArray();
var elementsArrayIndices = Enumerable.Range(0, elementsArrayNames.Length).ToArray();
var selectedIndex = selectedValue != null ? Array.IndexOf(elementsArrayNames, selectedValue.name) : 0;
var newSelectedIndex = EditorGUI.IntPopup(rect, selectedIndex, elementsArrayNames, elementsArrayIndices);
if (selectedIndex != newSelectedIndex)
selectedValue = elements.ElementAt(newSelectedIndex);
}
else
{
EditorGUI.LabelField(rect, "Can't draw an empty enumeration.");
}
return selectedValue;
}
}
/// <summary>
/// Builtin Drawer for History Enum Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.HistoryEnumField))]
public sealed class DebugUIDrawerHistoryEnumField : DebugUIFieldDrawer<int, DebugUI.HistoryEnumField, DebugStateEnum>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override int DoGUI(Rect rect, GUIContent label, DebugUI.HistoryEnumField field, DebugStateEnum state)
{
int index = -1;
int value = field.GetValue();
if (field.enumNames == null || field.enumValues == null)
{
EditorGUILayout.LabelField("Can't draw an empty enumeration.");
}
else
{
index = field.currentIndex;
// Fallback just in case, we may be handling sub/sectionned enums here
if (index < 0)
index = 0;
var labelRect = rect;
labelRect.width = EditorGUIUtility.labelWidth;
const int oneValueWidth = 70;
var valueRects = new Rect[field.historyDepth + 1];
for (int i = 0; i < field.historyDepth + 1; i++)
{
valueRects[i] = rect;
valueRects[i].x += EditorGUIUtility.labelWidth + i * oneValueWidth;
valueRects[i].width = oneValueWidth;
}
EditorGUI.LabelField(labelRect, label);
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0; //be at left of rects
index = EditorGUI.IntPopup(valueRects[0], index, field.enumNames, field.indexes);
value = field.enumValues[index];
using (new EditorGUI.DisabledScope(true))
{
for (int i = 0; i < field.historyDepth; i++)
EditorGUI.IntPopup(valueRects[i + 1], field.GetHistoryValue(i), field.enumNames, field.indexes);
}
EditorGUI.indentLevel = indent;
value = field.enumValues[index];
}
return value;
}
}
/// <summary>
/// Builtin Drawer for Bitfield Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.BitField))]
public sealed class DebugUIDrawerBitField : DebugUIFieldDrawer<Enum, DebugUI.BitField, DebugStateFlags>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override Enum DoGUI(Rect rect, GUIContent label, DebugUI.BitField field, DebugStateFlags state)
{
Enum value = field.GetValue();
// Skip first element (with value 0) because EditorGUI.MaskField adds a 'Nothing' field anyway
var enumNames = new string[field.enumNames.Length - 1];
for (int i = 0; i < enumNames.Length; i++)
enumNames[i] = field.enumNames[i + 1].text;
var index = EditorGUI.MaskField(rect, label, (int)Convert.ToInt32(value), enumNames);
value = Enum.Parse(value.GetType(), index.ToString()) as Enum;
return value;
}
}
/// <summary>
/// Builtin Drawer for Foldout Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Foldout))]
public sealed class DebugUIDrawerFoldout : DebugUIDrawer
{
const int k_HeaderVerticalMargin = 2;
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void Begin(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.Foldout>(widget);
var s = Cast<DebugStateBool>(state);
GUIStyle style = w.isHeader ? DebugWindow.Styles.foldoutHeaderStyle : EditorStyles.foldout;
Rect rect = PrepareControlRect(w.isHeader ? style.fixedHeight : -1, w.isHeader);
if (w.isHeader)
GUILayout.Space(k_HeaderVerticalMargin);
bool value = EditorGUI.Foldout(rect, (bool)w.GetValue(), EditorGUIUtility.TrTextContent(w.displayName, w.tooltip), false, style);
if (w.GetValue() != value)
Apply(w, s, value);
if (w.contextMenuItems != null)
{
float contextMenuButtonSize = style.fixedHeight;
var labelRect = EditorGUI.IndentedRect(GUILayoutUtility.GetRect(0f, /*17f*/ 0f));
labelRect.xMax -= 20f + 16 + 5;
var contextMenuRect = new Rect(labelRect.xMax + 3f + 16, labelRect.y - contextMenuButtonSize, contextMenuButtonSize, contextMenuButtonSize);
if (GUI.Button(contextMenuRect, CoreEditorStyles.contextMenuIcon, CoreEditorStyles.contextMenuStyle))
{
var menu = new GenericMenu();
foreach (var item in w.contextMenuItems)
{
menu.AddItem(EditorGUIUtility.TrTextContent(item.displayName), false, () => item.action.Invoke());
}
menu.DropDown(new Rect(new Vector2(contextMenuRect.x, contextMenuRect.yMax), Vector2.zero));
}
}
Rect drawRect = GUILayoutUtility.GetLastRect();
if (w.columnLabels != null && value)
{
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0; //be at left of rects
for (int i = 0; i < w.columnLabels.Length; i++)
{
var columnRect = drawRect;
columnRect.x += EditorGUIUtility.labelWidth + i * DebugWindow.Styles.foldoutColumnWidth;
columnRect.width = DebugWindow.Styles.foldoutColumnWidth;
string label = w.columnLabels[i] ?? "";
string tooltip = w.columnTooltips?.ElementAtOrDefault(i) ?? "";
EditorGUI.LabelField(columnRect, EditorGUIUtility.TrTextContent(label, tooltip), EditorStyles.miniBoldLabel);
}
EditorGUI.indentLevel = indent;
}
EditorGUI.indentLevel++;
}
/// <summary>
/// OnGUI implementation for Foldout DebugUIDrawer.
/// </summary>
/// <param name="widget">DebugUI Widget.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>The state of the widget.</returns>
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.Foldout>(widget);
return w.opened;
}
/// <summary>
/// End implementation for Foldout DebugUIDrawer.
/// </summary>
/// <param name="widget">DebugUI Widget.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void End(DebugUI.Widget widget, DebugState state)
{
EditorGUI.indentLevel--;
var w = Cast<DebugUI.Foldout>(widget);
if (w.isHeader)
GUILayout.Space(k_HeaderVerticalMargin);
}
}
/// <summary>
/// Builtin Drawer for Color Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.ColorField))]
public sealed class DebugUIDrawerColorField : DebugUIFieldDrawer<Color, DebugUI.ColorField, DebugStateColor>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override Color DoGUI(Rect rect, GUIContent label, DebugUI.ColorField field, DebugStateColor state)
{
return EditorGUI.ColorField(rect, label, field.GetValue(), field.showPicker, field.showAlpha, field.hdr);
}
}
/// <summary>
/// Builtin Drawer for Vector2 Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Vector2Field))]
public sealed class DebugUIDrawerVector2Field : DebugUIFieldDrawer<Vector2, DebugUI.Vector2Field, DebugStateVector2>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override Vector2 DoGUI(Rect rect, GUIContent label, DebugUI.Vector2Field field, DebugStateVector2 state)
{
return EditorGUILayout.Vector2Field(label, field.GetValue());
}
}
/// <summary>
/// Builtin Drawer for Vector3 Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Vector3Field))]
public sealed class DebugUIDrawerVector3Field : DebugUIFieldDrawer<Vector3, DebugUI.Vector3Field, DebugStateVector3>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override Vector3 DoGUI(Rect rect, GUIContent label, DebugUI.Vector3Field field, DebugStateVector3 state)
{
return EditorGUILayout.Vector3Field(label, field.GetValue());
}
}
/// <summary>
/// Builtin Drawer for Vector4 Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Vector4Field))]
public sealed class DebugUIDrawerVector4Field : DebugUIFieldDrawer<Vector4, DebugUI.Vector4Field, DebugStateVector4>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override Vector4 DoGUI(Rect rect, GUIContent label, DebugUI.Vector4Field field, DebugStateVector4 state)
{
return EditorGUILayout.Vector4Field(label, field.GetValue());
}
}
/// <summary>
/// Builtin Drawer for <see cref="DebugUI.ObjectField"/> items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.ObjectField))]
public sealed class DebugUIDrawerObjectField : DebugUIFieldDrawer<UnityEngine.Object, DebugUI.ObjectField, DebugStateObject>
{
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected override UnityEngine.Object DoGUI(Rect rect, GUIContent label, DebugUI.ObjectField field, DebugStateObject state)
{
return EditorGUI.ObjectField(rect, label, field.GetValue(), field.type, true);
}
}
/// <summary>
/// Builtin Drawer for <see cref="DebugUI.ObjectListField"/> Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.ObjectListField))]
public sealed class DebugUIDrawerObjectListField : DebugUIDrawer
{
/// <summary>
/// Implement this to execute UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>Returns the state of the widget.</returns>
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.ObjectListField>(widget);
var objects = w.GetValue();
float height = Math.Max(objects != null ? objects.Length : 0, 1) * DebugWindow.Styles.singleRowHeight;
var rect = PrepareControlRect(height);
rect = EditorGUI.PrefixLabel(rect, EditorGUIUtility.TrTextContent(widget.displayName));
EditorGUI.BeginChangeCheck();
DoObjectList(rect, w, objects);
if (EditorGUI.EndChangeCheck())
Apply(w, state, objects);
return true;
}
internal static void DoObjectList(Rect rect, DebugUI.ObjectListField widget, UnityEngine.Object[] objects)
{
if (objects == null || objects.Length == 0)
{
EditorGUI.LabelField(rect, GUIContent.none, EditorGUIUtility.TrTextContent("Empty"));
return;
}
rect.height = EditorGUIUtility.singleLineHeight;
for (int i = 0; i < objects.Length; i++)
{
objects[i] = EditorGUI.ObjectField(rect, GUIContent.none, objects[i], widget.type, true);
rect.y += DebugWindow.Styles.singleRowHeight;
}
}
}
/// <summary>
/// Builtin Drawer for MessageBox Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.MessageBox))]
public sealed class DebugUIDrawerMessageBox : DebugUIDrawer
{
/// <summary>
/// Implement this to execute UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>Returns the state of the widget.</returns>
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.MessageBox>(widget);
var type = w.style switch
{
DebugUI.MessageBox.Style.Info => MessageType.Info,
DebugUI.MessageBox.Style.Warning => MessageType.Warning,
DebugUI.MessageBox.Style.Error => MessageType.Error,
_ => MessageType.None
};
EditorGUILayout.HelpBox(w.displayName, type);
return true;
}
}
/// <summary>
/// Builtin Drawer for Container Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Container))]
public sealed class DebugUIDrawerContainer : DebugUIDrawer
{
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void Begin(DebugUI.Widget widget, DebugState state)
{
var w = Cast<DebugUI.Container>(widget);
if (!w.hideDisplayName)
EditorGUILayout.LabelField(EditorGUIUtility.TrTextContent(widget.displayName, widget.tooltip), EditorStyles.boldLabel);
EditorGUI.indentLevel++;
}
/// <summary>
/// Implement this to execute processing after UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void End(DebugUI.Widget widget, DebugState state)
{
EditorGUI.indentLevel--;
}
}
/// <summary>
/// Builtin Drawer for Horizontal Box Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.HBox))]
public sealed class DebugUIDrawerHBox : DebugUIDrawer
{
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void Begin(DebugUI.Widget widget, DebugState state)
{
EditorGUILayout.BeginHorizontal();
}
/// <summary>
/// Implement this to execute processing after UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void End(DebugUI.Widget widget, DebugState state)
{
EditorGUILayout.EndHorizontal();
}
}
/// <summary>
/// Builtin Drawer for Vertical Box Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.VBox))]
public sealed class DebugUIDrawerVBox : DebugUIDrawer
{
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void Begin(DebugUI.Widget widget, DebugState state)
{
EditorGUILayout.BeginVertical();
}
/// <summary>
/// Implement this to execute processing after UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void End(DebugUI.Widget widget, DebugState state)
{
EditorGUILayout.EndVertical();
}
}
/// <summary>
/// Builtin Drawer for Table Debug Items.
/// </summary>
[DebugUIDrawer(typeof(DebugUI.Table))]
public sealed class DebugUIDrawerTable : DebugUIDrawer
{
/// <summary>
/// Implement this to execute UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>Returns the state of the widget.</returns>
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
const float k_ScrollBarHeight = 15;
var w = Cast<DebugUI.Table>(widget);
var header = w.Header;
var visible = header.state.visibleColumns;
float contentHeight = 0.0f;
foreach (DebugUI.Table.Row row in w.children)
contentHeight += row != null ? GetRowHeight(row, visible) : EditorGUIUtility.singleLineHeight;
// Put some space before the array
PrepareControlRect(EditorGUIUtility.singleLineHeight * 0.5f);
// Draw an outline around the table
var rect = EditorGUI.IndentedRect(PrepareControlRect(header.height + contentHeight + k_ScrollBarHeight));
rect = DrawOutline(rect);
// Compute rects
var headerRect = new Rect(rect.x, rect.y, rect.width, header.height);
var contentRect = new Rect(rect.x, headerRect.yMax, rect.width, rect.height - headerRect.height);
var viewRect = new Rect(contentRect.x, contentRect.y, header.state.widthOfAllVisibleColumns, contentRect.height);
var rowRect = contentRect;
viewRect.height -= k_ScrollBarHeight;
// Show header
header.OnGUI(headerRect, Mathf.Max(w.scroll.x, 0f));
// Show array content
w.scroll = GUI.BeginScrollView(contentRect, w.scroll, viewRect);
{
var columns = header.state.columns;
for (int r = 0; r < w.children.Count; r++)
{
var row = Cast<DebugUI.Container>(w.children[r]);
rowRect.x = contentRect.x;
rowRect.width = columns[0].width;
rowRect.height = (row is DebugUI.Table.Row tableRow) ? GetRowHeight(tableRow, visible) : EditorGUIUtility.singleLineHeight;
rowRect.xMin += 2;
rowRect.xMax -= 2;
EditorGUI.LabelField(rowRect, GUIContent.none, EditorGUIUtility.TrTextContent(row.displayName), DebugWindow.Styles.centeredLeft);
rowRect.xMin -= 2;
rowRect.xMax += 2;
using (new EditorGUI.DisabledScope(w.isReadOnly))
{
for (int c = 1; c < visible.Length; c++)
{
rowRect.x += rowRect.width;
rowRect.width = columns[visible[c]].width;
if (!row.isHidden)
DisplayChild(rowRect, row.children[visible[c] - 1]);
}
rowRect.y += rowRect.height;
}
}
}
GUI.EndScrollView(false);
return false;
}
internal float GetRowHeight(DebugUI.Table.Row row, int[] visibleColumns)
{
float height = EditorGUIUtility.singleLineHeight;
for (int c = 1; c < visibleColumns.Length; c++)
{
var child = row.children[visibleColumns[c] - 1] as DebugUI.ObjectListField;
if (child == null || child.GetValue() == null)
continue;
height = Mathf.Max(height, child.GetValue().Length * DebugWindow.Styles.singleRowHeight);
}
return height;
}
internal Rect DrawOutline(Rect rect)
{
if (Event.current.type != EventType.Repaint)
return rect;
float size = 1.0f;
var color = EditorGUIUtility.isProSkin ? new Color(0.12f, 0.12f, 0.12f, 1.333f) : new Color(0.6f, 0.6f, 0.6f, 1.333f);
Color orgColor = GUI.color;
GUI.color = GUI.color * color;
GUI.DrawTexture(new Rect(rect.x, rect.y, rect.width, size), EditorGUIUtility.whiteTexture);
GUI.DrawTexture(new Rect(rect.x, rect.yMax - size, rect.width, size), EditorGUIUtility.whiteTexture);
GUI.DrawTexture(new Rect(rect.x, rect.y + 1, size, rect.height - 2 * size), EditorGUIUtility.whiteTexture);
GUI.DrawTexture(new Rect(rect.xMax - size, rect.y + 1, size, rect.height - 2 * size), EditorGUIUtility.whiteTexture);
GUI.color = orgColor;
return new Rect(rect.x + size, rect.y + size, rect.width - 2 * size, rect.height - 2 * size);
}
internal void DisplayChild(Rect rect, DebugUI.Widget child)
{
rect.xMin += 2;
rect.xMax -= 2;
if (child.isHidden)
{
EditorGUI.LabelField(rect, "-");
}
else
{
if (child.GetType() == typeof(DebugUI.Value))
{
var widget = Cast<DebugUI.Value>(child);
EditorGUI.LabelField(rect, GUIContent.none, EditorGUIUtility.TrTextContent(widget.GetValue().ToString()));
}
else if (child.GetType() == typeof(DebugUI.ColorField))
{
var widget = Cast<DebugUI.ColorField>(child);
EditorGUI.ColorField(rect, GUIContent.none, widget.GetValue(), false, widget.showAlpha, widget.hdr);
}
else if (child.GetType() == typeof(DebugUI.BoolField))
{
var widget = Cast<DebugUI.BoolField>(child);
EditorGUI.Toggle(rect, GUIContent.none, widget.GetValue());
}
else if (child.GetType() == typeof(DebugUI.ObjectField))
{
var widget = Cast<DebugUI.ObjectField>(child);
EditorGUI.ObjectField(rect, GUIContent.none, widget.GetValue(), widget.type, true);
}
else if (child.GetType() == typeof(DebugUI.ObjectListField))
{
var widget = Cast<DebugUI.ObjectListField>(child);
DebugUIDrawerObjectListField.DoObjectList(rect, widget, widget.GetValue());
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f9e548e5e2920b47987881c21171ef6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,244 @@
using System;
using System.Text;
using UnityEditor.Rendering.Analytics;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
/// <summary>
/// Attribute specifying wich type of Debug Item should this drawer be used with.
/// </summary>
public class DebugUIDrawerAttribute : Attribute
{
internal readonly Type type;
/// <summary>
/// Constructor for DebugUIDraw Attribute
/// </summary>
/// <param name="type">Type of Debug Item this draw should be used with.</param>
public DebugUIDrawerAttribute(Type type)
{
this.type = type;
}
}
/// <summary>
/// Debug Item Drawer
/// </summary>
public class DebugUIDrawer
{
/// <summary>
/// Cast into the proper type.
/// </summary>
/// <typeparam name="T">Type of the drawer</typeparam>
/// <param name="o">Object to be cast</param>
/// <returns>Returns o cast to type T</returns>
protected T Cast<T>(object o)
where T : class
{
if (o == null) return null;
if (o is T casted)
return casted;
StringBuilder info = new StringBuilder("Cast Exception:");
switch (o)
{
case DebugUI.Widget value:
info.AppendLine($"Query Path : {value.queryPath}");
break;
case DebugState state:
info.AppendLine($"Query Path : {state.queryPath}");
break;
}
info.AppendLine($"Object to Cast Type : {o.GetType().AssemblyQualifiedName}");
info.AppendLine($"Target Cast Type : {typeof(T).AssemblyQualifiedName}");
throw new InvalidCastException(info.ToString());
}
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public virtual void Begin(DebugUI.Widget widget, DebugState state)
{ }
/// <summary>
/// Implement this to execute UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>Returns the state of the widget.</returns>
public virtual bool OnGUI(DebugUI.Widget widget, DebugState state)
{
return true;
}
/// <summary>
/// Implement this to execute processing after UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public virtual void End(DebugUI.Widget widget, DebugState state)
{ }
/// <summary>
/// Applies a value to the widget and the Debug State of the Debug Item.
/// </summary>
/// <param name="widget">Debug Item widget.</param>
/// <param name="state">Debug State associated with the Debug Item</param>
/// <param name="value">Input value.</param>
protected void Apply(DebugUI.IValueField widget, DebugState state, object value)
{
Undo.RegisterCompleteObjectUndo(state, $"Modified Value '{state.queryPath}'");
state.SetValue(value, widget);
widget.SetValue(value);
EditorUtility.SetDirty(state);
DebugState.m_CurrentDirtyState = state;
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
}
/// <summary>
/// Prepares the rendering Rect of the Drawer.
/// </summary>
/// <param name="height">Height of the rect.</param>
/// <param name="fullWidth">Whether to reserve full width for the element.</param>
/// <returns>Appropriate Rect for drawing.</returns>
protected Rect PrepareControlRect(float height = -1, bool fullWidth = false)
{
if (height < 0)
height = EditorGUIUtility.singleLineHeight;
var rect = GUILayoutUtility.GetRect(1f, 1f, height, height);
const float paddingLeft = 4f;
rect.width -= paddingLeft;
rect.xMin += paddingLeft;
EditorGUIUtility.labelWidth = fullWidth ? rect.width : rect.width / 2f;
return rect;
}
}
/// <summary>
/// Common class to help drawing fields
/// </summary>
/// <typeparam name="TValue">The internal value of the field</typeparam>
/// <typeparam name="TField">The type of the field widget</typeparam>
/// <typeparam name="TState">The state of the field</typeparam>
public abstract class DebugUIFieldDrawer<TValue, TField, TState> : DebugUIDrawer
where TField : DebugUI.Field<TValue>
where TState : DebugState
{
private TValue value { get; set; }
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void Begin(DebugUI.Widget widget, DebugState state)
{
EditorGUI.BeginChangeCheck();
}
/// <summary>
/// Implement this to execute UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>Returns the state of the widget.</returns>
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
value = DoGUI(
PrepareControlRect(),
EditorGUIUtility.TrTextContent(widget.displayName, widget.tooltip),
Cast<TField>(widget),
Cast<TState>(state)
);
return true;
}
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="field">The field</param>
/// <param name="state">The state</param>
/// <returns>The current value from the UI</returns>
protected abstract TValue DoGUI(Rect rect, GUIContent label, TField field, TState state);
/// <summary>
/// Implement this to execute processing after UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void End(DebugUI.Widget widget, DebugState state)
{
if (EditorGUI.EndChangeCheck())
{
var w = Cast<TField>(widget);
var s = Cast<TState>(state);
Apply(w, s, value);
DebugManagerWidgetUsedAnalytic.Send(widget.queryPath, value);
}
}
}
/// <summary>
/// Common class to help drawing widgets
/// </summary>
/// <typeparam name="TWidget">The widget</typeparam>
public abstract class DebugUIWidgetDrawer<TWidget> : DebugUIDrawer
where TWidget : DebugUI.Widget
{
/// <summary>
/// Implement this to execute processing before UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void Begin(DebugUI.Widget widget, DebugState state)
{
}
/// <summary>
/// Implement this to execute UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
/// <returns>Returns the state of the widget.</returns>
public override bool OnGUI(DebugUI.Widget widget, DebugState state)
{
DoGUI(
PrepareControlRect(),
EditorGUIUtility.TrTextContent(widget.displayName, widget.tooltip),
Cast<TWidget>(widget)
);
return true;
}
/// <summary>
/// Does the field of the given type
/// </summary>
/// <param name="rect">The rect to draw the field</param>
/// <param name="label">The label for the field</param>
/// <param name="w">The widget</param>
protected abstract void DoGUI(Rect rect, GUIContent label, TWidget w);
/// <summary>
/// Implement this to execute processing after UI rendering.
/// </summary>
/// <param name="widget">Widget that is going to be rendered.</param>
/// <param name="state">Debug State associated with the Debug Item.</param>
public override void End(DebugUI.Widget widget, DebugState state)
{
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3581d3ff7a65eee458feb865b7f29154
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,79 @@
using System;
using System.Linq;
using UnityEditor;
using UnityEditor.Rendering;
using UnityEditorInternal;
using UnityEngine.Rendering;
namespace UnityEngine.Rendering.UI
{
[CustomEditor(typeof(DebugUIHandlerCanvas))]
sealed class DebugUIHandlerCanvasEditor : Editor
{
SerializedProperty m_PanelPrefab;
SerializedProperty m_Prefabs;
ReorderableList m_PrefabList;
static string[] s_Types; // Assembly qualified names
static string[] s_DisplayTypes; // Pretty names
static DebugUIHandlerCanvasEditor()
{
s_Types = CoreUtils.GetAllTypesDerivedFrom<DebugUI.Widget>()
.Where(t => !t.IsAbstract)
.Select(t => t.AssemblyQualifiedName)
.ToArray();
s_DisplayTypes = new string[s_Types.Length];
for (int i = 0; i < s_Types.Length; i++)
s_DisplayTypes[i] = Type.GetType(s_Types[i]).Name;
}
void OnEnable()
{
var o = new PropertyFetcher<DebugUIHandlerCanvas>(serializedObject);
m_PanelPrefab = o.Find(x => x.panelPrefab);
m_Prefabs = o.Find(x => x.prefabs);
m_PrefabList = new ReorderableList(serializedObject, m_Prefabs, true, true, true, true)
{
drawHeaderCallback = rect => EditorGUI.LabelField(rect, "Widget Prefabs"),
drawElementCallback = (rect, index, isActive, isFocused) =>
{
var element = m_PrefabList.serializedProperty.GetArrayElementAtIndex(index);
rect.y += 2f;
const float kTypeWidth = 100f;
// Type selector
var typeProp = element.FindPropertyRelative("type");
int typeIndex = ArrayUtility.IndexOf(s_Types, typeProp.stringValue);
typeIndex = Mathf.Max(typeIndex, 0);
typeIndex = EditorGUI.Popup(new Rect(rect.x, rect.y, kTypeWidth, EditorGUIUtility.singleLineHeight), typeIndex, s_DisplayTypes);
typeProp.stringValue = s_Types[typeIndex];
// Prefab
EditorGUI.PropertyField(
new Rect(rect.x + kTypeWidth + 2f, rect.y, rect.width - kTypeWidth - 2f, EditorGUIUtility.singleLineHeight),
element.FindPropertyRelative("prefab"), GUIContent.none);
},
onSelectCallback = list =>
{
var prefab = list.serializedProperty.GetArrayElementAtIndex(list.index).FindPropertyRelative("prefab").objectReferenceValue as GameObject;
if (prefab)
EditorGUIUtility.PingObject(prefab.gameObject);
}
};
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_PanelPrefab);
EditorGUILayout.Space();
m_PrefabList.DoLayoutList();
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f1d41f85c15ac6048a850b51ff36c098
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,626 @@
#if ENABLE_INPUT_SYSTEM && ENABLE_INPUT_SYSTEM_PACKAGE
#define USE_INPUT_SYSTEM
#endif
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.Callbacks;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
#pragma warning disable 414
[Serializable]
sealed class WidgetStateDictionary : SerializedDictionary<string, DebugState> { }
sealed class DebugWindowSettings : ScriptableObject
{
// Keep these settings in a separate scriptable object so we can handle undo/redo on them
// without the rest of the debug window interfering
public int currentStateHash;
public int selectedPanel;
void OnEnable()
{
hideFlags = HideFlags.HideAndDontSave;
}
}
sealed class DebugWindow : EditorWindow, IHasCustomMenu
{
static Styles s_Styles;
static GUIStyle s_SplitterLeft;
static float splitterPos = 150f;
const float minSideBarWidth = 100;
const float minContentWidth = 100;
bool dragging = false;
[SerializeField]
WidgetStateDictionary m_WidgetStates;
[SerializeField]
DebugWindowSettings m_Settings;
[SerializeField]
int m_DebugTreeState;
bool m_IsDirty;
Vector2 m_PanelScroll;
Vector2 m_ContentScroll;
static bool s_TypeMapDirty;
static Dictionary<Type, Type> s_WidgetStateMap; // DebugUI.Widget type -> DebugState type
static Dictionary<Type, DebugUIDrawer> s_WidgetDrawerMap; // DebugUI.Widget type -> DebugUIDrawer
public static bool open
{
get => DebugManager.instance.displayEditorUI;
private set => DebugManager.instance.displayEditorUI = value;
}
[DidReloadScripts]
static void OnEditorReload()
{
s_TypeMapDirty = true;
//find if it where open, relink static event end propagate the info
open = (Resources.FindObjectsOfTypeAll<DebugWindow>()?.Length ?? 0) > 0;
}
static void RebuildTypeMaps()
{
// Map states to widget (a single state can map to several widget types if the value to
// serialize is the same)
var attrType = typeof(DebugStateAttribute);
var stateTypes = CoreUtils.GetAllTypesDerivedFrom<DebugState>()
.Where(
t => t.IsDefined(attrType, false)
&& !t.IsAbstract
);
s_WidgetStateMap = new Dictionary<Type, Type>();
foreach (var stateType in stateTypes)
{
var attr = (DebugStateAttribute)stateType.GetCustomAttributes(attrType, false)[0];
foreach (var t in attr.types)
s_WidgetStateMap.Add(t, stateType);
}
// Drawers
attrType = typeof(DebugUIDrawerAttribute);
var types = CoreUtils.GetAllTypesDerivedFrom<DebugUIDrawer>()
.Where(
t => t.IsDefined(attrType, false)
&& !t.IsAbstract
);
s_WidgetDrawerMap = new Dictionary<Type, DebugUIDrawer>();
foreach (var t in types)
{
var attr = (DebugUIDrawerAttribute)t.GetCustomAttributes(attrType, false)[0];
var inst = (DebugUIDrawer)Activator.CreateInstance(t);
s_WidgetDrawerMap.Add(attr.type, inst);
}
// Done
s_TypeMapDirty = false;
}
[MenuItem("Window/Analysis/Rendering Debugger", priority = 10005)]
static void Init()
{
var window = GetWindow<DebugWindow>();
window.titleContent = Styles.windowTitle;
}
[MenuItem("Window/Analysis/Rendering Debugger", validate = true)]
static bool ValidateMenuItem()
{
return RenderPipelineManager.currentPipeline != null;
}
void OnEnable()
{
open = true;
DebugManager.instance.refreshEditorRequested = false;
hideFlags = HideFlags.HideAndDontSave;
autoRepaintOnSceneChange = true;
if (m_Settings == null)
m_Settings = CreateInstance<DebugWindowSettings>();
// States are ScriptableObjects (necessary for Undo/Redo) but are not saved on disk so when the editor is closed then reopened, any existing debug window will have its states set to null
// Since we don't care about persistence in this case, we just re-init everything.
if (m_WidgetStates == null || !AreWidgetStatesValid())
m_WidgetStates = new WidgetStateDictionary();
if (s_WidgetStateMap == null || s_WidgetDrawerMap == null || s_TypeMapDirty)
RebuildTypeMaps();
Undo.undoRedoPerformed += OnUndoRedoPerformed;
DebugManager.instance.onSetDirty += MarkDirty;
// First init
m_DebugTreeState = DebugManager.instance.GetState();
UpdateWidgetStates();
EditorApplication.update -= Repaint;
var panels = DebugManager.instance.panels;
var selectedPanelIndex = m_Settings.selectedPanel;
if (selectedPanelIndex >= 0
&& selectedPanelIndex < panels.Count
&& panels[selectedPanelIndex].editorForceUpdate)
EditorApplication.update += Repaint;
}
// Note: this won't get called if the window is opened when the editor itself is closed
void OnDestroy()
{
open = false;
DebugManager.instance.onSetDirty -= MarkDirty;
Undo.ClearUndo(m_Settings);
DestroyWidgetStates();
}
public void DestroyWidgetStates()
{
if (m_WidgetStates == null)
return;
// Clear all the states from memory
foreach (var state in m_WidgetStates)
{
var s = state.Value;
Undo.ClearUndo(s); // Don't leave dangling states in the global undo/redo stack
DestroyImmediate(s);
}
m_WidgetStates.Clear();
}
public void ReloadWidgetStates()
{
if (m_WidgetStates == null)
return;
// Clear all the states from memory
foreach (var state in m_WidgetStates)
{
var widget = DebugManager.instance.GetItem(state.Key);
if (widget == null)
{
var s = state.Value;
Undo.ClearUndo(s); // Don't leave dangling states in the global undo/redo stack
DestroyImmediate(s);
}
}
UpdateWidgetStates();
}
bool AreWidgetStatesValid()
{
foreach (var state in m_WidgetStates)
{
if (state.Value == null)
{
return false;
}
}
return true;
}
void MarkDirty()
{
m_IsDirty = true;
}
// We use item states to keep a cached value of each serializable debug items in order to
// handle domain reloads, play mode entering/exiting and undo/redo
// Note: no removal of orphan states
void UpdateWidgetStates()
{
foreach (var panel in DebugManager.instance.panels)
UpdateWidgetStates(panel);
}
void UpdateWidgetStates(DebugUI.IContainer container)
{
// Skip runtime only containers, we won't draw them so no need to serialize them either
if (container is DebugUI.Widget actualWidget && actualWidget.isInactiveInEditor)
return;
// Recursively update widget states
foreach (var widget in container.children)
{
// Skip non-serializable widgets but still traverse them in case one of their
// children needs serialization support
if (widget is DebugUI.IValueField valueField)
{
// Skip runtime & readonly only items
if (widget.isInactiveInEditor)
return;
string guid = widget.queryPath;
if (!m_WidgetStates.TryGetValue(guid, out var state) || state == null)
{
var widgetType = widget.GetType();
if (s_WidgetStateMap.TryGetValue(widgetType, out Type stateType))
{
Assert.IsNotNull(stateType);
var inst = (DebugState)CreateInstance(stateType);
inst.queryPath = guid;
inst.SetValue(valueField.GetValue(), valueField);
m_WidgetStates[guid] = inst;
}
}
}
// Recurse if the widget is a container
if (widget is DebugUI.IContainer containerField)
UpdateWidgetStates(containerField);
}
}
public void ApplyStates(bool forceApplyAll = false)
{
if (!forceApplyAll && DebugState.m_CurrentDirtyState != null)
{
ApplyState(DebugState.m_CurrentDirtyState.queryPath, DebugState.m_CurrentDirtyState);
DebugState.m_CurrentDirtyState = null;
return;
}
foreach (var state in m_WidgetStates)
ApplyState(state.Key, state.Value);
DebugState.m_CurrentDirtyState = null;
}
void ApplyState(string queryPath, DebugState state)
{
if (!(DebugManager.instance.GetItem(queryPath) is DebugUI.IValueField widget))
return;
widget.SetValue(state.GetValue());
}
void OnUndoRedoPerformed()
{
int stateHash = ComputeStateHash();
// Something has been undone / redone, re-apply states to the debug tree
if (stateHash != m_Settings.currentStateHash)
{
ApplyStates(true);
m_Settings.currentStateHash = stateHash;
}
Repaint();
}
int ComputeStateHash()
{
unchecked
{
int hash = 13;
foreach (var state in m_WidgetStates)
hash = hash * 23 + state.Value.GetHashCode();
return hash;
}
}
void Update()
{
// If the render pipeline asset has been reloaded we force-refresh widget states in case
// some debug values need to be refresh/recreated as well (e.g. frame settings on HD)
if (DebugManager.instance.refreshEditorRequested)
{
ReloadWidgetStates();
m_IsDirty = true;
DebugManager.instance.refreshEditorRequested = false;
}
int? requestedPanelIndex = DebugManager.instance.GetRequestedEditorWindowPanelIndex();
if (requestedPanelIndex != null)
{
m_Settings.selectedPanel = requestedPanelIndex.Value;
}
int treeState = DebugManager.instance.GetState();
if (m_DebugTreeState != treeState || m_IsDirty)
{
UpdateWidgetStates();
ApplyStates();
m_DebugTreeState = treeState;
m_IsDirty = false;
}
}
void OnGUI()
{
if (s_Styles == null)
{
s_Styles = new Styles();
s_SplitterLeft = new GUIStyle();
}
var panels = DebugManager.instance.panels;
int itemCount = panels.Count(x => !x.isInactiveInEditor && x.children.Count(w => !w.isInactiveInEditor) > 0);
if (itemCount == 0)
{
EditorGUILayout.HelpBox("No debug item found.", MessageType.Info);
return;
}
// Background color
var wrect = position;
wrect.x = 0;
wrect.y = 0;
var oldColor = GUI.color;
GUI.color = s_Styles.skinBackgroundColor;
GUI.DrawTexture(wrect, EditorGUIUtility.whiteTexture);
GUI.color = oldColor;
GUILayout.BeginHorizontal(EditorStyles.toolbar);
GUILayout.FlexibleSpace();
if (GUILayout.Button(Styles.resetButtonContent, EditorStyles.toolbarButton))
{
DebugManager.instance.Reset();
DestroyWidgetStates();
UpdateWidgetStates();
InternalEditorUtility.RepaintAllViews();
}
GUILayout.EndHorizontal();
using (new EditorGUILayout.HorizontalScope())
{
// Side bar
using (var scrollScope = new EditorGUILayout.ScrollViewScope(m_PanelScroll, s_Styles.sectionScrollView, GUILayout.Width(splitterPos)))
{
if (m_Settings.selectedPanel >= panels.Count)
m_Settings.selectedPanel = 0;
// Validate container id
while (panels[m_Settings.selectedPanel].isInactiveInEditor || panels[m_Settings.selectedPanel].children.Count(x => !x.isInactiveInEditor) == 0)
{
m_Settings.selectedPanel++;
if (m_Settings.selectedPanel >= panels.Count)
m_Settings.selectedPanel = 0;
}
// Root children are containers
for (int i = 0; i < panels.Count; i++)
{
var panel = panels[i];
if (panel.isInactiveInEditor)
continue;
if (panel.children.Count(x => !x.isInactiveInEditor) == 0)
continue;
var elementRect = GUILayoutUtility.GetRect(EditorGUIUtility.TrTextContent(panel.displayName), s_Styles.sectionElement, GUILayout.ExpandWidth(true));
if (m_Settings.selectedPanel == i && Event.current.type == EventType.Repaint)
s_Styles.selected.Draw(elementRect, false, false, false, false);
EditorGUI.BeginChangeCheck();
GUI.Toggle(elementRect, m_Settings.selectedPanel == i, panel.displayName, s_Styles.sectionElement);
if (EditorGUI.EndChangeCheck())
{
Undo.RegisterCompleteObjectUndo(m_Settings, $"Debug Panel '{panel.displayName}' Selection");
var previousPanel = m_Settings.selectedPanel >= 0 && m_Settings.selectedPanel < panels.Count
? panels[m_Settings.selectedPanel]
: null;
if (previousPanel != null && previousPanel.editorForceUpdate && !panel.editorForceUpdate)
EditorApplication.update -= Repaint;
else if ((previousPanel == null || !previousPanel.editorForceUpdate) && panel.editorForceUpdate)
EditorApplication.update += Repaint;
m_Settings.selectedPanel = i;
}
}
m_PanelScroll = scrollScope.scrollPosition;
}
Rect splitterRect = new Rect(splitterPos - 3, 0, 6, Screen.height);
GUI.Box(splitterRect, "", s_SplitterLeft);
const float topMargin = 2f;
GUILayout.Space(topMargin);
// Main section - traverse current container
using (var changedScope = new EditorGUI.ChangeCheckScope())
{
using (new EditorGUILayout.VerticalScope())
{
const float leftMargin = 4f;
GUILayout.Space(leftMargin);
var selectedPanel = panels[m_Settings.selectedPanel];
using (var scrollScope = new EditorGUILayout.ScrollViewScope(m_ContentScroll))
{
TraverseContainerGUI(selectedPanel);
m_ContentScroll = scrollScope.scrollPosition;
}
}
if (changedScope.changed)
{
m_Settings.currentStateHash = ComputeStateHash();
DebugManager.instance.ReDrawOnScreenDebug();
}
}
// Splitter events
if (Event.current != null)
{
switch (Event.current.rawType)
{
case EventType.MouseDown:
if (splitterRect.Contains(Event.current.mousePosition))
{
dragging = true;
}
break;
case EventType.MouseDrag:
if (dragging)
{
splitterPos += Event.current.delta.x;
splitterPos = Mathf.Clamp(splitterPos, minSideBarWidth, Screen.width - minContentWidth);
Repaint();
}
break;
case EventType.MouseUp:
if (dragging)
{
dragging = false;
}
break;
}
}
EditorGUIUtility.AddCursorRect(splitterRect, MouseCursor.ResizeHorizontal);
}
}
void OnWidgetGUI(DebugUI.Widget widget)
{
if (widget.isInactiveInEditor || widget.isHidden)
return;
// State will be null for stateless widget
m_WidgetStates.TryGetValue(widget.queryPath, out DebugState state);
GUILayout.Space(4);
if (!s_WidgetDrawerMap.TryGetValue(widget.GetType(), out DebugUIDrawer drawer))
{
EditorGUILayout.LabelField("Drawer not found (" + widget.GetType() + ").");
}
else
{
drawer.Begin(widget, state);
if (drawer.OnGUI(widget, state))
{
if (widget is DebugUI.IContainer container)
TraverseContainerGUI(container);
}
drawer.End(widget, state);
}
}
void TraverseContainerGUI(DebugUI.IContainer container)
{
// /!\ SHAAAAAAAME ALERT /!\
// A container can change at runtime because of the way IMGUI works and how we handle
// onValueChanged on widget so we have to take this into account while iterating
try
{
foreach (var widget in container.children)
OnWidgetGUI(widget);
}
catch (InvalidOperationException)
{
Repaint();
}
}
public class Styles
{
public static float s_DefaultLabelWidth = 0.5f;
public static GUIContent windowTitle { get; } = EditorGUIUtility.TrTextContent("Rendering Debugger");
public static GUIContent resetButtonContent { get; } = EditorGUIUtility.TrTextContent("Reset");
public static GUIStyle foldoutHeaderStyle { get; } = new GUIStyle(EditorStyles.foldoutHeader)
{
fixedHeight = 20,
fontStyle = FontStyle.Bold,
margin = new RectOffset(0, 0, 0, 0)
};
public readonly GUIStyle sectionScrollView = "PreferencesSectionBox";
public readonly GUIStyle sectionElement = new GUIStyle("PreferencesSection");
public readonly GUIStyle selected = "OL SelectedRow";
public readonly GUIStyle sectionHeader = new GUIStyle(EditorStyles.largeLabel);
public readonly Color skinBackgroundColor;
public static GUIStyle centeredLeft = new GUIStyle(EditorStyles.label) { alignment = TextAnchor.MiddleLeft };
public static float singleRowHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
public static int foldoutColumnWidth = 70;
public Styles()
{
Color textColorDarkSkin = new Color32(210, 210, 210, 255);
Color textColorLightSkin = new Color32(102, 102, 102, 255);
Color backgroundColorDarkSkin = new Color32(38, 38, 38, 128);
Color backgroundColorLightSkin = new Color32(128, 128, 128, 96);
sectionScrollView = new GUIStyle(sectionScrollView);
sectionScrollView.overflow.bottom += 1;
sectionElement.alignment = TextAnchor.MiddleLeft;
sectionHeader.fontStyle = FontStyle.Bold;
sectionHeader.fontSize = 18;
sectionHeader.margin.top = 10;
sectionHeader.margin.left += 1;
sectionHeader.normal.textColor = EditorGUIUtility.isProSkin ? textColorDarkSkin : textColorLightSkin;
skinBackgroundColor = EditorGUIUtility.isProSkin ? backgroundColorDarkSkin : backgroundColorLightSkin;
}
}
public void AddItemsToMenu(GenericMenu menu)
{
menu.AddItem(EditorGUIUtility.TrTextContent("Expand All"), false, () => SetExpanded(true));
menu.AddItem(EditorGUIUtility.TrTextContent("Collapse All"), false, () => SetExpanded(false));
}
void SetExpanded(bool value)
{
var panels = DebugManager.instance.panels;
foreach (var p in panels)
{
foreach (var w in p.children)
{
if (w.GetType() == typeof(DebugUI.Foldout))
{
if (m_WidgetStates.TryGetValue(w.queryPath, out DebugState state))
{
var foldout = (DebugUI.Foldout)w;
state.SetValue(value, foldout);
foldout.SetValue(value);
}
}
}
}
}
}
#pragma warning restore 414
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 41147144ff556e246b736135eb26f185
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
using UnityEngine.Rendering.UI;
namespace UnityEditor.Rendering.UI
{
[CustomEditor(typeof(UIFoldout), true)]
sealed class UIFoldoutEditor : Editor
{
SerializedProperty m_IsOn;
SerializedProperty m_Content;
SerializedProperty m_ArrowClosed;
SerializedProperty m_ArrowOpened;
void OnEnable()
{
var o = new PropertyFetcher<UIFoldout>(serializedObject);
m_IsOn = o.Find("m_IsOn");
m_Content = o.Find(x => x.content);
m_ArrowClosed = o.Find(x => x.arrowClosed);
m_ArrowOpened = o.Find(x => x.arrowOpened);
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_IsOn);
EditorGUILayout.PropertyField(m_Content);
EditorGUILayout.PropertyField(m_ArrowClosed);
EditorGUILayout.PropertyField(m_ArrowOpened);
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e89075dc59755d8479f34df9e20926e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary>
/// This attributes tells a <see cref="VolumeComponentEditor"/> class which type of
/// <see cref="VolumeComponent"/> it's an editor for.
/// When you make a custom editor for a component, you need put this attribute on the editor
/// class.
/// </summary>
/// <seealso cref="VolumeComponentEditor"/>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
[Obsolete("VolumeComponentEditor property has been deprecated. Please use CustomEditor. #from(2022.2)")]
public sealed class VolumeComponentEditorAttribute : CustomEditor
{
/// <summary>
/// A type derived from <see cref="VolumeComponent"/>.
/// </summary>
public readonly Type componentType;
/// <summary>
/// Creates a new <see cref="VolumeComponentEditorAttribute"/> instance.
/// </summary>
/// <param name="componentType">A type derived from <see cref="VolumeComponent"/></param>
public VolumeComponentEditorAttribute(Type componentType)
: base(componentType, true)
{
this.componentType = componentType;
}
}
/// <summary>
/// Interface that should be used with [ScriptableRenderPipelineExtension(type))] attribute to dispatch ContextualMenu calls on the different SRPs
/// </summary>
/// <typeparam name="T">This must be a component that require AdditionalData in your SRP</typeparam>
[Obsolete("The menu items are handled automatically for components with the AdditionalComponentData attribute. #from(2022.2)", false)]
public interface IRemoveAdditionalDataContextualMenu<T>
where T : Component
{
/// <summary>
/// Remove the given component
/// </summary>
/// <param name="component">The component to remove</param>
/// <param name="dependencies">Dependencies.</param>
void RemoveComponent(T component, IEnumerable<Component> dependencies);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6e199542932514047864b6e8dcd90aa0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
using System;
namespace UnityEditor.Rendering
{
/// <summary>
/// Bool saved in EditorPref.
/// </summary>
public struct EditorPrefBool
{
readonly string m_Key;
/// <summary>
/// Value of the boolean in editor preferences.
/// </summary>
public bool value
{
get => EditorPrefs.GetBool(m_Key);
set => EditorPrefs.SetBool(m_Key, value);
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="key">Key in the editor preferences.</param>
/// <param name="defaultValue">Default value of the preference.</param>
public EditorPrefBool(string key, bool defaultValue = false)
{
m_Key = key;
//register key if not already there
if (!EditorPrefs.HasKey(m_Key))
{
EditorPrefs.SetBool(m_Key, defaultValue);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a68889b608ba2d247920c6b4983df6f1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,81 @@
using System;
namespace UnityEditor.Rendering
{
/// <summary>Bool flag saved in EditorPref</summary>
/// <typeparam name="T">Underlying enum type</typeparam>
public struct EditorPrefBoolFlags<T> : IEquatable<T>, IEquatable<EditorPrefBoolFlags<T>>
where T : struct, IConvertible
{
readonly string m_Key;
/// <summary>The value as the underlying enum type used</summary>
public T value
{ get => (T)(object)EditorPrefs.GetInt(m_Key); set => EditorPrefs.SetInt(m_Key, (int)(object)value); }
/// <summary>The raw value</summary>
public uint rawValue
{ get => (uint)EditorPrefs.GetInt(m_Key); set => EditorPrefs.SetInt(m_Key, (int)value); }
/// <summary>Constructor</summary>
/// <param name="key">Name of the Key in EditorPrefs to save the value</param>
public EditorPrefBoolFlags(string key) => m_Key = key;
/// <summary>Test if saved value is equal to the one given</summary>
/// <param name="other">Given value</param>
/// <returns>True if value are the same</returns>
public bool Equals(T other) => (int)(object)value == (int)(object)other;
/// <summary>Test if this EditorPrefBoolFlags is the same than the given one</summary>
/// <param name="other">Given EditorPrefBoolFlags</param>
/// <returns>True if they use the same value</returns>
public bool Equals(EditorPrefBoolFlags<T> other) => m_Key == other.m_Key;
/// <summary>Test if the given flags are set</summary>
/// <param name="v">Given flags</param>
/// <returns>True: all the given flags are set</returns>
public bool HasFlag(T v) => ((uint)(int)(object)v & rawValue) == (uint)(int)(object)v;
/// <summary>Set or unset the flags</summary>
/// <param name="f">Flags to edit</param>
/// <param name="v">Boolean value to set to the given flags</param>
public void SetFlag(T f, bool v)
{
if (v) rawValue |= (uint)(int)(object)f;
else rawValue &= ~(uint)(int)(object)f;
}
/// <summary>Explicit conversion operator to the underlying type</summary>
/// <param name="v">The EditorPrefBoolFlags to convert</param>
/// <returns>The converted value</returns>
public static explicit operator T(EditorPrefBoolFlags<T> v) => v.value;
/// <summary>Or operator between a EditorPrefBoolFlags and a value</summary>
/// <param name="l">The EditorPrefBoolFlags</param>
/// <param name="r">The value</param>
/// <returns>A EditorPrefBoolFlags with OR operator performed</returns>
public static EditorPrefBoolFlags<T> operator |(EditorPrefBoolFlags<T> l, T r)
{
l.rawValue |= (uint)(int)(object)r;
return l;
}
/// <summary>And operator between a EditorPrefBoolFlags and a value</summary>
/// <param name="l">The EditorPrefBoolFlags</param>
/// <param name="r">The value</param>
/// <returns>A EditorPrefBoolFlags with AND operator performed</returns>
public static EditorPrefBoolFlags<T> operator &(EditorPrefBoolFlags<T> l, T r)
{
l.rawValue &= (uint)(int)(object)r;
return l;
}
/// <summary>Xor operator between a EditorPrefBoolFlags and a value</summary>
/// <param name="l">The EditorPrefBoolFlags</param>
/// <param name="r">The value</param>
/// <returns>A EditorPrefBoolFlags with XOR operator performed</returns>
public static EditorPrefBoolFlags<T> operator ^(EditorPrefBoolFlags<T> l, T r)
{
l.rawValue ^= (uint)(int)(object)r;
return l;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1f766cc928a2d60469867f03d60c9f01
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,25 @@
using System;
using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary>Editor window that adds a button to browse the help url, specify <see cref="HelpURLAttribute"/> when defining your inherited class</summary>
public class EditorWindowWithHelpButton : EditorWindow
{
static Lazy<GUIContent> m_IconHelpGUIContent;
GUIContent iconHelpGUIContent => m_IconHelpGUIContent.Value;
static EditorWindowWithHelpButton()
{
m_IconHelpGUIContent = new Lazy<GUIContent>(() => new GUIContent(CoreEditorStyles.iconHelp));
}
/// <summary>Shows a button with help icon and opens the url defined by <see cref="HelpURLAttribute"/></summary>
/// <param name="r">The rect to show the button</param>
protected virtual void ShowButton(Rect r)
{
if (GUI.Button(r, iconHelpGUIContent, CoreEditorStyles.iconHelpStyle))
Help.ShowHelpForObject(this);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: db823efdb635ec84d8ecae05788f8143
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,68 @@
using System;
namespace UnityEditor.Rendering
{
/// <summary>Used in editor drawer part to store the state of expendable areas.</summary>
/// <typeparam name="TState">An enum to use to describe the state.</typeparam>
/// <typeparam name="TTarget">A type given to automatically compute the key.</typeparam>
public struct ExpandedState<TState, TTarget>
where TState : struct, IConvertible
{
EditorPrefBoolFlags<TState> m_State;
/// <summary>
/// Constructor will create the key to store in the EditorPref the state given generic type passed.
/// The key will be formated as such prefix:TTarget:TState:UI_State.
/// </summary>
/// <param name="defaultValue">If key did not exist, it will be created with this value for initialization.</param>
/// <param name="prefix">[Optional] Prefix scope of the key (Default is CoreRP)</param>
public ExpandedState(TState defaultValue, string prefix = "CoreRP")
{
string key = $"{prefix}:{typeof(TTarget).Name}:{typeof(TState).Name}:UI_State";
m_State = new EditorPrefBoolFlags<TState>(key);
//register key if not already there
if (!EditorPrefs.HasKey(key))
{
EditorPrefs.SetInt(key, (int)(object)defaultValue);
}
}
/// <summary>Get or set the state given the mask.</summary>
/// <param name="mask">The filtering mask</param>
/// <returns>True: All flagged area are expended</returns>
public bool this[TState mask]
{
get { return m_State.HasFlag(mask); }
set { m_State.SetFlag(mask, value); }
}
/// <summary>Accessor to the expended state of this specific mask.</summary>
/// <param name="mask">The filtering mask</param>
/// <returns>True: All flagged area are expended</returns>
public bool GetExpandedAreas(TState mask)
{
return m_State.HasFlag(mask);
}
/// <summary>Setter to the expended state.</summary>
/// <param name="mask">The filtering mask</param>
/// <param name="value">The expended state to set</param>
public void SetExpandedAreas(TState mask, bool value)
{
m_State.SetFlag(mask, value);
}
/// <summary> Utility to set all states to true </summary>
public void ExpandAll()
{
m_State.rawValue = ~(-1);
}
/// <summary> Utility to set all states to false </summary>
public void CollapseAll()
{
m_State.rawValue = 0;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1f4f20df9923ed04ea15d89f8456b8f0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,788 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary>
/// An utility window used to list and filter a set of elements, as seen in the inspector when
/// clicking on the "Add Component" button.
/// </summary>
[InitializeOnLoad]
public class FilterWindow : EditorWindow
{
/// <summary>
/// The interface to implement to populate the list or tree and traverse its elements.
/// </summary>
public interface IProvider
{
/// <summary>
/// The position of the window on screen.
/// </summary>
Vector2 position { get; set; }
/// <summary>
/// Implement this method to populate the list or tree of elements.
/// </summary>
/// <param name="tree">The list to populate.</param>
void CreateComponentTree(List<Element> tree);
/// <summary>
/// Implement this method to define the behavior when an item is selected.
/// </summary>
/// <param name="element">The selected element.</param>
/// <param name="addIfComponent"></param>
/// <returns><c>true</c> if the window should close, <c>false</c> otherwise.</returns>
bool GoToChild(Element element, bool addIfComponent);
}
/// <summary>
/// The default width for the window.
/// </summary>
public static readonly float DefaultWidth = 250f;
/// <summary>
/// The default height for the window.
/// </summary>
public static readonly float DefaultHeight = 300f;
#region BaseElements
/// <summary>
/// An element from the filtered list or tree.
/// </summary>
/// <seealso cref="GroupElement"/>
public class Element : IComparable
{
/// <summary>
/// The current hierarchical level in the tree.
/// </summary>
public int level;
/// <summary>
/// The displayed content for the element.
/// </summary>
public GUIContent content;
/// <summary>
/// The name of the element as displayed in the UI.
/// </summary>
public string name
{
get { return content.text; }
}
/// <summary>
/// Compares this element to another object.
/// </summary>
/// <param name="o">The object to compare to.</param>
/// <returns><c>true</c> if both objects are the same, <c>false</c> otherwise.</returns>
public int CompareTo(object o)
{
return name.CompareTo((o as Element).name);
}
}
/// <summary>
/// A meta element used to group several elements in the list or tree.
/// </summary>
/// <seealso cref="Element"/>
[Serializable]
public class GroupElement : Element
{
/// <summary>
/// The current scroll position in the UI.
/// </summary>
public Vector2 scroll;
/// <summary>
/// The current selected index in the group.
/// </summary>
public int selectedIndex;
/// <summary>
/// Requests focus for the element.
/// </summary>
public bool WantsFocus { get; protected set; }
/// <summary>
/// Returns <c>true</c> if this group and its content should appear disabled in the UI.
/// </summary>
public virtual bool ShouldDisable
{
get { return false; }
}
/// <summary>
/// Creates a new <see cref="GroupElement"/>
/// </summary>
/// <param name="level">The group level.</param>
/// <param name="name">The display name for the group.</param>
public GroupElement(int level, string name)
{
this.level = level;
content = new GUIContent(name);
}
/// <summary>
/// Handles custom keyboard events on this group.
/// </summary>
/// <param name="evt">The event.</param>
/// <param name="window">A reference to the parent <see cref="FilterWindow"/>.</param>
/// <param name="goToParent">The action to execute if a "back" action is triggered in the UI.</param>
/// <returns><c>true</c> if the builtin events should execute for this group, <c>false</c> otherwise.</returns>
public virtual bool HandleKeyboard(Event evt, FilterWindow window, Action goToParent)
{
return false;
}
/// <summary>
/// A custom drawing method for this group.
/// </summary>
/// <param name="sFilterWindow">A reference to the parent <see cref="FilterWindow"/>.</param>
/// <returns><c>true</c> if the builtin drawing function should execute for this group,
/// <c>false</c> otherwise.</returns>
public virtual bool OnGUI(FilterWindow sFilterWindow)
{
return false;
}
}
#endregion
// Styles
class Styles
{
public GUIStyle header = (GUIStyle)typeof(EditorStyles).GetProperty("inspectorBig", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null, null);
public GUIStyle componentButton = new GUIStyle("PR Label");
public GUIStyle groupButton;
public GUIStyle background = "grey_border";
public GUIStyle rightArrow = "AC RightArrow";
public GUIStyle leftArrow = "AC LeftArrow";
public Styles()
{
header.font = EditorStyles.boldLabel.font;
componentButton.alignment = TextAnchor.MiddleLeft;
componentButton.fixedHeight = 20;
componentButton.imagePosition = ImagePosition.ImageAbove;
groupButton = new GUIStyle(componentButton);
}
}
const int k_HeaderHeight = 30;
const int k_WindowHeight = 400 - 80;
const int k_HelpHeight = 80 * 0;
const string k_ComponentSearch = "NodeSearchString";
static Styles s_Styles;
static FilterWindow s_FilterWindow;
static long s_LastClosedTime;
static bool s_DirtyList;
IProvider m_Provider;
Element[] m_Tree;
Element[] m_SearchResultTree;
List<GroupElement> m_Stack = new List<GroupElement>();
float m_Anim = 1;
int m_AnimTarget = 1;
long m_LastTime;
bool m_ScrollToSelected;
string m_DelayedSearch;
string m_Search = "";
bool m_HasSearch { get { return !string.IsNullOrEmpty(m_Search); } }
GroupElement m_ActiveParent { get { return m_Stack[m_Stack.Count - 2 + m_AnimTarget]; } }
Element[] m_ActiveTree { get { return m_HasSearch ? m_SearchResultTree : m_Tree; } }
Element m_ActiveElement
{
get
{
if (m_ActiveTree == null)
return null;
var children = GetChildren(m_ActiveTree, m_ActiveParent);
return children.Count == 0
? null
: children[m_ActiveParent.selectedIndex];
}
}
bool m_IsAnimating { get { return !Mathf.Approximately(m_Anim, m_AnimTarget); } }
static FilterWindow()
{
s_DirtyList = true;
}
void OnEnable()
{
s_FilterWindow = this;
m_Search = "";
}
void OnDisable()
{
s_LastClosedTime = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
s_FilterWindow = null;
}
void OnLostFocus() => Close();
internal static bool ValidateAddComponentMenuItem()
{
return true;
}
/// <summary>
/// Shows the filter window using the given provider.
/// </summary>
/// <param name="position">The position to show the filter window at.</param>
/// <param name="provider">The provider of items for the filter window.</param>
/// <returns>Returns true if the window is shown, false otherwise.</returns>
public static bool Show(Vector2 position, IProvider provider)
{
// If the window is already open, close it instead.
var wins = Resources.FindObjectsOfTypeAll(typeof(FilterWindow));
if (wins.Length > 0)
{
try
{
((EditorWindow)wins[0]).Close();
return false;
}
catch (Exception)
{
s_FilterWindow = null;
}
}
// We could not use realtimeSinceStartUp since it is set to 0 when entering/exitting
// playmode, we assume an increasing time when comparing time.
long nowMilliSeconds = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
bool justClosed = nowMilliSeconds < s_LastClosedTime + 50;
if (!justClosed)
{
Event.current.Use();
if (s_FilterWindow == null)
{
s_FilterWindow = CreateInstance<FilterWindow>();
s_FilterWindow.hideFlags = HideFlags.HideAndDontSave;
}
s_FilterWindow.Init(position, provider);
return true;
}
return false;
}
static object Invoke(Type t, object inst, string method, params object[] args)
{
var flags = (inst == null ? BindingFlags.Static : BindingFlags.Instance) | BindingFlags.NonPublic;
var mi = t.GetMethod(method, flags);
return mi.Invoke(inst, args);
}
void Init(Vector2 position, IProvider provider)
{
m_Provider = provider;
// Has to be done before calling Show / ShowWithMode
m_Provider.position = position;
position = GUIUtility.GUIToScreenPoint(position);
var buttonRect = new Rect(position.x - DefaultWidth / 2, position.y - 16, DefaultWidth, 1);
CreateComponentTree();
ShowAsDropDown(buttonRect, new Vector2(buttonRect.width, k_WindowHeight));
Focus();
wantsMouseMove = true;
}
void CreateComponentTree()
{
var tree = new List<Element>();
m_Provider.CreateComponentTree(tree);
m_Tree = tree.ToArray();
// Rebuild stack
if (m_Stack.Count == 0)
{
m_Stack.Add(m_Tree[0] as GroupElement);
}
else
{
// The root is always the match for level 0
var match = m_Tree[0] as GroupElement;
int level = 0;
while (true)
{
// Assign the match for the current level
var oldElement = m_Stack[level];
m_Stack[level] = match;
m_Stack[level].selectedIndex = oldElement.selectedIndex;
m_Stack[level].scroll = oldElement.scroll;
// See if we reached last element of stack
level++;
if (level == m_Stack.Count)
break;
// Try to find a child of the same name as we had before
var children = GetChildren(m_ActiveTree, match);
var childMatch = children.FirstOrDefault(c => c.name == m_Stack[level].name);
if (childMatch is GroupElement)
{
match = childMatch as GroupElement;
}
else
{
// If we couldn't find the child, remove all further elements from the stack
while (m_Stack.Count > level)
m_Stack.RemoveAt(level);
}
}
}
s_DirtyList = false;
RebuildSearch();
}
internal void OnGUI()
{
// Avoids errors in the console if a domain reload is triggered while the filter window
// is opened
if (m_Provider == null)
return;
if (s_Styles == null)
s_Styles = new Styles();
GUI.Label(new Rect(0, 0, position.width, position.height), GUIContent.none, s_Styles.background);
if (s_DirtyList)
CreateComponentTree();
// Keyboard
HandleKeyboard();
GUILayout.Space(7);
// Search
if (!m_ActiveParent.WantsFocus)
{
EditorGUI.FocusTextInControl("ComponentSearch");
}
var searchRect = GUILayoutUtility.GetRect(10, 20);
searchRect.x += 8;
searchRect.width -= 16;
GUI.SetNextControlName("ComponentSearch");
using (new EditorGUI.DisabledScope(m_ActiveParent.ShouldDisable))
{
string newSearch = (string)Invoke(typeof(EditorGUI), null, "SearchField", searchRect, m_DelayedSearch ?? m_Search);
if (newSearch != m_Search || m_DelayedSearch != null)
{
if (!m_IsAnimating)
{
m_Search = m_DelayedSearch ?? newSearch;
EditorPrefs.SetString(k_ComponentSearch, m_Search);
RebuildSearch();
m_DelayedSearch = null;
}
else
{
m_DelayedSearch = newSearch;
}
}
}
// Show lists
ListGUI(m_ActiveTree, m_Anim, GetElementRelative(0), GetElementRelative(-1));
if (m_Anim < 1)
ListGUI(m_ActiveTree, m_Anim + 1, GetElementRelative(-1), GetElementRelative(-2));
// Animate
if (m_IsAnimating && Event.current.type == EventType.Repaint)
{
long now = DateTime.Now.Ticks;
float deltaTime = (now - m_LastTime) / (float)TimeSpan.TicksPerSecond;
m_LastTime = now;
m_Anim = Mathf.MoveTowards(m_Anim, m_AnimTarget, deltaTime * 4);
if (m_AnimTarget == 0 && Mathf.Approximately(m_Anim, 0))
{
m_Anim = 1;
m_AnimTarget = 1;
m_Stack.RemoveAt(m_Stack.Count - 1);
}
Repaint();
}
}
void HandleKeyboard()
{
var evt = Event.current;
if (evt.type == EventType.KeyDown)
{
// Special handling when in new script panel
if (!m_ActiveParent.HandleKeyboard(evt, s_FilterWindow, GoToParent))
{
// Always do these
if (evt.keyCode == KeyCode.DownArrow)
{
m_ActiveParent.selectedIndex++;
m_ActiveParent.selectedIndex = Mathf.Min(m_ActiveParent.selectedIndex, GetChildren(m_ActiveTree, m_ActiveParent).Count - 1);
m_ScrollToSelected = true;
evt.Use();
}
if (evt.keyCode == KeyCode.UpArrow)
{
m_ActiveParent.selectedIndex--;
m_ActiveParent.selectedIndex = Mathf.Max(m_ActiveParent.selectedIndex, 0);
m_ScrollToSelected = true;
evt.Use();
}
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter)
{
if (m_ActiveElement != null)
{
GoToChild(m_ActiveElement, true);
evt.Use();
}
}
// Do these if we're not in search mode
if (!m_HasSearch)
{
if (evt.keyCode == KeyCode.LeftArrow || evt.keyCode == KeyCode.Backspace)
{
GoToParent();
evt.Use();
}
if (evt.keyCode == KeyCode.RightArrow)
{
GoToChild(m_ActiveElement, false);
evt.Use();
}
if (evt.keyCode == KeyCode.Escape)
{
Close();
evt.Use();
}
}
}
}
}
const string k_SearchHeader = "Search";
void RebuildSearch()
{
if (!m_HasSearch)
{
m_SearchResultTree = null;
if (m_Stack[m_Stack.Count - 1].name == k_SearchHeader)
{
m_Stack.Clear();
m_Stack.Add(m_Tree[0] as GroupElement);
}
m_AnimTarget = 1;
m_LastTime = DateTime.Now.Ticks;
return;
}
// Support multiple search words separated by spaces.
var searchWords = m_Search.ToLower().Split(' ');
// We keep two lists. Matches that matches the start of an item always get first priority.
var matchesStart = new List<Element>();
var matchesWithin = new List<Element>();
foreach (var e in m_Tree)
{
if (e is GroupElement)
continue;
string name = e.name.ToLower().Replace(" ", "");
bool didMatchAll = true;
bool didMatchStart = false;
// See if we match ALL the seaarch words.
for (int w = 0; w < searchWords.Length; w++)
{
string search = searchWords[w];
if (name.Contains(search))
{
// If the start of the item matches the first search word, make a note of that.
if (w == 0 && name.StartsWith(search))
didMatchStart = true;
}
else
{
// As soon as any word is not matched, we disregard this item.
didMatchAll = false;
break;
}
}
// We always need to match all search words.
// If we ALSO matched the start, this item gets priority.
if (didMatchAll)
{
if (didMatchStart)
matchesStart.Add(e);
else
matchesWithin.Add(e);
}
}
matchesStart.Sort();
matchesWithin.Sort();
// Create search tree
var tree = new List<Element>();
// Add parent
tree.Add(new GroupElement(0, k_SearchHeader));
// Add search results
tree.AddRange(matchesStart);
tree.AddRange(matchesWithin);
// Create search result tree
m_SearchResultTree = tree.ToArray();
m_Stack.Clear();
m_Stack.Add(m_SearchResultTree[0] as GroupElement);
// Always select the first search result when search is changed (e.g. a character was typed in or deleted),
// because it's usually the best match.
if (GetChildren(m_ActiveTree, m_ActiveParent).Count >= 1)
m_ActiveParent.selectedIndex = 0;
else
m_ActiveParent.selectedIndex = -1;
}
GroupElement GetElementRelative(int rel)
{
int i = m_Stack.Count + rel - 1;
if (i < 0)
return null;
return m_Stack[i];
}
void GoToParent()
{
if (m_Stack.Count > 1)
{
m_AnimTarget = 0;
m_LastTime = DateTime.Now.Ticks;
}
}
void ListGUI(Element[] tree, float anim, GroupElement parent, GroupElement grandParent)
{
// Smooth the fractional part of the anim value
anim = Mathf.Floor(anim) + Mathf.SmoothStep(0, 1, Mathf.Repeat(anim, 1));
// Calculate rect for animated area
var animRect = position;
animRect.x = position.width * (1 - anim) + 1;
animRect.y = k_HeaderHeight;
animRect.height -= k_HeaderHeight + k_HelpHeight;
animRect.width -= 2;
// Start of animated area (the part that moves left and right)
GUILayout.BeginArea(animRect);
// Header
var headerRect = GUILayoutUtility.GetRect(10, 25);
string name = parent.name;
GUI.Label(headerRect, name, s_Styles.header);
// Back button
if (grandParent != null)
{
var arrowRect = new Rect(headerRect.x + 4, headerRect.y + 7, 13, 13);
var e = Event.current;
if (e.type == EventType.Repaint)
s_Styles.leftArrow.Draw(arrowRect, false, false, false, false);
if (e.type == EventType.MouseDown && headerRect.Contains(e.mousePosition))
{
GoToParent();
e.Use();
}
}
if (!parent.OnGUI(s_FilterWindow))
ListGUI(tree, parent);
GUILayout.EndArea();
}
void GoToChild(Element e, bool addIfComponent)
{
if (m_Provider.GoToChild(e, addIfComponent))
{
Close();
}
else if (!m_HasSearch)
{
m_LastTime = DateTime.Now.Ticks;
if (m_AnimTarget == 0)
{
m_AnimTarget = 1;
}
else if (Mathf.Approximately(m_Anim, 1f))
{
m_Anim = 0;
m_Stack.Add(e as GroupElement);
}
}
}
void ListGUI(Element[] tree, GroupElement parent)
{
// Start of scroll view list
parent.scroll = GUILayout.BeginScrollView(parent.scroll);
EditorGUIUtility.SetIconSize(new Vector2(16, 16));
var children = GetChildren(tree, parent);
var selectedRect = new Rect();
var evt = Event.current;
// Iterate through the children
for (int i = 0; i < children.Count; i++)
{
var e = children[i];
var r = GUILayoutUtility.GetRect(16, 20, GUILayout.ExpandWidth(true));
// Select the element the mouse cursor is over.
// Only do it on mouse move - keyboard controls are allowed to overwrite this until the next time the mouse moves.
if (evt.type == EventType.MouseMove || evt.type == EventType.MouseDown)
{
if (parent.selectedIndex != i && r.Contains(evt.mousePosition))
{
parent.selectedIndex = i;
Repaint();
}
}
bool selected = false;
// Handle selected item
if (i == parent.selectedIndex)
{
selected = true;
selectedRect = r;
}
// Draw element
if (evt.type == EventType.Repaint)
{
var labelStyle = (e is GroupElement) ? s_Styles.groupButton : s_Styles.componentButton;
labelStyle.Draw(r, e.content, false, false, selected, selected);
if (e is GroupElement)
{
var arrowRect = new Rect(r.x + r.width - 13, r.y + 4, 13, 13);
s_Styles.rightArrow.Draw(arrowRect, false, false, false, false);
}
}
if (evt.type == EventType.MouseDown && r.Contains(evt.mousePosition))
{
evt.Use();
parent.selectedIndex = i;
GoToChild(e, true);
}
}
EditorGUIUtility.SetIconSize(Vector2.zero);
GUILayout.EndScrollView();
// Scroll to show selected
if (m_ScrollToSelected && evt.type == EventType.Repaint)
{
m_ScrollToSelected = false;
var scrollRect = GUILayoutUtility.GetLastRect();
if (selectedRect.yMax - scrollRect.height > parent.scroll.y)
{
parent.scroll.y = selectedRect.yMax - scrollRect.height;
Repaint();
}
if (selectedRect.y < parent.scroll.y)
{
parent.scroll.y = selectedRect.y;
Repaint();
}
}
}
List<Element> GetChildren(Element[] tree, Element parent)
{
var children = new List<Element>();
int level = -1;
int i;
for (i = 0; i < tree.Length; i++)
{
if (tree[i] == parent)
{
level = parent.level + 1;
i++;
break;
}
}
if (level == -1)
return children;
for (; i < tree.Length; i++)
{
var e = tree[i];
if (e.level < level)
break;
if (e.level > level && !m_HasSearch)
continue;
children.Add(e);
}
return children;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e1dbde6eddba87f4ea272a5d3af5b7ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bce47e7c42ffde449812bbbf027555d7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,37 @@
using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary>
/// Set of utilities for gizmos
/// </summary>
public static class GizmoUtility
{
/// <summary>Modifies the given <see cref="Color"/> for handles</summary>
/// <param name="baseColor">The color to be modified</param>
/// <returns>a <see cref="Color"/></returns>
public static Color GetHandleColor(Color baseColor)
{
baseColor.a = 1f;
return baseColor;
}
/// <summary>Modifies the given <see cref="Color"/> for wire frames</summary>
/// <param name="baseColor">The color to be modified</param>
/// <returns>a <see cref="Color"/></returns>
public static Color GetWireframeColor(Color baseColor)
{
baseColor.a = .7f;
return baseColor;
}
/// <summary>Modifies the given <see cref="Color"/> for wire frames behind objects</summary>
/// <param name="baseColor">The color to be modified</param>
/// <returns>a <see cref="Color"/></returns>
public static Color GetWireframeColorBehindObjects(Color baseColor)
{
baseColor.a = .2f;
return baseColor;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 13d32c9cdf2984447b4802095ce716ae
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,491 @@
using System;
using UnityEngine;
using System.Reflection;
namespace UnityEditor.Rendering
{
/// <summary>
/// Provide a gizmo/handle representing a box where all face can be moved independently.
/// Also add a contained sub gizmo/handle box if contained is used at creation.
/// </summary>
/// <example>
/// <code>
/// class MyComponentEditor : Editor
/// {
/// static HierarchicalBox box;
/// static HierarchicalBox containedBox;
///
/// static MyComponentEditor()
/// {
/// Color[] handleColors = new Color[]
/// {
/// Color.red,
/// Color.green,
/// Color.Blue,
/// new Color(0.5f, 0f, 0f, 1f),
/// new Color(0f, 0.5f, 0f, 1f),
/// new Color(0f, 0f, 0.5f, 1f)
/// };
/// box = new HierarchicalBox(new Color(1f, 1f, 1f, 0.25), handleColors);
/// containedBox = new HierarchicalBox(new Color(1f, 0f, 1f, 0.25), handleColors, container: box);
/// }
///
/// [DrawGizmo(GizmoType.Selected|GizmoType.Active)]
/// void DrawGizmo(MyComponent comp, GizmoType gizmoType)
/// {
/// box.center = comp.transform.position;
/// box.size = comp.transform.scale;
/// box.DrawHull(gizmoType == GizmoType.Selected);
///
/// containedBox.center = comp.innerposition;
/// containedBox.size = comp.innerScale;
/// containedBox.DrawHull(gizmoType == GizmoType.Selected);
/// }
///
/// void OnSceneGUI()
/// {
/// EditorGUI.BeginChangeCheck();
///
/// //container box must be also set for contained box for clamping
/// box.center = comp.transform.position;
/// box.size = comp.transform.scale;
/// box.DrawHandle();
///
/// containedBox.DrawHandle();
/// containedBox.center = comp.innerposition;
/// containedBox.size = comp.innerScale;
///
/// if(EditorGUI.EndChangeCheck())
/// {
/// comp.innerposition = containedBox.center;
/// comp.innersize = containedBox.size;
/// }
/// }
/// }
/// </code>
/// </example>
public class HierarchicalBox
{
const float k_HandleSizeCoef = 0.05f;
static Material k_Material_Cache;
static Material k_Material => (k_Material_Cache == null || k_Material_Cache.Equals(null) ? (k_Material_Cache = new Material(Shader.Find("Hidden/UnlitTransparentColored"))) : k_Material_Cache);
static Mesh k_MeshQuad_Cache;
static Mesh k_MeshQuad => k_MeshQuad_Cache == null || k_MeshQuad_Cache.Equals(null) ? (k_MeshQuad_Cache = Resources.GetBuiltinResource<Mesh>("Quad.fbx")) : k_MeshQuad_Cache;
enum NamedFace { Right, Top, Front, Left, Bottom, Back, None }
Material m_Material;
readonly Color[] m_PolychromeHandleColor;
readonly HierarchicalBox m_Parent;
Color m_MonochromeFillColor;
Color m_MonochromeHandleColor;
Color m_WireframeColor;
Color m_WireframeColorBehind;
int[] m_ControlIDs = new int[6] { 0, 0, 0, 0, 0, 0 };
bool m_MonoHandle = true;
Material material
{
get
{
if (m_Material == null || m_Material.Equals(null))
m_Material = new Material(k_Material);
//material can be lost when exiting play mode so gather the color again when reconstructing it
m_Material.color = m_MonochromeFillColor;
return m_Material;
}
}
/// <summary>
/// Allow to switch between the mode where all axis are controlled together or not
/// Note that if there is several handles, they will use the polychrome colors.
/// </summary>
public bool monoHandle { get => m_MonoHandle; set => m_MonoHandle = value; }
/// <summary>The position of the center of the box in Handle.matrix space.</summary>
public Vector3 center { get; set; }
/// <summary>The size of the box in Handle.matrix space.</summary>
public Vector3 size { get; set; }
/// <summary>The baseColor used to fill hull. All other colors are deduced from it except specific handle colors.</summary>
public Color baseColor
{
get { return material.color; }
set
{
value.a = 8f / 255;
SetBaseColor(value);
}
}
/// <summary>
/// Set the baseColor used to fill hull. All other colors are deduced from it except specific handle colors.
/// Instead of <see cref="baseColor">baseColor</see> set, this will not force the opacity and keep what is provided for the filled faces.
/// </summary>
/// <param name="color">The color to use</param>
public void SetBaseColor(Color color)
{
m_MonochromeFillColor = color;
material.color = m_MonochromeFillColor;
m_MonochromeHandleColor = GizmoUtility.GetHandleColor(color);
m_WireframeColor = GizmoUtility.GetWireframeColor(color);
m_WireframeColorBehind = GizmoUtility.GetWireframeColorBehindObjects(color);
}
//Note: Handles.Slider not allow to use a specific ControlID.
//Thus Slider1D is used (with reflection)
static Type k_Slider1D = Type.GetType("UnityEditorInternal.Slider1D, UnityEditor");
static MethodInfo k_Slider1D_Do = k_Slider1D
.GetMethod(
"Do",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
null,
CallingConventions.Any,
new[] { typeof(int), typeof(Vector3), typeof(Vector3), typeof(float), typeof(Handles.CapFunction), typeof(float) },
null);
static void Slider1D(int controlID, ref Vector3 handlePosition, Vector3 handleOrientation, float snapScale, Color color)
{
using (new Handles.DrawingScope(color))
{
handlePosition = (Vector3)k_Slider1D_Do.Invoke(null, new object[]
{
controlID,
handlePosition,
handleOrientation,
HandleUtility.GetHandleSize(handlePosition) * k_HandleSizeCoef,
new Handles.CapFunction(Handles.DotHandleCap),
snapScale
});
}
}
/// <summary>Constructor. Used to setup colors and also the container if any.</summary>
/// <param name="baseColor">The color of each face of the box. Other colors are deduced from it.</param>
/// <param name="polychromeHandleColors">The color of handle when they are separated. When they are grouped, they use a variation of the faceColor instead.</param>
/// <param name="parent">The HierarchicalBox containing this box. If null, the box will not be limited in size.</param>
public HierarchicalBox(Color baseColor, Color[] polychromeHandleColors = null, HierarchicalBox parent = null)
{
if (polychromeHandleColors != null && polychromeHandleColors.Length != 6)
throw new ArgumentException("polychromeHandleColors must be null or have a size of 6.");
m_Parent = parent;
m_Material = new Material(k_Material);
this.baseColor = baseColor;
m_PolychromeHandleColor = polychromeHandleColors ?? new Color[]
{
Handles.xAxisColor, Handles.yAxisColor, Handles.zAxisColor,
Handles.xAxisColor, Handles.yAxisColor, Handles.zAxisColor
};
}
/// <summary>Draw the hull which means the boxes without the handles</summary>
/// <param name="filled">If true, also fill the faces of the hull</param>
public void DrawHull(bool filled)
{
Color previousColor = Handles.color;
if (filled)
{
// Draw the hull
var xSize = new Vector3(size.z, size.y, 1f);
material.SetPass(0);
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.x * .5f * Vector3.left, Quaternion.FromToRotation(Vector3.forward, Vector3.left), xSize));
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.x * .5f * Vector3.right, Quaternion.FromToRotation(Vector3.forward, Vector3.right), xSize));
var ySize = new Vector3(size.x, size.z, 1f);
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.y * .5f * Vector3.up, Quaternion.FromToRotation(Vector3.forward, Vector3.up), ySize));
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.y * .5f * Vector3.down, Quaternion.FromToRotation(Vector3.forward, Vector3.down), ySize));
var zSize = new Vector3(size.x, size.y, 1f);
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.z * .5f * Vector3.forward, Quaternion.identity, zSize));
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.z * .5f * Vector3.back, Quaternion.FromToRotation(Vector3.forward, Vector3.back), zSize));
//if as a parent, also draw handle distance to the parent
if (m_Parent != null)
{
var centerDiff = center - m_Parent.center;
var xRecal = centerDiff;
var yRecal = centerDiff;
var zRecal = centerDiff;
xRecal.x = 0;
yRecal.y = 0;
zRecal.z = 0;
Handles.color = GetHandleColor(NamedFace.Left);
Handles.DrawLine(m_Parent.center + xRecal + m_Parent.size.x * .5f * Vector3.left, center + size.x * .5f * Vector3.left);
Handles.color = GetHandleColor(NamedFace.Right);
Handles.DrawLine(m_Parent.center + xRecal + m_Parent.size.x * .5f * Vector3.right, center + size.x * .5f * Vector3.right);
Handles.color = GetHandleColor(NamedFace.Top);
Handles.DrawLine(m_Parent.center + yRecal + m_Parent.size.y * .5f * Vector3.up, center + size.y * .5f * Vector3.up);
Handles.color = GetHandleColor(NamedFace.Bottom);
Handles.DrawLine(m_Parent.center + yRecal + m_Parent.size.y * .5f * Vector3.down, center + size.y * .5f * Vector3.down);
Handles.color = GetHandleColor(NamedFace.Front);
Handles.DrawLine(m_Parent.center + zRecal + m_Parent.size.z * .5f * Vector3.forward, center + size.z * .5f * Vector3.forward);
Handles.color = GetHandleColor(NamedFace.Back);
Handles.DrawLine(m_Parent.center + zRecal + m_Parent.size.z * .5f * Vector3.back, center + size.z * .5f * Vector3.back);
}
}
Handles.color = m_WireframeColor;
Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual;
Handles.DrawWireCube(center, size);
Handles.color = m_WireframeColorBehind;
Handles.zTest = UnityEngine.Rendering.CompareFunction.Greater;
Handles.DrawWireCube(center, size);
Handles.zTest = UnityEngine.Rendering.CompareFunction.Always;
Handles.color = previousColor;
}
/// <summary>Draw the manipulable handles</summary>
public void DrawHandle()
{
Event evt = Event.current;
bool useHomothety = evt.shift;
bool useSymetry = evt.alt || evt.command;
// Note: snapping is handled natively on ctrl for each Slider1D
for (int i = 0, count = m_ControlIDs.Length; i < count; ++i)
m_ControlIDs[i] = GUIUtility.GetControlID("HierarchicalBox".GetHashCode() + i, FocusType.Passive);
var leftPosition = center + size.x * .5f * Vector3.left;
var rightPosition = center + size.x * .5f * Vector3.right;
var topPosition = center + size.y * .5f * Vector3.up;
var bottomPosition = center + size.y * .5f * Vector3.down;
var frontPosition = center + size.z * .5f * Vector3.forward;
var backPosition = center + size.z * .5f * Vector3.back;
var theChangedFace = NamedFace.None;
EditorGUI.BeginChangeCheck();
EditorGUI.BeginChangeCheck();
Slider1D(m_ControlIDs[(int)NamedFace.Left], ref leftPosition, Vector3.left, EditorSnapSettings.scale, GetHandleColor(NamedFace.Left));
if (EditorGUI.EndChangeCheck())
theChangedFace = NamedFace.Left;
EditorGUI.BeginChangeCheck();
Slider1D(m_ControlIDs[(int)NamedFace.Right], ref rightPosition, Vector3.right, EditorSnapSettings.scale, GetHandleColor(NamedFace.Right));
if (EditorGUI.EndChangeCheck())
theChangedFace = NamedFace.Right;
EditorGUI.BeginChangeCheck();
Slider1D(m_ControlIDs[(int)NamedFace.Top], ref topPosition, Vector3.up, EditorSnapSettings.scale, GetHandleColor(NamedFace.Top));
if (EditorGUI.EndChangeCheck())
theChangedFace = NamedFace.Top;
EditorGUI.BeginChangeCheck();
Slider1D(m_ControlIDs[(int)NamedFace.Bottom], ref bottomPosition, Vector3.down, EditorSnapSettings.scale, GetHandleColor(NamedFace.Bottom));
if (EditorGUI.EndChangeCheck())
theChangedFace = NamedFace.Bottom;
EditorGUI.BeginChangeCheck();
Slider1D(m_ControlIDs[(int)NamedFace.Front], ref frontPosition, Vector3.forward, EditorSnapSettings.scale, GetHandleColor(NamedFace.Front));
if (EditorGUI.EndChangeCheck())
theChangedFace = NamedFace.Front;
EditorGUI.BeginChangeCheck();
Slider1D(m_ControlIDs[(int)NamedFace.Back], ref backPosition, Vector3.back, EditorSnapSettings.scale, GetHandleColor(NamedFace.Back));
if (EditorGUI.EndChangeCheck())
theChangedFace = NamedFace.Back;
if (EditorGUI.EndChangeCheck())
{
float delta = 0f;
switch (theChangedFace)
{
case NamedFace.Left: delta = (leftPosition - center - size.x * .5f * Vector3.left).x; break;
case NamedFace.Right: delta = -(rightPosition - center - size.x * .5f * Vector3.right).x; break;
case NamedFace.Top: delta = -(topPosition - center - size.y * .5f * Vector3.up).y; break;
case NamedFace.Bottom: delta = (bottomPosition - center - size.y * .5f * Vector3.down).y; break;
case NamedFace.Front: delta = -(frontPosition - center - size.z * .5f * Vector3.forward).z; break;
case NamedFace.Back: delta = (backPosition - center - size.z * .5f * Vector3.back).z; break;
}
if (monoHandle || useHomothety && useSymetry)
{
var tempSize = size - Vector3.one * delta;
//ensure that the box face are still facing outside
for (int axis = 0; axis < 3; ++axis)
{
if (tempSize[axis] < 0)
{
delta += tempSize[axis];
tempSize = size - Vector3.one * delta;
}
}
//ensure containedBox do not exit container
if (m_Parent != null)
{
for (int axis = 0; axis < 3; ++axis)
{
if (tempSize[axis] > m_Parent.size[axis])
tempSize[axis] = m_Parent.size[axis];
}
}
size = tempSize;
}
else
{
if (useSymetry)
{
switch (theChangedFace)
{
case NamedFace.Left: rightPosition.x -= delta; break;
case NamedFace.Right: leftPosition.x += delta; break;
case NamedFace.Top: bottomPosition.y += delta; break;
case NamedFace.Bottom: topPosition.y -= delta; break;
case NamedFace.Front: backPosition.z += delta; break;
case NamedFace.Back: frontPosition.z -= delta; break;
}
//ensure that the box face are still facing outside
switch (theChangedFace)
{
case NamedFace.Left:
case NamedFace.Right:
if (rightPosition.x < leftPosition.x)
rightPosition.x = leftPosition.x = center.x;
break;
case NamedFace.Top:
case NamedFace.Bottom:
if (topPosition.y < bottomPosition.y)
topPosition.y = bottomPosition.y = center.y;
break;
case NamedFace.Front:
case NamedFace.Back:
if (frontPosition.z < backPosition.z)
frontPosition.z = backPosition.z = center.z;
break;
}
}
if (useHomothety)
{
float halfDelta = delta * 0.5f;
switch (theChangedFace)
{
case NamedFace.Left:
case NamedFace.Right:
bottomPosition.y += halfDelta;
topPosition.y -= halfDelta;
backPosition.z += halfDelta;
frontPosition.z -= halfDelta;
break;
case NamedFace.Top:
case NamedFace.Bottom:
rightPosition.x -= halfDelta;
leftPosition.x += halfDelta;
backPosition.z += halfDelta;
frontPosition.z -= halfDelta;
break;
case NamedFace.Front:
case NamedFace.Back:
rightPosition.x -= halfDelta;
leftPosition.x += halfDelta;
bottomPosition.y += halfDelta;
topPosition.y -= halfDelta;
break;
}
//ensure that the box face are still facing outside
switch (theChangedFace)
{
case NamedFace.Left:
if (rightPosition.x < leftPosition.x)
leftPosition.x = rightPosition.x;
if (topPosition.y < bottomPosition.y)
topPosition.y = bottomPosition.y = center.y;
if (frontPosition.z < backPosition.z)
frontPosition.z = backPosition.z = center.z;
break;
case NamedFace.Right:
if (rightPosition.x < leftPosition.x)
rightPosition.x = leftPosition.x;
if (topPosition.y < bottomPosition.y)
topPosition.y = bottomPosition.y = center.y;
if (frontPosition.z < backPosition.z)
frontPosition.z = backPosition.z = center.z;
break;
case NamedFace.Top:
if (topPosition.y < bottomPosition.y)
topPosition.y = bottomPosition.y;
if (rightPosition.x < leftPosition.x)
rightPosition.x = leftPosition.x = center.x;
if (frontPosition.z < backPosition.z)
frontPosition.z = backPosition.z = center.z;
break;
case NamedFace.Bottom:
if (topPosition.y < bottomPosition.y)
bottomPosition.y = topPosition.y;
if (rightPosition.x < leftPosition.x)
rightPosition.x = leftPosition.x = center.x;
if (frontPosition.z < backPosition.z)
frontPosition.z = backPosition.z = center.z;
break;
case NamedFace.Front:
if (frontPosition.z < backPosition.z)
frontPosition.z = backPosition.z;
if (rightPosition.x < leftPosition.x)
rightPosition.x = leftPosition.x = center.x;
if (topPosition.y < bottomPosition.y)
topPosition.y = bottomPosition.y = center.y;
break;
case NamedFace.Back:
if (frontPosition.z < backPosition.z)
backPosition.z = frontPosition.z;
if (rightPosition.x < leftPosition.x)
rightPosition.x = leftPosition.x = center.x;
if (topPosition.y < bottomPosition.y)
topPosition.y = bottomPosition.y = center.y;
break;
}
}
var max = new Vector3(rightPosition.x, topPosition.y, frontPosition.z);
var min = new Vector3(leftPosition.x, bottomPosition.y, backPosition.z);
if (!useSymetry && !useHomothety)
{
//ensure that the box face are still facing outside
for (int axis = 0; axis < 3; ++axis)
{
if (min[axis] > max[axis])
{
// Control IDs in m_ControlIDs[0-3[ are for positive axes
if (GUIUtility.hotControl == m_ControlIDs[axis])
max[axis] = min[axis];
else
min[axis] = max[axis];
}
}
}
//ensure containedBox do not exit container
if (m_Parent != null)
{
for (int axis = 0; axis < 3; ++axis)
{
if (min[axis] < m_Parent.center[axis] - m_Parent.size[axis] * 0.5f)
min[axis] = m_Parent.center[axis] - m_Parent.size[axis] * 0.5f;
if (max[axis] > m_Parent.center[axis] + m_Parent.size[axis] * 0.5f)
max[axis] = m_Parent.center[axis] + m_Parent.size[axis] * 0.5f;
}
}
center = (max + min) * .5f;
size = max - min;
}
}
}
Color GetHandleColor(NamedFace name) => monoHandle ? m_MonochromeHandleColor : m_PolychromeHandleColor[(int)name];
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9c0adda657f204c4199b6870832abb80
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,176 @@
using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary>
/// Provide a gizmo/handle representing a box where all face can be moved independently.
/// Also add a contained sub gizmo/handle box if contained is used at creation.
/// </summary>
/// <example>
/// <code>
/// class MyComponentEditor : Editor
/// {
/// static HierarchicalSphere sphere;
/// static HierarchicalSphere containedSphere;
///
/// static MyComponentEditor()
/// {
/// Color[] handleColors = new Color[]
/// {
/// Color.red,
/// Color.green,
/// Color.Blue,
/// new Color(0.5f, 0f, 0f, 1f),
/// new Color(0f, 0.5f, 0f, 1f),
/// new Color(0f, 0f, 0.5f, 1f)
/// };
/// sphere = new HierarchicalSphere(new Color(1f, 1f, 1f, 0.25));
/// containedSphere = new HierarchicalSphere(new Color(1f, 0f, 1f, 0.25), container: sphere);
/// }
///
/// [DrawGizmo(GizmoType.Selected|GizmoType.Active)]
/// void DrawGizmo(MyComponent comp, GizmoType gizmoType)
/// {
/// sphere.center = comp.transform.position;
/// sphere.size = comp.transform.scale;
/// sphere.DrawHull(gizmoType == GizmoType.Selected);
///
/// containedSphere.center = comp.innerposition;
/// containedSphere.size = comp.innerScale;
/// containedSphere.DrawHull(gizmoType == GizmoType.Selected);
/// }
///
/// void OnSceneGUI()
/// {
/// EditorGUI.BeginChangeCheck();
///
/// //container sphere must be also set for contained sphere for clamping
/// sphere.center = comp.transform.position;
/// sphere.size = comp.transform.scale;
/// sphere.DrawHandle();
///
/// containedSphere.center = comp.innerposition;
/// containedSphere.size = comp.innerScale;
/// containedSphere.DrawHandle();
///
/// if(EditorGUI.EndChangeCheck())
/// {
/// comp.innerposition = containedSphere.center;
/// comp.innersize = containedSphere.size;
/// }
/// }
/// }
/// </code>
/// </example>
public class HierarchicalSphere
{
const float k_HandleSizeCoef = 0.05f;
static Material k_Material_Cache;
static Material k_Material => (k_Material_Cache == null || k_Material_Cache.Equals(null) ? (k_Material_Cache = new Material(Shader.Find("Hidden/UnlitTransparentColored"))) : k_Material_Cache);
static Mesh k_MeshSphere_Cache;
static Mesh k_MeshSphere => k_MeshSphere_Cache == null || k_MeshSphere_Cache.Equals(null) ? (k_MeshSphere_Cache = Resources.GetBuiltinResource<Mesh>("New-Sphere.fbx")) : k_MeshSphere_Cache;
Material m_Material;
readonly HierarchicalSphere m_Parent;
Color m_HandleColor;
Color m_WireframeColor;
Color m_WireframeColorBehind;
Material material => m_Material == null || m_Material.Equals(null)
? (m_Material = new Material(k_Material))
: m_Material;
/// <summary>The position of the center of the box in Handle.matrix space.</summary>
public Vector3 center { get; set; }
/// <summary>The size of the box in Handle.matrix space.</summary>
public float radius { get; set; }
/// <summary>The baseColor used to fill hull. All other colors are deduced from it.</summary>
public Color baseColor
{
get { return material.color; }
set
{
value.a = 8f / 255;
material.color = value;
value.a = 1f;
m_HandleColor = value;
value.a = 0.7f;
m_WireframeColor = value;
value.a = 0.2f;
m_WireframeColorBehind = value;
}
}
/// <summary>Constructor. Used to setup colors and also the container if any.</summary>
/// <param name="baseColor">The color of filling. All other colors are deduced from it.</param>
/// <param name="parent">The HierarchicalSphere containing this sphere. If null, the sphere will not be limited in size.</param>
public HierarchicalSphere(Color baseColor, HierarchicalSphere parent = null)
{
m_Parent = parent;
m_Material = new Material(k_Material);
this.baseColor = baseColor;
}
/// <summary>Draw the hull which means the boxes without the handles</summary>
/// <param name="filled">If true, also draw the surface of the hull's sphere</param>
public void DrawHull(bool filled)
{
Color wireframeColor = m_HandleColor;
wireframeColor.a = 0.8f;
using (new Handles.DrawingScope(m_WireframeColor, Matrix4x4.TRS((Vector3)Handles.matrix.GetColumn(3) + center, Quaternion.identity, Vector3.one)))
{
if (filled)
{
material.SetPass(0);
Matrix4x4 drawMatrix = Matrix4x4.TRS((Vector3)Handles.matrix.GetColumn(3), Quaternion.identity, Vector3.one * radius * 2f);
Graphics.DrawMeshNow(k_MeshSphere, drawMatrix);
}
var drawCenter = Vector3.zero;
var viewPlaneNormal = Vector3.zero;
var drawnRadius = radius;
if (Camera.current.orthographic)
viewPlaneNormal = Camera.current.transform.forward;
else
{
viewPlaneNormal = (Vector3)Handles.matrix.GetColumn(3) - Camera.current.transform.position;
var sqrDist = viewPlaneNormal.sqrMagnitude; // squared distance from camera to center
var sqrRadius = radius * radius; // squared radius
var sqrOffset = sqrRadius * sqrRadius / sqrDist; // squared distance from actual center to drawn disc center
var insideAmount = sqrOffset / sqrRadius;
// If we are not inside the sphere, calculate where to draw the periphery
if (insideAmount < 1)
{
drawnRadius = Mathf.Sqrt(sqrRadius - sqrOffset); // the radius of the drawn disc
drawCenter -= (sqrRadius / sqrDist) * viewPlaneNormal;
}
}
Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual;
Handles.DrawWireDisc(Vector3.zero, Vector3.up, radius);
Handles.DrawWireDisc(drawCenter, viewPlaneNormal, drawnRadius);
Handles.color = m_WireframeColorBehind;
Handles.zTest = UnityEngine.Rendering.CompareFunction.Greater;
Handles.DrawWireDisc(Vector3.zero, Vector3.up, radius);
Handles.DrawWireDisc(drawCenter, viewPlaneNormal, drawnRadius);
Handles.zTest = UnityEngine.Rendering.CompareFunction.Always;
}
}
/// <summary>Draw the manipulable handles</summary>
public void DrawHandle()
{
using (new Handles.DrawingScope(m_HandleColor))
{
radius = Handles.RadiusHandle(Quaternion.identity, center, radius, handlesOnly: true);
if (m_Parent != null)
radius = Mathf.Min(radius, m_Parent.radius - Vector3.Distance(center, m_Parent.center));
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7ecd4b4b0c2b54742baf8d885609f3b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
Shader "Hidden/UnlitTransparentColored" {
Properties {
_Color ("Main Color", Color) = (1,1,1,1)
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
ZWrite Off
Lighting Off
Fog { Mode Off }
Cull Off
Blend SrcAlpha OneMinusSrcAlpha
Pass {
Color [_Color]
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 2df96b66b5510f94b98df2ac6f926ef4
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Rendering
{
/// <summary>
/// Interface to extend to provide UI
/// </summary>
public interface ICoreRenderPipelinePreferencesProvider
{
/// <summary>
/// The list of keywords for user search
/// </summary>
List<string> keywords { get; }
/// <summary>
/// The header of the panel
/// </summary>
GUIContent header { get; }
/// <summary>
/// Renders the Preferences UI for this provider
/// </summary>
void PreferenceGUI();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 097dbb6933d598544930378228f8b423
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,979 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Rendering
{
// A good chunk of this class is recycled from Post-processing v2 and could use a cleaning pass
/// <summary>
/// A custom curve editor made to be embedded in the inspector instead of a separate window.
/// </summary>
public sealed class InspectorCurveEditor
{
enum EditMode
{
None,
Moving,
TangentEdit
}
enum Tangent
{
In,
Out
}
/// <summary>
/// A structure holding settings used for the curve editor.
/// </summary>
public struct Settings
{
/// <summary>
/// The boundaries of the curve.
/// </summary>
public Rect bounds;
/// <summary>
/// The visual padding used when rendering the curve in the inspector.
/// </summary>
public RectOffset padding;
/// <summary>
/// The color to use when a curve is selected.
/// </summary>
public Color selectionColor;
/// <summary>
/// The distance in pixels to check for curve selection on mouse click.
/// </summary>
public float curvePickingDistance;
/// <summary>
/// The distance to clamp keys at compared to the previous and next keys.
/// </summary>
public float keyTimeClampingDistance;
/// <summary>
/// Default settings.
/// - <see cref="bounds"/> are set to (0, 0, 1, 1).
/// - <see cref="padding"/> is set to (0, 0, 0, 0).
/// - <see cref="selectionColor"/> is set to yellow.
/// - <see cref="curvePickingDistance"/> is set to 6 pixels.
/// - <see cref="keyTimeClampingDistance"/> is set to 1e-4.
/// </summary>
public static Settings defaultSettings => new Settings
{
bounds = new Rect(0f, 0f, 1f, 1f),
padding = new RectOffset(0, 0, 0, 0),
selectionColor = Color.yellow,
curvePickingDistance = 6f,
keyTimeClampingDistance = 1e-4f
};
}
/// <summary>
/// A structure holding the state of a single curve in the editor.
/// </summary>
public struct CurveState
{
/// <summary>
/// Is the curve visible?
/// </summary>
public bool visible;
/// <summary>
/// Is the curve editable?
/// </summary>
public bool editable;
/// <summary>
/// The minimum allowed number of points on the curve.
/// </summary>
public uint minPointCount;
/// <summary>
/// A constant value to use when the curve doesn't have any point.
/// </summary>
public float zeroKeyConstantValue;
/// <summary>
/// The color used to draw the curve.
/// </summary>
public Color color;
/// <summary>
/// The visual thickness of the curve.
/// </summary>
public float width;
/// <summary>
/// The visual thickness of the curve handles.
/// </summary>
public float handleWidth;
/// <summary>
/// Should the handles be visible on non-editable curves?
/// </summary>
public bool showNonEditableHandles;
/// <summary>
/// Should the handles only be visible when the curve is selected?
/// </summary>
public bool onlyShowHandlesOnSelection;
/// <summary>
/// Does this curve loop in the defined boudaries?
/// </summary>
public bool loopInBounds;
/// <summary>
/// Default curve state.
/// - <see cref="visible"/> is set to true.
/// - <see cref="editable"/> is set to true.
/// - <see cref="minPointCount"/> is set to 2.
/// - <see cref="zeroKeyConstantValue"/> is set to 0.
/// - <see cref="color"/> is set to white.
/// - <see cref="width"/> is set to 2.
/// - <see cref="handleWidth"/> is set to 2.
/// - <see cref="showNonEditableHandles"/> is set to true.
/// - <see cref="onlyShowHandlesOnSelection"/> is set to false.
/// - <see cref="loopInBounds"/> is set to false.
/// </summary>
public static CurveState defaultState => new CurveState
{
visible = true,
editable = true,
minPointCount = 2,
zeroKeyConstantValue = 0f,
color = Color.white,
width = 2f,
handleWidth = 2f,
showNonEditableHandles = true,
onlyShowHandlesOnSelection = false,
loopInBounds = false
};
}
/// <summary>
/// A structure holding the state of the current selection.
/// </summary>
public struct Selection
{
/// <summary>
/// A reference to the serialized <c>AnimationCurve</c>.
/// </summary>
public SerializedProperty curve;
/// <summary>
/// The currently selected key index, or -1 if none is selected.
/// </summary>
public int keyframeIndex;
/// <summary>
/// The key itself, or <c>null</c> if none is selected.
/// </summary>
public Keyframe? keyframe;
/// <summary>
/// Creates a new selection state.
/// </summary>
/// <param name="curve">A reference to the serialized curve.</param>
/// <param name="keyframeIndex">The currently selected key index, or -1 if none is selected.</param>
/// <param name="keyframe">The key itself, or <c>null</c> if none is selected.</param>
public Selection(SerializedProperty curve, int keyframeIndex, Keyframe? keyframe)
{
this.curve = curve;
this.keyframeIndex = keyframeIndex;
this.keyframe = keyframe;
}
}
internal struct MenuAction
{
internal SerializedProperty curve;
internal int index;
internal Vector3 position;
internal MenuAction(SerializedProperty curve)
{
this.curve = curve;
this.index = -1;
this.position = Vector3.zero;
}
internal MenuAction(SerializedProperty curve, int index)
{
this.curve = curve;
this.index = index;
this.position = Vector3.zero;
}
internal MenuAction(SerializedProperty curve, Vector3 position)
{
this.curve = curve;
this.index = -1;
this.position = position;
}
}
/// <summary>
/// The current settings used for the curve editor.
/// </summary>
public readonly Settings settings;
readonly Dictionary<SerializedProperty, CurveState> m_Curves;
Rect m_CurveArea;
SerializedProperty m_SelectedCurve;
int m_SelectedKeyframeIndex = -1;
EditMode m_EditMode = EditMode.None;
Tangent m_TangentEditMode;
bool m_Dirty;
/// <summary>
/// Creates a curve editor with default settings.
/// </summary>
/// <seealso cref="Settings.defaultSettings"/>
public InspectorCurveEditor()
: this(Settings.defaultSettings) { }
/// <summary>
/// Creates a curve editor with the given settings.
/// </summary>
/// <param name="settings">The settings to use to create the curve editor.</param>
public InspectorCurveEditor(Settings settings)
{
this.settings = settings;
m_Curves = new Dictionary<SerializedProperty, CurveState>();
}
/// <summary>
/// Adds an arbitrary number of serialized curves to the editor.
/// </summary>
/// <param name="curves">The curves to add.</param>
public void Add(params SerializedProperty[] curves)
{
foreach (var curve in curves)
Add(curve, CurveState.defaultState);
}
/// <summary>
/// Adds a serialized curve to the editor.
/// </summary>
/// <param name="curve">The curve to add.</param>
public void Add(SerializedProperty curve)
{
Add(curve, CurveState.defaultState);
}
/// <summary>
/// Adds a serialized curve to the editor with a given state.
/// </summary>
/// <param name="curve">The curve to add.</param>
/// <param name="state">The state to use for the curve.</param>
public void Add(SerializedProperty curve, CurveState state)
{
// Make sure the property is in fact an AnimationCurve
var animCurve = curve.animationCurveValue;
if (animCurve == null)
throw new ArgumentException("curve");
if (m_Curves.ContainsKey(curve))
Debug.LogWarning("Curve has already been added to the editor");
m_Curves.Add(curve, state);
}
/// <summary>
/// Removes a single curve from the editor.
/// </summary>
/// <param name="curve">The curve to remove.</param>
public void Remove(SerializedProperty curve)
{
m_Curves.Remove(curve);
}
/// <summary>
/// Removes all the curve from the editor.
/// </summary>
public void RemoveAll()
{
m_Curves.Clear();
}
/// <summary>
/// Grabs the state for a given curve.
/// </summary>
/// <param name="curve">The curve to grab the state from.</param>
/// <returns>The state of the curve.</returns>
public CurveState GetCurveState(SerializedProperty curve)
{
if (!m_Curves.TryGetValue(curve, out var state))
throw new KeyNotFoundException("curve");
return state;
}
/// <summary>
/// Sets the state for a given curve.
/// </summary>
/// <param name="curve">The curve to set the state of.</param>
/// <param name="state">The state to set for the curve.</param>
public void SetCurveState(SerializedProperty curve, CurveState state)
{
if (!m_Curves.ContainsKey(curve))
throw new KeyNotFoundException("curve");
m_Curves[curve] = state;
}
/// <summary>
/// Gets the current selection.
/// </summary>
/// <returns>The current selection.</returns>
public Selection GetSelection()
{
Keyframe? key = null;
if (m_SelectedKeyframeIndex > -1)
{
var curve = m_SelectedCurve.animationCurveValue;
if (m_SelectedKeyframeIndex >= curve.length)
m_SelectedKeyframeIndex = -1;
else
key = curve[m_SelectedKeyframeIndex];
}
return new Selection(m_SelectedCurve, m_SelectedKeyframeIndex, key);
}
/// <summary>
/// Sets a key for a given curve.
/// </summary>
/// <param name="curve">The curve to modify.</param>
/// <param name="keyframeIndex">The index of the key to set.</param>
/// <param name="keyframe">The new keyframe to put at the index.</param>
public void SetKeyframe(SerializedProperty curve, int keyframeIndex, Keyframe keyframe)
{
var animCurve = curve.animationCurveValue;
SetKeyframe(animCurve, keyframeIndex, keyframe);
SaveCurve(curve, animCurve);
}
/// <summary>
/// Draws the curve editor. This is meant to be called in your custom editors.
/// </summary>
/// <param name="rect">The rectangle to draw into.</param>
/// <returns><c>true</c> if the user modified the curve, <c>false</c> otherwise.</returns>
public bool OnGUI(Rect rect)
{
if (Event.current.type == EventType.Repaint)
m_Dirty = false;
GUI.BeginClip(rect);
{
var area = new Rect(Vector2.zero, rect.size);
m_CurveArea = settings.padding.Remove(area);
foreach (var curve in m_Curves)
OnCurveGUI(area, curve.Key, curve.Value);
OnGeneralUI();
}
GUI.EndClip();
return m_Dirty;
}
void OnCurveGUI(Rect rect, SerializedProperty curve, CurveState state)
{
// Discard invisible curves
if (!state.visible)
return;
var animCurve = curve.animationCurveValue;
var keys = animCurve.keys;
var length = keys.Length;
// Curve drawing
// Slightly dim non-editable curves
var color = state.color;
if (!state.editable || !GUI.enabled)
color.a *= 0.5f;
Handles.color = color;
var bounds = settings.bounds;
if (length == 0)
{
var p1 = CurveToCanvas(new Vector3(bounds.xMin, state.zeroKeyConstantValue));
var p2 = CurveToCanvas(new Vector3(bounds.xMax, state.zeroKeyConstantValue));
Handles.DrawAAPolyLine(state.width, p1, p2);
}
else if (length == 1)
{
var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value));
var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[0].value));
Handles.DrawAAPolyLine(state.width, p1, p2);
}
else
{
var prevKey = keys[0];
for (int k = 1; k < length; k++)
{
var key = keys[k];
var pts = BezierSegment(prevKey, key);
if (float.IsInfinity(prevKey.outTangent) || float.IsInfinity(key.inTangent))
{
var s = HardSegment(prevKey, key);
Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
}
else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
prevKey = key;
}
// Curve extents & loops
if (keys[0].time > bounds.xMin)
{
if (state.loopInBounds)
{
var p1 = keys[length - 1];
p1.time -= settings.bounds.width;
var p2 = keys[0];
var pts = BezierSegment(p1, p2);
if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent))
{
var s = HardSegment(p1, p2);
Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
}
else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
}
else
{
var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value));
var p2 = CurveToCanvas(keys[0]);
Handles.DrawAAPolyLine(state.width, p1, p2);
}
}
if (keys[length - 1].time < bounds.xMax)
{
if (state.loopInBounds)
{
var p1 = keys[length - 1];
var p2 = keys[0];
p2.time += settings.bounds.width;
var pts = BezierSegment(p1, p2);
if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent))
{
var s = HardSegment(p1, p2);
Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]);
}
else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width);
}
else
{
var p1 = CurveToCanvas(keys[length - 1]);
var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[length - 1].value));
Handles.DrawAAPolyLine(state.width, p1, p2);
}
}
}
// Make sure selection is correct (undo can break it)
bool isCurrentlySelectedCurve = curve == m_SelectedCurve;
if (isCurrentlySelectedCurve && m_SelectedKeyframeIndex >= length)
m_SelectedKeyframeIndex = -1;
if (!state.editable)
m_SelectedKeyframeIndex = -1;
float enabledFactor = GUI.enabled ? 1f : 0.8f;
// Handles & keys
for (int k = 0; k < length; k++)
{
bool isCurrentlySelectedKeyframe = k == m_SelectedKeyframeIndex;
var e = Event.current;
var pos = CurveToCanvas(keys[k]);
var hitRect = new Rect(pos.x - 8f, pos.y - 8f, 16f, 16f);
var offset = isCurrentlySelectedCurve
? new RectOffset(5, 5, 5, 5)
: new RectOffset(6, 6, 6, 6);
var outTangent = pos + CurveTangentToCanvas(keys[k].outTangent).normalized * 40f;
var inTangent = pos - CurveTangentToCanvas(keys[k].inTangent).normalized * 40f;
var inTangentHitRect = new Rect(inTangent.x - 7f, inTangent.y - 7f, 14f, 14f);
var outTangentHitrect = new Rect(outTangent.x - 7f, outTangent.y - 7f, 14f, 14f);
// Draw
if (state.editable || state.showNonEditableHandles)
{
if (e.type == EventType.Repaint)
{
var selectedColor = (isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
? settings.selectionColor
: state.color;
// Keyframe
EditorGUI.DrawRect(offset.Remove(hitRect), selectedColor * enabledFactor);
// Tangents
if (length > 1 && isCurrentlySelectedCurve && (!state.onlyShowHandlesOnSelection || (state.onlyShowHandlesOnSelection && isCurrentlySelectedKeyframe)))
{
Handles.color = selectedColor * enabledFactor;
if (k > 0 || state.loopInBounds)
{
Handles.DrawAAPolyLine(state.handleWidth, pos, inTangent);
EditorGUI.DrawRect(offset.Remove(inTangentHitRect), selectedColor);
}
if (k < length - 1 || state.loopInBounds)
{
Handles.DrawAAPolyLine(state.handleWidth, pos, outTangent);
EditorGUI.DrawRect(offset.Remove(outTangentHitrect), selectedColor);
}
}
}
}
// Events
if (state.editable)
{
// Keyframe move
if (m_EditMode == EditMode.Moving && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
{
EditMoveKeyframe(animCurve, keys, k);
}
// Tangent editing
if (length > 1 && m_EditMode == EditMode.TangentEdit && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe)
{
bool alreadyBroken = !(Mathf.Approximately(keys[k].inTangent, keys[k].outTangent) || (float.IsInfinity(keys[k].inTangent) && float.IsInfinity(keys[k].outTangent)));
EditMoveTangent(animCurve, keys, k, m_TangentEditMode, e.shift || !(alreadyBroken || e.control));
}
// Keyframe selection & context menu
if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition))
{
if (hitRect.Contains(e.mousePosition))
{
if (e.button == 0)
{
SelectKeyframe(curve, k);
m_EditMode = EditMode.Moving;
e.Use();
}
else if (e.button == 1)
{
// Keyframe context menu
var menu = new GenericMenu();
menu.AddItem(new GUIContent("Delete Key"), false, (x) =>
{
var action = (MenuAction)x;
var curveValue = action.curve.animationCurveValue;
action.curve.serializedObject.Update();
RemoveKeyframe(curveValue, action.index);
m_SelectedKeyframeIndex = -1;
SaveCurve(action.curve, curveValue);
action.curve.serializedObject.ApplyModifiedProperties();
}, new MenuAction(curve, k));
menu.ShowAsContext();
e.Use();
}
}
}
// Tangent selection & edit mode
if (e.type == EventType.MouseDown && length > 1 && rect.Contains(e.mousePosition))
{
if (inTangentHitRect.Contains(e.mousePosition) && (k > 0 || state.loopInBounds))
{
SelectKeyframe(curve, k);
m_EditMode = EditMode.TangentEdit;
m_TangentEditMode = Tangent.In;
e.Use();
}
else if (outTangentHitrect.Contains(e.mousePosition) && (k < length - 1 || state.loopInBounds))
{
SelectKeyframe(curve, k);
m_EditMode = EditMode.TangentEdit;
m_TangentEditMode = Tangent.Out;
e.Use();
}
}
// Mouse up - clean up states
if (e.rawType == EventType.MouseUp && m_EditMode != EditMode.None)
{
m_EditMode = EditMode.None;
}
// Set cursors
{
EditorGUIUtility.AddCursorRect(hitRect, MouseCursor.MoveArrow);
if (k > 0 || state.loopInBounds)
EditorGUIUtility.AddCursorRect(inTangentHitRect, MouseCursor.RotateArrow);
if (k < length - 1 || state.loopInBounds)
EditorGUIUtility.AddCursorRect(outTangentHitrect, MouseCursor.RotateArrow);
}
}
}
Handles.color = Color.white;
SaveCurve(curve, animCurve);
}
void OnGeneralUI()
{
var e = Event.current;
// Selection
if (e.type == EventType.MouseDown)
{
GUI.FocusControl(null);
m_SelectedCurve = null;
m_SelectedKeyframeIndex = -1;
bool used = false;
var hit = CanvasToCurve(e.mousePosition);
float curvePickValue = CurveToCanvas(hit).y;
// Try and select a curve
foreach (var curve in m_Curves)
{
if (!curve.Value.editable || !curve.Value.visible)
continue;
var prop = curve.Key;
var state = curve.Value;
var animCurve = prop.animationCurveValue;
float hitY = animCurve.length == 0
? state.zeroKeyConstantValue
: animCurve.Evaluate(hit.x);
var curvePos = CurveToCanvas(new Vector3(hit.x, hitY));
if (Mathf.Abs(curvePos.y - curvePickValue) < settings.curvePickingDistance)
{
m_SelectedCurve = prop;
if (e.clickCount == 2 && e.button == 0)
{
// Create a keyframe on double-click on this curve
EditCreateKeyframe(animCurve, hit, true, state.zeroKeyConstantValue);
SaveCurve(prop, animCurve);
}
else if (e.button == 1)
{
// Curve context menu
var menu = new GenericMenu();
menu.AddItem(new GUIContent("Add Key"), false, (x) =>
{
var action = (MenuAction)x;
var curveValue = action.curve.animationCurveValue;
action.curve.serializedObject.Update();
EditCreateKeyframe(curveValue, hit, true, 0f);
SaveCurve(action.curve, curveValue);
action.curve.serializedObject.ApplyModifiedProperties();
}, new MenuAction(prop, hit));
menu.ShowAsContext();
e.Use();
used = true;
}
}
}
if (e.clickCount == 2 && e.button == 0 && m_SelectedCurve == null)
{
// Create a keyframe on every curve on double-click
foreach (var curve in m_Curves)
{
if (!curve.Value.editable || !curve.Value.visible)
continue;
var prop = curve.Key;
var state = curve.Value;
var animCurve = prop.animationCurveValue;
EditCreateKeyframe(animCurve, hit, e.alt, state.zeroKeyConstantValue);
SaveCurve(prop, animCurve);
}
}
else if (!used && e.button == 1)
{
// Global context menu
var menu = new GenericMenu();
menu.AddItem(new GUIContent("Add Key At Position"), false, () => ContextMenuAddKey(hit, false));
menu.AddItem(new GUIContent("Add Key On Curves"), false, () => ContextMenuAddKey(hit, true));
menu.ShowAsContext();
}
e.Use();
}
// Delete selected key(s)
if (e.type == EventType.KeyDown && (e.keyCode == KeyCode.Delete || e.keyCode == KeyCode.Backspace))
{
if (m_SelectedKeyframeIndex != -1 && m_SelectedCurve != null)
{
var animCurve = m_SelectedCurve.animationCurveValue;
var length = animCurve.length;
if (m_Curves[m_SelectedCurve].minPointCount < length && length >= 0)
{
EditDeleteKeyframe(animCurve, m_SelectedKeyframeIndex);
m_SelectedKeyframeIndex = -1;
SaveCurve(m_SelectedCurve, animCurve);
}
e.Use();
}
}
}
void SaveCurve(SerializedProperty prop, AnimationCurve curve)
{
prop.animationCurveValue = curve;
}
void Invalidate()
{
m_Dirty = true;
}
void SelectKeyframe(SerializedProperty curve, int keyframeIndex)
{
m_SelectedKeyframeIndex = keyframeIndex;
m_SelectedCurve = curve;
Invalidate();
}
void ContextMenuAddKey(Vector3 hit, bool createOnCurve)
{
SerializedObject serializedObject = null;
foreach (var curve in m_Curves)
{
if (!curve.Value.editable || !curve.Value.visible)
continue;
var prop = curve.Key;
var state = curve.Value;
if (serializedObject == null)
{
serializedObject = prop.serializedObject;
serializedObject.Update();
}
var animCurve = prop.animationCurveValue;
EditCreateKeyframe(animCurve, hit, createOnCurve, state.zeroKeyConstantValue);
SaveCurve(prop, animCurve);
}
if (serializedObject != null)
serializedObject.ApplyModifiedProperties();
Invalidate();
}
void EditCreateKeyframe(AnimationCurve curve, Vector3 position, bool createOnCurve, float zeroKeyConstantValue)
{
float tangent = EvaluateTangent(curve, position.x);
if (createOnCurve)
{
position.y = curve.length == 0
? zeroKeyConstantValue
: curve.Evaluate(position.x);
}
AddKeyframe(curve, new Keyframe(position.x, position.y, tangent, tangent));
}
void EditDeleteKeyframe(AnimationCurve curve, int keyframeIndex)
{
RemoveKeyframe(curve, keyframeIndex);
}
void AddKeyframe(AnimationCurve curve, Keyframe newValue)
{
curve.AddKey(newValue);
Invalidate();
}
void RemoveKeyframe(AnimationCurve curve, int keyframeIndex)
{
curve.RemoveKey(keyframeIndex);
Invalidate();
}
void SetKeyframe(AnimationCurve curve, int keyframeIndex, Keyframe newValue)
{
var keys = curve.keys;
if (keyframeIndex > 0)
newValue.time = Mathf.Max(keys[keyframeIndex - 1].time + settings.keyTimeClampingDistance, newValue.time);
if (keyframeIndex < keys.Length - 1)
newValue.time = Mathf.Min(keys[keyframeIndex + 1].time - settings.keyTimeClampingDistance, newValue.time);
curve.MoveKey(keyframeIndex, newValue);
Invalidate();
}
void EditMoveKeyframe(AnimationCurve curve, Keyframe[] keys, int keyframeIndex)
{
var key = CanvasToCurve(Event.current.mousePosition);
float inTgt = keys[keyframeIndex].inTangent;
float outTgt = keys[keyframeIndex].outTangent;
SetKeyframe(curve, keyframeIndex, new Keyframe(key.x, key.y, inTgt, outTgt));
}
void EditMoveTangent(AnimationCurve curve, Keyframe[] keys, int keyframeIndex, Tangent targetTangent, bool linkTangents)
{
var pos = CanvasToCurve(Event.current.mousePosition);
float time = keys[keyframeIndex].time;
float value = keys[keyframeIndex].value;
pos -= new Vector3(time, value);
if (targetTangent == Tangent.In && pos.x > 0f)
pos.x = 0f;
if (targetTangent == Tangent.Out && pos.x < 0f)
pos.x = 0f;
float tangent;
if (Mathf.Approximately(pos.x, 0f))
tangent = float.PositiveInfinity;
else
tangent = pos.y / pos.x;
float inTangent = keys[keyframeIndex].inTangent;
float outTangent = keys[keyframeIndex].outTangent;
if (targetTangent == Tangent.In || linkTangents)
inTangent = tangent;
if (targetTangent == Tangent.Out || linkTangents)
outTangent = tangent;
SetKeyframe(curve, keyframeIndex, new Keyframe(time, value, inTangent, outTangent));
}
Vector3 CurveToCanvas(Keyframe keyframe)
{
return CurveToCanvas(new Vector3(keyframe.time, keyframe.value));
}
Vector3 CurveToCanvas(Vector3 position)
{
var bounds = settings.bounds;
var output = new Vector3((position.x - bounds.x) / (bounds.xMax - bounds.x), (position.y - bounds.y) / (bounds.yMax - bounds.y));
output.x = output.x * (m_CurveArea.xMax - m_CurveArea.xMin) + m_CurveArea.xMin;
output.y = (1f - output.y) * (m_CurveArea.yMax - m_CurveArea.yMin) + m_CurveArea.yMin;
return output;
}
Vector3 CanvasToCurve(Vector3 position)
{
var bounds = settings.bounds;
var output = position;
output.x = (output.x - m_CurveArea.xMin) / (m_CurveArea.xMax - m_CurveArea.xMin);
output.y = (output.y - m_CurveArea.yMin) / (m_CurveArea.yMax - m_CurveArea.yMin);
output.x = Mathf.Lerp(bounds.x, bounds.xMax, output.x);
output.y = Mathf.Lerp(bounds.yMax, bounds.y, output.y);
return output;
}
Vector3 CurveTangentToCanvas(float tangent)
{
if (!float.IsInfinity(tangent))
{
var bounds = settings.bounds;
float ratio = (m_CurveArea.width / m_CurveArea.height) / ((bounds.xMax - bounds.x) / (bounds.yMax - bounds.y));
return new Vector3(1f, -tangent / ratio).normalized;
}
return Vector3.up; // Positive infinity
}
Vector3[] BezierSegment(Keyframe start, Keyframe end)
{
var segment = new Vector3[4];
segment[0] = CurveToCanvas(new Vector3(start.time, start.value));
segment[3] = CurveToCanvas(new Vector3(end.time, end.value));
float middle = start.time + ((end.time - start.time) * 0.333333f);
float middle2 = start.time + ((end.time - start.time) * 0.666666f);
segment[1] = CurveToCanvas(new Vector3(middle, ProjectTangent(start.time, start.value, start.outTangent, middle)));
segment[2] = CurveToCanvas(new Vector3(middle2, ProjectTangent(end.time, end.value, end.inTangent, middle2)));
return segment;
}
Vector3[] HardSegment(Keyframe start, Keyframe end)
{
var segment = new Vector3[3];
segment[0] = CurveToCanvas(start);
segment[1] = CurveToCanvas(new Vector3(end.time, start.value));
segment[2] = CurveToCanvas(end);
return segment;
}
float ProjectTangent(float inPosition, float inValue, float inTangent, float projPosition)
{
return inValue + ((projPosition - inPosition) * inTangent);
}
float EvaluateTangent(AnimationCurve curve, float time)
{
int prev = -1, next = 0;
for (int i = 0; i < curve.keys.Length; i++)
{
if (time > curve.keys[i].time)
{
prev = i;
next = i + 1;
}
else break;
}
if (next == 0)
return 0f;
if (prev == curve.keys.Length - 1)
return 0f;
const float kD = 1e-3f;
float tp = Mathf.Max(time - kD, curve.keys[prev].time);
float tn = Mathf.Min(time + kD, curve.keys[next].time);
float vp = curve.Evaluate(tp);
float vn = curve.Evaluate(tn);
if (Mathf.Approximately(tn, tp))
return float.PositiveInfinity;
return (vn - vp) / (tn - tp);
}
}
}

Some files were not shown because too many files have changed in this diff Show More