From 2dde3e08383abdac577fa7005ca345d491dc5ffb Mon Sep 17 00:00:00 2001 From: ruccho Date: Fri, 13 Dec 2024 14:38:38 +0900 Subject: [PATCH 01/10] Add left pane tree view and reordering behavior --- .../Editor/Assets/Uss/BuildMagicWindow.uss | 6 +- .../Editor/Assets/Uxml/LeftPane.uxml | 2 +- .../Editor/Assets/Uxml/LeftPaneListEntry.uxml | 4 +- .../Editor/Assets/Uxml/Window.uxml | 2 +- .../Editor/BuildMagicWindowView.cs | 1 + .../Editor/Elements/LeftPaneTreeView.cs | 192 ++++++++++++++++++ .../Editor/Elements/LeftPaneTreeView.cs.meta | 3 + .../Editor/Elements/LeftPaneView.cs | 68 +++---- 8 files changed, 230 insertions(+), 48 deletions(-) create mode 100644 Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneTreeView.cs create mode 100644 Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneTreeView.cs.meta diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow.uss b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow.uss index e72e908..51b5c50 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow.uss +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow.uss @@ -11,7 +11,6 @@ ListView.hide-empty .unity-list-view__empty-label { } Label.left-entry { - padding-left: 18px; } .right-pane-container { @@ -73,3 +72,8 @@ Label.configuration-name { margin-bottom: 1px; margin-left: 4px; } + + +LeftPaneListEntryView { + flex-grow: 1; +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/LeftPane.uxml b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/LeftPane.uxml index 94ce02d..f1ca0b4 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/LeftPane.uxml +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/LeftPane.uxml @@ -3,5 +3,5 @@ - + diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/LeftPaneListEntry.uxml b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/LeftPaneListEntry.uxml index 3e8031c..58feb83 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/LeftPaneListEntry.uxml +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/LeftPaneListEntry.uxml @@ -1,6 +1,6 @@ - - + + diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/Window.uxml b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/Window.uxml index d671b0f..e41496a 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/Window.uxml +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/Window.uxml @@ -1,5 +1,5 @@ - + diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowView.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowView.cs index e0232d9..8816423 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowView.cs +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowView.cs @@ -43,6 +43,7 @@ public void Dispose() public void Bind(SerializedObject so) { _rootVisualElement.Bind(so); + _leftPaneView.OnBind(so); } public void Unbind() diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneTreeView.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneTreeView.cs new file mode 100644 index 0000000..1fed427 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneTreeView.cs @@ -0,0 +1,192 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using BuildMagicEditor; +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine.UIElements; + +namespace BuildMagic.Window.Editor.Elements +{ + internal class LeftPaneTreeView : TreeView + { + private SerializedProperty _currentSchemeListProp; + private BuildScheme[] _currentSchemes; + private (BuildScheme scheme, SerializedProperty prop)[] _currentSchemesById; + private VisualElement _currentTracker; + + public LeftPaneTreeView() + { + makeItem = () => + { + var item = new LeftPaneListEntryView(); + item.AddManipulator(new ContextualMenuManipulator(ev => + { + ev.menu.AppendAction("Create a build scheme copy from this", + _ => CopyCreateRequested?.Invoke(item.Value)); + ev.menu.AppendAction("Create a build scheme inherit this", + _ => InheritCreateRequested?.Invoke(item.Value), + item.Inheritable ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled); + ev.menu.AppendAction("Remove this", _ => RemoveRequested?.Invoke(item.Value)); + ev.menu.AppendAction("Switch to this", _ => PreBuildRequestedByName?.Invoke(item.Value)); + ev.menu.AppendAction("Set as primary build scheme", + _ => BuildMagicSettings.instance.PrimaryBuildScheme = item.Value, + _ => item.Value == BuildMagicSettings.instance.PrimaryBuildScheme + ? DropdownMenuAction.Status.Disabled + : DropdownMenuAction.Status.Normal); + })); + return item; + }; + bindItem = (element, index) => + { + var prop = _currentSchemesById[GetIdForIndex(index)].prop; + ((LeftPaneListEntryView)element).CustomBind(prop); + }; + unbindItem = (element, _) => { ((LeftPaneListEntryView)element).CustomUnbind(); }; + selectedIndicesChanged += indices => + { + var tmpArray = indices.ToArray(); + var index = tmpArray.Length > 0 ? tmpArray[0] : -1; + var scheme = index >= 0 ? _currentSchemesById[GetIdForIndex(index)].scheme : null; + OnSelectionChanged?.Invoke(Array.IndexOf(_currentSchemes, scheme)); + }; + + itemIndexChanged += (sourceId, parentId) => + { + var pair = _currentSchemesById[sourceId]; + var baseName = ""; + if (parentId >= 0) + { + var parentPair = _currentSchemesById[parentId]; + baseName = parentPair.scheme.Name; + ExpandItem(parentId); + } + + pair.prop.FindPropertyRelative("_baseSchemeName").stringValue = baseName; + _currentSchemeListProp.serializedObject.ApplyModifiedProperties(); + RebuildTree(_currentSchemeListProp); + }; + } + + public event Action CopyCreateRequested; + public event Action InheritCreateRequested; + public event Action RemoveRequested; + public event Action PreBuildRequestedByName; + public event Action OnSelectionChanged; + + public void SelectIndex(int index) + { + var id = -1; + if (index >= 0 && index < _currentSchemes.Length) + id = Array.FindIndex(_currentSchemesById, pair => pair.scheme == _currentSchemes[index]); + + if (id >= 0) + SetSelectionById(id); + else + ClearSelection(); + } + + public void BindSchemeList(SerializedProperty schemeListProp) + { + _currentSchemeListProp = schemeListProp; + var sizeProp = schemeListProp.FindPropertyRelative("Array"); + sizeProp.Next(true); + _currentTracker?.RemoveFromHierarchy(); + (_currentTracker = new VisualElement()).TrackPropertyValue(sizeProp, + _ => RebuildTree(schemeListProp)); // rebuild on resize + hierarchy.Add(_currentTracker); + RebuildTree(schemeListProp); + } + + private void RebuildTree(SerializedProperty schemeListProp) + { + var prop = schemeListProp.FindPropertyRelative("Array"); + + var end = prop.GetEndProperty(); + + prop.NextVisible(true); + + List<(BuildScheme, SerializedProperty)> rootSchemes = new(); + Dictionary> hierarchy = new(); + List schemes = new(); + do + { + if (SerializedProperty.EqualContents(prop, end)) break; + if (prop.propertyType is SerializedPropertyType.ArraySize) continue; + + var scheme = prop.managedReferenceValue as BuildScheme; + if (scheme == null) continue; + var baseSchemeName = scheme.BaseSchemeName; + if (string.IsNullOrEmpty(baseSchemeName)) + { + // root + rootSchemes.Add((scheme, prop.Copy())); + } + else + { + if (!hierarchy.TryGetValue(baseSchemeName, out var children)) + children = hierarchy[baseSchemeName] = new HashSet<(BuildScheme, SerializedProperty)>(); + + children.Add((scheme, prop.Copy())); + } + + schemes.Add(scheme); + } while (prop.NextVisible(false)); + + _currentSchemes = schemes.ToArray(); + + // create tree + + List> rootItems = new(); + var schemesById = + new (BuildScheme scheme, SerializedProperty prop)[schemes.Count]; + + var id = 0; + + HashSet visited = new(); + + TreeViewItemData SetupChild((BuildScheme scheme, SerializedProperty prop) pair, ref int id) + { + if (!visited.Add(pair.scheme)) throw new InvalidOperationException("Circular inheritance detected!"); + List> childrenItems = new(); + var name = pair.scheme.Name; + if (hierarchy.TryGetValue(name, out var children)) + { + foreach (var child in children) + childrenItems.Add(SetupChild(child, ref id)); + + hierarchy.Remove(name); + } + + schemesById[id] = pair; + + return new TreeViewItemData(id++, pair.scheme, childrenItems); + } + + foreach (var pair in rootSchemes) + rootItems.Add(SetupChild(pair, ref id)); + + foreach (var (missingBaseSchemeName, children) in hierarchy) + foreach (var pair in children) + rootItems.Add(SetupChild(pair, ref id)); + + _currentSchemesById = schemesById; + + Clear(); + SetRootItems(rootItems); + Rebuild(); + } + + public new class UxmlFactory : UxmlFactory + { + } + + public new class UxmlTraits : TreeView.UxmlTraits + { + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneTreeView.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneTreeView.cs.meta new file mode 100644 index 0000000..0d9ed54 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneTreeView.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b6d5c583a39a4346ac3bac7637a8c7d0 +timeCreated: 1733885184 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneView.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneView.cs index 45257cb..50eeaa1 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneView.cs +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneView.cs @@ -3,8 +3,6 @@ // -------------------------------------------------------------- using System; -using System.Linq; -using BuildMagicEditor; using BuildMagic.Window.Editor.SubWindows; using UnityEditor; using UnityEditor.UIElements; @@ -15,15 +13,15 @@ namespace BuildMagic.Window.Editor.Elements { internal sealed class LeftPaneView : VisualElement, ILeftPaneView { - private readonly ListView _listView; private readonly VisualTreeAsset _listEntryTemplate; + private readonly LeftPaneTreeView _treeView; public LeftPaneView() { var visualTree = AssetLoader.LoadUxml("LeftPane"); Assert.IsNotNull(visualTree); visualTree.CloneTree(this); - + var saveButton = this.Q(); Assert.IsNotNull(saveButton); saveButton.clickable.clicked += () => SaveRequested?.Invoke(); @@ -33,65 +31,49 @@ public LeftPaneView() toolbarMenu.menu.AppendAction("Create a build scheme", _ => CopyCreateRequested?.Invoke(string.Empty)); toolbarMenu.menu.AppendSeparator(); - toolbarMenu.menu.AppendAction("Remove the selected build scheme", _ => RemoveRequested?.Invoke(string.Empty), - OnMenuActionStatus); + toolbarMenu.menu.AppendAction("Remove the selected build scheme", + _ => RemoveRequested?.Invoke(string.Empty), + OnMenuActionStatus); toolbarMenu.menu.AppendAction("Switch to the selected build scheme", _ => PreBuildRequested?.Invoke(), - OnMenuActionStatus); + OnMenuActionStatus); toolbarMenu.menu.AppendAction("Build with the selected scheme", _ => BuildRequested?.Invoke(), - OnMenuActionStatus); + OnMenuActionStatus); toolbarMenu.menu.AppendSeparator(); toolbarMenu.menu.AppendAction("Open diff window", _ => DiffWindow.Open()); - _listView = this.Q(); - Assert.IsNotNull(_listView); - _listView.bindingPath = "_schemes"; - _listView.makeItem = () => - { - var item = new LeftPaneListEntryView(); - item.AddManipulator(new ContextualMenuManipulator(ev => - { - ev.menu.AppendAction("Create a build scheme copy from this", _ => CopyCreateRequested?.Invoke(item.Value)); - ev.menu.AppendAction("Create a build scheme inherit this", _ => InheritCreateRequested?.Invoke(item.Value), item.Inheritable ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled); - ev.menu.AppendAction("Remove this", _ => RemoveRequested?.Invoke(item.Value)); - ev.menu.AppendAction("Switch to this", _ => PreBuildRequestedByName?.Invoke(item.Value)); - ev.menu.AppendAction("Set as primary build scheme", _ => BuildMagicSettings.instance.PrimaryBuildScheme = item.Value, - _ => item.Value == BuildMagicSettings.instance.PrimaryBuildScheme ? DropdownMenuAction.Status.Disabled : DropdownMenuAction.Status.Normal); - })); - return item; - }; - _listView.bindItem = (element, index) => - { - var item = _listView.itemsSource[index] as SerializedProperty; - ((LeftPaneListEntryView)element).CustomBind(item); - }; - _listView.unbindItem = (element, index) => - { - ((LeftPaneListEntryView)element).CustomUnbind(); - }; - _listView.selectedIndicesChanged += indices => - { - var tmpArray = indices.ToArray(); - OnSelectionChanged?.Invoke(tmpArray.Length > 0 ? tmpArray[0] : -1); - }; + _treeView = this.Q(); + Assert.IsNotNull(_treeView); + _treeView.CopyCreateRequested += value => CopyCreateRequested(value); + _treeView.InheritCreateRequested += value => InheritCreateRequested(value); + _treeView.RemoveRequested += value => RemoveRequested(value); + _treeView.PreBuildRequestedByName += value => PreBuildRequestedByName(value); + _treeView.OnSelectionChanged += index => OnSelectionChanged(index); + // _treeView.viewDataKey = $"{nameof(LeftPaneView)}.{nameof(LeftPaneTreeView)}"; + // _treeView.makeItem = () => new LeftPaneListEntryView(); } public event Action CopyCreateRequested; public event Action InheritCreateRequested; public event Action RemoveRequested; - public event Action PreBuildRequested; public event Action PreBuildRequestedByName; - public event Action BuildRequested; public event Action OnSelectionChanged; + public event Action PreBuildRequested; + public event Action BuildRequested; public event Action SaveRequested; + public void OnBind(SerializedObject model) + { + _treeView.BindSchemeList(model.FindProperty("_schemes")); + } + public void UpdateSelectedIndex(int index) { - _listView.selectedIndex = index; + _treeView.SelectIndex(index); } private DropdownMenuAction.Status OnMenuActionStatus(DropdownMenuAction _) { - var index = _listView.selectedIndex; + var index = _treeView.selectedIndex; return index >= 0 ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled; } From e02be0c99b8f5a2f33f2d90b1842da29a82578d1 Mon Sep 17 00:00:00 2001 From: ruccho Date: Fri, 13 Dec 2024 14:39:48 +0900 Subject: [PATCH 02/10] Remove unused code --- .../BuildMagic.Window/Editor/Elements/LeftPaneView.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneView.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneView.cs index 50eeaa1..0d368a7 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneView.cs +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneView.cs @@ -48,8 +48,6 @@ public LeftPaneView() _treeView.RemoveRequested += value => RemoveRequested(value); _treeView.PreBuildRequestedByName += value => PreBuildRequestedByName(value); _treeView.OnSelectionChanged += index => OnSelectionChanged(index); - // _treeView.viewDataKey = $"{nameof(LeftPaneView)}.{nameof(LeftPaneTreeView)}"; - // _treeView.makeItem = () => new LeftPaneListEntryView(); } public event Action CopyCreateRequested; From e39420517afc24ff9327d7599d21a6a4222ae359 Mon Sep 17 00:00:00 2001 From: ruccho Date: Fri, 13 Dec 2024 15:47:39 +0900 Subject: [PATCH 03/10] Add support for indirect overrides to the pipeline --- .../Editor/BuildMagicCLI.cs | 43 +++++------ .../Editor/BuildMagicWindowModel.cs | 19 ++--- .../Editor/BuildConfigurationUtility.cs | 28 ------- .../Editor/BuildConfigurationUtility.cs.meta | 3 - .../BuildMagic/Editor/BuildSchemeUtility.cs | 73 +++++++++++++++++++ .../Editor/BuildSchemeUtility.cs.meta | 3 + 6 files changed, 100 insertions(+), 69 deletions(-) delete mode 100644 Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildConfigurationUtility.cs delete mode 100644 Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildConfigurationUtility.cs.meta create mode 100644 Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildSchemeUtility.cs create mode 100644 Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildSchemeUtility.cs.meta diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagicCLI.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagicCLI.cs index 40b94d8..4d943d8 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagicCLI.cs +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagicCLI.cs @@ -43,9 +43,9 @@ public static void PreBuild() // TODO: System.Commandline ライクな実装に仕上げたいが後回し RunCommand(context => { - var configurations = BuildConfigurationUtility.ResolveConfigurations( - context.BaseScheme?.PreBuildConfigurations ?? Enumerable.Empty(), - context.CurrentScheme.PreBuildConfigurations); + var configurations = + BuildSchemeUtility.EnumerateComposedConfigurations(context + .InheritanceTreeFromLeafToRoot); var preBuildTasks = configurations .Select(c => CreateBuildTask(c, context)) @@ -68,9 +68,9 @@ public static void Build() { var internalPrepareTasks = CreateInternalPrepareTasks(context); - var configurations = BuildConfigurationUtility.ResolveConfigurations( - context.BaseScheme?.PostBuildConfigurations ?? Enumerable.Empty(), - context.CurrentScheme.PostBuildConfigurations); + var configurations = + BuildSchemeUtility.EnumerateComposedConfigurations(context + .InheritanceTreeFromLeafToRoot); var postBuildTasks = configurations .Select(c => CreateBuildTask(c, context)) @@ -142,11 +142,13 @@ private static int InvokeCommand(Action command) } } - internal static BuildScheme LoadScheme(CommandLineParser parser) + internal static BuildScheme LoadScheme(CommandLineParser parser, IEnumerable allSchemes = null) { var name = parser.ParseFirst(SchemeOption); - var scheme = BuildSchemeLoader.LoadAll().FirstOrDefault(s => s.Name == name); + allSchemes ??= BuildSchemeLoader.LoadAll(); + + var scheme = allSchemes.FirstOrDefault(s => s.Name == name); if (scheme == null) { throw new CommandLineArgumentException($"No such scheme found: {name}"); @@ -155,18 +157,6 @@ internal static BuildScheme LoadScheme(CommandLineParser parser) return scheme; } - internal static BuildScheme LoadBaseScheme(BuildScheme scheme) - { - if (string.IsNullOrEmpty(scheme.BaseSchemeName)) - return null; - - var baseScheme = BuildSchemeLoader.LoadAll().FirstOrDefault(s => s.Name == scheme.BaseSchemeName); - if (baseScheme == null) - throw new CommandLineArgumentException($"No such base scheme found: {scheme.BaseSchemeName}"); - - return baseScheme; - } - internal static List ParseOverrideProperties( CommandLineParser parser, IDictionary aliases = null) @@ -204,7 +194,7 @@ internal class Context { public CommandLineParser CommandLineParser { get; } public IBuildScheme CurrentScheme { get; } - public IBuildScheme BaseScheme { get; } + public IEnumerable InheritanceTreeFromLeafToRoot { get; } public BuildPropertyResolver BuildPropertyResolver { get; } public BuildTaskBuilderProvider TaskBuilderProvider { get; } @@ -215,25 +205,26 @@ internal class Context public static Context Create(ILogger logger) { var parser = CommandLineParser.Create(); - var scheme = LoadScheme(parser); - var baseScheme = LoadBaseScheme(scheme); + var allSchemes = BuildSchemeLoader.LoadAll(); + var scheme = LoadScheme(parser, allSchemes); + var tree = BuildSchemeUtility.EnumerateSchemeTreeFromLeafToRoot(scheme, allSchemes); var resolver = BuildPropertyResolver.CreateDefault(ParseOverrideProperties(parser, OverrideAliases)); var provider = BuildTaskBuilderProvider.CreateDefault(); - return new Context(parser, scheme, baseScheme, provider, resolver, logger); + return new Context(parser, scheme, tree, provider, resolver, logger); } private Context( CommandLineParser parser, IBuildScheme scheme, - IBuildScheme baseScheme, + IEnumerable inheritanceTreeFromLeafToRoot, BuildTaskBuilderProvider provider, BuildPropertyResolver resolver, ILogger logger) { CommandLineParser = parser; CurrentScheme = scheme; - BaseScheme = baseScheme; + InheritanceTreeFromLeafToRoot = inheritanceTreeFromLeafToRoot; BuildPropertyResolver = resolver; TaskBuilderProvider = provider; Logger = logger; diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowModel.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowModel.cs index 7ea9290..ee5ab67 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowModel.cs +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowModel.cs @@ -105,25 +105,22 @@ public void PreBuild() { Assert.IsNotNull(_selected, "No selected scheme"); - PreBuildInternal(_selected, _selectedBase); + PreBuildInternal(_selected); } public void PreBuildByName(string targetSchemeName) { var targetScheme = _schemes.FirstOrDefault(s => s.Name == targetSchemeName); Assert.IsNotNull(targetScheme, $"{targetSchemeName} is not found"); - - var baseScheme = _schemes.FirstOrDefault(s => s.Name == targetScheme.BaseSchemeName) ?? EmptyBuildScheme; - PreBuildInternal(targetScheme, baseScheme); + PreBuildInternal(targetScheme); } - private static void PreBuildInternal(BuildScheme targetScheme, BuildScheme baseScheme) + private void PreBuildInternal(BuildScheme targetScheme) { - var configurations = - BuildConfigurationUtility.ResolveConfigurations(baseScheme?.PreBuildConfigurations ?? Enumerable.Empty(), - targetScheme.PreBuildConfigurations); - + var configurations = + BuildSchemeUtility.EnumerateComposedConfigurations(targetScheme, _schemes); + BuildPipeline.PreBuild(BuildTaskBuilderUtility.CreateBuildTasks(configurations)); } @@ -138,9 +135,7 @@ public void Build(string buildPath) _selected.InternalPrepareConfigurations)); var configurations = - BuildConfigurationUtility.ResolveConfigurations( - _selectedBase?.PostBuildConfigurations ?? Enumerable.Empty(), - _selected.PostBuildConfigurations); + BuildSchemeUtility.EnumerateComposedConfigurations(_selected, _schemes); var postBuildTasks = BuildTaskBuilderUtility .CreateBuildTasks(configurations).ToList(); diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildConfigurationUtility.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildConfigurationUtility.cs deleted file mode 100644 index 0014ff5..0000000 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildConfigurationUtility.cs +++ /dev/null @@ -1,28 +0,0 @@ -// -------------------------------------------------------------- -// Copyright 2024 CyberAgent, Inc. -// -------------------------------------------------------------- - -using System.Collections.Generic; - -namespace BuildMagicEditor -{ - internal static class BuildConfigurationUtility - { - internal static IList ResolveConfigurations( - IEnumerable baseSchemeConfigurations, - IEnumerable derivedSchemeConfigurations) - { - var configurations = new List(baseSchemeConfigurations); - foreach (var derivedConfiguration in derivedSchemeConfigurations) - { - var index = configurations.FindIndex(c => c.TaskType == derivedConfiguration.TaskType); - if (index >= 0) - configurations[index] = derivedConfiguration; - else - configurations.Add(derivedConfiguration); - } - - return configurations; - } - } -} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildConfigurationUtility.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildConfigurationUtility.cs.meta deleted file mode 100644 index 3d4fb13..0000000 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildConfigurationUtility.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: bc107e3655ef4b4593e3336e4d18afe2 -timeCreated: 1727422960 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildSchemeUtility.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildSchemeUtility.cs new file mode 100644 index 0000000..30deed7 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildSchemeUtility.cs @@ -0,0 +1,73 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BuildMagicEditor +{ + internal static class BuildSchemeUtility + { + /// + /// Loads the base schemes in leaf-to-root order. + /// + /// + /// + /// + public static IEnumerable EnumerateSchemeTreeFromLeafToRoot(BuildScheme scheme, + IEnumerable allSchemes) + { + static IEnumerable LoadBaseSchemesCore(BuildScheme scheme, IEnumerable allSchemes, + HashSet visited) + { + if (!visited.Add(scheme)) throw new InvalidOperationException("Circular inheritance detected!"); + + yield return scheme; + + if (string.IsNullOrEmpty(scheme.BaseSchemeName)) yield break; + + var baseScheme = allSchemes.FirstOrDefault(s => s.Name == scheme.BaseSchemeName); + if (baseScheme == null) + throw new InvalidOperationException($"No such base scheme found: {scheme.BaseSchemeName}"); + + foreach (var ancestor in LoadBaseSchemesCore(baseScheme, allSchemes, visited)) + yield return ancestor; + } + + return LoadBaseSchemesCore(scheme, allSchemes, new HashSet()); + } + + public static IEnumerable EnumerateComposedConfigurations( + BuildScheme scheme, + IEnumerable allSchemes) where TContext : IBuildContext + { + return EnumerateComposedConfigurations(EnumerateSchemeTreeFromLeafToRoot(scheme, allSchemes)); + } + + public static IEnumerable EnumerateComposedConfigurations( + IEnumerable treeFromLeafToRoot) where TContext : IBuildContext + { + HashSet taskTypes = new(); + + foreach (var scheme in treeFromLeafToRoot) + { + if (typeof(TContext).IsAssignableFrom(typeof(IPreBuildContext))) + foreach (var configuration in scheme.PreBuildConfigurations) + if (taskTypes.Add(configuration.TaskType)) + yield return configuration; + + if (typeof(TContext).IsAssignableFrom(typeof(IInternalPrepareContext))) + foreach (var configuration in scheme.InternalPrepareConfigurations) + if (taskTypes.Add(configuration.TaskType)) + yield return configuration; + + if (typeof(TContext).IsAssignableFrom(typeof(IPostBuildContext))) + foreach (var configuration in scheme.PostBuildConfigurations) + if (taskTypes.Add(configuration.TaskType)) + yield return configuration; + } + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildSchemeUtility.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildSchemeUtility.cs.meta new file mode 100644 index 0000000..e3f5a6b --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuildSchemeUtility.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 527210976abd4a29ab5d491c860278bd +timeCreated: 1734069371 \ No newline at end of file From 0de92096faafc034b51a4f57dc741645a0571b94 Mon Sep 17 00:00:00 2001 From: ruccho Date: Wed, 25 Dec 2024 16:44:15 +0900 Subject: [PATCH 04/10] Add list of derived configurations including indirect --- .../Editor/Assets/Uxml/RightPane.uxml | 15 +- .../Editor/BuildMagicWindowModel.cs | 105 ++++---- .../Editor/BuildMagicWindowPresenter.cs | 7 +- .../Editor/BuildMagicWindowView.cs | 2 + .../Editor/Elements/ConfigurationListView.cs | 237 ++++++++++++++++++ .../Elements/ConfigurationListView.cs.meta | 3 + .../Editor/Elements/LeftPaneTreeView.cs | 1 + .../Editor/Elements/RightPaneView.cs | 90 +------ 8 files changed, 322 insertions(+), 138 deletions(-) create mode 100644 Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/ConfigurationListView.cs create mode 100644 Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/ConfigurationListView.cs.meta diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/RightPane.uxml b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/RightPane.uxml index 369ec05..49240ac 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/RightPane.uxml +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/RightPane.uxml @@ -9,22 +9,13 @@ - - - - + - - - - + - - - - + diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowModel.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowModel.cs index ee5ab67..c03a0cc 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowModel.cs +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowModel.cs @@ -18,26 +18,23 @@ namespace BuildMagic.Window.Editor { internal sealed class BuildMagicWindowModel : ScriptableObject { - // HACK: this is used to indicate _selectedBase is empty without making UI Toolkit binding system confused. - private static readonly BuildScheme EmptyBuildScheme = new(); - private const int DefaultSelectedIndex = -1; + private const string BuiltinTemplateName = "< BuiltinTemplate >"; - [SerializeReference] - private BuildScheme - _selected = new(); // HACK: Assign a dummy as the initial value because if it is null at binding, the change will not be applied. + private static readonly BuildSchemeContainer EmptyBuildSchemeContainer = new(new(), null); - [SerializeReference] private BuildScheme _selectedBase = EmptyBuildScheme; + // HACK: Assign a dummy as the initial value because if it is null at binding, the change will not be applied. + [SerializeReference] private BuildSchemeContainer _selected = EmptyBuildSchemeContainer; [SerializeReference] private List _schemes = new(); - [SerializeField] - private ObservableProperty _selectedIndex = new(DefaultSelectedIndex); + [SerializeField] private ObservableProperty _selectedIndex = new(DefaultSelectedIndex); public IReadOnlyObservableProperty SelectedIndex => _selectedIndex; - public ICollection SchemeNamesWithTemplate => _schemes.Select(s => s.Name).Prepend(BuiltinTemplateName).ToArray(); + public ICollection SchemeNamesWithTemplate => + _schemes.Select(s => s.Name).Prepend(BuiltinTemplateName).ToArray(); public ICollection RootSchemeNames => _schemes.Where(s => string.IsNullOrEmpty(s.BaseSchemeName)) .Select(s => s.Name).ToArray(); @@ -76,7 +73,7 @@ public void Create(string newSchemeName, string copyFromName, string baseSchemeN _schemes.Add(newSetting); _schemes.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); if (_selected != null) - _selectedIndex.Value = _schemes.IndexOf(_selected); + _selectedIndex.Value = _schemes.IndexOf(_selected.Self); EditorUtility.SetDirty(this); } @@ -85,15 +82,16 @@ public void Remove(string targetSchemeName) Undo.RecordObject(this, $"{UndoRedoEventName.Remove}|{targetSchemeName}"); BuildScheme targetScheme; - if (_selected != null && _selected.Name == targetSchemeName) + if (_selected != null && _selected.Self.Name == targetSchemeName) { - targetScheme = _selected; + targetScheme = _selected.Self; _selected = null; - _selectedBase = EmptyBuildScheme; _selectedIndex.Value = DefaultSelectedIndex; } else + { targetScheme = _schemes.FirstOrDefault(s => s.Name == targetSchemeName); + } Assert.IsNotNull(targetScheme, "No selected scheme"); _schemes.Remove(targetScheme); @@ -105,9 +103,9 @@ public void PreBuild() { Assert.IsNotNull(_selected, "No selected scheme"); - PreBuildInternal(_selected); + PreBuildInternal(_selected.Self); } - + public void PreBuildByName(string targetSchemeName) { var targetScheme = _schemes.FirstOrDefault(s => s.Name == targetSchemeName); @@ -115,7 +113,7 @@ public void PreBuildByName(string targetSchemeName) PreBuildInternal(targetScheme); } - + private void PreBuildInternal(BuildScheme targetScheme) { var configurations = @@ -132,10 +130,10 @@ public void Build(string buildPath) internalPrepareTasks.Add(new BuildPlayerOptionsApplyEditorSettingsTask()); internalPrepareTasks.AddRange( BuildTaskBuilderUtility.CreateBuildTasks( - _selected.InternalPrepareConfigurations)); + _selected.Self.InternalPrepareConfigurations)); var configurations = - BuildSchemeUtility.EnumerateComposedConfigurations(_selected, _schemes); + BuildSchemeUtility.EnumerateComposedConfigurations(_selected.Self, _schemes); var postBuildTasks = BuildTaskBuilderUtility .CreateBuildTasks(configurations).ToList(); @@ -153,13 +151,11 @@ public void Select(int index) if (index < 0) { _selected = null; - _selectedBase = EmptyBuildScheme; _selectedIndex.Value = DefaultSelectedIndex; } else { - _selected = _schemes[index]; - _selectedBase = _schemes.FirstOrDefault(s => s.Name == _selected.BaseSchemeName) ?? EmptyBuildScheme; + _selected = new(_schemes[index], _schemes); _selectedIndex.Value = index; } } @@ -168,9 +164,7 @@ public void Select(string schemeName) { var index = _schemes.FindIndex(scheme => scheme.Name == schemeName); if (index < 0) - { return; - } Select(index); } @@ -195,7 +189,7 @@ void CreateEvent(in UndoRedoInfo info) _selectedIndex.SetValueAndNotify(_selectedIndex.Value); EditorUtility.SetDirty(this); } - + void RemoveEvent(in UndoRedoInfo info) { var schemeName = info.undoName.Split('|')[1]; @@ -205,7 +199,7 @@ void RemoveEvent(in UndoRedoInfo info) BuildSchemeLoader.Save(_schemes.FirstOrDefault(s => s.Name == schemeName)); _selectedIndex.SetValueAndNotify(_selectedIndex.Value); } - + void ChangeIndexEvent(in UndoRedoInfo _) { _selectedIndex.SetValueAndNotify(_selectedIndex.Value); @@ -217,16 +211,16 @@ public void AddConfiguration(ConfigurationType configurationType, Type type) Assert.IsNotNull(_selected, "No selected scheme"); Assert.IsTrue(configurationType != ConfigurationType.None, "Invalid configuration type"); Assert.IsNotNull(type, "type is null"); - + Undo.RecordObject(this, $"{UndoRedoEventName.AddConfiguration}|{configurationType}|{type.Name}"); - + var configuration = (IBuildConfiguration)Activator.CreateInstance(type); Assert.IsNotNull(configuration, "Failed to create configuration"); switch (configurationType) { case ConfigurationType.PreBuild: - _selected.AddPreBuildConfiguration(configuration); + _selected.Self.AddPreBuildConfiguration(configuration); break; #if BUILDMAGIC_DEVELOPER case ConfigurationType.InternalPrepare: @@ -234,29 +228,32 @@ public void AddConfiguration(ConfigurationType configurationType, Type type) break; #endif case ConfigurationType.PostBuild: - _selected.AddPostBuildConfiguration(configuration); + _selected.Self.AddPostBuildConfiguration(configuration); break; case ConfigurationType.None: default: throw new ArgumentOutOfRangeException(nameof(configurationType), configurationType, null); } + EditorUtility.SetDirty(this); } - public void RemoveConfiguration(ConfigurationType configurationType, int index, IBuildConfiguration configuration) + public void RemoveConfiguration(ConfigurationType configurationType, int index, + IBuildConfiguration configuration) { Assert.IsNotNull(_selected, "No selected scheme"); Assert.IsTrue(configurationType != ConfigurationType.None, "Invalid configuration type"); - - Undo.RecordObject(this, $"{UndoRedoEventName.RemoveConfiguration}|{configurationType}|{configuration?.GetType().Name ?? "Missing"}"); + + Undo.RecordObject(this, + $"{UndoRedoEventName.RemoveConfiguration}|{configurationType}|{configuration?.GetType().Name ?? "Missing"}"); var targetList = configurationType switch { - ConfigurationType.PreBuild => _selected.PreBuildConfigurations, + ConfigurationType.PreBuild => _selected.Self.PreBuildConfigurations, #if BUILDMAGIC_DEVELOPER - ConfigurationType.InternalPrepare => _selected.InternalPrepareConfigurations, + ConfigurationType.InternalPrepare => _selected.Self.InternalPrepareConfigurations, #endif - ConfigurationType.PostBuild => _selected.PostBuildConfigurations, + ConfigurationType.PostBuild => _selected.Self.PostBuildConfigurations, _ => throw new ArgumentOutOfRangeException(nameof(configurationType), configurationType, null) }; Assert.AreEqual(configuration, targetList[index], "Invalid configuration"); @@ -264,15 +261,15 @@ public void RemoveConfiguration(ConfigurationType configurationType, int index, switch (configurationType) { case ConfigurationType.PreBuild: - _selected.RemovePreBuildConfiguration(index); + _selected.Self.RemovePreBuildConfiguration(index); break; #if BUILDMAGIC_DEVELOPER case ConfigurationType.InternalPrepare: - _selected.RemovePrepareBuildPlayerConfiguration(index); + _selected.Self.RemovePrepareBuildPlayerConfiguration(index); break; #endif case ConfigurationType.PostBuild: - _selected.RemovePostBuildConfiguration(index); + _selected.Self.RemovePostBuildConfiguration(index); break; default: throw new ArgumentOutOfRangeException(nameof(configurationType), configurationType, null); @@ -280,7 +277,7 @@ public void RemoveConfiguration(ConfigurationType configurationType, int index, EditorUtility.SetDirty(this); } - + public void Save() { _schemes.ForEach(BuildSchemeLoader.Save); @@ -290,13 +287,13 @@ public void Save() public HashSet GetConfigurationTypes() { Assert.IsNotNull(_selected, "No selected scheme"); - + var list = new List(); - list.AddRange(_selected.PreBuildConfigurations.Select(c => c.GetType())); + list.AddRange(_selected.Self.PreBuildConfigurations.Select(c => c.GetType())); #if BUILDMAGIC_DEVELOPER list.AddRange(_selected.InternalPrepareConfigurations.Select(c => c.GetType())); #endif - list.AddRange(_selected.PostBuildConfigurations.Select(c => c.GetType())); + list.AddRange(_selected.Self.PostBuildConfigurations.Select(c => c.GetType())); return list.ToHashSet(); } @@ -308,5 +305,27 @@ private static class UndoRedoEventName public const string AddConfiguration = "Add configuration"; public const string RemoveConfiguration = "Remove configuration"; } + + [Serializable] + private class BuildSchemeContainer + { + [SerializeReference] private BuildScheme _self; + [SerializeReference] private BuildSchemeContainer _base; + + public BuildScheme Self => _self; + public BuildSchemeContainer Base => _base; + + public BuildSchemeContainer(BuildScheme self, IEnumerable allSchemes) + { + _self = self; + var baseName = self.BaseSchemeName; + if (!string.IsNullOrEmpty(baseName)) + { + var baseScheme = allSchemes.FirstOrDefault(s => s.Name == baseName); + if (baseScheme != null) + _base = new BuildSchemeContainer(baseScheme, allSchemes); + } + } + } } } diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowPresenter.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowPresenter.cs index 5226e03..2705bf0 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowPresenter.cs +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowPresenter.cs @@ -75,16 +75,13 @@ private void CleanupViewEventHandlers() private void Bind() { - var so = new SerializedObject(_model); - using (new DebugLogDisabledScope()) - _view.Bind(so); - + // TinyRx runs OnNext immediately _model.SelectedIndex .Subscribe(index => { - _view.UpdateSelectedIndex(index); using (new DebugLogDisabledScope()) _view.Bind(new SerializedObject(_model)); // HACK: SerializeReference bindings are still not working properly in some cases. Rebind and force update + _view.UpdateSelectedIndex(index); }) .DisposeWith(_bindDisposable); } diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowView.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowView.cs index 8816423..0d91250 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowView.cs +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/BuildMagicWindowView.cs @@ -42,6 +42,8 @@ public void Dispose() public void Bind(SerializedObject so) { + _rootVisualElement.Unbind(); + _rightPaneView.OnBind(so); _rootVisualElement.Bind(so); _leftPaneView.OnBind(so); } diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/ConfigurationListView.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/ConfigurationListView.cs new file mode 100644 index 0000000..dccf7b7 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/ConfigurationListView.cs @@ -0,0 +1,237 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using BuildMagicEditor; +using UnityEditor; +using UnityEditor.UIElements; +using UnityEngine.Assertions; +using UnityEngine.Serialization; +using UnityEngine.UIElements; + +namespace BuildMagic.Window.Editor.Elements +{ + internal class ConfigurationListView : BindableElement + { + private readonly Label _label; + private readonly ListView _listView; + private VisualElement _arraySizeTracker; + private ConfigurationListView _baseView; + private Action _requestRemove; + private ConfigurationType _type; + + public ConfigurationListView() + { + Add(_label = new Label()); + Add(_listView = new ListView + { + reorderable = true, + showFoldoutHeader = false, + reorderMode = ListViewReorderMode.Animated, + showBoundCollectionSize = false, + virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight + }); + _listView.AddToClassList("hide-size"); + _listView.AddToClassList("hide-empty"); + _listView.AddToClassList("configuration-list"); + _listView.style.marginBottom = 20f; + _label.style.marginTop = 10f; + _label.style.marginBottom = 5f; + } + + public ConfigurationListView(ConfigurationType type) : this() + { + _type = type; + } + + public void Bind(SerializedProperty buildSchemeContainerProp, + Action requestRemove) + { + Bind(buildSchemeContainerProp, null, false, requestRemove); + } + + private void Bind(SerializedProperty buildSchemeContainerProp, IConfigurationFilter filter, + bool isDerived = false, + Action requestRemove = null) + { + _requestRemove = requestRemove; + var listView = _listView; + bindingPath = buildSchemeContainerProp.propertyPath; + if (isDerived) + { + _label.style.display = new StyleEnum(StyleKeyword.Undefined); + _label.text = + $"Derived from {buildSchemeContainerProp.FindPropertyRelative("_self._name").stringValue}"; + } + else + { + _label.style.display = new StyleEnum(StyleKeyword.None); + } + + var relativeBindingPath = _type switch + { + ConfigurationType.PreBuild => "_self._preBuildConfigurations", + ConfigurationType.InternalPrepare => "_self._internalPrepareConfigurations", + ConfigurationType.PostBuild => "_self._postBuildConfigurations", + _ => null + }; + listView.bindingPath = relativeBindingPath; + listView.makeItem = () => MakeConfigurationEntryView(_type); + listView.bindItem = (e, index) => BindConfiguration(e, index, listView.itemsSource, _type, filter); + listView.unbindItem = (e, _) => UnbindConfiguration(e); + listView.reorderable = !isDerived; + + // HACK: refresh item height cache and rebuild + listView.Rebuild(); + + RebuildBaseView(); + + if (_arraySizeTracker != null) + { + _arraySizeTracker.RemoveFromHierarchy(); + _arraySizeTracker = null; + } + + if (!isDerived) + { + // track array size to trigger filter rebinding + var arrayProp = buildSchemeContainerProp.FindPropertyRelative(relativeBindingPath); + var arraySizeProp = arrayProp.FindPropertyRelative("Array"); + arraySizeProp.Next(true); + + Add(_arraySizeTracker = new VisualElement()); + _arraySizeTracker.TrackPropertyValue(arraySizeProp, _ => { RebuildBaseView(); }); + } + + void RebuildBaseView() + { + var baseProp = buildSchemeContainerProp.FindPropertyRelative("_base"); + if (baseProp.managedReferenceId is ManagedReferenceUtility.RefIdNull + or ManagedReferenceUtility.RefIdUnknown) + { + // root + _baseView?.RemoveFromHierarchy(); + _baseView = null; + } + else + { + if (_baseView == null) + { + _baseView = new ConfigurationListView(_type); + _baseView.SetEnabled(false); + Add(_baseView); + } + + static IEnumerable GetTaskTypes(SerializedProperty prop) + { + for (var i = 0; i < prop.arraySize; i++) + if (prop.GetArrayElementAtIndex(i).managedReferenceValue is IBuildConfiguration + configuration) + yield return configuration.TaskType; + } + + var configurationsProp = buildSchemeContainerProp.FindPropertyRelative(relativeBindingPath); + _baseView.Bind(baseProp, new ConfigurationFilter(GetTaskTypes(configurationsProp), filter), true); + } + } + } + + private VisualElement MakeConfigurationEntryView(ConfigurationType type) + { + var entry = new ConfigurationEntryView(); + entry.AddManipulator(new ContextualMenuManipulator(ev => + { + ev.menu.AppendAction("Copy the configuration key", + _ => EditorGUIUtility.systemCopyBuffer = entry.Configuration.PropertyName, + entry.Configuration != null + ? DropdownMenuAction.Status.Normal + : DropdownMenuAction.Status.Disabled); + ev.menu.AppendAction( + "Collect a project setting", + _ => entry.CollectProjectSetting(), + entry.Collectable ? DropdownMenuAction.Status.Normal : DropdownMenuAction.Status.Disabled); + ev.menu.AppendAction( + "Remove the configuration", + _ => _requestRemove?.Invoke(type, entry.Index, entry.Configuration)); + })); + return entry; + } + + private static void BindConfiguration(VisualElement element, int index, IList sourceList, + ConfigurationType type, IConfigurationFilter filter = null) + { + var entry = element as ConfigurationEntryView; + Assert.IsNotNull(entry); + var source = sourceList[index] as SerializedProperty; + Assert.IsNotNull(source); + var configuration = source.managedReferenceValue as IBuildConfiguration; + + if (!(filter?.IsVisible(configuration) ?? true)) + { + entry.style.height = 1f; // HACK: avoid hidden items allocate empty space + return; + } + + entry.style.height = new StyleLength(StyleKeyword.Auto); + + using var scope = new DebugLogDisabledScope(); + entry.Bind(type, index, configuration); + entry.BindProperty(source); + } + + private static void UnbindConfiguration(VisualElement element) + { + var entry = element as ConfigurationEntryView; + Assert.IsNotNull(entry); + entry.Bind(ConfigurationType.None, -1, null); + entry.Unbind(); + } + + public new class UxmlFactory : UxmlFactory + { + } + + public new class UxmlTraits : BindableElement.UxmlTraits + { + private readonly UxmlEnumAttributeDescription _type = new() + { + name = "configuration-type" + }; + + public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc) + { + base.Init(ve, bag, cc); + + var derived = (ConfigurationListView)ve; + derived._type = _type.GetValueFromBag(bag, cc); + } + } + + private interface IConfigurationFilter + { + bool IsVisible(IBuildConfiguration configuration); + } + + private class ConfigurationFilter : IConfigurationFilter + { + private readonly Type[] _excludedTaskTypes; + private readonly IConfigurationFilter _parent; + + public ConfigurationFilter(IEnumerable excludedTypes, IConfigurationFilter parent = null) + { + _parent = parent; + _excludedTaskTypes = excludedTypes.ToArray(); + } + + public bool IsVisible(IBuildConfiguration configuration) + { + return (_parent?.IsVisible(configuration) ?? true) && + !_excludedTaskTypes.Contains(configuration.TaskType); + } + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/ConfigurationListView.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/ConfigurationListView.cs.meta new file mode 100644 index 0000000..542a42b --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/ConfigurationListView.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fe58fd6388f0464a988a171146da1d43 +timeCreated: 1734325243 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneTreeView.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneTreeView.cs index 1fed427..1401f22 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneTreeView.cs +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/LeftPaneTreeView.cs @@ -81,6 +81,7 @@ public LeftPaneTreeView() public void SelectIndex(int index) { var id = -1; + if (_currentSchemes == null) return; if (index >= 0 && index < _currentSchemes.Length) id = Array.FindIndex(_currentSchemesById, pair => pair.scheme == _currentSchemes[index]); diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/RightPaneView.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/RightPaneView.cs index 3909853..a330cb4 100644 --- a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/RightPaneView.cs +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Elements/RightPaneView.cs @@ -9,6 +9,7 @@ using UnityEditor.UIElements; using UnityEngine; using UnityEngine.Assertions; +using UnityEngine.Serialization; using UnityEngine.UIElements; namespace BuildMagic.Window.Editor.Elements @@ -29,7 +30,7 @@ public RightPaneView() var bindableElement = this.Q("container"); Assert.IsNotNull(bindableElement); - bindableElement.bindingPath = "_selected"; + bindableElement.bindingPath = "_selected._self"; var nameLabel = this.Q