From 613127153454594b7189a1f2af09caa3ecd68070 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Sep 2024 14:06:06 +0200 Subject: [PATCH 01/16] Add missing LSP navigation options to context menu #686 --- Assets/SEE/Controls/Actions/ContextMenuAction.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Assets/SEE/Controls/Actions/ContextMenuAction.cs b/Assets/SEE/Controls/Actions/ContextMenuAction.cs index be1e4d402e..20200da19c 100644 --- a/Assets/SEE/Controls/Actions/ContextMenuAction.cs +++ b/Assets/SEE/Controls/Actions/ContextMenuAction.cs @@ -229,6 +229,14 @@ private static IEnumerable GetNodeOptions(Node node, GameObject { actions.Add(new("Show Definition", () => ShowTargets(LSP.Definition).Forget(), Icons.OutgoingEdge)); } + if (node.OutgoingsOfType(LSP.Extend).Any()) + { + actions.Add(new("Show Supertype", () => ShowTargets(LSP.Extend).Forget(), Icons.OutgoingEdge)); + } + if (node.OutgoingsOfType(LSP.Call).Any()) + { + actions.Add(new("Show Outgoing Calls", () => ShowTargets(LSP.Call).Forget(), Icons.OutgoingEdge)); + } if (node.OutgoingsOfType(LSP.OfType).Any()) { actions.Add(new("Show Type", () => ShowTargets(LSP.OfType).Forget(), 'T')); From 23464230d5a9fd34f4a4a98bb78faa806f268fb2 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Sep 2024 14:10:04 +0200 Subject: [PATCH 02/16] Do not open context menu after dragging mouse Before this commit, there has been the somewhat annoying behavior that whenever we look around by right-clicking and dragging, if the mouse cursor happened to be over a node in the code city, the context menu would open after the drag has stopped. To fix this, the context menu is now only opened if the user did not drag their mouse before letting go of right click (or at least, if they only dragged very slightly). --- Assets/SEE/Controls/Actions/ContextMenuAction.cs | 11 ++++++++++- Assets/SEE/Controls/SEEInput.cs | 15 ++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Assets/SEE/Controls/Actions/ContextMenuAction.cs b/Assets/SEE/Controls/Actions/ContextMenuAction.cs index 20200da19c..d223fda756 100644 --- a/Assets/SEE/Controls/Actions/ContextMenuAction.cs +++ b/Assets/SEE/Controls/Actions/ContextMenuAction.cs @@ -27,6 +27,11 @@ public class ContextMenuAction : MonoBehaviour /// private PopupMenu popupMenu; + /// + /// The position of the mouse when the user started opening the context menu. + /// + private Vector3 startMousePosition = Vector3.zero; + private void Start() { popupMenu = gameObject.AddComponent(); @@ -34,7 +39,11 @@ private void Start() private void Update() { - if (SEEInput.OpenContextMenu()) + if (SEEInput.OpenContextMenuStart()) + { + startMousePosition = Input.mousePosition; + } + else if (SEEInput.OpenContextMenuEnd() && (Input.mousePosition - startMousePosition).magnitude < 1) { // TODO (#664): Detect if multiple elements are selected and adjust options accordingly. HitGraphElement hit = Raycasting.RaycastInteractableObject(out _, out InteractableObject o); diff --git a/Assets/SEE/Controls/SEEInput.cs b/Assets/SEE/Controls/SEEInput.cs index 21d5c28d4a..42f7d1366f 100644 --- a/Assets/SEE/Controls/SEEInput.cs +++ b/Assets/SEE/Controls/SEEInput.cs @@ -243,10 +243,19 @@ public static bool ToggleMetricHoveringSelection() //----------------------------------------------------- /// - /// True if the user wants to open the context menu. + /// True if the user starts the mouse interaction to open the context menu. /// - /// True if the user wants to open the context menu. - internal static bool OpenContextMenu() + /// true if the user starts the mouse interaction to open the context menu + internal static bool OpenContextMenuStart() + { + return Input.GetMouseButtonDown(rightMouseButton) && !Raycasting.IsMouseOverGUI(); + } + + /// + /// True if the user ends the mouse interaction to open the context menu. + /// + /// true if the user ends the mouse interaction to open the context menu + internal static bool OpenContextMenuEnd() { return Input.GetMouseButtonUp(rightMouseButton) && !Raycasting.IsMouseOverGUI(); } From 58946886683c859fcd5e68d82a31ffd70d122aaa Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Sep 2024 14:19:14 +0200 Subject: [PATCH 03/16] Fix Eclipse JDTLS server's package hierarchies #686 --- Assets/SEE/DataModel/DG/IO/LSPImporter.cs | 74 ++++++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/Assets/SEE/DataModel/DG/IO/LSPImporter.cs b/Assets/SEE/DataModel/DG/IO/LSPImporter.cs index e4ee030187..d8f3098682 100644 --- a/Assets/SEE/DataModel/DG/IO/LSPImporter.cs +++ b/Assets/SEE/DataModel/DG/IO/LSPImporter.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading; using Cysharp.Threading.Tasks; -using Markdig; using OmniSharp.Extensions.LanguageServer.Protocol.Models; using SEE.Tools; using SEE.Tools.LSP; @@ -260,6 +259,12 @@ public async UniTask LoadAsync(Graph graph, Action changePercentage = nul IList relevantNodes = graph.Nodes().Except(originalNodes).Where(x => x.SourceRange != null).ToList(); Debug.Log($"LSPImporter: Found {documentCount} documents with relevant extensions ({string.Join(", ", relevantExtensions)})."); + if (Handler.Server == LSPServer.EclipseJdtls) + { + // This server requires manual correction of the Java package hierarchies. + HandleJavaClasses(relevantNodes); + } + if (relevantNodes.Count == 0) { Debug.LogError("LSPImporter: No relevant nodes found. Aborting import.\n"); @@ -313,7 +318,7 @@ public async UniTask LoadAsync(Graph graph, Action changePercentage = nul // The remaining 80% of the progress is made by connecting the nodes. // The Count+1 prevents the progress from reaching 1.0, since the diagnostics may not yet be pulled. - changePercentage?.Invoke(1 - edgeProgressFactor + edgeProgressFactor * i++ / (relevantNodes.Count+1)); + changePercentage?.Invoke(1 - edgeProgressFactor + edgeProgressFactor * i++ / (relevantNodes.Count + 1)); } } Debug.Log($"LSPImporter: Imported {graph.Nodes().Except(originalNodes).Count()} new nodes and {newEdges} new edges.\n"); @@ -345,6 +350,71 @@ IEnumerable RelevantDocumentsForPath(string path) { return relevantExtensions.SelectMany(x => Directory.EnumerateFiles(path, $"*.{x}", SearchOption.AllDirectories)); } + + void HandleJavaClasses(IList nodes) + { + Dictionary packageNodes = new(); + // Java package hierarchies are not collected properly by the language server. + // Instead, we will infer them from the file paths. + foreach (Node node in nodes.Where(x => x.Type == NodeKind.Class.ToString())) + { + // Aside from the hierarchies, we also want to remember as a metric how many methods are in a class. + node.SetInt("Num_Methods", nodes.Count(x => x.Parent == node && x.Type == NodeKind.Method.ToString())); + + string relativePath = Path.GetRelativePath(Handler.ProjectPath, node.Directory); + string packageName = relativePath.Replace(Path.DirectorySeparatorChar, '.').TrimEnd('.'); + + if (packageNodes.TryGetValue(packageName, out Node package)) + { + // The package node already exists, so we reparent the class node to it. + node.Reparent(package); + continue; + } + Node packageNode = new() + { + ID = packageName, + SourceName = packageName, + Directory = node.Directory, + Type = NodeKind.Package.ToString() + }; + graph.AddNode(packageNode); + packageNodes[packageName] = packageNode; + + // Reparent the class node to the package node (if it previously was just inside a directory). + if (node.Parent != null && + (node.Parent.Type == NodeKind.File.ToString() + || node.Parent.Type == NodeKind.Package.ToString() + || node.Parent.Type == "Directory")) + { + node.Reparent(packageNode); + } + } + + // Finally, another run through the nodes to reparent the package nodes to their parent packages. + foreach (Node packageNode in packageNodes.Values) + { + Node parentPackage = GetParentPackage(packageNode.ID); + if (parentPackage != null) + { + packageNode.Reparent(parentPackage); + } + } + + return; + + Node GetParentPackage(string package) + { + for (int lastDot = package.LastIndexOf('.'); lastDot != -1; lastDot = package.LastIndexOf('.')) + { + package = package[..lastDot]; + if (packageNodes.TryGetValue(package, out Node parent)) + { + return parent; + } + } + return null; + } + } } /// From 8dc8eabf49ed2ae5233a4b6c79452b716a9017b6 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Sep 2024 14:19:37 +0200 Subject: [PATCH 04/16] Set BasePath properly when creating a subgraph --- Assets/SEE/DataModel/DG/Graph.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Assets/SEE/DataModel/DG/Graph.cs b/Assets/SEE/DataModel/DG/Graph.cs index eccff6f781..9c9ca7fdc2 100644 --- a/Assets/SEE/DataModel/DG/Graph.cs +++ b/Assets/SEE/DataModel/DG/Graph.cs @@ -1255,6 +1255,7 @@ public Graph SubgraphBy(Func includeElement, bool ignoreSelf { // The following will also clone the graph attributes. Graph subgraph = (Graph)CloneAttributes(); + subgraph.BasePath = BasePath; // This is not an actual attribute, so we need to set it manually. Dictionary mapsTo = AddNodesToSubgraph(subgraph, includeElement); AddEdgesToSubgraph(subgraph, mapsTo, includeElement, ignoreSelfLoops); return subgraph; From 797a0949a17ad03d2c83bcfeb2da78ca8c45a733 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Sep 2024 14:24:16 +0200 Subject: [PATCH 05/16] Fix spear highlighting for nodes The width and height have been mixed up before. --- Assets/SEE/Game/Operator/GraphElementOperator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/SEE/Game/Operator/GraphElementOperator.cs b/Assets/SEE/Game/Operator/GraphElementOperator.cs index f2c00018bc..dad9b99565 100644 --- a/Assets/SEE/Game/Operator/GraphElementOperator.cs +++ b/Assets/SEE/Game/Operator/GraphElementOperator.cs @@ -135,7 +135,7 @@ public IOperationCallback Highlight(float duration, bool showNotificatio // Display marker above the element // FIXME: marker is not displayed above edge. - MarkerFactory marker = new(new MarkerAttributes(0.01f, 1f, Color.red, default, default)); + MarkerFactory marker = new(new MarkerAttributes(height: 1f, width: 0.01f, Color.red, default, default)); marker.MarkBorn(gameObject); // The factor of 1.3 causes the element to blink slightly more than once per second, // which seems visually fitting. From 63b30aaac1b667b93cdd432b1563e718fa0fb1a4 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Sep 2024 16:26:17 +0200 Subject: [PATCH 06/16] Handle null values in async enumerables correctly --- Assets/SEE/Tools/LSP/LSPHandler.cs | 2 +- Assets/SEE/Utils/AsyncUtils.cs | 31 +++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Assets/SEE/Tools/LSP/LSPHandler.cs b/Assets/SEE/Tools/LSP/LSPHandler.cs index e7da49d10a..4bb7a05453 100644 --- a/Assets/SEE/Tools/LSP/LSPHandler.cs +++ b/Assets/SEE/Tools/LSP/LSPHandler.cs @@ -682,7 +682,7 @@ public IUniTaskAsyncEnumerable OutgoingCalls(Func Client.RequestCallHierarchyOutgoing(outgoingParams, t), TimeoutSpan).Select(x => x.To); return AsyncUtils.RunWithTimeoutAsync(MakeOutgoingCallRequest(outgoingParams), TimeoutSpan) - .AsUniTaskAsyncEnumerable() + .AsUniTaskAsyncEnumerable(logErrors: true) .Select(y => y.To); }); diff --git a/Assets/SEE/Utils/AsyncUtils.cs b/Assets/SEE/Utils/AsyncUtils.cs index 18c1cf392f..b460b453ad 100644 --- a/Assets/SEE/Utils/AsyncUtils.cs +++ b/Assets/SEE/Utils/AsyncUtils.cs @@ -21,14 +21,39 @@ public static class AsyncUtils public static int MainThreadId = 0; /// - /// Converts the given to an asynchronous UniTask enumerable. + /// Converts the given of enumerables to an asynchronous UniTask enumerable. /// /// The task of enumerables to convert. + /// Whether to log errors that occur during conversion instead of throwing them. /// The type of the elements in the enumerable. /// An asynchronous UniTask enumerable that emits the elements of the enumerable. - public static IUniTaskAsyncEnumerable AsUniTaskAsyncEnumerable(this UniTask> task) + public static IUniTaskAsyncEnumerable AsUniTaskAsyncEnumerable(this UniTask> task, + bool logErrors = false) { - return task.ToUniTaskAsyncEnumerable().SelectMany(x => x.ToUniTaskAsyncEnumerable()); + return task.ToUniTaskAsyncEnumerable().SelectMany(x => + { + if (x == null) + { + return UniTaskAsyncEnumerable.Empty(); + } + + if (!logErrors) + { + return x.ToUniTaskAsyncEnumerable(); + } + else + { + try + { + return x.ToUniTaskAsyncEnumerable(); + } + catch (Exception e) + { + Debug.LogError($"Error converting enumerable to UniTaskAsyncEnumerable: {e}"); + return UniTaskAsyncEnumerable.Empty(); + } + } + }); } /// From 731a6979905808cbf8a479084cb8d32a7c655d31 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Sep 2024 14:03:43 +0200 Subject: [PATCH 07/16] Automated changes by Unity --- .../AutoHand/Examples/Scenes/XR/Scripts/Autohand.XR.asmdef | 4 ++-- .../Scenes/XR/Scripts/Editor/Autohand.XR.Editor.asmdef | 4 ++-- .../FaceMaskExample/Editor/FaceMaskExampleEditor.asmdef | 4 ++-- Assets/Plugins/FinalIK_UMA2/FinalIK_UMA2.asmdef | 4 ++-- Assets/Plugins/RootMotion/Editor/FinalIKEditor.asmdef | 4 ++-- .../Editor/Shared Demo Scripts/FinalIKSharedDemoEditor.asmdef | 4 ++-- .../Plugins/ShaderControl/Editor/Resources/BuiltShaders.asset | 2 +- Assets/SEE/SEE.asmdef | 4 ++-- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Assets/Plugins/AutoHand/Examples/Scenes/XR/Scripts/Autohand.XR.asmdef b/Assets/Plugins/AutoHand/Examples/Scenes/XR/Scripts/Autohand.XR.asmdef index 2007a3a693..548a116e55 100644 --- a/Assets/Plugins/AutoHand/Examples/Scenes/XR/Scripts/Autohand.XR.asmdef +++ b/Assets/Plugins/AutoHand/Examples/Scenes/XR/Scripts/Autohand.XR.asmdef @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c9b08871f4a1a2bed09a1baa240853b69d06a250f179384d8752face78a6234 -size 391 +oid sha256:7ea10bb7f03d0e84c083b3188ffa720dacce1471ace3f4dd6fd8712c9b4b304e +size 463 diff --git a/Assets/Plugins/AutoHand/Examples/Scenes/XR/Scripts/Editor/Autohand.XR.Editor.asmdef b/Assets/Plugins/AutoHand/Examples/Scenes/XR/Scripts/Editor/Autohand.XR.Editor.asmdef index b85452a0b3..d0ee1e273c 100644 --- a/Assets/Plugins/AutoHand/Examples/Scenes/XR/Scripts/Editor/Autohand.XR.Editor.asmdef +++ b/Assets/Plugins/AutoHand/Examples/Scenes/XR/Scripts/Editor/Autohand.XR.Editor.asmdef @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a98163a96069074110a49b279e4254a6cdd5b2277513ac72d77f257b7f2a2230 -size 428 +oid sha256:292be6108a73998c08cc732dec4e468b352eb16559ea217cc720b63648459725 +size 498 diff --git a/Assets/Plugins/FaceMaskExample/Editor/FaceMaskExampleEditor.asmdef b/Assets/Plugins/FaceMaskExample/Editor/FaceMaskExampleEditor.asmdef index 86eaf6a922..d4967cf7ed 100644 --- a/Assets/Plugins/FaceMaskExample/Editor/FaceMaskExampleEditor.asmdef +++ b/Assets/Plugins/FaceMaskExample/Editor/FaceMaskExampleEditor.asmdef @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:44c72dacf28a906f033cfe748603652fc54eb67aa64af742942db3ed28019d41 -size 430 +oid sha256:fd22ea9892c0366f4e34f25a2ac82738bf06ebc8608f3e5da5534f47c4d275b3 +size 528 diff --git a/Assets/Plugins/FinalIK_UMA2/FinalIK_UMA2.asmdef b/Assets/Plugins/FinalIK_UMA2/FinalIK_UMA2.asmdef index 4bd94c70de..c5852c81d3 100644 --- a/Assets/Plugins/FinalIK_UMA2/FinalIK_UMA2.asmdef +++ b/Assets/Plugins/FinalIK_UMA2/FinalIK_UMA2.asmdef @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:979b730dc48e1ce5b06b0caa81996d4224967c3cad9ee96b02d0213b1fb18fa7 -size 448 +oid sha256:d9bcb7dd355220030cee6fcee1d567c45371aa93b7e1508f3c65e71b8a583551 +size 497 diff --git a/Assets/Plugins/RootMotion/Editor/FinalIKEditor.asmdef b/Assets/Plugins/RootMotion/Editor/FinalIKEditor.asmdef index bc6121f370..2f20dd9815 100644 --- a/Assets/Plugins/RootMotion/Editor/FinalIKEditor.asmdef +++ b/Assets/Plugins/RootMotion/Editor/FinalIKEditor.asmdef @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:368be948aa9bd9c15a220ab0a5b20a11cfc533abd0ea6f90fbf2eb3a6f66e929 -size 520 +oid sha256:ca1eb778d1099a58811d64571790c341533594c7cbe96f6e20ec1788f2b00431 +size 569 diff --git a/Assets/Plugins/RootMotion/Editor/Shared Demo Scripts/FinalIKSharedDemoEditor.asmdef b/Assets/Plugins/RootMotion/Editor/Shared Demo Scripts/FinalIKSharedDemoEditor.asmdef index eaa34c5945..d4c2b74943 100644 --- a/Assets/Plugins/RootMotion/Editor/Shared Demo Scripts/FinalIKSharedDemoEditor.asmdef +++ b/Assets/Plugins/RootMotion/Editor/Shared Demo Scripts/FinalIKSharedDemoEditor.asmdef @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d198220f011c260e0607a07e28d19afb7b74b3a561e1b2c410cc310198aea9e4 -size 431 +oid sha256:fe39e0eb0c4aeff5d13ea4726bf0b08bdfca7366b4fc4a616b9db3724bea07c5 +size 480 diff --git a/Assets/Plugins/ShaderControl/Editor/Resources/BuiltShaders.asset b/Assets/Plugins/ShaderControl/Editor/Resources/BuiltShaders.asset index 6307c41f24..aa2a3ba15d 100644 --- a/Assets/Plugins/ShaderControl/Editor/Resources/BuiltShaders.asset +++ b/Assets/Plugins/ShaderControl/Editor/Resources/BuiltShaders.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6dc59e429810fea6418f87834a6f8240adc8147c0d092adeed4932d5b5dd880b +oid sha256:d8e92144a0cc1923f50a9503f29407fddb77c1d8572c9cdcef1dd43ed47845ea size 105310 diff --git a/Assets/SEE/SEE.asmdef b/Assets/SEE/SEE.asmdef index 44c80596b3..95456ed461 100644 --- a/Assets/SEE/SEE.asmdef +++ b/Assets/SEE/SEE.asmdef @@ -21,7 +21,6 @@ "GUID:343deaaf83e0cee4ca978e7df0b80d21", "GUID:0aaf0e45a00efed438c28168ee53a889", "GUID:efd4fd8bc6c56104a96b6054c7a775d6", - "GUID:f4cb6d2c24715bd4884385645277f011", "GUID:ad04e4f5949be9644989a3ea11bdc60e", "GUID:e40ba710768534012815d3193fa296cb", "GUID:3248779d86bd31747b5d2214f30b01ac", @@ -35,7 +34,8 @@ "GUID:4d1e63f328d08c14b8f0029b5bb1aa40", "GUID:fe685ec1767f73d42b749ea8045bfe43", "GUID:5c01796d064528144a599661eaab93a6", - "GUID:e0cd26848372d4e5c891c569017e11f1" + "GUID:e0cd26848372d4e5c891c569017e11f1", + "GUID:13e34609dade4964e97712dc04b1cc6a" ], "includePlatforms": [], "excludePlatforms": [], From 4fbcb8b0f0b357ab2345b019f57660e0c0533d80 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sat, 14 Sep 2024 00:55:14 +0200 Subject: [PATCH 08/16] Allow hiding edges on startup by type --- Assets/SEE/Game/City/AbstractSEECity.cs | 31 +++++++++++++---------- Assets/SEE/Game/City/AbstractSEECityIO.cs | 8 +++++- Assets/SEE/Game/City/EdgeMeshScheduler.cs | 10 +++++++- Assets/SEE/Game/City/SEECity.cs | 27 ++++++++++++++++++-- 4 files changed, 59 insertions(+), 17 deletions(-) diff --git a/Assets/SEE/Game/City/AbstractSEECity.cs b/Assets/SEE/Game/City/AbstractSEECity.cs index 45df154b92..591c80160f 100644 --- a/Assets/SEE/Game/City/AbstractSEECity.cs +++ b/Assets/SEE/Game/City/AbstractSEECity.cs @@ -118,20 +118,24 @@ public DataPath SourceCodeDirectory [OdinSerialize, Tooltip("Edge types of hierarchical edges."), TabGroup(EdgeFoldoutGroup), RuntimeTab(EdgeFoldoutGroup)] public HashSet HierarchicalEdges = HierarchicalEdgeTypes(); + [OdinSerialize, Tooltip("Edge types of hidden edges (will only be shown upon hovering)."), + TabGroup(EdgeFoldoutGroup), RuntimeTab(EdgeFoldoutGroup)] + public HashSet HiddenEdges = new(); + /// /// A mapping of all node types of the nodes in the graph onto whether /// they should be visualized or not and if so, how. /// [NonSerialized, OdinSerialize, Tooltip("Visual attributes of nodes."), HideReferenceObjectPicker] [DictionaryDrawerSettings(KeyLabel = "Node type", ValueLabel = "Visual attributes", - DisplayMode = DictionaryDisplayOptions.CollapsedFoldout), TabGroup(NodeFoldoutGroup), RuntimeTab(NodeFoldoutGroup)] + DisplayMode = DictionaryDisplayOptions.CollapsedFoldout), TabGroup(NodeFoldoutGroup), RuntimeTab(NodeFoldoutGroup)] public NodeTypeVisualsMap NodeTypes = new(); /// /// Attributes to mark changes of nodes. /// [Tooltip("How changes of nodes should be marked."), - TabGroup(NodeFoldoutGroup), RuntimeTab(NodeFoldoutGroup), HideReferenceObjectPicker] + TabGroup(NodeFoldoutGroup), RuntimeTab(NodeFoldoutGroup), HideReferenceObjectPicker] [NonSerialized, OdinSerialize] public MarkerAttributes MarkerAttributes = new(); @@ -278,6 +282,7 @@ public ColorRange GetColorForMetric(string metricName) public BoardAttributes BoardSettings = new(); #region LabelLineMaterial + /// /// The material for the line connecting a node and its label. We use exactly one material /// for all connecting lines within this city, different from the lines used for labels in @@ -285,7 +290,10 @@ public ColorRange GetColorForMetric(string metricName) /// [HideInInspector] public Material LabelLineMaterial - { get; private set; } + { + get; + private set; + } /// /// Returns the material for the line connecting a node and its label. @@ -297,6 +305,7 @@ private static Material LineMaterial(Color lineColor) return Materials.New(Materials.ShaderType.TransparentLine, lineColor, texture: null, renderQueueOffset: (int)(RenderQueue.Transparent + 1)); } + #endregion /// @@ -364,17 +373,13 @@ public void Load(string filename) /// /// The names of the edge types of hierarchical edges. /// - public static HashSet HierarchicalEdgeTypes() + public static HashSet HierarchicalEdgeTypes() => new() { - HashSet result = new() - { - "Enclosing", - "Belongs_To", - "Part_Of", - "Defined_In" - }; - return result; - } + "Enclosing", + "Belongs_To", + "Part_Of", + "Defined_In" + }; /// /// Resets everything that is specific to a given graph. Here: diff --git a/Assets/SEE/Game/City/AbstractSEECityIO.cs b/Assets/SEE/Game/City/AbstractSEECityIO.cs index bf5862675c..8eed383df9 100644 --- a/Assets/SEE/Game/City/AbstractSEECityIO.cs +++ b/Assets/SEE/Game/City/AbstractSEECityIO.cs @@ -15,6 +15,10 @@ public partial class AbstractSEECity /// private const string hierarchicalEdgesLabel = "HierarchicalEdges"; /// + /// Label in the configuration file for . + /// + private const string hiddenEdgesLabel = "HiddenEdges"; + /// /// Label in the configuration file for . /// private const string nodeTypesLabel = "NodeTypes"; @@ -105,6 +109,7 @@ protected virtual void Save(ConfigWriter writer) SolutionPath.Save(writer, solutionPathLabel); writer.Save(LODCulling, lodCullingLabel); writer.Save(HierarchicalEdges.ToList(), hierarchicalEdgesLabel); + writer.Save(HiddenEdges.ToList(), hiddenEdgesLabel); NodeTypes.Save(writer, nodeTypesLabel); writer.Save(IgnoreSelfLoopsInLifting, ignoreSelfLoopsInLiftingLabel); writer.Save(MaximalAntennaSegmentHeight, maximalAntennaSegmentHeightLabel); @@ -133,6 +138,7 @@ protected virtual void Restore(Dictionary attributes) SolutionPath.Restore(attributes, solutionPathLabel); ConfigIO.Restore(attributes, lodCullingLabel, ref LODCulling); ConfigIO.Restore(attributes, hierarchicalEdgesLabel, ref HierarchicalEdges); + ConfigIO.Restore(attributes, hiddenEdgesLabel, ref HiddenEdges); NodeTypes.Restore(attributes, nodeTypesLabel); ConfigIO.Restore(attributes, ignoreSelfLoopsInLiftingLabel, ref IgnoreSelfLoopsInLifting); ConfigIO.Restore(attributes, maximalAntennaSegmentHeightLabel, ref MaximalAntennaSegmentHeight); @@ -150,4 +156,4 @@ protected virtual void Restore(Dictionary attributes) MarkerAttributes.Restore(attributes, markerAttributesLabel); } } -} \ No newline at end of file +} diff --git a/Assets/SEE/Game/City/EdgeMeshScheduler.cs b/Assets/SEE/Game/City/EdgeMeshScheduler.cs index 554a51431a..3f9dce449f 100644 --- a/Assets/SEE/Game/City/EdgeMeshScheduler.cs +++ b/Assets/SEE/Game/City/EdgeMeshScheduler.cs @@ -156,14 +156,17 @@ private void LateUpdate() continue; } + bool hideSplines; // fail-safe if (layout == null) { Debug.LogWarning("Layout settings are missing. Falling back to defaults.\n"); + hideSplines = false; } else { - spline.Radius = layout.EdgeWidth / 2; + spline.Radius = layout.EdgeWidth / 4; + hideSplines = layout.AnimationKind == EdgeAnimationKind.Buildup; } // fail-safe @@ -178,6 +181,11 @@ private void LateUpdate() } spline.CreateMesh(); + + if (hideSplines && edge.HasToggle(Edge.IsHiddenToggle)) + { + spline.SubsplineEndT = 0; + } } } diff --git a/Assets/SEE/Game/City/SEECity.cs b/Assets/SEE/Game/City/SEECity.cs index 6b2e0acecf..e6f9ed0974 100644 --- a/Assets/SEE/Game/City/SEECity.cs +++ b/Assets/SEE/Game/City/SEECity.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading; using Cysharp.Threading.Tasks; +using MoreLinq; using SEE.DataModel.DG; using SEE.UI.RuntimeConfigMenu; using SEE.GO; @@ -209,15 +210,37 @@ protected virtual void InitializeAfterDrawn() else { Debug.LogError($"Could not load city {name}.\n"); + return; } + // Set the hidden edges according to the EdgeLayoutSettings. + subGraph.Edges().Where(x => HiddenEdges.Contains(x.Type)) + .ForEach(edge => edge.SetToggle(Edge.IsHiddenToggle)); + // Add EdgeMeshScheduler to convert edge lines to meshes over time. - gameObject.AddOrGetComponent().Init(EdgeLayoutSettings, EdgeSelectionSettings, - subGraph); + EdgeMeshScheduler edgeMeshScheduler = gameObject.AddOrGetComponent(); + edgeMeshScheduler.Init(EdgeLayoutSettings, EdgeSelectionSettings, subGraph); + edgeMeshScheduler.OnInitialEdgesDone += HideHiddenEdges; + // This must be loadedGraph. It must not be LoadedGraph. The latter would reset the graph. loadedGraph = subGraph; UpdateGraphElementIDMap(gameObject); + return; + + void HideHiddenEdges() + { + if (EdgeLayoutSettings.AnimationKind is EdgeAnimationKind.None or EdgeAnimationKind.Buildup) + { + // If None: Nothing needs to be done. + // If Buildup: The edges are already hidden by the EdgeMeshScheduler. + return; + } + foreach (Edge edge in subGraph.Edges().Where(x => x.HasToggle(Edge.IsHiddenToggle))) + { + edge.Operator().Hide(EdgeLayoutSettings.AnimationKind); + } + } } /// From d51f1eb49fa5c4ea57242a2d15f783f50b6e5c23 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sat, 14 Sep 2024 00:57:39 +0200 Subject: [PATCH 09/16] Properly handle `AreSelectable` edge layout setting Previously, this field was seemingly ignored. --- Assets/SEE/Game/City/EdgeMeshScheduler.cs | 1 + Assets/SEE/GameObjects/SEESpline.cs | 34 ++++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/Assets/SEE/Game/City/EdgeMeshScheduler.cs b/Assets/SEE/Game/City/EdgeMeshScheduler.cs index 3f9dce449f..9fddbc63a6 100644 --- a/Assets/SEE/Game/City/EdgeMeshScheduler.cs +++ b/Assets/SEE/Game/City/EdgeMeshScheduler.cs @@ -178,6 +178,7 @@ private void LateUpdate() { spline.TubularSegments = selection.TubularSegments; spline.RadialSegments = selection.RadialSegments; + spline.IsSelectable = selection.AreSelectable && !edge.HasToggle(Edge.IsHiddenToggle); } spline.CreateMesh(); diff --git a/Assets/SEE/GameObjects/SEESpline.cs b/Assets/SEE/GameObjects/SEESpline.cs index b94ba4b7b5..651de0af86 100644 --- a/Assets/SEE/GameObjects/SEESpline.cs +++ b/Assets/SEE/GameObjects/SEESpline.cs @@ -195,6 +195,25 @@ public int RadialSegments } } + /// + /// Whether the spline shall be selectable, that is, whether a shall be added to it. + /// + [SerializeField] + private bool isSelectable = true; + + /// + /// Whether the spline shall be selectable, that is, whether a shall be added to it. + /// + public bool IsSelectable + { + get => isSelectable; + set + { + isSelectable = value; + needsUpdate = true; + } + } + /// /// Tuple of the start color of the gradient and the end color of it. /// Should only be changed via . @@ -509,10 +528,17 @@ private Mesh CreateOrUpdateMesh() mesh.uv = uvs; mesh.SetIndices(indices, MeshTopology.Triangles, 0); - // IMPORTANT: Null the shared mesh of the collider before assigning the updated mesh. - MeshCollider collider = gameObject.AddOrGetComponent(); - collider.sharedMesh = null; // https://forum.unity.com/threads/how-to-update-a-mesh-collider.32467/ - collider.sharedMesh = mesh; + if (IsSelectable) + { + // IMPORTANT: Null the shared mesh of the collider before assigning the updated mesh. + MeshCollider splineCollider = gameObject.AddOrGetComponent(); + splineCollider.sharedMesh = null; // https://forum.unity.com/threads/how-to-update-a-mesh-collider.32467/ + splineCollider.sharedMesh = mesh; + } + else if (gameObject.TryGetComponent(out MeshCollider splineCollider)) + { + Destroyer.Destroy(splineCollider); + } meshRenderer = gameObject.AddOrGetComponent(); if (updateMaterial) From 675f87f4174d136669318eb85e75b5216f5ee26c Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sat, 14 Sep 2024 01:01:06 +0200 Subject: [PATCH 10/16] Implement transitive edge animation Specifically, this adds a new option in the edge layout settings which, when enabled, iteratively animates in not only the directly connected edges of a hovered/selected node, but also recursively all sources/targets of those nodes. For a more detailed explanation, please see the documentation of ShowEdges:RelevantEdges. --- Assets/SEE/Controls/Actions/ShowEdges.cs | 174 ++++++++++++++++--- Assets/SEE/Game/City/EdgeLayoutAttributes.cs | 12 ++ Assets/SEE/Game/Operator/EdgeOperator.cs | 2 +- 3 files changed, 160 insertions(+), 28 deletions(-) diff --git a/Assets/SEE/Controls/Actions/ShowEdges.cs b/Assets/SEE/Controls/Actions/ShowEdges.cs index 57afaa1a5b..fe8812fe48 100644 --- a/Assets/SEE/Controls/Actions/ShowEdges.cs +++ b/Assets/SEE/Controls/Actions/ShowEdges.cs @@ -1,11 +1,16 @@ +using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using Cysharp.Threading.Tasks; +using MoreLinq; using SEE.DataModel.DG; using SEE.Game; using SEE.Game.City; using SEE.GO; using SEE.Utils; using UnityEngine; +using Node = SEE.DataModel.DG.Node; namespace SEE.Controls.Actions { @@ -29,6 +34,23 @@ public class ShowEdges : InteractableObjectAction /// private AbstractSEECity codeCity; + /// + /// A token that's used to cancel the transitive edge toggling. + /// We need to use this instead of the edge operator's built-in conflict mechanism + /// because the edge operator only controls a single edge, instead of the whole transitive closure. + /// + private CancellationTokenSource edgeToggleToken; + + /// + /// The toggle that is used to determine whether an edge is currently selected. + /// + private const string EdgeIsSelectedToggle = "IsSelected"; + + /// + /// The delay between each depth level when showing/hiding the transitive closure of edges. + /// + public static readonly TimeSpan TransitiveDelay = TimeSpan.FromMilliseconds(500); + /// /// Registers On() and Off() for the respective hovering and selection events. /// @@ -78,17 +100,21 @@ private void SelectionOn(InteractableObject interactableObject, bool isInitiator if (isInitiator) { isSelected = true; - // if the object is currently hovered over, the edges are already shown - if (!isHovered) + // if the object is currently hovered over, the edges are already shown. + // However, a selection must be triggered anyway, as it may need to override a previous selection. + OnOff(true, true); + + if (gameObject.TryGetNode(out Node node)) { - On(); + RelevantEdges(node, followSource: false, followTarget: true, true) + .SelectMany(x => x).ForEach(x => x.SetToggle(EdgeIsSelectedToggle, true)); } } } /// /// Called when the object is deselected. If is false, a remote - /// player has triggered this event and, hence, nothing will be done. Otherwise + /// player has triggered this event and, hence, nothing will be done. Otherwise, /// the shown edges are hidden unless the object is still hovered. /// /// the object being selected @@ -100,7 +126,13 @@ private void SelectionOff(InteractableObject interactableObject, bool isInitiato isSelected = false; if (!isHovered) { - Off(); + OnOff(false, true); + } + + if (gameObject.TryGetNode(out Node node)) + { + RelevantEdges(node, followSource: false, followTarget: true, true) + .SelectMany(x => x).ForEach(x => x.SetToggle(EdgeIsSelectedToggle, false)); } } } @@ -120,7 +152,7 @@ private void HoverOn(InteractableObject interactableObject, bool isInitiator) // if the object is currently selected, the edges are already shown if (!isSelected) { - On(); + OnOff(true, false); } } } @@ -139,7 +171,7 @@ private void HoverOff(InteractableObject interactableObject, bool isInitiator) isHovered = false; if (!isSelected) { - Off(); + OnOff(false, false); } } } @@ -162,44 +194,132 @@ private AbstractSEECity City() } /// - /// Shows all incoming/outgoing edges of the node this component is - /// attached to. + /// Returns a list of lists of edges that are relevant (i.e., should be shown/hidden) + /// for the given . The second level of the list corresponds to + /// the depth within the transitive closure of the edges, ordered by the distance to the given node, + /// and only becomes relevant if or is true. + /// + /// To give an example, consider the following graph: + /// C -> D -> E <- F ]]> + /// + /// For this graph, the result of this method for node B with = false + /// and = true would be [[B-A, B-C], [C-D], [D-E]], + /// where the first list contains the edges directly originating from B, + /// the second list contains the edges originating from the nodes in the first list, and so on. + /// + /// As a consequence, if both and are true, + /// and the graph is connected, the result will contain all edges of the graph. /// - private void On() + /// The node for which to find the relevant edges. + /// Whether to follow the source of the edges. + /// Whether to follow the target of the edges. + /// Whether the call is from a selection event. + /// This is necessary because we do not want hover events to override selection events. + /// A list of lists of edges that are relevant for the given node. + /// It is fine if the graph is cyclic—this method will still terminate. + private List> RelevantEdges(Node node, bool followSource, bool followTarget, bool fromSelection) { - OnOff(true); - } + // Directly connected edges first. + IEnumerable> edges = IteratedConnectedEdges(_ => null, _ => null); - /// - /// Hides all incoming/outgoing edges of the node this component is - /// attached to. - /// - private void Off() - { - OnOff(false); + if (followSource) + { + edges = edges.ZipLongest(IteratedConnectedEdges(x => x.Source, x => x.Target), MergeEdgeLevel); + } + if (followTarget) + { + edges = edges.ZipLongest(IteratedConnectedEdges(x => x.Target, x => x.Source), MergeEdgeLevel); + } + + return edges.Select(x => x.ToList()).ToList(); + + IEnumerable> IteratedConnectedEdges(Func getSuccessorNode, + Func getPredecessorNode) + { + HashSet visitedNodes = new(); + // We will do a breadth-first traversal of the subgraph induced by the connected edges. + Queue<(Node node, int distance)> nodeQueue = new(new[] { (node, 0) }); + DefaultDictionary> results = new(); + while (nodeQueue.Count > 0) + { + (Node currentNode, int distance) = nodeQueue.Dequeue(); + if (!visitedNodes.Add(currentNode)) + { + // We already handled this node. + continue; + } + List connected = DirectlyConnectedEdges(currentNode).ToList(); + results[distance].AddRange(connected.Where(x => x.HasToggle(Edge.IsHiddenToggle) + // Hover should not override edges shown by selection. + && (fromSelection || !x.HasToggle(EdgeIsSelectedToggle)))); + // Queue successors, if there are any. + connected.Select(getSuccessorNode) + .Where(x => x != null) + .Select(x => (x, distance+1)) + .ForEach(nodeQueue.Enqueue); + } + return results.OrderBy(x => x.Key).Select(x => x.Value); + + IEnumerable DirectlyConnectedEdges(Node forNode) + { + return codeCity.EdgeLayoutSettings.AnimateInnerEdges + ? forNode.PostOrderDescendants().SelectMany(x => x.Edges.Where(e => RelevantEdge(e, x))) + : forNode.Edges.Where(e => RelevantEdge(e, forNode)); + } + + bool RelevantEdge(Edge edge, Node forNode) => getPredecessorNode(edge) == forNode; + } + + IEnumerable MergeEdgeLevel(IEnumerable first, IEnumerable second) + { + return (first ?? Enumerable.Empty()).Union(second ?? Enumerable.Empty()); + } } /// /// Shows/hides all incoming/outgoing edges of the node this component is attached to. /// /// if true, the edges are shown; otherwise hidden - private void OnOff(bool show) + /// Whether the call is from a selection event rather than a hover event. + private void OnOff(bool show, bool fromSelection) { if (gameObject.TryGetNode(out Node node)) { codeCity ??= City(); + if (!isSelected) + { + edgeToggleToken?.Cancel(); + edgeToggleToken = new CancellationTokenSource(); + } + EdgeLayoutAttributes layout = codeCity.EdgeLayoutSettings; + List> edges = RelevantEdges(node, + followSource: layout.AnimateTransitiveSourceEdges, + followTarget: layout.AnimateTransitiveTargetEdges, + fromSelection); + ToggleEdges(edges, edgeToggleToken.Token).Forget(); + } + return; - IEnumerable edges = codeCity.EdgeLayoutSettings.AnimateInnerEdges - ? node.PostOrderDescendants().SelectMany(x => x.Edges) - : node.Edges; - + async UniTaskVoid ToggleEdges(IEnumerable> edges, CancellationToken token) + { EdgeAnimationKind animationKind = codeCity.EdgeLayoutSettings.AnimationKind; - foreach (Edge edge in edges.Distinct().Where(x => x.HasToggle(Edge.IsHiddenToggle))) + foreach (IList edgeLevel in edges) { - edge.Operator().ShowOrHide(show, animationKind); + foreach (Edge edge in edgeLevel) + { + edge.Operator(mustFind: false)?.ShowOrHide(show, animationKind); + } + if (show) + { + await UniTask.Delay(TransitiveDelay, cancellationToken: token); + } + if (token.IsCancellationRequested) + { + return; + } } } } } -} \ No newline at end of file +} diff --git a/Assets/SEE/Game/City/EdgeLayoutAttributes.cs b/Assets/SEE/Game/City/EdgeLayoutAttributes.cs index 6c365c3c74..d405fde20c 100644 --- a/Assets/SEE/Game/City/EdgeLayoutAttributes.cs +++ b/Assets/SEE/Game/City/EdgeLayoutAttributes.cs @@ -31,6 +31,12 @@ public class EdgeLayoutAttributes : LayoutSettings nameof(WarnAboutInnerEdgeAnimation))] public bool AnimateInnerEdges = true; + [Tooltip("When hovering over nodes, repeatedly animate the edges of source nodes, one after another.")] + public bool AnimateTransitiveSourceEdges = false; + + [Tooltip("When hovering over nodes, repeatedly animate the edges of target nodes, one after another.")] + public bool AnimateTransitiveTargetEdges = false; + /// /// True if the user should be warned about animating inner edges due to performance issues. /// @@ -68,6 +74,8 @@ public override void Save(ConfigWriter writer, string label) writer.Save(Kind.ToString(), edgeLayoutLabel); writer.Save(AnimationKind.ToString(), animationKindLabel); writer.Save(AnimateInnerEdges, animateInnerEdgesLabel); + writer.Save(AnimateTransitiveSourceEdges, animateTransitiveSourceEdgesLabel); + writer.Save(AnimateTransitiveTargetEdges, animateTransitiveTargetEdgesLabel); writer.Save(EdgeWidth, edgeWidthLabel); writer.Save(EdgesAboveBlocks, edgesAboveBlocksLabel); writer.Save(Tension, tensionLabel); @@ -83,6 +91,8 @@ public override void Restore(Dictionary attributes, string label ConfigIO.RestoreEnum(values, edgeLayoutLabel, ref Kind); ConfigIO.RestoreEnum(values, animationKindLabel, ref AnimationKind); ConfigIO.Restore(values, animateInnerEdgesLabel, ref AnimateInnerEdges); + ConfigIO.Restore(values, animateTransitiveSourceEdgesLabel, ref AnimateTransitiveSourceEdges); + ConfigIO.Restore(values, animateTransitiveTargetEdgesLabel, ref AnimateTransitiveTargetEdges); ConfigIO.Restore(values, edgeWidthLabel, ref EdgeWidth); ConfigIO.Restore(values, edgesAboveBlocksLabel, ref EdgesAboveBlocks); ConfigIO.Restore(values, tensionLabel, ref Tension); @@ -95,5 +105,7 @@ public override void Restore(Dictionary attributes, string label private const string tensionLabel = "Tension"; private const string animationKindLabel = "AnimationKind"; private const string animateInnerEdgesLabel = "AnimateInnerEdges"; + private const string animateTransitiveSourceEdgesLabel = "AnimateTransitiveSourceEdges"; + private const string animateTransitiveTargetEdgesLabel = "AnimateTransitiveTargetEdges"; } } diff --git a/Assets/SEE/Game/Operator/EdgeOperator.cs b/Assets/SEE/Game/Operator/EdgeOperator.cs index 818f327011..b281b81cee 100644 --- a/Assets/SEE/Game/Operator/EdgeOperator.cs +++ b/Assets/SEE/Game/Operator/EdgeOperator.cs @@ -194,7 +194,7 @@ Tween[] ConstructAction(bool extending, float duration) DOTween.To(() => spline.SubsplineEndT, u => spline.SubsplineEndT = u, extending ? 1.0f : 0.0f, - duration).SetEase(Ease.InOutExpo).Play() + duration).SetEase(Ease.InOutCubic).Play() }; } } From 026a2cc1fb655ec43dd47662a7606cd16b945df3 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sat, 14 Sep 2024 01:03:17 +0200 Subject: [PATCH 11/16] Fix label not being displayed in certain cases Specifically, this fixes two things: * When a node is selected, its label is not shown correctly. * Text is cut off outside of the portal, which is a problem for longer names. --- .../LiberationSans SDF - Portal Overlay.asset | 46 ++++++++++++++++++- .../Shaders/TMP_SDF-Mobile Portal.shader | 3 +- Assets/SEE/Controls/Actions/ShowLabel.cs | 11 +---- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/Assets/Resources/Fonts/LiberationSans SDF - Portal Overlay.asset b/Assets/Resources/Fonts/LiberationSans SDF - Portal Overlay.asset index 646353153f..f0a5f91819 100644 --- a/Assets/Resources/Fonts/LiberationSans SDF - Portal Overlay.asset +++ b/Assets/Resources/Fonts/LiberationSans SDF - Portal Overlay.asset @@ -8,7 +8,7 @@ Material: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_Name: LiberationSans SDF Material - m_Shader: {fileID: 4800000, guid: 0ca862acf8d440a59dc4aea2a4965698, type: 3} + m_Shader: {fileID: 4800000, guid: f2d33e526047a3a45a14ed790753bb20, type: 3} m_Parent: {fileID: 0} m_ModifiedSerializedProperties: 0 m_ValidKeywords: [] @@ -23,28 +23,64 @@ Material: m_SavedProperties: serializedVersion: 3 m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _Cube: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _FaceTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} - _MainTex: m_Texture: {fileID: 28684132378477856} m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} + - _OutlineTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} m_Ints: [] m_Floats: + - _Ambient: 0.5 + - _Bevel: 0.5 + - _BevelClamp: 0 + - _BevelOffset: 0 + - _BevelRoundness: 0 + - _BevelWidth: 0 + - _BumpFace: 0 + - _BumpOutline: 0 - _ColorMask: 15 - _Cutoff: 0.5 + - _Diffuse: 0.5 - _FaceDilate: 0 + - _FaceUVSpeedX: 0 + - _FaceUVSpeedY: 0 + - _GlowInner: 0.05 + - _GlowOffset: 0 + - _GlowOuter: 0.05 + - _GlowPower: 0.75 - _GradientScale: 10 + - _LightAngle: 3.1416 - _MaskSoftnessX: 0 - _MaskSoftnessY: 0 - _OutlineSoftness: 0 + - _OutlineUVSpeedX: 0 + - _OutlineUVSpeedY: 0 - _OutlineWidth: 0.1 - _PerspectiveFilter: 0.875 + - _Reflectivity: 10 - _ScaleRatioA: 0.9 - - _ScaleRatioB: 1 + - _ScaleRatioB: 0.73125 - _ScaleRatioC: 0.73125 - _ScaleX: 1 - _ScaleY: 1 - _ShaderFlags: 0 - _Sharpness: 0 + - _SpecularPower: 2 - _Stencil: 0 - _StencilComp: 8 - _StencilOp: 0 @@ -62,10 +98,16 @@ Material: - _WeightNormal: 0 m_Colors: - _ClipRect: {r: -32767, g: -32767, b: 32767, a: 32767} + - _EnvMatrixRotation: {r: 0, g: 0, b: 0, a: 0} - _FaceColor: {r: 1, g: 1, b: 1, a: 1} + - _GlowColor: {r: 0, g: 1, b: 0, a: 0.5} + - _MaskCoord: {r: 0, g: 0, b: 32767, a: 32767} - _OutlineColor: {r: 1, g: 1, b: 1, a: 1} - _PortalMax: {r: -0.94397986, g: -1.8898091, b: 0, a: 0} - _PortalMin: {r: -2.41602, g: -5.110191, b: 0, a: 0} + - _ReflectFaceColor: {r: 0, g: 0, b: 0, a: 1} + - _ReflectOutlineColor: {r: 0, g: 0, b: 0, a: 1} + - _SpecularColor: {r: 1, g: 1, b: 1, a: 1} - _UnderlayColor: {r: 0, g: 0, b: 0, a: 0.5} m_BuildTextureStacks: [] --- !u!114 &11400000 diff --git a/Assets/Resources/Shaders/TMP_SDF-Mobile Portal.shader b/Assets/Resources/Shaders/TMP_SDF-Mobile Portal.shader index b9beeac331..a06cf0f177 100644 --- a/Assets/Resources/Shaders/TMP_SDF-Mobile Portal.shader +++ b/Assets/Resources/Shaders/TMP_SDF-Mobile Portal.shader @@ -261,7 +261,8 @@ SubShader { input.v.x > _PortalMax.x || input.v.z > _PortalMax.y ) { - c = fixed4(0.0f, 0.0f, 0.0f, 0.0f); + // Text should not actually be cut off outside the portal. + // c = fixed4(0.0f, 0.0f, 0.0f, 0.0f); } return c; diff --git a/Assets/SEE/Controls/Actions/ShowLabel.cs b/Assets/SEE/Controls/Actions/ShowLabel.cs index 2396f23a38..f4e86969d9 100644 --- a/Assets/SEE/Controls/Actions/ShowLabel.cs +++ b/Assets/SEE/Controls/Actions/ShowLabel.cs @@ -127,11 +127,7 @@ private void HoverOn(InteractableObject interactableObject, bool isInitiator) if (isInitiator) { isHovered = true; - // if the object is currently selected, the label is already shown - if (!isSelected) - { - On(); - } + On(); } } @@ -147,10 +143,7 @@ private void HoverOff(InteractableObject interactableObject, bool isInitiator) if (isInitiator) { isHovered = false; - if (!isSelected) - { - Off(); - } + Off(); } } From b411365655f5f202272f7ba82b7bffb12877b8cb Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sat, 14 Sep 2024 01:03:45 +0200 Subject: [PATCH 12/16] Fix metric window sometimes cutting off metric values --- .../Resources/Prefabs/UI/MetricRowLine.prefab | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/Assets/Resources/Prefabs/UI/MetricRowLine.prefab b/Assets/Resources/Prefabs/UI/MetricRowLine.prefab index 78d21cd688..c158f6b681 100644 --- a/Assets/Resources/Prefabs/UI/MetricRowLine.prefab +++ b/Assets/Resources/Prefabs/UI/MetricRowLine.prefab @@ -36,8 +36,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} - m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 582.867, y: 100} + m_AnchoredPosition: {x: 118.4, y: 0} + m_SizeDelta: {x: 776.1, y: 100} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &7221035067078308661 CanvasRenderer: @@ -68,7 +68,7 @@ MonoBehaviour: m_Spacing: 5 m_ChildForceExpandWidth: 1 m_ChildForceExpandHeight: 0 - m_ChildControlWidth: 0 + m_ChildControlWidth: 1 m_ChildControlHeight: 0 m_ChildScaleWidth: 0 m_ChildScaleHeight: 0 @@ -105,10 +105,10 @@ RectTransform: m_Children: [] m_Father: {fileID: 4417611932811040179} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 211.49, y: -50} - m_SizeDelta: {x: 422.98, y: 100} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 100} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &3220663573982420325 CanvasRenderer: @@ -218,6 +218,7 @@ GameObject: - component: {fileID: 2256359333229225443} - component: {fileID: 5575769617067488252} - component: {fileID: 5791917304804281608} + - component: {fileID: 1081146575147054472} m_Layer: 5 m_Name: ValueLine m_TagString: Untagged @@ -239,10 +240,10 @@ RectTransform: m_Children: [] m_Father: {fileID: 4417611932811040179} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 643.99, y: -50} - m_SizeDelta: {x: 432.02002, y: 100} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 100} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &5575769617067488252 CanvasRenderer: @@ -341,3 +342,23 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} +--- !u!114 &1081146575147054472 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6677135121746064833} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreLayout: 0 + m_MinWidth: 400 + m_MinHeight: -1 + m_PreferredWidth: -1 + m_PreferredHeight: -1 + m_FlexibleWidth: -1 + m_FlexibleHeight: -1 + m_LayoutPriority: 1 From 81b4b8650536b2e514d848bc5cffb4173e9be4cf Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sat, 14 Sep 2024 01:04:48 +0200 Subject: [PATCH 13/16] Minor various formatting improvements --- .../SEE/Controls/Actions/ActionStateTypes.cs | 42 +++++++++---------- Assets/SEE/Game/City/ColorMap.cs | 4 +- Assets/SEE/GameObjects/SEESpline.cs | 34 +++++++++------ 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/Assets/SEE/Controls/Actions/ActionStateTypes.cs b/Assets/SEE/Controls/Actions/ActionStateTypes.cs index 75dabcaedc..1ec83d88ac 100644 --- a/Assets/SEE/Controls/Actions/ActionStateTypes.cs +++ b/Assets/SEE/Controls/Actions/ActionStateTypes.cs @@ -324,27 +324,27 @@ static ActionStateTypes() public static readonly ActionStateType LoadBoard; public static readonly ActionStateType SaveBoard; - public readonly static ActionStateTypeGroup Drawable; - public readonly static ActionStateType DrawFreehand; - public readonly static ActionStateType DrawShapes; - public readonly static ActionStateType WriteText; - public readonly static ActionStateType AddImage; - public readonly static ActionStateType MindMap; - public readonly static ActionStateType StickyNote; - public readonly static ActionStateType ColorPicker; - public readonly static ActionStateType Edit; - public readonly static ActionStateType MoveRotator; - public readonly static ActionStateType LayerChanger; - public readonly static ActionStateType CutCopyPaste; - public readonly static ActionStateType Scale; - public readonly static ActionStateType MovePoint; - public readonly static ActionStateType LineSplit; - public readonly static ActionStateType LinePointErase; - public readonly static ActionStateType LineConnectionErase; - public readonly static ActionStateType Erase; - public readonly static ActionStateType Clear; - public readonly static ActionStateType Save; - public readonly static ActionStateType Load; + public static readonly ActionStateTypeGroup Drawable; + public static readonly ActionStateType DrawFreehand; + public static readonly ActionStateType DrawShapes; + public static readonly ActionStateType WriteText; + public static readonly ActionStateType AddImage; + public static readonly ActionStateType MindMap; + public static readonly ActionStateType StickyNote; + public static readonly ActionStateType ColorPicker; + public static readonly ActionStateType Edit; + public static readonly ActionStateType MoveRotator; + public static readonly ActionStateType LayerChanger; + public static readonly ActionStateType CutCopyPaste; + public static readonly ActionStateType Scale; + public static readonly ActionStateType MovePoint; + public static readonly ActionStateType LineSplit; + public static readonly ActionStateType LinePointErase; + public static readonly ActionStateType LineConnectionErase; + public static readonly ActionStateType Erase; + public static readonly ActionStateType Clear; + public static readonly ActionStateType Save; + public static readonly ActionStateType Load; #endregion diff --git a/Assets/SEE/Game/City/ColorMap.cs b/Assets/SEE/Game/City/ColorMap.cs index e43fbdfe2f..1274f57317 100644 --- a/Assets/SEE/Game/City/ColorMap.cs +++ b/Assets/SEE/Game/City/ColorMap.cs @@ -29,8 +29,8 @@ public class ColorMap : ConfigIO.IPersistentConfigItem, IEnumerableretrieved color for public ColorRange this[string name] { - get { return map[name]; } - set { map[name] = value; } + get => map[name]; + set => map[name] = value; } /// diff --git a/Assets/SEE/GameObjects/SEESpline.cs b/Assets/SEE/GameObjects/SEESpline.cs index 651de0af86..55095d671b 100644 --- a/Assets/SEE/GameObjects/SEESpline.cs +++ b/Assets/SEE/GameObjects/SEESpline.cs @@ -387,7 +387,7 @@ private void UpdateColor() /// The created or updated mesh private Mesh CreateOrUpdateMesh() { - int totalVertices = (tubularSegments+1) * (radialSegments+1); + int totalVertices = (tubularSegments + 1) * (radialSegments + 1); int totalIndices = tubularSegments * radialSegments * 6; Vector3[] vertices = new Vector3[totalVertices]; Vector3[] normals = new Vector3[totalVertices]; @@ -454,7 +454,7 @@ private Mesh CreateOrUpdateMesh() Vector3 frPosition = TinySplineInterop.VectorToVector(fr.Position); Vector3 frNormal = TinySplineInterop.VectorToVector(fr.Normal); Vector3 frBinormal = TinySplineInterop.VectorToVector(fr.Binormal); - Vector4 frTangent = TinySplineInterop.VectorToVector(fr.Tangent); // w = 0f by default. + Vector4 frTangent = TinySplineInterop.VectorToVector(fr.Tangent); // w = 0f by default. // TODO: This was previously (before the optimization) implicit behavior. Is this intentional? if (float.IsNaN(frNormal.x)) @@ -486,8 +486,12 @@ private Mesh CreateOrUpdateMesh() int d = index - segmentPlusOne; // faces - indices[indexIndex++] = a; indices[indexIndex++] = d; indices[indexIndex++] = b; - indices[indexIndex++] = b; indices[indexIndex++] = d; indices[indexIndex++] = c; + indices[indexIndex++] = a; + indices[indexIndex++] = d; + indices[indexIndex++] = b; + indices[indexIndex++] = b; + indices[indexIndex++] = d; + indices[indexIndex++] = c; } index++; @@ -499,17 +503,19 @@ private Mesh CreateOrUpdateMesh() bool updateMaterial; // Whether to call `UpdateMaterial'. if (gameObject.TryGetComponent(out MeshFilter filter)) - { // Does this game object already have a mesh which we can reuse? + { + // Does this game object already have a mesh which we can reuse? mesh = filter.mesh; updateMaterial = // The geometrics of the mesh have changed. - mesh.vertices.Length != vertices.Length || - mesh.normals.Length != normals.Length || - mesh.tangents.Length != tangents.Length || - mesh.uv.Length != uvs.Length || - needsColorUpdate; // Or the color of the mesh has been changed. + mesh.vertices.Length != vertices.Length || + mesh.normals.Length != normals.Length || + mesh.tangents.Length != tangents.Length || + mesh.uv.Length != uvs.Length || + needsColorUpdate; // Or the color of the mesh has been changed. } else - { // Create a new mesh for this game object. + { + // Create a new mesh for this game object. mesh = new Mesh(); mesh.MarkDynamic(); // May improve performance. filter = gameObject.AddComponent(); @@ -542,12 +548,14 @@ private Mesh CreateOrUpdateMesh() meshRenderer = gameObject.AddOrGetComponent(); if (updateMaterial) - { // Needs the meshRenderer. + { + // Needs the meshRenderer. UpdateMaterial(); } if (gameObject.TryGetComponent(out LineRenderer lineRenderer)) - { // Remove line meshRenderer. + { + // Remove line meshRenderer. Destroyer.Destroy(lineRenderer); } From fee5db2bb2888955813dadbfd398a3be6896fb66 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sat, 14 Sep 2024 01:13:02 +0200 Subject: [PATCH 14/16] Handle multiple open windows correctly (fix #708) --- Assets/SEE/UI/Window/DesktopWindowSpace.cs | 153 +++++++++++++++------ 1 file changed, 110 insertions(+), 43 deletions(-) diff --git a/Assets/SEE/UI/Window/DesktopWindowSpace.cs b/Assets/SEE/UI/Window/DesktopWindowSpace.cs index 3b3a22c93f..908c516ba5 100644 --- a/Assets/SEE/UI/Window/DesktopWindowSpace.cs +++ b/Assets/SEE/UI/Window/DesktopWindowSpace.cs @@ -1,9 +1,11 @@ using System; +using System.Collections.Generic; using System.Linq; using DynamicPanels; using SEE.GO; using SEE.Utils; using UnityEngine; +using CollectionExtensions = SEE.Utils.CollectionExtensions; namespace SEE.UI.Window { @@ -13,9 +15,9 @@ namespace SEE.UI.Window public partial class WindowSpace { /// - /// The containing the windows. + /// The panels containing the windows. /// - private Panel panel; + private readonly Dictionary> panels = new(); /// /// The dynamic canvas of the panel. @@ -60,7 +62,7 @@ protected override void StartDesktop() /// private void UpdateActiveTab() { - if (panel != null && ActiveWindow != null && ActiveWindow.Window != null) + if (panels.Count > 0 && ActiveWindow != null && ActiveWindow.Window != null) { if (!windows.Contains(ActiveWindow)) { @@ -79,40 +81,49 @@ private void UpdateActiveTab() // recursive one to be more efficient, but this will just "improve" an O(1) space complexity to // O(1) space complexity, because the recursion will at most happen once per call. // Additionally, the readability of the iterative version is (in my opinion) much worse, this is - // why I have left it the way it is. The following disables this recommendation in some IDEs. - // ReSharper disable once TailRecursiveCall + // why I have left it the way it is. UpdateActiveTab(); return; } - panel.ActiveTab = panel.GetTabIndex((RectTransform)ActiveWindow.Window.transform); + if (PanelTabForWindow(ActiveWindow) is ({ } panel, { } tab)) + { + panel.ActiveTab = tab.Index; + } } } - protected override void UpdateDesktop() + /// + /// Returns the panel and tab for a given window. + /// + /// The window to find the panel and tab for. + /// The panel and tab for the window, or null if the window is not part of the space. + private (Panel, PanelTab)? PanelTabForWindow(BaseWindow window) { - if (panel && !windows.Any()) - { - // We need to destroy the panel now - Destroyer.Destroy(panel); - } - else if (!panel && windows.Any(x => x.Window)) + if (window == null || window.Window == null) { - InitializePanel(); - } - else if (!panel) - { - // If no window is initialized yet, there's nothing we can do - return; + return null; } + RectTransform windowTransform = (RectTransform)window.Window.transform; + return panels.Keys.Select(x => (x, x.GetTab(windowTransform))).SingleOrDefault(x => x.Item2 != null); + } - if (currentActiveWindow != ActiveWindow) + protected override void UpdateDesktop() + { + switch (panels.Count) { - // Nominal active window has been changed, so we change the actual active window as well. - UpdateActiveTab(); - - // The window will only be actually changed when UpdateActiveTab() didn't throw an exception, - // so currentActiveWindow is guaranteed to be part of windows. - currentActiveWindow = ActiveWindow; + case > 0: + // We need to destroy any empty panels now. + foreach (Panel emptyPanel in panels.Where(x => x.Value.Count == 0).Select(x => x.Key).ToList()) + { + Destroyer.Destroy(emptyPanel); + panels.Remove(emptyPanel); + } + break; + case 0 when windows.Any(x => x.Window): + // We need to initialize at least one panel. + InitializePanel(); + break; + case 0: return; // If no window is initialized yet, there's nothing we can do. } // Now we need to detect changes in the open windows. @@ -122,18 +133,24 @@ protected override void UpdateDesktop() // First, close old windows that are not open anymore foreach (BaseWindow window in currentWindows.Except(windows).ToList()) { - panel.RemoveTab(panel.GetTab((RectTransform)window.Window.transform)); + if (PanelTabForWindow(window) is ({ } panel, { } tab)) + { + panel.RemoveTab(tab); + panels[panel].Remove(window); + } currentWindows.Remove(window); Destroyer.Destroy(window); } // Then, add new tabs // We need to skip windows which weren't initialized yet - foreach (BaseWindow window in windows.Except(currentWindows).Where(x => x.Window != null).ToList()) + foreach (BaseWindow window in windows.Except(currentWindows).Where(x => x.Window).ToList()) { RectTransform rectTransform = (RectTransform)window.Window.transform; - // Add the new window as a tab to our panel - PanelTab tab = panel.AddTab(rectTransform); + // Add the new window as a tab to the latest panel + Panel targetPanel = panels.Keys.Last(); + panels[targetPanel].Add(window); + PanelTab tab = targetPanel.AddTab(rectTransform); tab.Label = window.Title; tab.Icon = null; currentWindows.Add(window); @@ -150,11 +167,22 @@ protected override void UpdateDesktop() window.RebuildLayout(); } + if (currentActiveWindow != ActiveWindow && ActiveWindow) + { + // Nominal active window has been changed, so we change the actual active window as well. + UpdateActiveTab(); + + // The window will only be actually changed when UpdateActiveTab() didn't throw an exception, + // so currentActiveWindow is guaranteed to be part of windows. + currentActiveWindow = ActiveWindow; + } + return; + void CloseTab(PanelTab panelTab) { - if (panelTab.Panel == panel) + if (panels.TryGetValue(panelTab.Panel, out List panel)) { - BaseWindow window = windows.FirstOrDefault(x => x.Window.GetInstanceID() == panelTab.Content.gameObject.GetInstanceID()); + BaseWindow window = panel.FirstOrDefault(x => x.Window.GetInstanceID() == panelTab.Content.gameObject.GetInstanceID()); if (window != null) { CloseWindow(window); @@ -186,35 +214,74 @@ private void InitializePanel() windows.Clear(); return; } - panel = PanelUtils.CreatePanelFor((RectTransform)windows[0].Window.transform, panelsCanvas); - // When the active tab *on this panel* is changed, we invoke the corresponding event + + // Create the first panel. + Panel firstPanel = PanelUtils.CreatePanelFor((RectTransform)windows[0].Window.transform, panelsCanvas); + panels[firstPanel] = new(); + + // The user may create panels themselves. + PanelNotificationCenter.OnPanelCreated += HandleNewPanel; PanelNotificationCenter.OnPanelClosed += ClosePanel; + PanelNotificationCenter.OnStoppedDraggingTab += HandleMovedTab; + // When the active tab *on one of our panels* is changed, we invoke the corresponding event PanelNotificationCenter.OnActiveTabChanged += ChangeActiveTab; return; + void HandleNewPanel(Panel panel) + { + // There may already be windows in the panel (if the user created it by dragging tabs into the void) + ISet panelWindows = CollectionExtensions.GetValueOrDefault(panels, panel, new()).ToHashSet(); + for (int i = 0; i < panel.NumberOfTabs; i++) + { + if (Windows.SingleOrDefault(w => w.Window == panel[i].Content.gameObject) is { } window) + { + panelWindows.Add(window); + } + } + panels[panel] = panelWindows.ToList(); + } + + void HandleMovedTab(PanelTab tab) + { + foreach ((Panel key, List value) in panels) + { + foreach (BaseWindow baseWindow in value.Where(baseWindow => baseWindow.Window == tab.Content.gameObject)) + { + panels[key].Remove(baseWindow); + panels.GetOrAdd(tab.Panel, () => new()).Add(baseWindow); + return; + } + } + } + void ChangeActiveTab(PanelTab tab) { - if (panel == tab.Panel) + if (panels.TryGetValue(tab.Panel, out List panel)) { - ActiveWindow = Windows.First(x => x.Window.GetInstanceID() == tab.Content.gameObject.GetInstanceID()); - OnActiveWindowChanged.Invoke(); + BaseWindow activePanel = panel.FirstOrDefault(x => x.Window == tab.Content.gameObject); + if (activePanel != null) + { + ActiveWindow = activePanel; + OnActiveWindowChanged.Invoke(); + } } } void ClosePanel(Panel panel) { - if (panel == this.panel) + if (panels.ContainsKey(panel)) { - // Close each tab - foreach (BaseWindow window in windows) + // Close each tab in this panel. + foreach (BaseWindow window in panels[panel].Where(x => x.Window != null)) { - this.panel.RemoveTab(this.panel.GetTab((RectTransform)window.Window.transform)); + panel.RemoveTab(panel.GetTab((RectTransform)window.Window.transform)); Destroyer.Destroy(window); } - windows.Clear(); + windows.RemoveAll(panels[panel]); + panels.Remove(panel); OnActiveWindowChanged.Invoke(); - Destroyer.Destroy(this.panel); + Destroyer.Destroy(panel); } } } From 2160d78b21f5b0d77b6b1b5991567ff5cbe7de2a Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sun, 15 Sep 2024 16:22:56 +0200 Subject: [PATCH 15/16] CI: Update dependencies to newest version This should also get rid of the depracation warnings. --- .github/workflows/main.yml | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 34e1613c3b..b4a096c992 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,11 +7,11 @@ on: workflow_dispatch: inputs: windows: - description: 'Create Windows build?' + description: "Create Windows build?" default: true type: boolean linux: - description: 'Create Linux build?' + description: "Create Linux build?" default: true type: boolean @@ -82,7 +82,7 @@ jobs: fi fi echo "PATTERNS=none" >> $GITHUB_ENV - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 name: Check for bad patterns with: retries: 3 @@ -147,7 +147,7 @@ jobs: Library-SEE - name: Restore NuGet packages run: /home/see/.dotnet/tools/nugetforunity restore . - - uses: game-ci/unity-test-runner@v4.1.1 + - uses: game-ci/unity-test-runner@v4 timeout-minutes: 60 name: Run Tests id: tests @@ -158,10 +158,10 @@ jobs: with: githubToken: ${{ secrets.GITHUB_TOKEN }} checkName: Test Results - testMode: EditMode # PlayMode tests get stuck in batchmode + testMode: EditMode # PlayMode tests get stuck in batchmode runAsHostUser: true customParameters: -testCategory "!NonDeterministic" - coverageOptions: 'generateAdditionalMetrics;generateHtmlReport;assemblyFilters:+SEE' + coverageOptions: "generateAdditionalMetrics;generateHtmlReport;assemblyFilters:+SEE" - uses: actions/upload-artifact@v4 continue-on-error: true name: Upload test results @@ -203,7 +203,6 @@ jobs: echo "targetPlatforms=$(jq --compact-output --null-input '$ARGS.positional' --args -- ${targetPlatforms[@]})" >> $GITHUB_OUTPUT fi - build: name: Create build runs-on: ${{ needs.setup.outputs.runner }} @@ -248,7 +247,7 @@ jobs: with: name: SEE-${{ matrix.targetPlatform }}-${{ github.sha }}.zip path: build-${{ matrix.targetPlatform }}.zip - retention-days: 7 # Due to high space usage. + retention-days: 7 # Due to high space usage. release: name: Release @@ -266,7 +265,7 @@ jobs: tag: pr-${{ github.event.pull_request.number }} message: ${{ github.event.pull_request.title }} - name: Publish release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: prerelease: true name: ${{ github.event.pull_request.title }} @@ -277,20 +276,19 @@ jobs: body: | This release incorporates the changes by @${{ github.event.pull_request.user.login }} from pull request #${{ github.event.pull_request.number }}. Builds for Windows and Linux are available below. - + ## Details ${{ github.event.pull_request.body }} - + --- - - > [See original pull request for details.](${{ github.event.pull_request.html_url }}) + > [See original pull request for details.](${{ github.event.pull_request.html_url }}) cleanup: name: Cleanup runs-on: ${{ needs.setup.outputs.runner }} if: ${{ always() }} - needs: [setup, test, build, release] # "test", "build" and "release" use docker and sometimes mess up permissions. + needs: [setup, test, build, release] # "test", "build" and "release" use docker and sometimes mess up permissions. strategy: fail-fast: false steps: @@ -302,4 +300,3 @@ jobs: if: always() shell: bash run: sudo /mnt/scripts/set-work-permissions.sh - From e53bde958ec56823541278474eda270857cab63a Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Mon, 16 Sep 2024 18:46:53 +0200 Subject: [PATCH 16/16] Address review suggestion by @koschke --- Assets/SEE/Controls/Actions/ShowEdges.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Assets/SEE/Controls/Actions/ShowEdges.cs b/Assets/SEE/Controls/Actions/ShowEdges.cs index fe8812fe48..a7ca535325 100644 --- a/Assets/SEE/Controls/Actions/ShowEdges.cs +++ b/Assets/SEE/Controls/Actions/ShowEdges.cs @@ -44,7 +44,7 @@ public class ShowEdges : InteractableObjectAction /// /// The toggle that is used to determine whether an edge is currently selected. /// - private const string EdgeIsSelectedToggle = "IsSelected"; + private const string edgeIsSelectedToggle = "IsSelected"; /// /// The delay between each depth level when showing/hiding the transitive closure of edges. @@ -107,7 +107,7 @@ private void SelectionOn(InteractableObject interactableObject, bool isInitiator if (gameObject.TryGetNode(out Node node)) { RelevantEdges(node, followSource: false, followTarget: true, true) - .SelectMany(x => x).ForEach(x => x.SetToggle(EdgeIsSelectedToggle, true)); + .SelectMany(x => x).ForEach(x => x.SetToggle(edgeIsSelectedToggle, true)); } } } @@ -132,7 +132,7 @@ private void SelectionOff(InteractableObject interactableObject, bool isInitiato if (gameObject.TryGetNode(out Node node)) { RelevantEdges(node, followSource: false, followTarget: true, true) - .SelectMany(x => x).ForEach(x => x.SetToggle(EdgeIsSelectedToggle, false)); + .SelectMany(x => x).ForEach(x => x.SetToggle(edgeIsSelectedToggle, false)); } } } @@ -251,7 +251,7 @@ IEnumerable> IteratedConnectedEdges(Func getSucces List connected = DirectlyConnectedEdges(currentNode).ToList(); results[distance].AddRange(connected.Where(x => x.HasToggle(Edge.IsHiddenToggle) // Hover should not override edges shown by selection. - && (fromSelection || !x.HasToggle(EdgeIsSelectedToggle)))); + && (fromSelection || !x.HasToggle(edgeIsSelectedToggle)))); // Queue successors, if there are any. connected.Select(getSuccessorNode) .Where(x => x != null)