From 5c5659100f2b3ad62f2daa666bc1eacda1334c13 Mon Sep 17 00:00:00 2001 From: VladV Date: Mon, 30 May 2022 13:15:09 +0300 Subject: [PATCH] Closes #8 - Add TableList attribute --- Editor.Extras/Drawers/TableListDrawer.cs | 380 ++++++++++++++---- .../TriInspector.Editor.Extras.asmdef | 3 +- Editor/Elements/TriListElement.cs | 19 +- .../TriPropertyCollectionBaseElement.cs | 11 +- Editor/TriElement.cs | 2 + Editor/TriProperty.cs | 5 +- Editor/TriPropertyOverrideContext.cs | 2 +- README.md | 28 ++ Runtime/Attributes/ListDrawerSettings.cs | 2 +- Runtime/Attributes/TableListAttribute.cs | 2 +- .../AssemblyInfo.cs | 3 +- package.json | 2 +- 12 files changed, 367 insertions(+), 92 deletions(-) diff --git a/Editor.Extras/Drawers/TableListDrawer.cs b/Editor.Extras/Drawers/TableListDrawer.cs index 4e32b7b..7b082a8 100644 --- a/Editor.Extras/Drawers/TableListDrawer.cs +++ b/Editor.Extras/Drawers/TableListDrawer.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; -using System.Linq; using TriInspector; using TriInspector.Drawers; using TriInspector.Elements; using TriInspector.Utilities; +using TriInspectorUnityInternalBridge; using UnityEditor; using UnityEditor.IMGUI.Controls; +using UnityEditorInternal; using UnityEngine; [assembly: RegisterTriAttributeDrawer(typeof(TableListDrawer), TriDrawerOrder.Drawer)] @@ -30,23 +31,149 @@ namespace TriInspector.Drawers return new TableElement(property); } - private class TableElement : TriElement + private class TableElement : TriListElement { - private readonly TableMultiColumnTreeView _treeView; + private const float FooterExtraSpace = 4; - public TableElement(TriProperty property) + private readonly TriProperty _property; + private readonly TableMultiColumnTreeView _treeView; + private readonly bool _alwaysExpanded; + + private bool _reloadRequired; + private bool _heightDirty; + private bool _isExpanded; + private int _arraySize; + + public TableElement(TriProperty property) : base(property) { - _treeView = new TableMultiColumnTreeView(property, this); + _property = property; + _treeView = new TableMultiColumnTreeView(property, this, ListGui) + { + SelectionChangedCallback = SelectionChangedCallback, + }; + _reloadRequired = true; + } + + public override bool Update() + { + var dirty = base.Update(); + + dirty |= ReloadIfRequired(); + + if (dirty) + { + _heightDirty = true; + _treeView.multiColumnHeader.ResizeToFit(); + } + + return dirty; } public override float GetHeight(float width) { - return _treeView.totalHeight; + _treeView.Width = width; + + if (_heightDirty) + { + _heightDirty = false; + _treeView.RefreshHeight(); + } + + var height = 0f; + height += ListGui.headerHeight; + + if (_property.IsExpanded) + { + height += _treeView.totalHeight; + height += ListGui.footerHeight; + height += FooterExtraSpace; + } + + return height; } public override void OnGUI(Rect position) { - _treeView.OnGUI(position); + position = EditorGUI.IndentedRect(position); + + var headerRect = new Rect(position) + { + height = ListGui.headerHeight, + }; + var elementsRect = new Rect(position) + { + yMin = headerRect.yMax, + height = _treeView.totalHeight + FooterExtraSpace, + }; + var elementsContentRect = new Rect(elementsRect) + { + xMin = elementsRect.xMin + 1, + xMax = elementsRect.xMax - 1, + yMax = elementsRect.yMax - FooterExtraSpace, + }; + var footerRect = new Rect(position) + { + yMin = elementsRect.yMax, + }; + + if (Event.current.isMouse && Event.current.type == EventType.MouseDrag) + { + _treeView.multiColumnHeader.ResizeToFit(); + } + + if (Event.current.type == EventType.Repaint) + { + ReorderableList.defaultBehaviours.boxBackground.Draw(elementsRect, + false, false, false, false); + } + + ReorderableListProxy.DoListHeader(ListGui, headerRect); + + EditorGUI.BeginChangeCheck(); + + if (_property.IsExpanded) + { + _treeView.OnGUI(elementsContentRect); + } + + if (EditorGUI.EndChangeCheck()) + { + _heightDirty = true; + _property.PropertyTree.RequestRepaint(); + } + + if (_property.IsExpanded) + { + ReorderableList.defaultBehaviours.DrawFooter(footerRect, ListGui); + } + } + + private bool ReloadIfRequired() + { + if (!_reloadRequired && + _property.IsExpanded == _isExpanded && + _property.ArrayElementProperties.Count == _arraySize) + { + return false; + } + + _reloadRequired = false; + _isExpanded = _property.IsExpanded; + _arraySize = _property.ArrayElementProperties.Count; + + _treeView.Reload(); + + return true; + } + + protected override TriElement CreateItemElement(TriProperty property) + { + return new TableRowElement(property); + } + + private void SelectionChangedCallback(int index) + { + ListGui.index = index; } } @@ -55,131 +182,212 @@ namespace TriInspector.Drawers { private readonly TriProperty _property; private readonly TriElement _cellElementContainer; - private readonly Dictionary _cellIndexByName; - private readonly Dictionary _cellElements; + private readonly ReorderableList _listGui; private readonly TableListPropertyOverrideContext _propertyOverrideContext; - public TableMultiColumnTreeView(TriProperty property, TriElement container) - : base(new TreeViewState(), BuildHeader(property)) + public Action SelectionChangedCallback; + + public TableMultiColumnTreeView(TriProperty property, TriElement container, ReorderableList listGui) + : base(new TreeViewState(), new TableColumnHeader()) { _property = property; _cellElementContainer = container; + _listGui = listGui; + _propertyOverrideContext = new TableListPropertyOverrideContext(property); - _cellIndexByName = new Dictionary(); - _cellElements = new Dictionary(); - - _propertyOverrideContext = new TableListPropertyOverrideContext(); - - rowHeight = 20; showAlternatingRowBackgrounds = true; - showBorder = true; + showBorder = false; useScrollView = false; - Reload(); + multiColumnHeader.ResizeToFit(); + multiColumnHeader.visibleColumnsChanged += header => header.ResizeToFit(); + } + + public float Width { get; set; } + + public void RefreshHeight() + { + RefreshCustomRowHeights(); + } + + protected override void SelectionChanged(IList selectedIds) + { + base.SelectionChanged(selectedIds); + + if (SelectionChangedCallback != null && selectedIds.Count == 1) + { + SelectionChangedCallback.Invoke(selectedIds[0]); + } } protected override TreeViewItem BuildRoot() { var root = new TreeViewItem(0, -1, string.Empty); - - for (var index = 0; index < _property.ArrayElementProperties.Count; index++) + var columns = new List { - var rowChildProperty = _property.ArrayElementProperties[index]; - root.AddChild(new TableTreeElement(index, rowChildProperty)); - - foreach (var cellValueProperty in rowChildProperty.ChildrenProperties) + new MultiColumnHeaderState.Column { - if (!_cellIndexByName.ContainsKey(cellValueProperty.RawName)) + width = 16, autoResize = false, canSort = false, allowToggleVisibility = false, + }, + }; + + if (_property.IsExpanded) + { + for (var index = 0; index < _property.ArrayElementProperties.Count; index++) + { + var rowChildProperty = _property.ArrayElementProperties[index]; + root.AddChild(new TableTreeItem(index, rowChildProperty)); + + if (index == 0) { - _cellIndexByName.Add(cellValueProperty.RawName, _cellIndexByName.Count); + foreach (var kvp in ((TableRowElement) (_cellElementContainer.GetChild(0))).Elements) + { + columns.Add(new MultiColumnHeaderState.Column + { + headerContent = kvp.Value, + headerTextAlignment = TextAlignment.Center, + autoResize = true, + canSort = false, + }); + } } } } if (root.children == null) { - root.AddChild(new TreeViewItem(0, 0, "Empty")); + root.AddChild(new TableTreeEmptyItem()); + } + + if (multiColumnHeader.state == null || + multiColumnHeader.state.columns.Length == 1) + { + multiColumnHeader.state = new MultiColumnHeaderState(columns.ToArray()); } return root; } + protected override float GetCustomRowHeight(int row, TreeViewItem item) + { + if (item is TableTreeEmptyItem) + { + return EditorGUIUtility.singleLineHeight; + } + + var height = 0f; + var rowElement = (TableRowElement) _cellElementContainer.GetChild(row); + + foreach (var visibleColumnIndex in multiColumnHeader.state.visibleColumns) + { + var cellHeight = visibleColumnIndex == 0 + ? EditorGUIUtility.singleLineHeight + : rowElement.Elements[visibleColumnIndex - 1].Key.GetHeight(Width); + + height = Math.Max(height, cellHeight); + } + + return height + EditorGUIUtility.standardVerticalSpacing * 2; + } + protected override void RowGUI(RowGUIArgs args) { - var tableItem = args.item as TableTreeElement; - - if (tableItem == null) + if (args.item is TableTreeEmptyItem) { base.RowGUI(args); return; } - foreach (var cellValueProperty in tableItem.Property.ChildrenProperties) + var rowElement = (TableRowElement) _cellElementContainer.GetChild(args.row); + + for (var i = 0; i < multiColumnHeader.state.visibleColumns.Length; i++) { - if (!_cellIndexByName.TryGetValue(cellValueProperty.RawName, out var cellIndex)) + var visibleColumnIndex = multiColumnHeader.state.visibleColumns[i]; + var rowIndex = args.row; + + var cellRect = args.GetCellRect(i); + cellRect.yMin += EditorGUIUtility.standardVerticalSpacing; + + if (visibleColumnIndex == 0) { + ReorderableList.defaultBehaviours.DrawElementDraggingHandle(cellRect, rowIndex, + _listGui.index == rowIndex, _listGui.index == rowIndex, _listGui.draggable); continue; } - var cellRect = args.GetCellRect(cellIndex); - - if (!_cellElements.ContainsKey(cellValueProperty)) - { - var cellElement = new TriPropertyElement(cellValueProperty, new TriPropertyElement.Props - { - forceInline = true, - }); - _cellElements.Add(cellValueProperty, cellElement); - _cellElementContainer.AddChild(cellElement); - } + var cellElement = rowElement.Elements[visibleColumnIndex - 1].Key; + cellRect.height = cellElement.GetHeight(Width); + using (TriGuiHelper.PushIndentLevel(-EditorGUI.indentLevel)) + using (TriGuiHelper.PushLabelWidth(EditorGUIUtility.labelWidth / rowElement.ChildrenCount)) using (TriPropertyOverrideContext.BeginOverride(_propertyOverrideContext)) { - _cellElements[cellValueProperty].OnGUI(cellRect); + cellElement.OnGUI(cellRect); } } } + } - private static MultiColumnHeader BuildHeader(TriProperty property) + public class TableRowElement : TriPropertyCollectionBaseElement + { + public TableRowElement(TriProperty property) { - var columns = TriTypeDefinition - .GetCached(property.ArrayElementType) - .Properties - .Select(it => new MultiColumnHeaderState.Column - { - headerContent = new GUIContent(ObjectNames.NicifyVariableName(it.Name)), - autoResize = true, - canSort = false, - allowToggleVisibility = false, - }) - .Concat(new[] - { - new MultiColumnHeaderState.Column - { - headerContent = GUIContent.none, - autoResize = false, - canSort = false, - allowToggleVisibility = false, - width = 10, - }, - }) - .ToArray(); + DeclareGroups(property.ValueType); - var header = new MultiColumnHeader(new MultiColumnHeaderState(columns)) + Elements = new List>(); + + if (property.PropertyType == TriPropertyType.Generic) { - canSort = false, - }; + foreach (var childProperty in property.ChildrenProperties) + { + var oldChildrenCount = ChildrenCount; - header.ResizeToFit(); + var props = new TriPropertyElement.Props + { + forceInline = true, + }; + AddProperty(childProperty, props, out var group); - return header; + if (oldChildrenCount != ChildrenCount) + { + var element = GetChild(ChildrenCount - 1); + var headerContent = new GUIContent(group ?? childProperty.DisplayName); + + Elements.Add(new KeyValuePair(element, headerContent)); + } + } + } + else + { + AddChild(new TriLabelElement("[TableList] cannot be used on non generic elements")); + } + } + + public List> Elements { get; } + } + + [Serializable] + private class TableColumnHeader : MultiColumnHeader + { + public TableColumnHeader() : base(null) + { + canSort = false; + height = DefaultGUI.minimumHeight; } } [Serializable] - private class TableTreeElement : TreeViewItem + private class TableTreeEmptyItem : TreeViewItem { - public TableTreeElement(int id, TriProperty property) : base(id, 0) + public TableTreeEmptyItem() : base(0, 0, "Table is Empty") + { + } + } + + [Serializable] + private class TableTreeItem : TreeViewItem + { + public TableTreeItem(int id, TriProperty property) : base(id, 0) { Property = property; } @@ -189,9 +397,27 @@ namespace TriInspector.Drawers private class TableListPropertyOverrideContext : TriPropertyOverrideContext { - public override GUIContent GetDisplayName(TriProperty property) + private readonly TriProperty _grandParentProperty; + private readonly GUIContent _noneLabel = GUIContent.none; + + public TableListPropertyOverrideContext(TriProperty grandParentProperty) { - return GUIContent.none; + _grandParentProperty = grandParentProperty; + } + + public override bool TryGetDisplayName(TriProperty property, out GUIContent displayName) + { + if (property.PropertyType == TriPropertyType.Primitive && + property.Parent is TriProperty parentProperty && + parentProperty.Parent == _grandParentProperty && + !property.TryGetAttribute(out GroupAttribute _)) + { + displayName = _noneLabel; + return true; + } + + displayName = default; + return false; } } } diff --git a/Editor.Extras/TriInspector.Editor.Extras.asmdef b/Editor.Extras/TriInspector.Editor.Extras.asmdef index cb9fffb..cb2f0c9 100644 --- a/Editor.Extras/TriInspector.Editor.Extras.asmdef +++ b/Editor.Extras/TriInspector.Editor.Extras.asmdef @@ -3,7 +3,8 @@ "rootNamespace": "", "references": [ "TriInspector", - "TriInspector.Editor" + "TriInspector.Editor", + "Unity.InternalAPIEditorBridge.012" ], "includePlatforms": [ "Editor" diff --git a/Editor/Elements/TriListElement.cs b/Editor/Elements/TriListElement.cs index 3ba479d..e36999f 100644 --- a/Editor/Elements/TriListElement.cs +++ b/Editor/Elements/TriListElement.cs @@ -8,17 +8,19 @@ using UnityEngine; namespace TriInspector.Elements { - internal class TriListElement : TriElement + public class TriListElement : TriElement { private const float ListExtraWidth = 7f; private const float DraggableAreaExtraWidth = 14f; - + private readonly TriProperty _property; private readonly ReorderableList _reorderableListGui; private readonly bool _alwaysExpanded; private float _lastContentWidth; + protected ReorderableList ListGui => _reorderableListGui; + public TriListElement(TriProperty property) { property.TryGetAttribute(out ListDrawerSettings settings); @@ -242,10 +244,7 @@ namespace TriInspector.Elements while (ChildrenCount < count) { var property = _property.ArrayElementProperties[ChildrenCount]; - AddChild(new TriPropertyElement(property, new TriPropertyElement.Props - { - forceInline = true, - })); + AddChild(CreateItemElement(property)); } while (ChildrenCount > count) @@ -268,6 +267,14 @@ namespace TriInspector.Elements return true; } + protected virtual TriElement CreateItemElement(TriProperty property) + { + return new TriPropertyElement(property, new TriPropertyElement.Props + { + forceInline = true, + }); + } + private void DrawHeaderCallback(Rect rect) { using (TriGuiHelper.PushIndentLevel(-EditorGUI.indentLevel)) diff --git a/Editor/Elements/TriPropertyCollectionBaseElement.cs b/Editor/Elements/TriPropertyCollectionBaseElement.cs index 9206939..7942c13 100644 --- a/Editor/Elements/TriPropertyCollectionBaseElement.cs +++ b/Editor/Elements/TriPropertyCollectionBaseElement.cs @@ -32,7 +32,13 @@ namespace TriInspector.Elements [PublicAPI] public void AddProperty(TriProperty property) { - var propertyElement = new TriPropertyElement(property); + AddProperty(property, default, out _); + } + + [PublicAPI] + public void AddProperty(TriProperty property, TriPropertyElement.Props props, out string group) + { + var propertyElement = new TriPropertyElement(property, props); if (property.TryGetAttribute(out GroupAttribute groupAttribute)) { @@ -41,15 +47,18 @@ namespace TriInspector.Elements var remaining = path.GetEnumerator(); if (remaining.MoveNext()) { + group = remaining.Current; AddGroupedChild(propertyElement, property, remaining.Current, remaining.Current, remaining); } else { + group = null; AddPropertyChild(propertyElement, property); } } else { + group = null; AddPropertyChild(propertyElement, property); } } diff --git a/Editor/TriElement.cs b/Editor/TriElement.cs index 98e80d8..1ff99c4 100644 --- a/Editor/TriElement.cs +++ b/Editor/TriElement.cs @@ -151,6 +151,7 @@ namespace TriInspector if (_attached) { child.AttachInternal(); + child.Update(); } } @@ -168,6 +169,7 @@ namespace TriInspector foreach (var child in _children) { child.AttachInternal(); + child.Update(); } } diff --git a/Editor/TriProperty.cs b/Editor/TriProperty.cs index 86c3803..75d6430 100644 --- a/Editor/TriProperty.cs +++ b/Editor/TriProperty.cs @@ -66,9 +66,10 @@ namespace TriInspector { get { - if (TriPropertyOverrideContext.Current != null) + if (TriPropertyOverrideContext.Current != null && + TriPropertyOverrideContext.Current.TryGetDisplayName(this, out var overrideName)) { - return TriPropertyOverrideContext.Current.GetDisplayName(this); + return overrideName; } if (_displayNameBackingField == null) diff --git a/Editor/TriPropertyOverrideContext.cs b/Editor/TriPropertyOverrideContext.cs index d578bfa..3360c0b 100644 --- a/Editor/TriPropertyOverrideContext.cs +++ b/Editor/TriPropertyOverrideContext.cs @@ -8,7 +8,7 @@ namespace TriInspector private static TriPropertyOverrideContext Override { get; set; } public static TriPropertyOverrideContext Current { get; private set; } - public abstract GUIContent GetDisplayName(TriProperty property); + public abstract bool TryGetDisplayName(TriProperty property, out GUIContent displayName); public static EnterPropertyScope BeginProperty() { diff --git a/README.md b/README.md index 20da5e2..1cf88ff 100644 --- a/README.md +++ b/README.md @@ -326,6 +326,34 @@ public class MinMax public List list; ``` +#### TableList + +![TableList](https://user-images.githubusercontent.com/26966368/171021981-e0aa5d4b-96b2-40dd-96b3-3cd6b3af01e3.png) + +```csharp +[TableList(Draggable = true, + HideAddButton = false, + HideRemoveButton = false, + AlwaysExpanded = false)] +public List table; + +[Serializable] +public class TableItem +{ + public Texture icon; + public string description; + + [Group("Combined"), LabelWidth(16)] + public string A, B, C; + + [Button, Group("Actions")] + public void Test1() { } + + [Button, Group("Actions")] + public void Test2() { } +} +``` + ### Conditionals #### ShowIf diff --git a/Runtime/Attributes/ListDrawerSettings.cs b/Runtime/Attributes/ListDrawerSettings.cs index e193662..4315d45 100644 --- a/Runtime/Attributes/ListDrawerSettings.cs +++ b/Runtime/Attributes/ListDrawerSettings.cs @@ -5,7 +5,7 @@ namespace TriInspector { [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] [Conditional("UNITY_EDITOR")] - public sealed class ListDrawerSettings : Attribute + public class ListDrawerSettings : Attribute { public bool Draggable { get; set; } = true; public bool HideAddButton { get; set; } diff --git a/Runtime/Attributes/TableListAttribute.cs b/Runtime/Attributes/TableListAttribute.cs index 159cb1f..0a0a88a 100644 --- a/Runtime/Attributes/TableListAttribute.cs +++ b/Runtime/Attributes/TableListAttribute.cs @@ -5,7 +5,7 @@ namespace TriInspector { [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] [Conditional("UNITY_EDITOR")] - public sealed class TableListAttribute : Attribute + public sealed class TableListAttribute : ListDrawerSettings { } } \ No newline at end of file diff --git a/Unity.InternalAPIEditorBridge.012/AssemblyInfo.cs b/Unity.InternalAPIEditorBridge.012/AssemblyInfo.cs index 26f65e0..2e97631 100644 --- a/Unity.InternalAPIEditorBridge.012/AssemblyInfo.cs +++ b/Unity.InternalAPIEditorBridge.012/AssemblyInfo.cs @@ -1,3 +1,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("TriInspector.Editor")] \ No newline at end of file +[assembly: InternalsVisibleTo("TriInspector.Editor")] +[assembly: InternalsVisibleTo("TriInspector.Editor.Extras")] \ No newline at end of file diff --git a/package.json b/package.json index eef5586..41d2cba 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "com.codewriter.triinspector", "displayName": "Tri Inspector", "description": "Advanced inspector attributes for Unity", - "version": "1.0.4", + "version": "1.0.5", "unity": "2020.3", "author": "CodeWriter (https://github.com/orgs/codewriter-packages)", "homepage": "https://github.com/codewriter-packages/Tri-Inspector#readme",