From d7ff7b0bf2d691f34494fa7b3b7879de955ff26a Mon Sep 17 00:00:00 2001 From: VladV Date: Mon, 3 Aug 2026 17:31:17 +0400 Subject: [PATCH 1/6] Removed UiToolkit-in-IMGUI support --- Editor.Extras/Drawers/CustomBuiltInDrawer.cs | 30 +++--- Editor/Editors/TriEditorCore.cs | 10 -- .../Elements/TriUiToolkitPropertyElemenet.cs | 91 ------------------- .../TriUiToolkitPropertyElemenet.cs.meta | 3 - .../Others/DrawWithUnityAttribute.cs | 1 - 5 files changed, 15 insertions(+), 120 deletions(-) delete mode 100644 Editor/Elements/TriUiToolkitPropertyElemenet.cs delete mode 100644 Editor/Elements/TriUiToolkitPropertyElemenet.cs.meta diff --git a/Editor.Extras/Drawers/CustomBuiltInDrawer.cs b/Editor.Extras/Drawers/CustomBuiltInDrawer.cs index c4514ade..165fbc29 100644 --- a/Editor.Extras/Drawers/CustomBuiltInDrawer.cs +++ b/Editor.Extras/Drawers/CustomBuiltInDrawer.cs @@ -25,21 +25,21 @@ public override TriElement CreateElement(TriValue propertyValue, TriElem if (drawWithHandler) { - if (property.TryGetAttribute(out DrawWithUnityAttribute withUnityAttribute) && - withUnityAttribute.WithUiToolkit) - { - handler.SetPreferredLabel(property.DisplayName); - - var visualElement = handler.CreatePropertyGUI(serializedProperty); - - if (visualElement != null && - TriEditorCore.UiElementsRoots.TryGetValue(property.PropertyTree, out var rootElement)) - { - return new TriUiToolkitPropertyElement(property, serializedProperty, - visualElement, rootElement); - } - } - + // if (property.TryGetAttribute(out DrawWithUnityAttribute withUnityAttribute) && + // withUnityAttribute.WithUiToolkit) + // { + // handler.SetPreferredLabel(property.DisplayName); + // + // var visualElement = handler.CreatePropertyGUI(serializedProperty); + // + // if (visualElement != null && + // TriEditorCore.UiElementsRoots.TryGetValue(property.PropertyTree, out var rootElement)) + // { + // return new TriUiToolkitPropertyElement(property, serializedProperty, + // visualElement, rootElement); + // } + // } + return new TriBuiltInPropertyElement(property, serializedProperty, handler); } } diff --git a/Editor/Editors/TriEditorCore.cs b/Editor/Editors/TriEditorCore.cs index dc7334d7..8e9296cf 100644 --- a/Editor/Editors/TriEditorCore.cs +++ b/Editor/Editors/TriEditorCore.cs @@ -8,9 +8,6 @@ namespace TriInspector.Editors { public class TriEditorCore { - internal static readonly Dictionary UiElementsRoots - = new Dictionary(); - private readonly Editor _editor; private TriPropertyTreeForSerializedObject _inspector; @@ -24,8 +21,6 @@ public void Dispose() { if (_inspector != null) { - UiElementsRoots.Remove(_inspector); - _inspector.Dispose(); } @@ -61,11 +56,6 @@ public void OnInspectorGUI(VisualElement visualRoot = null) _inspector = new TriPropertyTreeForSerializedObject(serializedObject); } - if (visualRoot != null) - { - UiElementsRoots[_inspector] = visualRoot; - } - serializedObject.UpdateIfRequiredOrScript(); _inspector.Update(); diff --git a/Editor/Elements/TriUiToolkitPropertyElemenet.cs b/Editor/Elements/TriUiToolkitPropertyElemenet.cs deleted file mode 100644 index 8499415a..00000000 --- a/Editor/Elements/TriUiToolkitPropertyElemenet.cs +++ /dev/null @@ -1,91 +0,0 @@ -using TriInspectorUnityInternalBridge; -using UnityEditor; -using UnityEditor.UIElements; -using UnityEngine; -using UnityEngine.UIElements; - -namespace TriInspector.Elements -{ - internal class TriUiToolkitPropertyElement : TriElement - { - private readonly SerializedProperty _serializedProperty; - - private readonly VisualElement _rootElement; - private readonly VisualElement _selfElement; - - private bool _heightDirty; - - public TriUiToolkitPropertyElement( - TriProperty property, - SerializedProperty serializedProperty, - VisualElement selfElement, - VisualElement rootElement) - { - _serializedProperty = serializedProperty; - _selfElement = selfElement; - _rootElement = rootElement; - - _selfElement.style.position = Position.Absolute; - } - - protected override void OnAttachToPanel() - { - base.OnAttachToPanel(); - - _rootElement.schedule.Execute(() => - { - _rootElement.Add(_selfElement); - _selfElement.Bind(_serializedProperty.serializedObject); - }); - } - - protected override void OnDetachFromPanel() - { - _rootElement.schedule.Execute(() => - { - _selfElement.Unbind(); - _rootElement.Remove(_selfElement); - }); - - base.OnDetachFromPanel(); - } - - public override bool Update() - { - var dirty = base.Update(); - - if (_heightDirty) - { - _heightDirty = false; - dirty = true; - } - - return dirty; - } - - public override float GetHeight(float width) - { - var height = _selfElement.resolvedStyle.height; - - if (float.IsNaN(height)) - { - _heightDirty = true; - return 0f; - } - - return height; - } - - public override void OnGUI(Rect position) - { - if (Event.current.type == EventType.Repaint) - { - var pos = GUIClipProxy.UnClip(position.position); - - _selfElement.style.width = position.width; - _selfElement.style.left = pos.x; - _selfElement.style.top = pos.y; - } - } - } -} \ No newline at end of file diff --git a/Editor/Elements/TriUiToolkitPropertyElemenet.cs.meta b/Editor/Elements/TriUiToolkitPropertyElemenet.cs.meta deleted file mode 100644 index a43f1c8a..00000000 --- a/Editor/Elements/TriUiToolkitPropertyElemenet.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 535ce5f65f424a8c9e83943eda845fc6 -timeCreated: 1690621289 \ No newline at end of file diff --git a/Runtime/Attributes/Others/DrawWithUnityAttribute.cs b/Runtime/Attributes/Others/DrawWithUnityAttribute.cs index a357df08..4e13b90f 100644 --- a/Runtime/Attributes/Others/DrawWithUnityAttribute.cs +++ b/Runtime/Attributes/Others/DrawWithUnityAttribute.cs @@ -7,6 +7,5 @@ namespace TriInspector [Conditional("UNITY_EDITOR")] public class DrawWithUnityAttribute : Attribute { - public bool WithUiToolkit { get; set; } } } \ No newline at end of file From f081689c8dc7ba34dadefb1ba8bbc9d763faa1af Mon Sep 17 00:00:00 2001 From: VladV Date: Tue, 4 Aug 2026 17:53:50 +0400 Subject: [PATCH 2/6] Add UI Toolkit support for core elements --- Editor.Extras/Drawers/CustomBuiltInDrawer.cs | 23 +- .../Drawers/ObjectReferenceDrawer.cs | 40 +--- Editor.Extras/Drawers/TableListDrawer.cs | 17 +- Editor.Integrations/Odin/OdinFieldDrawer.cs | 8 +- Editor/Editors/TriEditor.cs | 6 - Editor/Editors/TriEditorCore.cs | 108 +++------ Editor/Editors/TriScriptedImporterEditor.cs | 7 - Editor/Elements/TriBuiltInPropertyElement.cs | 16 +- Editor/Elements/TriFoldoutElement.cs | 47 ++++ Editor/Elements/TriImguiContainerImpl.cs | 125 ++++++++++ Editor/Elements/TriImguiContainerImpl.cs.meta | 2 + Editor/Elements/TriInlineGenericElement.cs | 22 ++ Editor/Elements/TriListElement.cs | 166 ++++++++++--- Editor/Elements/TriNativeProjection.cs | 29 +++ Editor/Elements/TriNativeProjection.cs.meta | 2 + Editor/Elements/TriNoDrawerElement.cs | 16 +- Editor/Elements/TriObjectReferenceElement.cs | 68 ++++++ .../TriObjectReferenceElement.cs.meta | 2 + .../TriPropertyCollectionBaseElement.cs | 14 ++ Editor/Elements/TriPropertyElement.cs | 31 ++- Editor/Elements/TriReferenceElement.cs | 118 ++++++++++ Editor/Elements/TriValidatorsElement.cs | 219 ++++++++++++++++++ Editor/Elements/TriValidatorsElement.cs.meta | 2 + Editor/ITriElement.cs | 9 + Editor/ITriElement.cs.meta | 2 + Editor/TriElement.cs | 21 +- Editor/TriProperty.cs | 3 +- Editor/TriPropertyOverrideContext.cs | 53 +---- Editor/TriPropertyTree.cs | 61 ++--- Editor/TriPropertyTreeForSerializedObject.cs | 21 -- Editor/ValidatorsDrawer.cs | 70 +----- Editor/VisualElementExtensions.cs | 20 ++ Editor/VisualElementExtensions.cs.meta | 3 + Editor/VisualElements.meta | 3 + Editor/VisualElements/TriAlignedLabel.cs | 12 + Editor/VisualElements/TriAlignedLabel.cs.meta | 3 + .../TriLabelWidthContextElement.cs | 16 ++ .../TriLabelWidthContextElement.cs.meta | 3 + Editor/VisualElements/TriPropertyField.cs | 37 +++ .../VisualElements/TriPropertyField.cs.meta | 3 + package.json | 4 +- 41 files changed, 1061 insertions(+), 371 deletions(-) create mode 100644 Editor/Elements/TriImguiContainerImpl.cs create mode 100644 Editor/Elements/TriImguiContainerImpl.cs.meta create mode 100644 Editor/Elements/TriNativeProjection.cs create mode 100644 Editor/Elements/TriNativeProjection.cs.meta create mode 100644 Editor/Elements/TriObjectReferenceElement.cs create mode 100644 Editor/Elements/TriObjectReferenceElement.cs.meta create mode 100644 Editor/Elements/TriValidatorsElement.cs create mode 100644 Editor/Elements/TriValidatorsElement.cs.meta create mode 100644 Editor/ITriElement.cs create mode 100644 Editor/ITriElement.cs.meta create mode 100644 Editor/VisualElementExtensions.cs create mode 100644 Editor/VisualElementExtensions.cs.meta create mode 100644 Editor/VisualElements.meta create mode 100644 Editor/VisualElements/TriAlignedLabel.cs create mode 100644 Editor/VisualElements/TriAlignedLabel.cs.meta create mode 100644 Editor/VisualElements/TriLabelWidthContextElement.cs create mode 100644 Editor/VisualElements/TriLabelWidthContextElement.cs.meta create mode 100644 Editor/VisualElements/TriPropertyField.cs create mode 100644 Editor/VisualElements/TriPropertyField.cs.meta diff --git a/Editor.Extras/Drawers/CustomBuiltInDrawer.cs b/Editor.Extras/Drawers/CustomBuiltInDrawer.cs index 165fbc29..e573ba60 100644 --- a/Editor.Extras/Drawers/CustomBuiltInDrawer.cs +++ b/Editor.Extras/Drawers/CustomBuiltInDrawer.cs @@ -1,6 +1,5 @@ using TriInspector; using TriInspector.Drawers; -using TriInspector.Editors; using TriInspector.Elements; using TriInspector.Utilities; using TriInspectorUnityInternalBridge; @@ -13,6 +12,11 @@ public class CustomBuiltInDrawer : TriValueDrawer { public override TriElement CreateElement(TriValue propertyValue, TriElement next) { + if (propertyValue.Property.IsRootProperty) + { + return next; + } + var property = propertyValue.Property; if (property.TryGetSerializedProperty(out var serializedProperty)) @@ -25,26 +29,11 @@ public override TriElement CreateElement(TriValue propertyValue, TriElem if (drawWithHandler) { - // if (property.TryGetAttribute(out DrawWithUnityAttribute withUnityAttribute) && - // withUnityAttribute.WithUiToolkit) - // { - // handler.SetPreferredLabel(property.DisplayName); - // - // var visualElement = handler.CreatePropertyGUI(serializedProperty); - // - // if (visualElement != null && - // TriEditorCore.UiElementsRoots.TryGetValue(property.PropertyTree, out var rootElement)) - // { - // return new TriUiToolkitPropertyElement(property, serializedProperty, - // visualElement, rootElement); - // } - // } - return new TriBuiltInPropertyElement(property, serializedProperty, handler); } } - return base.CreateElement(propertyValue, next); + return next; } } } \ No newline at end of file diff --git a/Editor.Extras/Drawers/ObjectReferenceDrawer.cs b/Editor.Extras/Drawers/ObjectReferenceDrawer.cs index e77dc1ec..bba7fbe4 100644 --- a/Editor.Extras/Drawers/ObjectReferenceDrawer.cs +++ b/Editor.Extras/Drawers/ObjectReferenceDrawer.cs @@ -1,6 +1,6 @@ -using TriInspector; +using TriInspector; using TriInspector.Drawers; -using UnityEditor; +using TriInspector.Elements; using UnityEngine; [assembly: RegisterTriValueDrawer(typeof(ObjectReferenceDrawer), TriDrawerOrder.Fallback)] @@ -16,39 +16,7 @@ public override TriElement CreateElement(TriValue value, TriElement next return next; } - return new ObjectReferenceDrawerElement(value); - } - - private class ObjectReferenceDrawerElement : TriElement - { - private TriValue _propertyValue; - private readonly bool _allowSceneObjects; - - public ObjectReferenceDrawerElement(TriValue propertyValue) - { - _propertyValue = propertyValue; - _allowSceneObjects = propertyValue.Property.PropertyTree.TargetIsPersistent == false; - } - - public override float GetHeight(float width) - { - return EditorGUIUtility.singleLineHeight; - } - - public override void OnGUI(Rect position) - { - var value = _propertyValue.SmartValue; - - EditorGUI.BeginChangeCheck(); - - value = EditorGUI.ObjectField(position, _propertyValue.Property.DisplayNameContent, value, - _propertyValue.Property.FieldType, _allowSceneObjects); - - if (EditorGUI.EndChangeCheck()) - { - _propertyValue.SetValue(value); - } - } + return new TriObjectReferenceElement(value); } } -} \ No newline at end of file +} diff --git a/Editor.Extras/Drawers/TableListDrawer.cs b/Editor.Extras/Drawers/TableListDrawer.cs index 963c8ce7..863f3eca 100644 --- a/Editor.Extras/Drawers/TableListDrawer.cs +++ b/Editor.Extras/Drawers/TableListDrawer.cs @@ -60,6 +60,20 @@ public TableElement(TriProperty property) : base(property) _reloadRequired = true; } + protected override void OnAttachToPanel() + { + base.OnAttachToPanel(); + + _property.PropertyTree.AddPropertyOverride(_treeView.OverrideContext); + } + + protected override void OnDetachFromPanel() + { + _property.PropertyTree.RemovePropertyOverride(_treeView.OverrideContext); + + base.OnDetachFromPanel(); + } + public override bool Update() { var dirty = base.Update(); @@ -216,6 +230,8 @@ public TableMultiColumnTreeView(TriProperty property, TriElement container, Reor public float Width { get; set; } + public TriPropertyOverrideContext OverrideContext => _propertyOverrideContext; + public void RefreshHeight() { RefreshCustomRowHeights(); @@ -339,7 +355,6 @@ protected override void RowGUI(RowGUIArgs args) cellRect.height = cellElement.GetHeight(cellRect.width); using (TriGuiHelper.PushLabelWidth(EditorGUIUtility.labelWidth / rowElement.ChildrenCount)) - using (TriPropertyOverrideContext.BeginOverride(_propertyOverrideContext)) { cellElement.OnGUI(cellRect); } diff --git a/Editor.Integrations/Odin/OdinFieldDrawer.cs b/Editor.Integrations/Odin/OdinFieldDrawer.cs index ad86df1c..d2c6b1a5 100644 --- a/Editor.Integrations/Odin/OdinFieldDrawer.cs +++ b/Editor.Integrations/Odin/OdinFieldDrawer.cs @@ -70,6 +70,9 @@ protected override void DrawPropertyLayout(GUIContent label) _initialized = true; _propertyTree = new TriPropertyTreeForOdin(ValueEntry); _labelOverrideContext = new LabelOverrideContext(_propertyTree); + + // Scoped to the root property, so it is safe to keep registered for the tree's whole lifetime. + _propertyTree.AddPropertyOverride(_labelOverrideContext); } _propertyTree.Update(); @@ -77,10 +80,7 @@ protected override void DrawPropertyLayout(GUIContent label) _labelOverrideContext.Label = label ?? GUIContent.none; - using (TriPropertyOverrideContext.BeginOverride(_labelOverrideContext)) - { - _propertyTree.Draw(); - } + _propertyTree.Draw(); if (_propertyTree.RepaintRequired) { diff --git a/Editor/Editors/TriEditor.cs b/Editor/Editors/TriEditor.cs index 668e7829..e49a35ce 100644 --- a/Editor/Editors/TriEditor.cs +++ b/Editor/Editors/TriEditor.cs @@ -17,12 +17,6 @@ protected virtual void OnDisable() _core.Dispose(); } - - public override void OnInspectorGUI() - { - _core.OnInspectorGUI(); - } - public override VisualElement CreateInspectorGUI() { return _core.CreateVisualElement(); diff --git a/Editor/Editors/TriEditorCore.cs b/Editor/Editors/TriEditorCore.cs index 8e9296cf..825f5b90 100644 --- a/Editor/Editors/TriEditorCore.cs +++ b/Editor/Editors/TriEditorCore.cs @@ -1,7 +1,5 @@ -using System.Collections.Generic; -using TriInspector.Utilities; -using UnityEditor; -using UnityEngine; +using UnityEditor; +using UnityEditor.UIElements; using UnityEngine.UIElements; namespace TriInspector.Editors @@ -27,108 +25,54 @@ public void Dispose() _inspector = null; } - public void OnInspectorGUI(VisualElement visualRoot = null) + public VisualElement CreateVisualElement() { var serializedObject = _editor.serializedObject; - if (serializedObject.targetObjects.Length == 0) + var container = new VisualElement(); + + if (serializedObject.targetObjects.Length == 0 || serializedObject.targetObject == null) { - return; + container.Add(new HelpBox("Script is missing", HelpBoxMessageType.Warning)); + return container; } - if (serializedObject.targetObject == null) + if (_inspector == null) { - EditorGUILayout.HelpBox("Script is missing", MessageType.Warning); - return; + _inspector = new TriPropertyTreeForSerializedObject(serializedObject); } - foreach (var targetObject in serializedObject.targetObjects) + if (!_inspector.RootProperty.TryGetAttribute(out HideMonoScriptAttribute _)) { - if (TriGuiHelper.IsEditorTargetPushed(targetObject)) + var scriptProperty = serializedObject.FindProperty("m_Script"); + if (scriptProperty != null) { - GUILayout.Label("Recursive inline editors not supported"); - return; + var scriptField = new PropertyField(scriptProperty); + scriptField.SetEnabled(false); + scriptField.Bind(serializedObject); + container.Add(scriptField); } } - if (_inspector == null) - { - _inspector = new TriPropertyTreeForSerializedObject(serializedObject); - } - serializedObject.UpdateIfRequiredOrScript(); - _inspector.Update(); - _inspector.RunValidationIfRequired(); - - EditorGUIUtility.hierarchyMode = false; - - using (TriGuiHelper.PushEditorTarget(serializedObject.targetObject)) - { - _inspector.Draw(); - } - - if (serializedObject.ApplyModifiedProperties()) - { - _inspector.RequestValidation(); - } - if (_inspector.RepaintRequired) - { - _editor.Repaint(); - } - } - - public VisualElement CreateVisualElement() - { - var container = new VisualElement(); - var root = new VisualElement() - { - style = - { - position = Position.Absolute, - }, - }; + container.Add(_inspector.GetRootElement().CreateVisualElement(_inspector.RootProperty)); - container.Add(new IMGUIContainer(() => + container.schedule.Execute(() => { - const float labelExtraPadding = 2; - const float labelWidthRatio = 0.45f; - const float labelMinWidth = 120; + serializedObject.UpdateIfRequiredOrScript(); - var space = container.resolvedStyle.left + container.resolvedStyle.right + labelExtraPadding; + _inspector.Update(); + _inspector.RunValidationIfRequired(); - EditorGUIUtility.wideMode = true; - EditorGUIUtility.hierarchyMode = false; - EditorGUIUtility.labelWidth = Mathf.Max(labelMinWidth, - container.resolvedStyle.width * labelWidthRatio - space); - - GUILayout.BeginVertical(Styles.RootLayout); - OnInspectorGUI(root); - GUILayout.EndVertical(); - }) - { - style = + if (serializedObject.ApplyModifiedProperties()) { - marginLeft = -Styles.RootMarginLeft, - marginRight = -Styles.RootMarginRight, - }, - }); - - container.Add(root); + _inspector.RequestValidation(); + } + }).Every(0); return container; } - - private static class Styles - { - public const int RootMarginLeft = 15; - public const int RootMarginRight = 6; - - public static readonly GUIStyle RootLayout = new GUIStyle - { - padding = new RectOffset(RootMarginLeft, RootMarginRight, 0, 0), - }; - } } } \ No newline at end of file diff --git a/Editor/Editors/TriScriptedImporterEditor.cs b/Editor/Editors/TriScriptedImporterEditor.cs index de4f90fe..f5a977f8 100644 --- a/Editor/Editors/TriScriptedImporterEditor.cs +++ b/Editor/Editors/TriScriptedImporterEditor.cs @@ -30,13 +30,6 @@ public override void OnDisable() base.OnDisable(); } - public override void OnInspectorGUI() - { - _core.OnInspectorGUI(); - - ApplyRevertGUI(); - } - public override VisualElement CreateInspectorGUI() { var root = new VisualElement(); diff --git a/Editor/Elements/TriBuiltInPropertyElement.cs b/Editor/Elements/TriBuiltInPropertyElement.cs index 0233c459..26adfa6c 100644 --- a/Editor/Elements/TriBuiltInPropertyElement.cs +++ b/Editor/Elements/TriBuiltInPropertyElement.cs @@ -1,6 +1,9 @@ -using TriInspectorUnityInternalBridge; +using TriInspector.VisualElements; +using TriInspectorUnityInternalBridge; using UnityEditor; +using UnityEditor.UIElements; using UnityEngine; +using UnityEngine.UIElements; namespace TriInspector.Elements { @@ -20,6 +23,17 @@ public TriBuiltInPropertyElement( _propertyHandler = propertyHandler; } + public override VisualElement CreateVisualElement(TriProperty property) + { + var label = _property.DisplayNameContent?.text ?? string.Empty; + + var field = new TriPropertyField(_serializedProperty, label); + field.BindProperty(_serializedProperty); + field.TrackPropertyValue(_serializedProperty, _ => _property.NotifyValueChanged()); + + return field; + } + public override float GetHeight(float width) { return _propertyHandler.GetHeight(_serializedProperty, _property.DisplayNameContent, true); diff --git a/Editor/Elements/TriFoldoutElement.cs b/Editor/Elements/TriFoldoutElement.cs index d2d92071..9bf938f2 100644 --- a/Editor/Elements/TriFoldoutElement.cs +++ b/Editor/Elements/TriFoldoutElement.cs @@ -1,6 +1,7 @@ using TriInspector.Utilities; using UnityEditor; using UnityEngine; +using UnityEngine.UIElements; namespace TriInspector.Elements { @@ -15,6 +16,52 @@ public TriFoldoutElement(TriProperty property) DeclareGroups(property.ValueType); } + public override VisualElement CreateVisualElement(TriProperty property) + { + var foldout = new Foldout + { + text = _property.DisplayName, + value = _property.IsExpanded, + }; + + var built = false; + + void BuildContentIfNeeded() + { + if (built) + { + return; + } + + built = true; + + GenerateChildren(); + foldout.Add(CreateChildrenColumn(property)); + } + + if (_property.IsExpanded) + { + BuildContentIfNeeded(); + } + + foldout.RegisterValueChangedCallback(evt => + { + if (evt.target != foldout) + { + return; + } + + _property.IsExpanded = evt.newValue; + + if (evt.newValue) + { + BuildContentIfNeeded(); + } + }); + + return foldout; + } + public override bool Update() { var dirty = false; diff --git a/Editor/Elements/TriImguiContainerImpl.cs b/Editor/Elements/TriImguiContainerImpl.cs new file mode 100644 index 00000000..e59f219d --- /dev/null +++ b/Editor/Elements/TriImguiContainerImpl.cs @@ -0,0 +1,125 @@ +using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; + +namespace TriInspector.Elements +{ + internal sealed class TriImguiContainerImpl : IMGUIContainer, ITriElement + { + private readonly TriProperty _property; + private readonly TriElement _element; + private readonly bool _applyPropertyContext; + + public TriImguiContainerImpl(TriProperty property, TriElement element, bool applyPropertyContext = false) + { + _property = property; + _element = element; + _applyPropertyContext = applyPropertyContext; + + onGUIHandler = OnElementGui; + + RegisterCallback(OnAttach); + RegisterCallback(OnDetach); + + // red color for legacy IMGUI elements so we can see which elements still not migrated + style.borderLeftColor = new StyleColor(Color.red); + style.borderLeftWidth = new StyleFloat(2); + style.backgroundColor = new StyleColor(new Color(1f, 0f, 0f, 0.05f)); + } + + public VisualElement CreateVisualElement(TriProperty property) + { + return this; + } + + private void OnAttach(AttachToPanelEvent evt) + { + if (!_element.IsAttached) + { + _element.AttachInternal(); + } + } + + private void OnDetach(DetachFromPanelEvent evt) + { + if (_element.IsAttached) + { + _element.DetachInternal(); + } + } + + private void OnElementGui() + { + if (!_element.IsAttached) + { + return; + } + + var hasSerializedProperty = _property.TryGetSerializedProperty(out var serializedProperty); + if (hasSerializedProperty) + { + serializedProperty.serializedObject.UpdateIfRequiredOrScript(); + } + + _element.Update(); + + var width = contentRect.width; + + if (width <= 0f || float.IsNaN(width)) + { + return; + } + + const float labelWidthRatio = 0.45f; + const float labelMinWidth = 120f; + EditorGUIUtility.wideMode = true; + EditorGUIUtility.hierarchyMode = false; + EditorGUIUtility.labelWidth = Mathf.Max(labelMinWidth, width * labelWidthRatio - 2f); + + var height = _element.GetHeight(width); + var rect = GUILayoutUtility.GetRect(width, height); + + if (_applyPropertyContext) + { + DrawElementWithPropertyContext(rect, hasSerializedProperty, serializedProperty); + } + else + { + _element.OnGUI(rect); + } + + if (hasSerializedProperty) + { + if (serializedProperty.serializedObject.ApplyModifiedProperties()) + { + _property.PropertyTree.RequestValidation(); + } + } + } + + private void DrawElementWithPropertyContext(Rect rect, bool hasSerializedProperty, + SerializedProperty serializedProperty) + { + var oldShowMixedValue = EditorGUI.showMixedValue; + var oldEnabled = GUI.enabled; + + GUI.enabled &= _property.IsEnabled; + EditorGUI.showMixedValue = _property.IsValueMixed; + + if (hasSerializedProperty) + { + EditorGUI.BeginProperty(rect, null, serializedProperty); + } + + _element.OnGUI(rect); + + if (hasSerializedProperty) + { + EditorGUI.EndProperty(); + } + + EditorGUI.showMixedValue = oldShowMixedValue; + GUI.enabled = oldEnabled; + } + } +} diff --git a/Editor/Elements/TriImguiContainerImpl.cs.meta b/Editor/Elements/TriImguiContainerImpl.cs.meta new file mode 100644 index 00000000..bfe8b91d --- /dev/null +++ b/Editor/Elements/TriImguiContainerImpl.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 632510e943ac7a54bbcba2014cafb1e2 \ No newline at end of file diff --git a/Editor/Elements/TriInlineGenericElement.cs b/Editor/Elements/TriInlineGenericElement.cs index d3fff047..92a9a7b8 100644 --- a/Editor/Elements/TriInlineGenericElement.cs +++ b/Editor/Elements/TriInlineGenericElement.cs @@ -1,7 +1,9 @@ using System; using TriInspector.Utilities; +using TriInspector.VisualElements; using UnityEditor; using UnityEngine; +using UnityEngine.UIElements; namespace TriInspector.Elements { @@ -30,6 +32,26 @@ public TriInlineGenericElement(TriProperty property, Props props = default) } } + public override VisualElement CreateVisualElement(TriProperty property) + { + var content = CreateChildrenColumn(property); + + if (_props.labelWidth > 0) + { + content = new TriLabelWidthContextElement(_props.labelWidth, content); + } + + if (!_props.drawPrefixLabel) + { + return content; + } + + // Unity aligns property labels relative to element with this style + content.AddToClassList("unity-inspector-main-container"); + + return new TriAlignedLabel(_property.DisplayName, content); + } + public override void OnGUI(Rect position) { if (_props.drawPrefixLabel) diff --git a/Editor/Elements/TriListElement.cs b/Editor/Elements/TriListElement.cs index 43e03425..2e29aef5 100644 --- a/Editor/Elements/TriListElement.cs +++ b/Editor/Elements/TriListElement.cs @@ -4,8 +4,10 @@ using TriInspectorUnityInternalBridge; using TriInspector.Utilities; using UnityEditor; +using UnityEditor.UIElements; using UnityEditorInternal; using UnityEngine; +using UnityEngine.UIElements; using Object = UnityEngine.Object; namespace TriInspector.Elements @@ -19,6 +21,7 @@ public class TriListElement : TriElement private readonly TriProperty _property; private readonly ReorderableList _reorderableListGui; + private readonly ListPropertyOverrideContext _elementLabelOverride; private readonly bool _alwaysExpanded; private readonly bool _showElementLabels; private readonly bool _showAlternatingBackground; @@ -37,6 +40,7 @@ public TriListElement(TriProperty property) _alwaysExpanded = settings?.AlwaysExpanded ?? false; _showElementLabels = settings?.ShowElementLabels ?? false; _showAlternatingBackground = settings?.ShowAlternatingBackground ?? true; + _elementLabelOverride = new ListPropertyOverrideContext(_property, _showElementLabels); _reorderableListGui = new ReorderableList(null, _property.ArrayElementType) { showDefaultBackground = settings?.ShowDefaultBackground ?? true, @@ -58,6 +62,11 @@ public TriListElement(TriProperty property) } } + public override VisualElement CreateVisualElement(TriProperty property) + { + return new ListViewTriElement(this); + } + public override bool Update() { var dirty = false; @@ -480,10 +489,7 @@ private void DrawElementCallback(Rect rect, int index, bool isActive, bool isFoc rect.xMin += DraggableAreaExtraWidth; } - using (TriPropertyOverrideContext.BeginOverride(ListPropertyOverrideContext.Instance)) - { - GetChild(index).OnGUI(rect); - } + GetChild(index).OnGUI(rect); } private float ElementHeightCallback(int index) @@ -549,44 +555,150 @@ private bool TryGetDragAndDropObject(Object obj, out Object result) return false; } - private class ListPropertyOverrideContext : TriPropertyOverrideContext + public sealed class ListViewTriElement : ListView, ITriElement { - public static readonly ListPropertyOverrideContext Instance = new ListPropertyOverrideContext(); + private readonly TriListElement _owner; + + public ListViewTriElement(TriListElement owner) + { + _owner = owner; + + var gui = owner._reorderableListGui; + + showFoldoutHeader = true; + headerTitle = owner._property.DisplayName; + showBoundCollectionSize = true; + showAddRemoveFooter = gui.displayAdd || gui.displayRemove; + reorderable = gui.draggable; + reorderMode = ListViewReorderMode.Animated; + showAlternatingRowBackgrounds = owner._showAlternatingBackground + ? AlternatingRowBackground.All + : AlternatingRowBackground.None; + virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight; + selectionType = SelectionType.None; + makeItem = () => new VisualElement(); + bindItem = BindListViewItem; + unbindItem = (itemRoot, _) => itemRoot.Clear(); + + if (owner._property.TryGetSerializedProperty(out var serializedProperty) && serializedProperty.isArray) + { + this.BindProperty(serializedProperty); + } + else + { + itemsSource = owner._property.Value as IList; + itemsAdded += _ => owner.AddElementCallback(gui, null); + itemsRemoved += indices => + { + foreach (var index in indices.OrderByDescending(i => i)) + { + gui.index = index; + owner.RemoveElementCallback(gui); + } + }; + itemIndexChanged += (from, to) => owner.ReorderCallback(gui, from, to); + } - private readonly GUIContent _noneLabel = GUIContent.none; + if (owner._alwaysExpanded) + { + var foldout = this.Q(); + if (foldout != null) + { + foldout.value = true; + foldout.RegisterValueChangedCallback(evt => + { + if (!evt.newValue) + { + foldout.SetValueWithoutNotify(true); + } + }); + + var toggle = foldout.Q(); + if (toggle != null) + { + toggle.SetEnabled(false); + } + } + } - public override bool TryGetDisplayName(TriProperty property, out GUIContent displayName) + RegisterListDragAndDrop(); + + RegisterCallback(_ => + owner._property.PropertyTree.AddPropertyOverride(owner._elementLabelOverride)); + RegisterCallback(_ => + owner._property.PropertyTree.RemovePropertyOverride(owner._elementLabelOverride)); + } + + public VisualElement CreateVisualElement(TriProperty property) { - var showLabels = property.TryGetAttribute(out ListDrawerSettingsAttribute settings) && - settings.ShowElementLabels; + return this; + } - if (!showLabels) + private void BindListViewItem(VisualElement itemRoot, int index) + { + itemRoot.Clear(); + + var elementProperties = _owner._property.ArrayElementProperties; + if (index < 0 || index >= elementProperties.Count) { - displayName = _noneLabel; - return true; + return; } - displayName = default; - return false; + var itemElement = _owner.CreateItemElement(elementProperties[index]); + + itemRoot.Add(itemElement.CreateVisualElement(elementProperties[index])); + } + + private void RegisterListDragAndDrop() + { + RegisterCallback(evt => + { + DragAndDrop.visualMode = + DragAndDrop.objectReferences.All(obj => _owner.TryGetDragAndDropObject(obj, out _)) + ? DragAndDropVisualMode.Copy + : DragAndDropVisualMode.Rejected; + evt.StopPropagation(); + }); + + RegisterCallback(evt => + { + DragAndDrop.AcceptDrag(); + + foreach (var obj in DragAndDrop.objectReferences) + { + if (_owner.TryGetDragAndDropObject(obj, out var addedReferenceValue)) + { + _owner.AddElementCallback(_owner._reorderableListGui, addedReferenceValue); + } + } + + evt.StopPropagation(); + }); } } - private static class Styles + private class ListPropertyOverrideContext : TriPropertyOverrideContext { - public static readonly GUIStyle ItemsCount; + private readonly TriProperty _listProperty; + private readonly bool _showElementLabels; + private readonly GUIContent _noneLabel = GUIContent.none; - static Styles() + public ListPropertyOverrideContext(TriProperty listProperty, bool showElementLabels) + { + _listProperty = listProperty; + _showElementLabels = showElementLabels; + } + + public override bool TryGetDisplayName(TriProperty property, out GUIContent displayName) { - ItemsCount = new GUIStyle(GUI.skin.label) + if (!_showElementLabels && property.Parent == _listProperty) { - alignment = TextAnchor.MiddleRight, - normal = - { - textColor = EditorGUIUtility.isProSkin - ? new Color(0.6f, 0.6f, 0.6f) - : new Color(0.3f, 0.3f, 0.3f), - }, - }; + displayName = _noneLabel; + return true; + } + + displayName = default; + return false; } } } diff --git a/Editor/Elements/TriNativeProjection.cs b/Editor/Elements/TriNativeProjection.cs new file mode 100644 index 00000000..fe61cbae --- /dev/null +++ b/Editor/Elements/TriNativeProjection.cs @@ -0,0 +1,29 @@ +using UnityEngine.UIElements; + +namespace TriInspector.Elements +{ + internal static class TriNativeProjection + { + public static bool IsNative(TriElement element) + { + return element is TriValidatorsElement + || element is TriListElement + || element is TriInlineGenericElement + || element is TriFoldoutElement + || element is TriReferenceElement + || element is TriNoDrawerElement + || element is TriBuiltInPropertyElement + || element is TriObjectReferenceElement; + } + + public static VisualElement ProjectField(TriProperty property, TriElement element) + { + if (IsNative(element)) + { + return element.CreateVisualElement(property); + } + + return new TriImguiContainerImpl(property, element, applyPropertyContext: true); + } + } +} diff --git a/Editor/Elements/TriNativeProjection.cs.meta b/Editor/Elements/TriNativeProjection.cs.meta new file mode 100644 index 00000000..09e4b0bb --- /dev/null +++ b/Editor/Elements/TriNativeProjection.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0007e256f37603a43a0eeac10580b847 \ No newline at end of file diff --git a/Editor/Elements/TriNoDrawerElement.cs b/Editor/Elements/TriNoDrawerElement.cs index ed0f4219..688fd72c 100644 --- a/Editor/Elements/TriNoDrawerElement.cs +++ b/Editor/Elements/TriNoDrawerElement.cs @@ -1,5 +1,7 @@ -using UnityEditor; +using TriInspector.VisualElements; +using UnityEditor; using UnityEngine; +using UnityEngine.UIElements; namespace TriInspector.Elements { @@ -14,6 +16,18 @@ public TriNoDrawerElement(TriProperty property) _message = new GUIContent($"No drawer for {property.FieldType}"); } + public override VisualElement CreateVisualElement(TriProperty property) + { + return new TriAlignedLabel(property.DisplayName, new Label(_message.text) + { + style = + { + flexGrow = 1, + unityTextAlign = TextAnchor.MiddleLeft, + }, + }); + } + public override float GetHeight(float width) { return EditorGUIUtility.singleLineHeight; diff --git a/Editor/Elements/TriObjectReferenceElement.cs b/Editor/Elements/TriObjectReferenceElement.cs new file mode 100644 index 00000000..53550ab9 --- /dev/null +++ b/Editor/Elements/TriObjectReferenceElement.cs @@ -0,0 +1,68 @@ +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine; +using UnityEngine.UIElements; +using Object = UnityEngine.Object; + +namespace TriInspector.Elements +{ + internal sealed class TriObjectReferenceElement : TriElement + { + private readonly TriValue _propertyValue; + private readonly bool _allowSceneObjects; + + public TriObjectReferenceElement(TriValue propertyValue) + { + _propertyValue = propertyValue; + _allowSceneObjects = propertyValue.Property.PropertyTree.TargetIsPersistent == false; + } + + public override VisualElement CreateVisualElement(TriProperty property) + { + var field = new ObjectField(_propertyValue.Property.DisplayNameContent?.text) + { + objectType = _propertyValue.Property.FieldType, + allowSceneObjects = _allowSceneObjects, + value = _propertyValue.SmartValue, + showMixedValue = _propertyValue.Property.IsValueMixed, + }; + + field.AddToClassList(BaseField.alignedFieldUssClassName); + + field.RegisterValueChangedCallback(evt => _propertyValue.SetValue(evt.newValue)); + + field.schedule.Execute(() => + { + field.showMixedValue = _propertyValue.Property.IsValueMixed; + + var current = _propertyValue.SmartValue; + if (field.value != current) + { + field.SetValueWithoutNotify(current); + } + }).Every(100); + + return field; + } + + public override float GetHeight(float width) + { + return EditorGUIUtility.singleLineHeight; + } + + public override void OnGUI(Rect position) + { + var value = _propertyValue.SmartValue; + + EditorGUI.BeginChangeCheck(); + + value = EditorGUI.ObjectField(position, _propertyValue.Property.DisplayNameContent, value, + _propertyValue.Property.FieldType, _allowSceneObjects); + + if (EditorGUI.EndChangeCheck()) + { + _propertyValue.SetValue(value); + } + } + } +} diff --git a/Editor/Elements/TriObjectReferenceElement.cs.meta b/Editor/Elements/TriObjectReferenceElement.cs.meta new file mode 100644 index 00000000..bd37310a --- /dev/null +++ b/Editor/Elements/TriObjectReferenceElement.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 662548d645478d248a65a8305ed83a22 \ No newline at end of file diff --git a/Editor/Elements/TriPropertyCollectionBaseElement.cs b/Editor/Elements/TriPropertyCollectionBaseElement.cs index 5cb79c79..d5ad6907 100644 --- a/Editor/Elements/TriPropertyCollectionBaseElement.cs +++ b/Editor/Elements/TriPropertyCollectionBaseElement.cs @@ -3,11 +3,25 @@ using System.Linq; using JetBrains.Annotations; using TriInspector.Utilities; +using UnityEngine.UIElements; namespace TriInspector.Elements { public abstract class TriPropertyCollectionBaseElement : TriElement { + protected VisualElement CreateChildrenColumn(TriProperty property) + { + var column = new VisualElement(); + + for (var i = 0; i < ChildrenCount; i++) + { + var child = GetChild(i).CreateVisualElement(property); + column.Add(child); + } + + return column; + } + private List _declarations = new List(); private Dictionary _groups; diff --git a/Editor/Elements/TriPropertyElement.cs b/Editor/Elements/TriPropertyElement.cs index 7cb33c1f..44f0d744 100644 --- a/Editor/Elements/TriPropertyElement.cs +++ b/Editor/Elements/TriPropertyElement.cs @@ -1,6 +1,7 @@ using System; using UnityEditor; using UnityEngine; +using UnityEngine.UIElements; namespace TriInspector.Elements { @@ -8,6 +9,8 @@ public class TriPropertyElement : TriElement { private readonly TriProperty _property; + private readonly TriElement _wrappedElement; + [Serializable] public struct Props { @@ -31,9 +34,35 @@ public TriPropertyElement(TriProperty property, Props props = default) element = drawers[index].CreateElementInternal(property, element); } + _wrappedElement = element; + AddChild(element); } + public override VisualElement CreateVisualElement(TriProperty property) + { + var canRecurse = _property.ExtensionErrors.Count == 0 + && TriNativeProjection.IsNative(_wrappedElement); + + if (!canRecurse) + { + return base.CreateVisualElement(property); + } + + var visualElement = _wrappedElement.CreateVisualElement(property); + + void Sync() + { + visualElement.style.display = _property.IsVisible ? DisplayStyle.Flex : DisplayStyle.None; + visualElement.SetEnabled(_property.IsEnabled); + } + + Sync(); + visualElement.schedule.Execute(Sync).Every(100); + + return visualElement; + } + public override float GetHeight(float width) { if (!_property.IsVisible) @@ -56,7 +85,6 @@ public override void OnGUI(Rect position) GUI.enabled &= _property.IsEnabled; EditorGUI.showMixedValue = _property.IsValueMixed; - var overrideCtx = TriPropertyOverrideContext.BeginProperty(); if (_property.TryGetSerializedProperty(out var serializedProperty)) { @@ -70,7 +98,6 @@ public override void OnGUI(Rect position) EditorGUI.EndProperty(); } - overrideCtx.EndProperty(); EditorGUI.showMixedValue = oldShowMixedValue; GUI.enabled = oldEnabled; } diff --git a/Editor/Elements/TriReferenceElement.cs b/Editor/Elements/TriReferenceElement.cs index 7ae29b62..9f5c5879 100644 --- a/Editor/Elements/TriReferenceElement.cs +++ b/Editor/Elements/TriReferenceElement.cs @@ -1,7 +1,9 @@ using System; using TriInspector.Utilities; +using TriInspector.VisualElements; using UnityEditor; using UnityEngine; +using UnityEngine.UIElements; namespace TriInspector.Elements { @@ -30,6 +32,122 @@ public TriReferenceElement(TriProperty property, Props props = default) _skipReferencePickerExtraLine = !_showReferencePicker && _props.inline; } + public override VisualElement CreateVisualElement(TriProperty property) + { + var content = new VisualElement(); + var builtType = default(Type); + var hasBuilt = false; + + void BuildChildren() + { + hasBuilt = true; + builtType = _property.ValueType; + + content.Clear(); + GenerateChildren(); + content.Add(CreateChildrenColumn(property)); + } + + void OnValueChanged(TriProperty changed) + { + if (hasBuilt && _property.ValueType != builtType) + { + BuildChildren(); + } + } + + void BindLifecycle(VisualElement root) + { + root.RegisterCallback(_ => + { + _property.ValueChanged += OnValueChanged; + OnValueChanged(_property); + }); + root.RegisterCallback(_ => _property.ValueChanged -= OnValueChanged); + } + + if (_props.inline) + { + var column = new VisualElement(); + + if (_showReferencePicker) + { + column.Add(CreateTypeSelectorIsland()); + } + + BuildChildren(); + column.Add(content); + + var inlineRoot = _props.drawPrefixLabel ? new TriAlignedLabel(_property.DisplayName, column) : column; + BindLifecycle(inlineRoot); + return inlineRoot; + } + + var foldout = new Foldout + { + text = _property.DisplayName, + value = _property.IsExpanded, + }; + + if (_showReferencePicker) + { + var toggle = foldout.Q(); + if (toggle != null) + { + var typeContainer = new TriAlignedLabel(" ", CreateTypeSelectorIsland()) + { + style = + { + position = Position.Absolute, + left = 0, + right = 0, + top = 0, + bottom = 0, + }, + }; + + toggle.Add(typeContainer); + } + } + + if (_property.IsExpanded) + { + BuildChildren(); + } + + foldout.RegisterValueChangedCallback(evt => + { + // Foldout also bubbles ChangeEvent from child toggles; only react to its own. + if (evt.target != foldout) + { + return; + } + + _property.IsExpanded = evt.newValue; + + if (evt.newValue && !hasBuilt) + { + BuildChildren(); + } + }); + + foldout.Add(content); + + BindLifecycle(foldout); + + return foldout; + } + + private IMGUIContainer CreateTypeSelectorIsland() + { + // The managed-reference picker is an IMGUI AdvancedDropdown with no native equivalent + return new IMGUIContainer(() => + { + var rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight); + TriManagedReferenceGui.DrawTypeSelector(rect, _property); + }); + } + public override bool Update() { var dirty = false; diff --git a/Editor/Elements/TriValidatorsElement.cs b/Editor/Elements/TriValidatorsElement.cs new file mode 100644 index 00000000..0f026ce5 --- /dev/null +++ b/Editor/Elements/TriValidatorsElement.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using TriInspectorUnityInternalBridge; +using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; + +namespace TriInspector.Elements +{ + internal sealed class TriValidatorsElement : TriElement + { + private readonly TriElement _next; + private readonly TriPropertyValidationResultElement _results; + + public TriValidatorsElement(TriProperty property, TriElement next) + { + _next = next; + _results = new TriPropertyValidationResultElement(property); + + AddChild(_results); + AddChild(next); + } + + public override VisualElement CreateVisualElement(TriProperty property) + { + var container = new VisualElement(); + container.Add(_results.CreateVisualElement(property)); + container.Add(TriNativeProjection.ProjectField(property, _next)); + return container; + } + } + + internal sealed class TriPropertyValidationResultElement : TriElement + { + private readonly TriProperty _property; + private IReadOnlyList _validationResults; + + public TriPropertyValidationResultElement(TriProperty property) + { + _property = property; + } + + public override float GetHeight(float width) + { + if (ChildrenCount == 0) + { + return -EditorGUIUtility.standardVerticalSpacing; + } + + return base.GetHeight(width); + } + + public override bool Update() + { + var dirty = base.Update(); + + dirty |= GenerateValidationResults(); + + return dirty; + } + + public override VisualElement CreateVisualElement(TriProperty property) + { + var container = new VisualElement(); + IReadOnlyList cachedResults = null; + + void Rebuild() + { + if (ReferenceEquals(_property.ValidationResults, cachedResults)) + { + return; + } + + cachedResults = _property.ValidationResults; + container.Clear(); + + var hasResults = cachedResults.Count != 0; + container.style.marginTop = hasResults ? EditorGUIUtility.standardVerticalSpacing + 5 : 0; + container.style.marginBottom = hasResults ? EditorGUIUtility.standardVerticalSpacing : 0; + + foreach (var result in cachedResults) + { + container.Add(CreateResultElement(result)); + } + } + + Rebuild(); + container.schedule.Execute(Rebuild).Every(100); + + return container; + } + + private VisualElement CreateResultElement(TriValidationResult result) + { + return new TriInfoBoxVisualElement( + result.Message, + result.MessageType, + result.FixAction != null ? () => ExecuteFix(result.FixAction) : null, + result.FixActionContent?.text); + } + + private bool GenerateValidationResults() + { + if (ReferenceEquals(_property.ValidationResults, _validationResults)) + { + return false; + } + + _validationResults = _property.ValidationResults; + + RemoveAllChildren(); + + foreach (var result in _validationResults) + { + var infoBox = result.FixAction != null + ? new TriInfoBoxElement(result.Message, result.MessageType, + inlineAction: () => ExecuteFix(result.FixAction), + inlineActionContent: result.FixActionContent) + : new TriInfoBoxElement(result.Message, result.MessageType); + + AddChild(infoBox); + } + + return true; + } + + private void ExecuteFix(Action fixAction) + { + _property.ModifyAndRecordForUndo(targetIndex => fixAction?.Invoke()); + } + } + + internal sealed class TriInfoBoxVisualElement : VisualElement + { + private const float ActionWidth = 100f; + + public TriInfoBoxVisualElement(string message, TriMessageType type, Action fixAction = null, + string fixActionText = null) + { + var isPro = EditorGUIUtility.isProSkin; + var tint = GetColor(type); + var baseGray = isPro ? 0.3f : 0.9f; + var baseAlpha = isPro ? 0.65f : 0.5f; + var borderGray = isPro ? 0.12f : 0.6f; + var borderColor = new Color(borderGray * tint.r, borderGray * tint.g, borderGray * tint.b, baseAlpha); + + style.flexDirection = FlexDirection.Row; + style.alignItems = Align.Center; + style.paddingLeft = style.paddingRight = style.paddingTop = style.paddingBottom = 2; + style.backgroundColor = new Color(baseGray * tint.r, baseGray * tint.g, baseGray * tint.b, baseAlpha); + style.borderTopWidth = style.borderLeftWidth = style.borderRightWidth = 1; + style.borderTopColor = style.borderLeftColor = style.borderRightColor = borderColor; + + var icon = EditorGUIUtilityProxy.GetHelpIcon(GetMessageType(type)); + if (icon != null) + { + Add(new Image + { + image = icon, + scaleMode = ScaleMode.ScaleToFit, + style = + { + width = 18, + height = 18, + flexShrink = 0, + marginRight = 2, + }, + }); + } + + Add(new Label(message) + { + style = + { + fontSize = 11, + flexGrow = 1, + whiteSpace = WhiteSpace.Normal, + unityTextAlign = TextAnchor.MiddleLeft, + }, + }); + + if (fixAction != null) + { + Add(new Button(fixAction) + { + text = fixActionText, + style = + { + width = ActionWidth, + flexShrink = 0, + marginLeft = 5, + whiteSpace = WhiteSpace.Normal, + }, + }); + } + } + + private static Color GetColor(TriMessageType type) + { + switch (type) + { + case TriMessageType.Error: return new Color(1f, 0.4f, 0.4f); + case TriMessageType.Warning: return new Color(1f, 0.8f, 0.2f); + default: return Color.white; + } + } + + private static MessageType GetMessageType(TriMessageType type) + { + switch (type) + { + case TriMessageType.Info: return MessageType.Info; + case TriMessageType.Warning: return MessageType.Warning; + case TriMessageType.Error: return MessageType.Error; + default: return MessageType.None; + } + } + } +} \ No newline at end of file diff --git a/Editor/Elements/TriValidatorsElement.cs.meta b/Editor/Elements/TriValidatorsElement.cs.meta new file mode 100644 index 00000000..a88d586b --- /dev/null +++ b/Editor/Elements/TriValidatorsElement.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ca80ef60b629b564bac4377aab853625 \ No newline at end of file diff --git a/Editor/ITriElement.cs b/Editor/ITriElement.cs new file mode 100644 index 00000000..3a0a665e --- /dev/null +++ b/Editor/ITriElement.cs @@ -0,0 +1,9 @@ +using UnityEngine.UIElements; + +namespace TriInspector +{ + public interface ITriElement + { + VisualElement CreateVisualElement(TriProperty property); + } +} diff --git a/Editor/ITriElement.cs.meta b/Editor/ITriElement.cs.meta new file mode 100644 index 00000000..fbb7cc54 --- /dev/null +++ b/Editor/ITriElement.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5b03e5acd2c507349bdd28c2a101c4d0 \ No newline at end of file diff --git a/Editor/TriElement.cs b/Editor/TriElement.cs index 2034e6f1..bab2af85 100644 --- a/Editor/TriElement.cs +++ b/Editor/TriElement.cs @@ -1,16 +1,17 @@ using System.Collections.Generic; using JetBrains.Annotations; +using TriInspector.Elements; using UnityEditor; using UnityEngine; +using UnityEngine.UIElements; namespace TriInspector { - public class TriElement + public class TriElement : ITriElement { private static readonly List Empty = new List(); private float _cachedHeight; - private bool _cachedheightDirty; private bool _attached; private List _children = Empty; @@ -47,13 +48,6 @@ public virtual float GetHeight(float width) Debug.LogError($"{GetType().Name} not attached"); } - if (Event.current.type != EventType.Layout && !_cachedheightDirty) - { - return _cachedHeight; - } - - _cachedheightDirty = false; - switch (_children.Count) { case 0: @@ -76,6 +70,12 @@ public virtual float GetHeight(float width) } } + [PublicAPI] + public virtual VisualElement CreateVisualElement(TriProperty property) + { + return new TriImguiContainerImpl(property, this); + } + [PublicAPI] public virtual void OnGUI(Rect position) { @@ -128,7 +128,6 @@ public void RemoveChildAt(int index) var child = _children[index]; _children.RemoveAt(index); - _cachedheightDirty = true; if (_attached) { @@ -148,7 +147,6 @@ public void RemoveAllChildren() } _children.Clear(); - _cachedheightDirty = true; } [PublicAPI] @@ -160,7 +158,6 @@ public void AddChild(TriElement child) } _children.Add(child); - _cachedheightDirty = true; if (_attached) { diff --git a/Editor/TriProperty.cs b/Editor/TriProperty.cs index 4c3f1413..1ac09f0a 100644 --- a/Editor/TriProperty.cs +++ b/Editor/TriProperty.cs @@ -103,8 +103,7 @@ public GUIContent DisplayNameContent { get { - if (TriPropertyOverrideContext.Current != null && - TriPropertyOverrideContext.Current.TryGetDisplayName(this, out var overrideName)) + if (PropertyTree.TryGetOverrideDisplayName(this, out var overrideName)) { return overrideName; } diff --git a/Editor/TriPropertyOverrideContext.cs b/Editor/TriPropertyOverrideContext.cs index 5aad6f63..83e6806d 100644 --- a/Editor/TriPropertyOverrideContext.cs +++ b/Editor/TriPropertyOverrideContext.cs @@ -1,60 +1,9 @@ -using System; using UnityEngine; namespace TriInspector { public abstract class TriPropertyOverrideContext { - private static TriPropertyOverrideContext Override { get; set; } - public static TriPropertyOverrideContext Current { get; private set; } - public abstract bool TryGetDisplayName(TriProperty property, out GUIContent displayName); - - public static EnterPropertyScope BeginProperty() - { - return new EnterPropertyScope().Init(); - } - - public static OverrideScope BeginOverride(TriPropertyOverrideContext overrideContext) - { - return new OverrideScope(overrideContext); - } - - public struct EnterPropertyScope - { - private TriPropertyOverrideContext _previousContext; - - public EnterPropertyScope Init() - { - _previousContext = Current; - Current = Override; - Override = null; - return this; - } - - public void EndProperty() - { - Override = Current; - Current = _previousContext; - } - } - - public readonly struct OverrideScope : IDisposable - { - public OverrideScope(TriPropertyOverrideContext context) - { - if (Override != null) - { - Debug.LogError($"TriPropertyContext already overriden with {Override.GetType()}"); - } - - Override = context; - } - - public void Dispose() - { - Override = null; - } - } } -} \ No newline at end of file +} diff --git a/Editor/TriPropertyTree.cs b/Editor/TriPropertyTree.cs index 5763e9cd..247e6907 100644 --- a/Editor/TriPropertyTree.cs +++ b/Editor/TriPropertyTree.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using TriInspector.Elements; using UnityEditor; using UnityEngine; @@ -8,8 +9,10 @@ namespace TriInspector { public abstract class TriPropertyTree : IDisposable { + private readonly List _propertyOverrides = + new List(); + private TriPropertyElement _rootPropertyElement; - private Rect _cachedOuterRect = new Rect(0, 0, 0, 0); public TriPropertyDefinition RootPropertyDefinition { get; protected set; } public TriProperty RootProperty { get; protected set; } @@ -19,7 +22,6 @@ public abstract class TriPropertyTree : IDisposable public bool TargetIsPersistent { get; protected set; } public bool ValidationRequired { get; private set; } = true; - public bool RepaintRequired { get; private set; } = true; public int RepaintFrame { get; private set; } = 0; @@ -62,40 +64,17 @@ public void RunValidation() RequestRepaint(); } - public virtual void Draw() + public ITriElement GetRootElement() { - RepaintRequired = false; - if (_rootPropertyElement == null) { _rootPropertyElement = new TriPropertyElement(RootProperty, new TriPropertyElement.Props { forceInline = !RootProperty.TryGetMemberInfo(out _), }); - _rootPropertyElement.AttachInternal(); } - Profiler.BeginSample("TriInspector.UpdateRootPropertyElement"); - _rootPropertyElement.Update(); - Profiler.EndSample(); - - var rectOuter = GUILayoutUtility.GetRect(0, 9999, 0, 0); - _cachedOuterRect = Event.current.type == EventType.Layout ? _cachedOuterRect : rectOuter; - - var rect = new Rect(_cachedOuterRect); - rect = EditorGUI.IndentedRect(rect); - rect.height = _rootPropertyElement.GetHeight(rect.width); - - var oldIndent = EditorGUI.indentLevel; - EditorGUI.indentLevel = 0; - - GUILayoutUtility.GetRect(_cachedOuterRect.width, rect.height); - - Profiler.BeginSample("TriInspector.DrawRootPropertyElement"); - _rootPropertyElement.OnGUI(rect); - Profiler.EndSample(); - - EditorGUI.indentLevel = oldIndent; + return _rootPropertyElement; } public void EnumerateValidationResults(Action call) @@ -103,18 +82,40 @@ public void EnumerateValidationResults(Action RootProperty.EnumerateValidationResults(call); } + [Obsolete("Legacy from IMGUI")] public void RequestRepaint() { - RepaintRequired = true; } public void RequestValidation() { ValidationRequired = true; - - RequestRepaint(); } public abstract void ForceCreateUndoGroup(); + + public void AddPropertyOverride(TriPropertyOverrideContext context) + { + _propertyOverrides.Add(context); + } + + public void RemovePropertyOverride(TriPropertyOverrideContext context) + { + _propertyOverrides.Remove(context); + } + + internal bool TryGetOverrideDisplayName(TriProperty property, out GUIContent displayName) + { + for (var i = _propertyOverrides.Count - 1; i >= 0; i--) + { + if (_propertyOverrides[i].TryGetDisplayName(property, out displayName)) + { + return true; + } + } + + displayName = default; + return false; + } } } \ No newline at end of file diff --git a/Editor/TriPropertyTreeForSerializedObject.cs b/Editor/TriPropertyTreeForSerializedObject.cs index f31fbf6d..574f390e 100644 --- a/Editor/TriPropertyTreeForSerializedObject.cs +++ b/Editor/TriPropertyTreeForSerializedObject.cs @@ -56,13 +56,6 @@ public override void Update(bool forceUpdate = false) base.Update(forceUpdate); } - public override void Draw() - { - DrawMonoScriptProperty(); - - base.Draw(); - } - public override bool ApplyChanges() { var changed = base.ApplyChanges(); @@ -86,19 +79,5 @@ private void OnPropertyChanged(TriProperty changedProperty) RequestValidation(); RequestRepaint(); } - - private void DrawMonoScriptProperty() - { - if (RootProperty.TryGetAttribute(out HideMonoScriptAttribute _)) - { - return; - } - - EditorGUI.BeginDisabledGroup(true); - var scriptRect = EditorGUILayout.GetControlRect(true); - scriptRect.xMin += 3; - EditorGUI.PropertyField(scriptRect, _scriptProperty); - EditorGUI.EndDisabledGroup(); - } } } \ No newline at end of file diff --git a/Editor/ValidatorsDrawer.cs b/Editor/ValidatorsDrawer.cs index a78f98af..e90abac4 100644 --- a/Editor/ValidatorsDrawer.cs +++ b/Editor/ValidatorsDrawer.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using TriInspector.Elements; -using UnityEditor; namespace TriInspector { @@ -14,70 +11,7 @@ public override TriElement CreateElementInternal(TriProperty property, TriElemen return next; } - var element = new TriElement(); - element.AddChild(new TriPropertyValidationResultElement(property)); - element.AddChild(next); - return element; - } - - public class TriPropertyValidationResultElement : TriElement - { - private readonly TriProperty _property; - private IReadOnlyList _validationResults; - - public TriPropertyValidationResultElement(TriProperty property) - { - _property = property; - } - - public override float GetHeight(float width) - { - if (ChildrenCount == 0) - { - return -EditorGUIUtility.standardVerticalSpacing; - } - - return base.GetHeight(width); - } - - public override bool Update() - { - var dirty = base.Update(); - - dirty |= GenerateValidationResults(); - - return dirty; - } - - private bool GenerateValidationResults() - { - if (ReferenceEquals(_property.ValidationResults, _validationResults)) - { - return false; - } - - _validationResults = _property.ValidationResults; - - RemoveAllChildren(); - - foreach (var result in _validationResults) - { - var infoBox = result.FixAction != null - ? new TriInfoBoxElement(result.Message, result.MessageType, - inlineAction: () => ExecuteFix(result.FixAction), - inlineActionContent: result.FixActionContent) - : new TriInfoBoxElement(result.Message, result.MessageType); - - AddChild(infoBox); - } - - return true; - } - - private void ExecuteFix(Action fixAction) - { - _property.ModifyAndRecordForUndo(targetIndex => fixAction?.Invoke()); - } + return new TriValidatorsElement(property, next); } } -} \ No newline at end of file +} diff --git a/Editor/VisualElementExtensions.cs b/Editor/VisualElementExtensions.cs new file mode 100644 index 00000000..59b03d63 --- /dev/null +++ b/Editor/VisualElementExtensions.cs @@ -0,0 +1,20 @@ +using UnityEngine.UIElements; + +namespace TriInspector +{ + public static class VisualElementExtensions + { + public static T FindAncestor(this VisualElement element) where T : VisualElement + { + for (var current = element.parent; current != null; current = current.parent) + { + if (current is T result) + { + return result; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/Editor/VisualElementExtensions.cs.meta b/Editor/VisualElementExtensions.cs.meta new file mode 100644 index 00000000..217b3c34 --- /dev/null +++ b/Editor/VisualElementExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ef4c7a36921f445a9a611f44d0e8cbac +timeCreated: 1785849248 \ No newline at end of file diff --git a/Editor/VisualElements.meta b/Editor/VisualElements.meta new file mode 100644 index 00000000..ddb911df --- /dev/null +++ b/Editor/VisualElements.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2ae05fdff25a47f7af9fdc1345f19f7b +timeCreated: 1785837702 \ No newline at end of file diff --git a/Editor/VisualElements/TriAlignedLabel.cs b/Editor/VisualElements/TriAlignedLabel.cs new file mode 100644 index 00000000..4644fc15 --- /dev/null +++ b/Editor/VisualElements/TriAlignedLabel.cs @@ -0,0 +1,12 @@ +using UnityEngine.UIElements; + +namespace TriInspector.VisualElements +{ + public class TriAlignedLabel : BaseField + { + public TriAlignedLabel(string label, VisualElement content) : base(label, content) + { + AddToClassList(alignedFieldUssClassName); + } + } +} \ No newline at end of file diff --git a/Editor/VisualElements/TriAlignedLabel.cs.meta b/Editor/VisualElements/TriAlignedLabel.cs.meta new file mode 100644 index 00000000..e89db886 --- /dev/null +++ b/Editor/VisualElements/TriAlignedLabel.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 34f2cb6db28d45f4a1525db153372803 +timeCreated: 1785837737 \ No newline at end of file diff --git a/Editor/VisualElements/TriLabelWidthContextElement.cs b/Editor/VisualElements/TriLabelWidthContextElement.cs new file mode 100644 index 00000000..1dec3de7 --- /dev/null +++ b/Editor/VisualElements/TriLabelWidthContextElement.cs @@ -0,0 +1,16 @@ +using UnityEngine.UIElements; + +namespace TriInspector.VisualElements +{ + public class TriLabelWidthContextElement : VisualElement + { + public float LabelWidth { get; } + + public TriLabelWidthContextElement(float labelWidth, VisualElement content) + { + LabelWidth = labelWidth; + + Add(content); + } + } +} \ No newline at end of file diff --git a/Editor/VisualElements/TriLabelWidthContextElement.cs.meta b/Editor/VisualElements/TriLabelWidthContextElement.cs.meta new file mode 100644 index 00000000..8bc5ffe8 --- /dev/null +++ b/Editor/VisualElements/TriLabelWidthContextElement.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 973bea5304d4458aa73517527185a51d +timeCreated: 1785846875 \ No newline at end of file diff --git a/Editor/VisualElements/TriPropertyField.cs b/Editor/VisualElements/TriPropertyField.cs new file mode 100644 index 00000000..2767d28c --- /dev/null +++ b/Editor/VisualElements/TriPropertyField.cs @@ -0,0 +1,37 @@ +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine.UIElements; + +namespace TriInspector.VisualElements +{ + public class TriPropertyField : PropertyField + { + private VisualElement _child; + + public TriPropertyField(SerializedProperty property, string label) : base(property, label) + { + } + + protected override void HandleEventBubbleUp(EventBase evt) + { + base.HandleEventBubbleUp(evt); + + var childChanged = childCount > 0 && _child != this[0]; + if (childChanged) + { + _child = this[0]; + OnChildChanged(); + } + } + + private void OnChildChanged() + { + if (this.FindAncestor() is { } labelContext && + this.Q