From 3311ca12d3956ff95d6e7c8a2696d45e0e05969a Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 6 Oct 2023 15:31:01 +0200 Subject: [PATCH 01/25] Make level color dependent on node color in TreeView --- .../UI/Window/TreeWindow/DesktopTreeWindow.cs | 45 ++++++++++++++----- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs index da2f7da65a..ea308bb154 100644 --- a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs +++ b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs @@ -2,10 +2,13 @@ using System.Collections.Generic; using System.Linq; using SEE.DataModel.DG; +using SEE.Game; using SEE.GO; using SEE.Utils; using UnityEngine; using UnityEngine.UI; +using Color = UnityEngine.Color; +using Transform = UnityEngine.Transform; namespace SEE.UI.Window.TreeWindow { @@ -44,10 +47,12 @@ public partial class TreeWindow /// The node to be added. private void AddNode(Node node) { + GameObject nodeGameObject = GraphElementIDMap.Find(node.ID, mustFindElement: true); + Color? nodeColor = nodeGameObject.GetComponent()?.material?.color; int children = node.NumberOfChildren() + Mathf.Min(node.Outgoings.Count, 1) + Mathf.Min(node.Incomings.Count, 1); AddItem(CleanupID(node.ID), CleanupID(node.Parent?.ID), - children, node.ToShortString(), node.Level, - nodeTypeUnicode, i => CollapseNode(node, i), i => ExpandNode(node, i)); + children, node.ToShortString(), node.Level, nodeTypeUnicode, nodeColor, + i => CollapseNode(node, i), i => ExpandNode(node, i, nodeColor)); } /// @@ -59,30 +64,47 @@ private void AddNode(Node node) /// The text of the item to be added. /// The level of the item to be added. /// The icon of the item to be added, given as a unicode character. + /// The color of the item to be added. /// A function that collapses the item. /// A function that expands the item. private void AddItem(string id, string parentId, int children, string text, int level, - char icon, Action collapseItem, Action expandItem) + char icon, Color? color, Action collapseItem, Action expandItem) { GameObject item = PrefabInstantiator.InstantiatePrefab(treeItemPrefab, content, false); + Transform foreground = item.transform.Find("Foreground"); + GameObject expandIcon = foreground.Find("Expand Icon").gameObject; + TMPro.TextMeshProUGUI textMesh = foreground.Find("Text").gameObject.GetComponent(); + TMPro.TextMeshProUGUI iconMesh = foreground.Find("Type Icon").gameObject.GetComponent(); + + textMesh.text = text; + iconMesh.text = icon.ToString(); + if (parentId != null) { // Position the item below its parent. - // TODO: Use colors from the city (e.g., depending on node type). // TODO: Include number badge in title. item.transform.SetSiblingIndex(content.Find(parentId).GetSiblingIndex() + 1); - item.transform.Find("Foreground").localPosition += new Vector3(indentShift * level, 0, 0); + foreground.localPosition += new Vector3(indentShift * level, 0, 0); + // TODO: If there is no color, inherit it from the parent. + if (color.HasValue) + { + item.transform.Find("Background").GetComponent().color = color.Value; + + // We also need to set the text color to a color that is readable on the background color. + Color foregroundColor = color.Value.IdealTextColor(); + textMesh.color = foregroundColor; + iconMesh.color = foregroundColor; + expandIcon.GetComponent().color = foregroundColor; + } } // Slashes will cause problems later on, so we replace them with backslashes. // NOTE: This becomes a problem if two nodes A and B exist where node A's name contains slashes and node B // has an identical name, except for all slashes being replaced by backslashes. // I hope this is unlikely enough to not be a problem for now. item.name = CleanupID(id); - item.transform.Find("Foreground/Text").gameObject.GetComponent().text = text; - item.transform.Find("Foreground/Type Icon").gameObject.GetComponent().text = icon.ToString(); if (children <= 0) { - item.transform.Find("Foreground/Expand Icon").gameObject.SetActive(false); + expandIcon.SetActive(false); } else if (item.TryGetComponentOrLog(out Button button)) { @@ -211,7 +233,8 @@ private void CollapseItem(GameObject item) /// /// The node represented by the item. /// The item to be expanded. - private void ExpandNode(Node node, GameObject item) + /// The color of the node. + private void ExpandNode(Node node, GameObject item, Color? nodeColor) { ExpandItem(item); @@ -237,7 +260,7 @@ void AddEdgeButton(string edgesType, char icon, ICollection edges) // Note that an edge may appear multiple times in the tree view, // hence we make its ID dependent on the node it is connected to, // and whether it is an incoming or outgoing edge (to cover self-loops). - AddItem(id, cleanedId, edges.Count, $"{edgesType} Edges", node.Level + 1, icon, + AddItem(id, cleanedId, edges.Count, $"{edgesType} Edges", node.Level + 1, icon, nodeColor, i => { CollapseItem(i); @@ -250,7 +273,7 @@ void AddEdgeButton(string edgesType, char icon, ICollection edges) ExpandItem(i); foreach (Edge edge in edges) { - AddItem($"{id}#{CleanupID(edge.ID)}", id, 0, edge.ToShortString(), node.Level + 2, edgeTypeUnicode, null, null); + AddItem($"{id}#{CleanupID(edge.ID)}", id, 0, edge.ToShortString(), node.Level + 2, edgeTypeUnicode, nodeColor, null, null); } }); } From 16ec8a294b478b5478df9fd58ad8b779cb73cf47 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Wed, 11 Oct 2023 15:30:31 +0200 Subject: [PATCH 02/25] Update Unity packages to new versions --- .../Font Awesome 6 Free-Solid-900 SDF.asset | 4 +- .../Settings/Open XR Package Settings.asset | 57 ++++++++++++++++++- Packages/manifest.json | 15 ++--- Packages/packages-lock.json | 39 ++++++++----- 4 files changed, 89 insertions(+), 26 deletions(-) diff --git a/Assets/Resources/External/FontAwesome/otfs/Font Awesome 6 Free-Solid-900 SDF.asset b/Assets/Resources/External/FontAwesome/otfs/Font Awesome 6 Free-Solid-900 SDF.asset index 46966ff0b3..9723e94bf0 100644 --- a/Assets/Resources/External/FontAwesome/otfs/Font Awesome 6 Free-Solid-900 SDF.asset +++ b/Assets/Resources/External/FontAwesome/otfs/Font Awesome 6 Free-Solid-900 SDF.asset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6893fc2b4999e5fd4c7b667ba8b9c6d2111c2599310ed9ecf15e90f389871680 -size 34257979 +oid sha256:a286f5578d04fb9e465ed1dce7d770e83e59491eec625aaf4b998f61d1fca5e9 +size 34258021 diff --git a/Assets/XR/Settings/Open XR Package Settings.asset b/Assets/XR/Settings/Open XR Package Settings.asset index 549943ec4c..5d10fd5634 100644 --- a/Assets/XR/Settings/Open XR Package Settings.asset +++ b/Assets/XR/Settings/Open XR Package Settings.asset @@ -206,6 +206,39 @@ MonoBehaviour: company: priority: 0 required: 0 +--- !u!114 &-445732088723173010 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 0} + m_Name: DPadInteraction Standalone + m_EditorClassIdentifier: Unity.XR.OpenXR:UnityEngine.XR.OpenXR.Features.Interactions:DPadInteraction + m_enabled: 0 + nameUi: + version: + featureIdInternal: + openxrExtensionStrings: + company: + priority: 0 + required: 0 + forceThresholdLeft: 0.5 + forceThresholdReleaseLeft: 0.4 + centerRegionLeft: 0.5 + wedgeAngleLeft: 1.5707964 + isStickyLeft: 0 + forceThresholdRight: 0.5 + forceThresholdReleaseRight: 0.4 + centerRegionRight: 0.5 + wedgeAngleRight: 1.5707964 + isStickyRight: 0 + extensionStrings: + - XR_KHR_binding_modification + - XR_EXT_dpad_binding --- !u!114 &11400000 MonoBehaviour: m_ObjectHideFlags: 0 @@ -356,7 +389,7 @@ MonoBehaviour: m_EditorClassIdentifier: features: - {fileID: 3659132584355106210} - - {fileID: -9183767700453206409} + - {fileID: 0} - {fileID: 8813224991119622085} - {fileID: -3509102339466431376} - {fileID: 356982443662187679} @@ -368,7 +401,7 @@ MonoBehaviour: - {fileID: 2035665097814175999} - {fileID: 5863123478492171226} - {fileID: -7973984030652435389} - - {fileID: 2350713162985483363} + - {fileID: 0} - {fileID: 8139941772144781997} - {fileID: 7242976004462796141} m_renderMode: 0 @@ -434,6 +467,26 @@ MonoBehaviour: priority: 0 required: 0 ignoreValidationErrors: 0 +--- !u!114 &6169281877885092733 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 0} + m_Name: PalmPoseInteraction Standalone + m_EditorClassIdentifier: Unity.XR.OpenXR:UnityEngine.XR.OpenXR.Features.Interactions:PalmPoseInteraction + m_enabled: 0 + nameUi: + version: + featureIdInternal: + openxrExtensionStrings: + company: + priority: 0 + required: 0 --- !u!114 &7242976004462796141 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Packages/manifest.json b/Packages/manifest.json index d7f7a3f7e3..ec7030052b 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -2,14 +2,14 @@ "dependencies": { "com.cakeslice.outline-effect": "https://github.com/cakeslice/Outline-Effect.git", "com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.2.5", - "com.openai.unity": "5.0.9", + "com.openai.unity": "5.0.10", "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", - "com.unity.ai.navigation": "1.1.4", + "com.unity.ai.navigation": "1.1.5", "com.unity.burst": "1.8.9", "com.unity.formats.fbx": "4.2.1", - "com.unity.ide.rider": "3.0.25", - "com.unity.ide.visualstudio": "2.0.21", + "com.unity.ide.rider": "3.0.26", + "com.unity.ide.visualstudio": "2.0.22", "com.unity.ide.vscode": "1.2.5", "com.unity.inputsystem": "1.7.0", "com.unity.netcode.gameobjects": "1.6.0", @@ -18,13 +18,13 @@ "com.unity.test-framework": "1.1.33", "com.unity.testtools.codecoverage": "1.2.4", "com.unity.textmeshpro": "3.0.6", - "com.unity.timeline": "1.7.5", + "com.unity.timeline": "1.7.6", "com.unity.toolchain.linux-x86_64": "2.0.6", "com.unity.ugui": "1.0.0", - "com.unity.xr.interaction.toolkit": "2.5.1", + "com.unity.xr.interaction.toolkit": "2.5.2", "com.unity.xr.legacyinputhelpers": "2.1.10", "com.unity.xr.management": "4.4.0", - "com.unity.xr.openxr": "1.8.1", + "com.unity.xr.openxr": "1.8.2", "judah4.hsvcolorpickerunity": "https://github.com/judah4/HSV-Color-Picker-Unity.git#upm", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", @@ -46,6 +46,7 @@ "com.unity.modules.ui": "1.0.0", "com.unity.modules.uielements": "1.0.0", "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.modules.unitywebrequestassetbundle": "1.0.0", "com.unity.modules.unitywebrequestaudio": "1.0.0", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 4c3c6c41d0..8c4e255232 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -12,7 +12,7 @@ "depth": 0, "source": "git", "dependencies": {}, - "hash": "3f5fc0c5be962957b11caf0b0772ca013ef27f02" + "hash": "5a1fa96bd2d75e8402a66b4e8480105472a9aca7" }, "com.cysharp.unitask": { "version": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.2.5", @@ -22,12 +22,12 @@ "hash": "72e620d169841f32bc6110ad0e12ee9eae6f1aaf" }, "com.openai.unity": { - "version": "5.0.9", + "version": "5.0.10", "depth": 0, "source": "registry", "dependencies": { - "com.utilities.rest": "2.1.6", - "com.utilities.encoder.wav": "1.0.4" + "com.utilities.rest": "2.1.7", + "com.utilities.encoder.wav": "1.0.6" }, "url": "https://package.openupm.com" }, @@ -47,7 +47,7 @@ } }, "com.unity.ai.navigation": { - "version": "1.1.4", + "version": "1.1.5", "depth": 0, "source": "registry", "dependencies": { @@ -99,7 +99,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.25", + "version": "3.0.26", "depth": 0, "source": "registry", "dependencies": { @@ -108,7 +108,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.21", + "version": "2.0.22", "depth": 0, "source": "registry", "dependencies": { @@ -253,7 +253,7 @@ "url": "https://packages.unity.com" }, "com.unity.timeline": { - "version": "1.7.5", + "version": "1.7.6", "depth": 0, "source": "registry", "dependencies": { @@ -304,7 +304,7 @@ "url": "https://packages.unity.com" }, "com.unity.xr.interaction.toolkit": { - "version": "2.5.1", + "version": "2.5.2", "depth": 0, "source": "registry", "dependencies": { @@ -342,7 +342,7 @@ "url": "https://packages.unity.com" }, "com.unity.xr.openxr": { - "version": "1.8.1", + "version": "1.8.2", "depth": 0, "source": "registry", "dependencies": { @@ -365,22 +365,22 @@ "url": "https://package.openupm.com" }, "com.utilities.audio": { - "version": "1.0.5", + "version": "1.0.6", "depth": 2, "source": "registry", "dependencies": { "com.unity.modules.audio": "1.0.0", "com.unity.modules.unitywebrequestaudio": "1.0.0", - "com.utilities.async": "1.2.3" + "com.utilities.async": "2.0.1" }, "url": "https://package.openupm.com" }, "com.utilities.encoder.wav": { - "version": "1.0.4", + "version": "1.0.6", "depth": 1, "source": "registry", "dependencies": { - "com.utilities.audio": "1.0.5" + "com.utilities.audio": "1.0.6" }, "url": "https://package.openupm.com" }, @@ -395,7 +395,7 @@ "url": "https://package.openupm.com" }, "com.utilities.rest": { - "version": "2.1.6", + "version": "2.1.7", "depth": 1, "source": "registry", "dependencies": { @@ -561,6 +561,15 @@ "source": "builtin", "dependencies": {} }, + "com.unity.modules.unityanalytics": { + "version": "1.0.0", + "depth": 0, + "source": "builtin", + "dependencies": { + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0" + } + }, "com.unity.modules.unitywebrequest": { "version": "1.0.0", "depth": 0, From 2b88b82f8b2d2a6b0ef9fb9761a21407fdbded87 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Wed, 11 Oct 2023 19:18:28 +0200 Subject: [PATCH 03/25] Add ConflictingOperations to AbstractOperation This makes it possible to specify operations that shall be stopped when this operation is started. --- Assets/SEE/Game/Operator/AbstractOperator.cs | 30 ++++++++++++++++--- Assets/SEE/Game/Operator/EdgeOperator.cs | 8 ++--- Assets/SEE/Game/Operator/NodeOperator.cs | 26 +++++++--------- .../SEE/Game/Operator/NotificationOperator.cs | 10 +++---- 4 files changed, 45 insertions(+), 29 deletions(-) diff --git a/Assets/SEE/Game/Operator/AbstractOperator.cs b/Assets/SEE/Game/Operator/AbstractOperator.cs index 110bc18813..f7269044cc 100644 --- a/Assets/SEE/Game/Operator/AbstractOperator.cs +++ b/Assets/SEE/Game/Operator/AbstractOperator.cs @@ -65,6 +65,11 @@ protected interface IOperation /// Whether to stop at the current value (false) /// or at the target (true) void KillAnimator(bool complete = false); + + /// + /// Whether the operation is currently running. + /// + bool IsRunning { get; } } /// @@ -87,6 +92,13 @@ protected abstract class Operation : IOperation where C : MulticastDele /// protected readonly Func AnimateToAction; + /// + /// A set of all operations that are conflicting with this one. + /// If an operation in this set is running at the time this operation is started, + /// the conflicting operation will be killed. + /// + protected readonly IList ConflictingOperations; + /// /// The animator that is controlling the current animation. /// May be null if no animation is running. @@ -117,12 +129,17 @@ protected abstract class Operation : IOperation where C : MulticastDele /// The equality comparer used to check whether the target value has changed. /// If null, the default equality comparer for is used. /// + /// + /// The operations that are conflicting with this one. + /// protected Operation(Func animateToAction, V targetValue, - IEqualityComparer equalityComparer = null) + IEqualityComparer equalityComparer = null, + IEnumerable conflictingOperations = null) { AnimateToAction = animateToAction; TargetValue = targetValue; EqualityComparer = equalityComparer ?? EqualityComparer.Default; + ConflictingOperations = conflictingOperations?.ToList() ?? new List(); } /// @@ -142,6 +159,11 @@ protected virtual void ChangeAnimatorTarget(V newTarget, float duration, bool co { // Usual approach: Kill old animator and replace it with new one KillAnimator(complete); + // We also need to kill any currently running, conflicting operations. + foreach (IOperation operation in ConflictingOperations.Where(x => x.IsRunning)) + { + operation.KillAnimator(true); + } Animator = AnimateToAction(newTarget, duration); } @@ -224,10 +246,10 @@ protected override void ChangeAnimatorTarget(V newTarget, float duration, bool c new AndCombinedOperationCallback(Animator.Select(x => new TweenOperationCallback(x)), x => new TweenCallback(x)); public TweenOperation(Func> animateToAction, V targetValue, - IEqualityComparer equalityComparer = null) - : base(animateToAction, targetValue, equalityComparer) + IEqualityComparer equalityComparer = null, IEnumerable conflictingOperations = null) + : base(animateToAction, targetValue, equalityComparer, conflictingOperations) { } } } -} \ No newline at end of file +} diff --git a/Assets/SEE/Game/Operator/EdgeOperator.cs b/Assets/SEE/Game/Operator/EdgeOperator.cs index 66b2c4342d..8d77068252 100644 --- a/Assets/SEE/Game/Operator/EdgeOperator.cs +++ b/Assets/SEE/Game/Operator/EdgeOperator.cs @@ -157,6 +157,10 @@ protected override void OnEnable() go.MustGetComponent(out spline); base.OnEnable(); + morphism = new MorphismOperation(AnimateToMorphismAction, spline.Spline, null); + construction = new TweenOperation(ConstructAction, spline.SubsplineEndT >= 1); + return; + SplineMorphism AnimateToMorphismAction((BSpline targetSpline, GameObject temporaryGameObject) s, float d) { SplineMorphism animator = go.AddOrGetComponent(); @@ -182,8 +186,6 @@ SplineMorphism AnimateToMorphismAction((BSpline targetSpline, GameObject tempora return animator; } - morphism = new MorphismOperation(AnimateToMorphismAction, spline.Spline, null); - Tween[] ConstructAction(bool extending, float duration) { return new Tween[] @@ -194,8 +196,6 @@ Tween[] ConstructAction(bool extending, float duration) duration).SetEase(Ease.InOutExpo).Play() }; } - - construction = new TweenOperation(ConstructAction, spline.SubsplineEndT >= 1); } protected override TweenOperation<(Color start, Color end)> InitializeColorOperation() diff --git a/Assets/SEE/Game/Operator/NodeOperator.cs b/Assets/SEE/Game/Operator/NodeOperator.cs index 27b4b991ba..624f7249cb 100644 --- a/Assets/SEE/Game/Operator/NodeOperator.cs +++ b/Assets/SEE/Game/Operator/NodeOperator.cs @@ -424,16 +424,10 @@ protected void OnEnable() Vector3 currentScale = transform.localScale; Quaternion currentRotation = transform.rotation; - Tween[] AnimateToXAction(float x, float d) => new Tween[] { transform.DOMoveX(x, d).Play() }; - Tween[] AnimateToYAction(float y, float d) => new Tween[] { transform.DOMoveY(y, d).Play() }; - Tween[] AnimateToZAction(float z, float d) => new Tween[] { transform.DOMoveZ(z, d).Play() }; - Tween[] AnimateToRotationAction(Quaternion r, float d) => new Tween[] { transform.DORotateQuaternion(r, d).Play() }; positionX = new TweenOperation(AnimateToXAction, currentPosition.x); positionY = new TweenOperation(AnimateToYAction, currentPosition.y); positionZ = new TweenOperation(AnimateToZAction, currentPosition.z); rotation = new TweenOperation(AnimateToRotationAction, currentRotation); - - Tween[] AnimateToScaleAction(Vector3 s, float d) => new Tween[] { transform.DOScale(s, d).Play() }; scale = new TweenOperation(AnimateToScaleAction, currentScale); PrepareLabel(); @@ -442,12 +436,18 @@ protected void OnEnable() labelStartLinePosition = new TweenOperation(AnimateLabelStartLinePositionAction, DesiredLabelStartLinePosition); labelEndLinePosition = new TweenOperation(AnimateLabelEndLinePositionAction, DesiredLabelEndLinePosition); + blinking = new TweenOperation(BlinkAction, 0, equalityComparer: new AlwaysFalseEqualityComparer(), + conflictingOperations: new[] { Color }); + return; + + Tween[] AnimateToXAction(float x, float d) => new Tween[] { transform.DOMoveX(x, d).Play() }; + Tween[] AnimateToYAction(float y, float d) => new Tween[] { transform.DOMoveY(y, d).Play() }; + Tween[] AnimateToZAction(float z, float d) => new Tween[] { transform.DOMoveZ(z, d).Play() }; + Tween[] AnimateToRotationAction(Quaternion r, float d) => new Tween[] { transform.DORotateQuaternion(r, d).Play() }; + Tween[] AnimateToScaleAction(Vector3 s, float d) => new Tween[] { transform.DOScale(s, d).Play() }; + Tween[] BlinkAction(int count, float duration) { - if (Color.IsRunning) - { - Color.KillAnimator(true); - } // If we're interrupting another blinking, we need to make sure the color still has the correct value. material.color = Color.TargetValue; @@ -457,10 +457,6 @@ Tween[] BlinkAction(int count, float duration) }; } - blinking = new TweenOperation(BlinkAction, 0, equalityComparer: new AlwaysFalseEqualityComparer()); - - #region Local Methods - static Node GetNode(GameObject gameObject) { // We allow a null value for artificial nodes, but at least a NodeRef must be attached. @@ -471,8 +467,6 @@ static Node GetNode(GameObject gameObject) return nodeRef.Value; } - - #endregion } /// diff --git a/Assets/SEE/Game/Operator/NotificationOperator.cs b/Assets/SEE/Game/Operator/NotificationOperator.cs index 3367374e69..9f13cd3a81 100644 --- a/Assets/SEE/Game/Operator/NotificationOperator.cs +++ b/Assets/SEE/Game/Operator/NotificationOperator.cs @@ -25,9 +25,9 @@ public class NotificationOperator : AbstractOperator /// The base animation duration for this operator. /// May be set from outside. /// - public float TheBaseAnimationDuration + private float TheBaseAnimationDuration { - private get; + get; set; } = 1f; @@ -50,13 +50,13 @@ public IOperationCallback MoveToY(float newY, float factor = 1) private void OnEnable() { RectTransform rectTransform = (RectTransform)transform; + positionY = new TweenOperation(PositionYAction, rectTransform.anchoredPosition.y); + return; Tween[] PositionYAction(float p, float d) => new Tween[] { rectTransform.DOAnchorPosY(p, d).Play() }; - - positionY = new TweenOperation(PositionYAction, rectTransform.anchoredPosition.y); } private void OnDisable() @@ -65,4 +65,4 @@ private void OnDisable() positionY = null; } } -} \ No newline at end of file +} From dc4eb6abb4c22f6c7d4f94483c4f9d1453c38151 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Wed, 11 Oct 2023 20:10:23 +0200 Subject: [PATCH 04/25] Make operator callbacks chainable They have also been renamed such that the `Set` prefix is removed, since this is a common pattern for chainable method calls like these. --- .../Evolution/EvolutionRendererAddEdges.cs | 2 +- .../Evolution/EvolutionRendererAddNodes.cs | 2 +- .../Game/Evolution/EvolutionRendererAdjust.cs | 2 +- .../Game/Evolution/EvolutionRendererMove.cs | 4 +- .../Game/Evolution/EvolutionRendererRemove.cs | 4 +- .../Operator/AndCombinedOperationCallback.cs | 37 +++++++++++-------- .../Game/Operator/DummyOperationCallback.cs | 27 ++++++++++---- .../SEE/Game/Operator/GraphElementOperator.cs | 2 +- .../SEE/Game/Operator/IOperationCallback.cs | 24 +++++++----- .../Game/Operator/TweenOperationCallback.cs | 23 ++++++++---- 10 files changed, 79 insertions(+), 48 deletions(-) diff --git a/Assets/SEE/Game/Evolution/EvolutionRendererAddEdges.cs b/Assets/SEE/Game/Evolution/EvolutionRendererAddEdges.cs index 360b4c7b71..161b4d4937 100644 --- a/Assets/SEE/Game/Evolution/EvolutionRendererAddEdges.cs +++ b/Assets/SEE/Game/Evolution/EvolutionRendererAddEdges.cs @@ -34,7 +34,7 @@ private void Phase5AddNewEdges() } edgeObject.AddOrGetComponent() .Show(animationKind, AnimationLagFactor) - .SetOnComplete(animationWatchDog.Finished); + .OnComplete(animationWatchDog.Finished); } } else diff --git a/Assets/SEE/Game/Evolution/EvolutionRendererAddNodes.cs b/Assets/SEE/Game/Evolution/EvolutionRendererAddNodes.cs index 0f2ec5a7db..004aad7362 100644 --- a/Assets/SEE/Game/Evolution/EvolutionRendererAddNodes.cs +++ b/Assets/SEE/Game/Evolution/EvolutionRendererAddNodes.cs @@ -64,7 +64,7 @@ void Add(GameObject gameNode, ILayoutNode layoutNode) gameNode.AddOrGetComponent() .MoveTo(layoutNode.CenterPosition, AnimationLagFactor, updateEdges: false) - .SetOnComplete(animationWatchDog.Finished); + .OnComplete(animationWatchDog.Finished); } } } diff --git a/Assets/SEE/Game/Evolution/EvolutionRendererAdjust.cs b/Assets/SEE/Game/Evolution/EvolutionRendererAdjust.cs index 3dcb0b0e05..8cb414eb93 100644 --- a/Assets/SEE/Game/Evolution/EvolutionRendererAdjust.cs +++ b/Assets/SEE/Game/Evolution/EvolutionRendererAdjust.cs @@ -74,7 +74,7 @@ void ScaleTo(GameObject gameNode, ILayoutNode layoutNode) gameNode.AddOrGetComponent() .ScaleTo(localScale, AnimationLagFactor, updateEdges: false) - .SetOnComplete(() => OnScalingFinished(gameNode)); + .OnComplete(() => OnScalingFinished(gameNode)); } } diff --git a/Assets/SEE/Game/Evolution/EvolutionRendererMove.cs b/Assets/SEE/Game/Evolution/EvolutionRendererMove.cs index 5ee67e662a..7f8fa200fb 100644 --- a/Assets/SEE/Game/Evolution/EvolutionRendererMove.cs +++ b/Assets/SEE/Game/Evolution/EvolutionRendererMove.cs @@ -85,7 +85,7 @@ void SetUpEdgeAnimation(ISet edges) { objectManager.GetEdge(edge, out GameObject gameEdge); gameEdge.AddOrGetComponent().MorphTo(newLayoutEdge.Spline, AnimationLagFactor) - .SetOnComplete(animationWatchDog.Finished); + .OnComplete(animationWatchDog.Finished); } else { @@ -127,7 +127,7 @@ void MoveTo(GameObject gameNode, ILayoutNode layoutNode) // currentGameNode is shifted to its new position through the animator. gameNode.AddOrGetComponent() .MoveTo(layoutNode.CenterPosition, AnimationLagFactor, updateEdges: false) - .SetOnComplete(animationWatchDog.Finished); + .OnComplete(animationWatchDog.Finished); } } } diff --git a/Assets/SEE/Game/Evolution/EvolutionRendererRemove.cs b/Assets/SEE/Game/Evolution/EvolutionRendererRemove.cs index 420417697e..a4ffaf42ac 100644 --- a/Assets/SEE/Game/Evolution/EvolutionRendererRemove.cs +++ b/Assets/SEE/Game/Evolution/EvolutionRendererRemove.cs @@ -96,13 +96,13 @@ void MoveTo(GameObject gameObject, Vector3 newPosition) { gameObject.AddOrGetComponent() .MoveTo(newPosition, AnimationLagFactor, updateEdges: false) - .SetOnComplete(() => OnRemoveFinishedAnimation(gameObject)); + .OnComplete(() => OnRemoveFinishedAnimation(gameObject)); } else if (gameObject.IsEdge()) { gameObject.AddOrGetComponent() .FadeTo(0, AnimationLagFactor) - .SetOnComplete(() => OnRemoveFinishedAnimation(gameObject)); + .OnComplete(() => OnRemoveFinishedAnimation(gameObject)); } else { diff --git a/Assets/SEE/Game/Operator/AndCombinedOperationCallback.cs b/Assets/SEE/Game/Operator/AndCombinedOperationCallback.cs index e3feea4d1e..de88bd997d 100644 --- a/Assets/SEE/Game/Operator/AndCombinedOperationCallback.cs +++ b/Assets/SEE/Game/Operator/AndCombinedOperationCallback.cs @@ -133,67 +133,74 @@ private void HandleSingleCallback(IOperationCallback callback, int index) } } - public void SetOnComplete(Action callback) + public IOperationCallback OnComplete(Action callback) { onComplete = callback; foreach (IOperationCallback operationCallback in callbacks) { - operationCallback.SetOnComplete(callbackConverter(() => HandleSingleCallback(operationCallback, 0))); + operationCallback.OnComplete(callbackConverter(() => HandleSingleCallback(operationCallback, 0))); } + return this; } - public void SetOnKill(Action callback) + public IOperationCallback OnKill(Action callback) { onKill = callback; foreach (IOperationCallback operationCallback in callbacks) { - operationCallback.SetOnKill(callbackConverter(() => HandleSingleCallback(operationCallback, 1))); + operationCallback.OnKill(callbackConverter(() => HandleSingleCallback(operationCallback, 1))); } + return this; } - public void SetOnPlay(Action callback) + public IOperationCallback OnPlay(Action callback) { onPlay = callback; foreach (IOperationCallback operationCallback in callbacks) { - operationCallback.SetOnPlay(callbackConverter(() => HandleSingleCallback(operationCallback, 2))); + operationCallback.OnPlay(callbackConverter(() => HandleSingleCallback(operationCallback, 2))); } + return this; } - public void SetOnPause(Action callback) + public IOperationCallback OnPause(Action callback) { onPause = callback; foreach (IOperationCallback operationCallback in callbacks) { - operationCallback.SetOnPause(callbackConverter(() => HandleSingleCallback(operationCallback, 3))); + operationCallback.OnPause(callbackConverter(() => HandleSingleCallback(operationCallback, 3))); } + return this; } - public void SetOnRewind(Action callback) + public IOperationCallback OnRewind(Action callback) { onRewind = callback; foreach (IOperationCallback operationCallback in callbacks) { - operationCallback.SetOnRewind(callbackConverter(() => HandleSingleCallback(operationCallback, 4))); + operationCallback.OnRewind(callbackConverter(() => HandleSingleCallback(operationCallback, 4))); } + return this; } - public void SetOnStart(Action callback) + public IOperationCallback OnStart(Action callback) { onStart = callback; foreach (IOperationCallback operationCallback in callbacks) { - operationCallback.SetOnStart(callbackConverter(() => HandleSingleCallback(operationCallback, 5))); + operationCallback.OnStart(callbackConverter(() => HandleSingleCallback(operationCallback, 5))); } + return this; } - public void SetOnUpdate(Action callback) + public IOperationCallback OnUpdate(Action callback) { onUpdate = callback; foreach (IOperationCallback operationCallback in callbacks) { - operationCallback.SetOnUpdate(callbackConverter(() => HandleSingleCallback(operationCallback, 6))); + operationCallback.OnUpdate(callbackConverter(() => HandleSingleCallback(operationCallback, 6))); } + return this; } } -} \ No newline at end of file +} diff --git a/Assets/SEE/Game/Operator/DummyOperationCallback.cs b/Assets/SEE/Game/Operator/DummyOperationCallback.cs index 189f7f5d89..55afe97cd2 100644 --- a/Assets/SEE/Game/Operator/DummyOperationCallback.cs +++ b/Assets/SEE/Game/Operator/DummyOperationCallback.cs @@ -1,4 +1,5 @@ using System; +using RTG; namespace SEE.Game.Operator { @@ -7,49 +8,59 @@ namespace SEE.Game.Operator /// Corresponding callbacks (e.g., OnComplete) will thus be called instantly. /// /// the type of the callback delegate + /// + /// Use callbacks of this type sparingly, as they are very inefficient. + /// public class DummyOperationCallback : IOperationCallback where T : MulticastDelegate { // NOTE: We use reflection to invoke the callback because of the generic type parameter. // This is very inefficient (by a factor of ~200, see https://stackoverflow.com/a/3465152), // but AFAIK there is no good way around this. - public void SetOnComplete(T callback) + public IOperationCallback OnComplete(T callback) { // Happens instantly. callback.DynamicInvoke(); + return this; } - public void SetOnKill(T callback) + public IOperationCallback OnKill(T callback) { // Happens instantly. callback.DynamicInvoke(); + return this; } - public void SetOnPlay(T callback) + public IOperationCallback OnPlay(T callback) { // Happens instantly. callback.DynamicInvoke(); + return this; } - public void SetOnPause(T callback) + public IOperationCallback OnPause(T callback) { // Nothing to be done. + return this; } - public void SetOnRewind(T callback) + public IOperationCallback OnRewind(T callback) { // Nothing to be done. + return this; } - public void SetOnStart(T callback) + public IOperationCallback OnStart(T callback) { // Happens instantly. callback.DynamicInvoke(); + return this; } - public void SetOnUpdate(T callback) + public IOperationCallback OnUpdate(T callback) { // Nothing to be done. + return this; } } -} \ No newline at end of file +} diff --git a/Assets/SEE/Game/Operator/GraphElementOperator.cs b/Assets/SEE/Game/Operator/GraphElementOperator.cs index 1542b0ac10..9b449064cd 100644 --- a/Assets/SEE/Game/Operator/GraphElementOperator.cs +++ b/Assets/SEE/Game/Operator/GraphElementOperator.cs @@ -345,4 +345,4 @@ public void OnError(Exception error) throw error; } } -} \ No newline at end of file +} diff --git a/Assets/SEE/Game/Operator/IOperationCallback.cs b/Assets/SEE/Game/Operator/IOperationCallback.cs index e49e674699..d944901930 100644 --- a/Assets/SEE/Game/Operator/IOperationCallback.cs +++ b/Assets/SEE/Game/Operator/IOperationCallback.cs @@ -13,23 +13,27 @@ public interface IOperationCallback where T : MulticastDelegate /// /// Sets a callback that will be fired the moment the animator reaches completion, all loops included. /// - void SetOnComplete(T callback); + /// Returns a reference to this object for chaining. + IOperationCallback OnComplete(T callback); /// /// Sets a callback that will be fired the moment the animator is killed. /// - void SetOnKill(T callback); + /// Returns a reference to this object for chaining. + IOperationCallback OnKill(T callback); /// /// Sets a callback that will be fired when the animator is set in a playing state, after any eventual delay. /// Also called each time the animator resumes playing from a paused state. /// - void SetOnPlay(T callback); + /// Returns a reference to this object for chaining. + IOperationCallback OnPlay(T callback); /// /// Sets a callback that will be fired when the animator state changes from playing to paused. /// - void SetOnPause(T callback); + /// Returns a reference to this object for chaining. + IOperationCallback OnPause(T callback); /// /// Sets a callback that will be fired when the animator is rewinded, @@ -38,20 +42,22 @@ public interface IOperationCallback where T : MulticastDelegate /// /// Rewinding an animator that is already rewinded will not fire this callback. /// - void SetOnRewind(T callback); + /// Returns a reference to this object for chaining. + IOperationCallback OnRewind(T callback); /// /// Sets a callback that will be fired once when the animator starts (meaning when the animator is set in a /// playing state the first time, after any eventual delay). /// - void SetOnStart(T callback); + /// Returns a reference to this object for chaining. + IOperationCallback OnStart(T callback); /// /// Sets a callback that will be fired every time the animator updates. /// - /// - void SetOnUpdate(T callback); + /// Returns a reference to this object for chaining. + IOperationCallback OnUpdate(T callback); // Missing from DOTween, we can add them once we need them: OnStepComplete, OnWaypointChange } -} \ No newline at end of file +} diff --git a/Assets/SEE/Game/Operator/TweenOperationCallback.cs b/Assets/SEE/Game/Operator/TweenOperationCallback.cs index ee8ee571a7..66c9fc6ad5 100644 --- a/Assets/SEE/Game/Operator/TweenOperationCallback.cs +++ b/Assets/SEE/Game/Operator/TweenOperationCallback.cs @@ -24,34 +24,40 @@ public TweenOperationCallback(Tween targetTween) this.targetTween = targetTween; } - public void SetOnComplete(TweenCallback callback) + public IOperationCallback OnComplete(TweenCallback callback) { targetTween.OnComplete((TweenCallback)Delegate.Combine(targetTween.onComplete, callback)); + return this; } - public void SetOnKill(TweenCallback callback) + public IOperationCallback OnKill(TweenCallback callback) { targetTween.OnKill((TweenCallback)Delegate.Combine(targetTween.onKill, callback)); + return this; } - public void SetOnPlay(TweenCallback callback) + public IOperationCallback OnPlay(TweenCallback callback) { targetTween.OnPlay((TweenCallback)Delegate.Combine(targetTween.onPlay, callback)); + return this; } - public void SetOnPause(TweenCallback callback) + public IOperationCallback OnPause(TweenCallback callback) { targetTween.OnPause((TweenCallback)Delegate.Combine(targetTween.onPause, callback)); + return this; } - public void SetOnRewind(TweenCallback callback) + public IOperationCallback OnRewind(TweenCallback callback) { targetTween.OnRewind((TweenCallback)Delegate.Combine(targetTween.onRewind, callback)); + return this; } - public void SetOnUpdate(TweenCallback callback) + public IOperationCallback OnUpdate(TweenCallback callback) { targetTween.OnUpdate((TweenCallback)Delegate.Combine(targetTween.onUpdate, callback)); + return this; } /// @@ -59,10 +65,11 @@ public void SetOnUpdate(TweenCallback callback) /// playing state the first time, after any eventual delay). /// **All existing callbacks for `OnStart` will be removed.** /// - public void SetOnStart(TweenCallback callback) + public IOperationCallback OnStart(TweenCallback callback) { // We can't combine delegates here because `onStart` is an internal property in DOTween. targetTween.OnStart(callback); + return this; } } -} \ No newline at end of file +} From c285a86c4379d75503197e4f28cabd6dfdccf73a Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Wed, 11 Oct 2023 20:14:05 +0200 Subject: [PATCH 05/25] Move node highlighting into operator --- Assets/SEE/Game/Operator/NodeOperator.cs | 14 ++++++++++++++ Assets/SEE/GameObjects/MarkerFactory.cs | 5 +++-- Assets/SEE/GameObjects/Menu/SearchMenu.cs | 17 ++++++----------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/Assets/SEE/Game/Operator/NodeOperator.cs b/Assets/SEE/Game/Operator/NodeOperator.cs index 624f7249cb..d80ede6812 100644 --- a/Assets/SEE/Game/Operator/NodeOperator.cs +++ b/Assets/SEE/Game/Operator/NodeOperator.cs @@ -113,6 +113,20 @@ public Node Node #region Public API + /// + /// Displays a marker above the node and makes it blink times + /// within the given . + /// + /// The amount of time in seconds the node should be highlighted. + /// An operation callback for the requested animation + public IOperationCallback Highlight(float duration, float blinkCount) + { + // Display marker above the node + MarkerFactory marker = new(markerWidth: 0.01f, markerHeight: 1f, UnityEngine.Color.red, default, default); + marker.MarkBorn(gameObject); + return Blink(blinkCount: 20, ToDuration(duration)).OnComplete(() => marker.Clear()); + } + /// /// Moves the node to the in world space. /// diff --git a/Assets/SEE/GameObjects/MarkerFactory.cs b/Assets/SEE/GameObjects/MarkerFactory.cs index 2e13af016b..309aaa6a9e 100644 --- a/Assets/SEE/GameObjects/MarkerFactory.cs +++ b/Assets/SEE/GameObjects/MarkerFactory.cs @@ -168,6 +168,7 @@ private void AddEmissionAndAnimation(GameObject gameObject) { materials[renderer.sharedMaterial] = renderer.sharedMaterial.color; renderer.sharedMaterial.SetFloat(emissionStrengthProperty, emissionStrength); + // FIXME: Gradual fade does not work. The marker blinks in and out of existence. renderer.sharedMaterial.DOFade(0.0f, animationDuration).SetEase(Ease.Linear).SetLoops(-1, LoopType.Yoyo); } } @@ -195,7 +196,7 @@ public void AdjustMarkerY(GameObject gameNode) foreach (Transform child in gameNode.transform) { if (child.CompareTag(Tags.Decoration) - && (child.name == changeMarkerName || child.name == bornMarkerName || child.name == deadMarkerName)) + && child.name is changeMarkerName or bornMarkerName or deadMarkerName) { GameObject marker = child.gameObject; // The scale of gameNode may have changed, but we want our markers to @@ -284,4 +285,4 @@ public void Clear() } } } -} \ No newline at end of file +} diff --git a/Assets/SEE/GameObjects/Menu/SearchMenu.cs b/Assets/SEE/GameObjects/Menu/SearchMenu.cs index c48b6a0ef7..2a4ba31c77 100644 --- a/Assets/SEE/GameObjects/Menu/SearchMenu.cs +++ b/Assets/SEE/GameObjects/Menu/SearchMenu.cs @@ -37,6 +37,11 @@ public class SearchMenu : MonoBehaviour /// private const float markerWidth = 0.01f; + /// + /// The color of the marker pointing to the found node. + /// + private static readonly Color markerColor = Color.red; + /// /// The dialog in which the search query can be entered. /// @@ -53,11 +58,6 @@ public class SearchMenu : MonoBehaviour /// private SimpleListMenu resultMenu; - /// - /// The color of the marker pointing to the found node. - /// - private static readonly Color markerColor = Color.red; - /// /// A mapping from names to a list of nodes with that name. /// Is constructed in the method in order not to call expensive Find methods every @@ -180,12 +180,7 @@ private static void HighlightNode(GameObject result, string resultName) "The selected node will be blinking and marked by a spear " + $"for {blinkSeconds} seconds."); NodeOperator nodeOperator = result.AddOrGetComponent(); - // Display marker above the node - MarkerFactory marker = new(markerWidth, markerHeight, markerColor, default, default); - marker.MarkBorn(result); - nodeOperator.Blink(blinkCount, blinkSeconds).SetOnComplete(RemoveMarker); - - void RemoveMarker() => marker.Clear(); + nodeOperator.Highlight(blinkSeconds, blinkCount); } /// From 9d044a429eae976287636c040cddfe2341e9e9bf Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Thu, 12 Oct 2023 00:49:43 +0200 Subject: [PATCH 06/25] Implement highlighting nodes upon clicking --- .../Resources/Prefabs/UI/TreeViewItem.prefab | 18 +++++ Assets/SEE/Game/Operator/NodeOperator.cs | 11 ++- Assets/SEE/GameObjects/Menu/SearchMenu.cs | 35 ++------- .../UI/Window/TreeWindow/DesktopTreeWindow.cs | 78 ++++++++++++------- 4 files changed, 81 insertions(+), 61 deletions(-) diff --git a/Assets/Resources/Prefabs/UI/TreeViewItem.prefab b/Assets/Resources/Prefabs/UI/TreeViewItem.prefab index 22005a1d9c..4c32132aa8 100644 --- a/Assets/Resources/Prefabs/UI/TreeViewItem.prefab +++ b/Assets/Resources/Prefabs/UI/TreeViewItem.prefab @@ -464,6 +464,7 @@ GameObject: m_Component: - component: {fileID: 6804734348468291106} - component: {fileID: 809286346028970807} + - component: {fileID: 8422174230029475794} m_Layer: 5 m_Name: TreeViewItem m_TagString: Untagged @@ -536,3 +537,20 @@ MonoBehaviour: m_OnClick: m_PersistentCalls: m_Calls: [] +--- !u!114 &8422174230029475794 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5088984669082115910} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0b148fe25e99eb48b9724523833bab1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Delegates: + - eventID: 4 + callback: + m_PersistentCalls: + m_Calls: [] diff --git a/Assets/SEE/Game/Operator/NodeOperator.cs b/Assets/SEE/Game/Operator/NodeOperator.cs index d80ede6812..d11f029620 100644 --- a/Assets/SEE/Game/Operator/NodeOperator.cs +++ b/Assets/SEE/Game/Operator/NodeOperator.cs @@ -7,6 +7,7 @@ using SEE.GO; using SEE.Layout; using SEE.Tools.ReflexionAnalysis; +using SEE.UI.Notification; using SEE.Utils; using UnityEngine; using UnityEngine.Assertions; @@ -119,12 +120,18 @@ public Node Node /// /// The amount of time in seconds the node should be highlighted. /// An operation callback for the requested animation - public IOperationCallback Highlight(float duration, float blinkCount) + public IOperationCallback Highlight(float duration) { + ShowNotification.Info($"Highlighting '{name}'", + $"The selected node will be blinking and marked by a spear for {duration} seconds.", + log: false); // Display marker above the node MarkerFactory marker = new(markerWidth: 0.01f, markerHeight: 1f, UnityEngine.Color.red, default, default); marker.MarkBorn(gameObject); - return Blink(blinkCount: 20, ToDuration(duration)).OnComplete(() => marker.Clear()); + // The factor of 1.3 causes the node to blink slightly more than once per second, + // which seems visually fitting. + int blinkCount = Mathf.RoundToInt(duration * 1.3f); + return Blink(blinkCount: blinkCount, ToDuration(duration)).OnComplete(() => marker.Clear()); } /// diff --git a/Assets/SEE/GameObjects/Menu/SearchMenu.cs b/Assets/SEE/GameObjects/Menu/SearchMenu.cs index 2a4ba31c77..dbeae257b1 100644 --- a/Assets/SEE/GameObjects/Menu/SearchMenu.cs +++ b/Assets/SEE/GameObjects/Menu/SearchMenu.cs @@ -22,26 +22,6 @@ public class SearchMenu : MonoBehaviour /// private const float blinkSeconds = 15; - /// - /// The number of times the found node will blink. - /// - private const int blinkCount = 20; - - /// - /// The height of the marker used to mark the found node. - /// - private const float markerHeight = 1f; - - /// - /// The width of the marker used to mark the found node. - /// - private const float markerWidth = 0.01f; - - /// - /// The color of the marker pointing to the found node. - /// - private static readonly Color markerColor = Color.red; - /// /// The dialog in which the search query can be entered. /// @@ -121,7 +101,7 @@ private void ExecuteSearch() + $"'{FilterString(searchString.Value)}'."); break; case 1: - HighlightNode(results.First().Item3, results.First().Item2); + HighlightNode(results.First().Item3); break; default: ShowResultsMenu(results); @@ -148,15 +128,15 @@ private void ShowResultsMenu(IEnumerable<(int, string, GameObject)> results) // Entries will be greyed out the further they go. resultMenuEntries.ForEach(resultMenu.RemoveEntry); // Clean up previous entries. resultMenuEntries.Clear(); - resultMenuEntries.AddRange(results.Select(x => new MenuEntry(() => MenuEntryAction(x.Item3, x.Item2), null, + resultMenuEntries.AddRange(results.Select(x => new MenuEntry(() => MenuEntryAction(x.Item3), null, x.Item2, entryColor: ScoreColor(x.Item1)))); resultMenuEntries.ForEach(resultMenu.AddEntry); resultMenu.ShowMenu = true; // Highlight node and close menu when entry was chosen. - void MenuEntryAction(GameObject chosen, string chosenName) + void MenuEntryAction(GameObject chosen) { - HighlightNode(chosen, chosenName); + HighlightNode(chosen); resultMenu.ShowMenu = false; } } @@ -174,13 +154,10 @@ void MenuEntryAction(GameObject chosen, string chosenName) /// /// The game object of the node which shall be highlighted. /// The name of the node which shall be highlighted. - private static void HighlightNode(GameObject result, string resultName) + private static void HighlightNode(GameObject result) { - ShowNotification.Info($"Highlighting '{resultName}'", - "The selected node will be blinking and marked by a spear " - + $"for {blinkSeconds} seconds."); NodeOperator nodeOperator = result.AddOrGetComponent(); - nodeOperator.Highlight(blinkSeconds, blinkCount); + nodeOperator.Highlight(blinkSeconds); } /// diff --git a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs index ea308bb154..d886937bba 100644 --- a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs +++ b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs @@ -3,9 +3,11 @@ using System.Linq; using SEE.DataModel.DG; using SEE.Game; +using SEE.Game.Operator; using SEE.GO; using SEE.Utils; using UnityEngine; +using UnityEngine.EventSystems; using UnityEngine.UI; using Color = UnityEngine.Color; using Transform = UnityEngine.Transform; @@ -48,11 +50,11 @@ public partial class TreeWindow private void AddNode(Node node) { GameObject nodeGameObject = GraphElementIDMap.Find(node.ID, mustFindElement: true); - Color? nodeColor = nodeGameObject.GetComponent()?.material?.color; int children = node.NumberOfChildren() + Mathf.Min(node.Outgoings.Count, 1) + Mathf.Min(node.Incomings.Count, 1); + AddItem(CleanupID(node.ID), CleanupID(node.Parent?.ID), - children, node.ToShortString(), node.Level, nodeTypeUnicode, nodeColor, - i => CollapseNode(node, i), i => ExpandNode(node, i, nodeColor)); + children, node.ToShortString(), node.Level, nodeTypeUnicode, nodeGameObject, + i => CollapseNode(node, i), i => ExpandNode(node, i, nodeGameObject)); } /// @@ -64,11 +66,11 @@ private void AddNode(Node node) /// The text of the item to be added. /// The level of the item to be added. /// The icon of the item to be added, given as a unicode character. - /// The color of the item to be added. + /// The game object of the element represented by the item. May be null. /// A function that collapses the item. /// A function that expands the item. private void AddItem(string id, string parentId, int children, string text, int level, - char icon, Color? color, Action collapseItem, Action expandItem) + char icon, GameObject nodeGameObject, Action collapseItem, Action expandItem) { GameObject item = PrefabInstantiator.InstantiatePrefab(treeItemPrefab, content, false); Transform foreground = item.transform.Find("Foreground"); @@ -82,20 +84,26 @@ private void AddItem(string id, string parentId, int children, string text, int if (parentId != null) { // Position the item below its parent. - // TODO: Include number badge in title. - item.transform.SetSiblingIndex(content.Find(parentId).GetSiblingIndex() + 1); + Transform parent = content.Find(parentId); + item.transform.SetSiblingIndex(parent.GetSiblingIndex() + 1); foreground.localPosition += new Vector3(indentShift * level, 0, 0); - // TODO: If there is no color, inherit it from the parent. - if (color.HasValue) - { - item.transform.Find("Background").GetComponent().color = color.Value; + // TODO: Include number badge in title. + } - // We also need to set the text color to a color that is readable on the background color. - Color foregroundColor = color.Value.IdealTextColor(); - textMesh.color = foregroundColor; - iconMesh.color = foregroundColor; - expandIcon.GetComponent().color = foregroundColor; - } + if (nodeGameObject != null) + { + Color nodeColor = nodeGameObject.GetComponent().material.color; + item.transform.Find("Background").GetComponent().color = nodeColor; + + // We also need to set the text color to a color that is readable on the background color. + Color foregroundColor = nodeColor.IdealTextColor(); + textMesh.color = foregroundColor; + iconMesh.color = foregroundColor; + expandIcon.GetComponent().color = foregroundColor; + } + else + { + // TODO: If there is no color, inherit it from the parent. } // Slashes will cause problems later on, so we replace them with backslashes. // NOTE: This becomes a problem if two nodes A and B exist where node A's name contains slashes and node B @@ -106,17 +114,28 @@ private void AddItem(string id, string parentId, int children, string text, int { expandIcon.SetActive(false); } - else if (item.TryGetComponentOrLog(out Button button)) + else if (item.TryGetComponentOrLog(out EventTrigger eventTrigger)) { - button.onClick.AddListener(() => + eventTrigger.triggers.Single().callback.AddListener(e => { - if (expandedItems.Contains(id)) + // TODO: In the future, highlighting the node should be one available option in a right-click menu. + if (((PointerEventData)e).button == PointerEventData.InputButton.Right) { - collapseItem(item); + if (nodeGameObject != null) + { + nodeGameObject.AddOrGetComponent().Highlight(duration: 10); + } } else { - expandItem(item); + if (expandedItems.Contains(id)) + { + collapseItem(item); + } + else + { + expandItem(item); + } } }); @@ -158,9 +177,7 @@ private void RemoveNodeChildren(Node node) IEnumerable<(string, Node)> appendEdgeChildren(string edgeType, IEnumerable edges) { return children.Append((cleanId + "#" + edgeType, null)) - .Concat(edges.Select( - x => ($"{cleanId}#{edgeType}#{CleanupID(x.ID)}", null)) - ); + .Concat(edges.Select(x => ($"{cleanId}#{edgeType}#{CleanupID(x.ID)}", null))); } } } @@ -173,7 +190,7 @@ private void RemoveNodeChildren(Node node) /// The initial item whose children will be removed. /// A function that returns the children, along with their ID, of an item. /// The type of the item. - private void RemoveItem(string id, T initial, Func> getChildItems) + private void RemoveItem(string id, T initial, Func> getChildItems) { GameObject item = content.Find(id)?.gameObject; if (item == null) @@ -233,8 +250,8 @@ private void CollapseItem(GameObject item) /// /// The node represented by the item. /// The item to be expanded. - /// The color of the node. - private void ExpandNode(Node node, GameObject item, Color? nodeColor) + /// The game object of the element represented by the node. + private void ExpandNode(Node node, GameObject item, GameObject nodeGameObject) { ExpandItem(item); @@ -260,7 +277,7 @@ void AddEdgeButton(string edgesType, char icon, ICollection edges) // Note that an edge may appear multiple times in the tree view, // hence we make its ID dependent on the node it is connected to, // and whether it is an incoming or outgoing edge (to cover self-loops). - AddItem(id, cleanedId, edges.Count, $"{edgesType} Edges", node.Level + 1, icon, nodeColor, + AddItem(id, cleanedId, edges.Count, $"{edgesType} Edges", node.Level + 1, icon, nodeGameObject, i => { CollapseItem(i); @@ -273,7 +290,8 @@ void AddEdgeButton(string edgesType, char icon, ICollection edges) ExpandItem(i); foreach (Edge edge in edges) { - AddItem($"{id}#{CleanupID(edge.ID)}", id, 0, edge.ToShortString(), node.Level + 2, edgeTypeUnicode, nodeColor, null, null); + // TODO: Pass in edge + AddItem($"{id}#{CleanupID(edge.ID)}", id, 0, edge.ToShortString(), node.Level + 2, edgeTypeUnicode, null, null, null); } }); } From ba3c8f52ac1c5568316dacf8c24ca8dfc42889b4 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Thu, 12 Oct 2023 01:59:11 +0200 Subject: [PATCH 07/25] Display color gradient of edge in TreeView --- .../Resources/Prefabs/UI/TreeViewItem.prefab | 528 ++++++++++-------- .../Prefabs/UI/TreeViewItem.prefab.meta | 2 +- .../SEE/Game/Operator/GraphElementOperator.cs | 5 + .../UI/Window/TreeWindow/DesktopTreeWindow.cs | 61 +- Assets/SEE/Utils/ColorExtensions.cs | 37 +- 5 files changed, 368 insertions(+), 265 deletions(-) diff --git a/Assets/Resources/Prefabs/UI/TreeViewItem.prefab b/Assets/Resources/Prefabs/UI/TreeViewItem.prefab index 4c32132aa8..271655e772 100644 --- a/Assets/Resources/Prefabs/UI/TreeViewItem.prefab +++ b/Assets/Resources/Prefabs/UI/TreeViewItem.prefab @@ -1,6 +1,6 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: ---- !u!1 &191658837907323476 +--- !u!1 &337901394664264623 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -8,50 +8,283 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 390693360110591878} - - component: {fileID: 6393630027895098040} - - component: {fileID: 7107027891242687887} + - component: {fileID: 7493521065603945485} + - component: {fileID: 8481403556691258000} + - component: {fileID: 2780406110822494520} m_Layer: 5 - m_Name: Type Icon + m_Name: Expand Icon + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7493521065603945485 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 337901394664264623} + m_LocalRotation: {x: 0, y: 0, z: -0.7071068, w: 0.7071068} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6107984335030005991} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: -90} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 15, y: 0} + m_SizeDelta: {x: 25, y: -78} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &8481403556691258000 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 337901394664264623} + m_CullTransparentMesh: 1 +--- !u!114 &2780406110822494520 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 337901394664264623} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.6901961, g: 0.6901961, b: 0.6901961, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 4b835e6972939d148aa7acfed964b9a6, type: 3} + m_Type: 0 + m_PreserveAspect: 1 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &2457842372413307603 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3612804172242837094} + - component: {fileID: 1287414283101376713} + - component: {fileID: 368891032765212448} + - component: {fileID: 8528920679602411433} + m_Layer: 5 + m_Name: Background + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3612804172242837094 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2457842372413307603} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7648732560831130052} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1287414283101376713 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2457842372413307603} + m_CullTransparentMesh: 1 +--- !u!114 &368891032765212448 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2457842372413307603} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Texture: {fileID: 0} + m_UVRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 +--- !u!114 &8528920679602411433 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2457842372413307603} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a9d2f9067277ade4e9b47012ea4d9e17, type: 3} + m_Name: + m_EditorClassIdentifier: + _gradientType: 0 + _blendMode: 2 + _modifyVertices: 0 + _offset: 0 + _zoom: 1 + _effectGradient: + serializedVersion: 2 + key0: {r: 0, g: 0.15059185, b: 1, a: 1} + key1: {r: 0, g: 0.0724779, b: 0.5803922, a: 1} + key2: {r: 0, g: 0.0724779, b: 0.5803922, a: 1} + key3: {r: 0, g: 0, b: 0, a: 0} + key4: {r: 0, g: 0, b: 0, a: 0} + key5: {r: 0, g: 0, b: 0, a: 0} + key6: {r: 0, g: 0, b: 0, a: 0} + key7: {r: 0, g: 0, b: 0, a: 0} + ctime0: 0 + ctime1: 65535 + ctime2: 65535 + ctime3: 0 + ctime4: 0 + ctime5: 0 + ctime6: 0 + ctime7: 0 + atime0: 0 + atime1: 65535 + atime2: 65535 + atime3: 0 + atime4: 0 + atime5: 0 + atime6: 0 + atime7: 0 + m_Mode: 2 + m_ColorSpace: 0 + m_NumColorKeys: 2 + m_NumAlphaKeys: 2 +--- !u!1 &3152911572347607451 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6107984335030005991} + m_Layer: 5 + m_Name: Foreground m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &390693360110591878 +--- !u!224 &6107984335030005991 RectTransform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 191658837907323476} + m_GameObject: {fileID: 3152911572347607451} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7493521065603945485} + - {fileID: 3541660964508758112} + - {fileID: 850845478035354944} + m_Father: {fileID: 7648732560831130052} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 100} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &3556668903542347433 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 850845478035354944} + - component: {fileID: 2351212017304989447} + - component: {fileID: 2656358682537043824} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &850845478035354944 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3556668903542347433} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] - m_Father: {fileID: 6956213211243388673} + m_Father: {fileID: 6107984335030005991} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 45, y: 2.5499973} - m_SizeDelta: {x: 25, y: -72.899994} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 10.505001, y: -3} + m_SizeDelta: {x: -121.01, y: -66} m_Pivot: {x: 0.5, y: 0.5} ---- !u!222 &6393630027895098040 +--- !u!222 &2351212017304989447 CanvasRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 191658837907323476} + m_GameObject: {fileID: 3556668903542347433} m_CullTransparentMesh: 1 ---- !u!114 &7107027891242687887 +--- !u!114 &2656358682537043824 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 191658837907323476} + m_GameObject: {fileID: 3556668903542347433} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} @@ -65,11 +298,10 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: \uf1b2 + m_text: This is an item m_isRightToLeft: 0 - m_fontAsset: {fileID: 11400000, guid: 4ebb98a3c87fa521a888029274c92b79, type: 2} - m_sharedMaterial: {fileID: -8620075009897487826, guid: 4ebb98a3c87fa521a888029274c92b79, - type: 2} + m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} + m_sharedMaterial: {fileID: 2100000, guid: 79459efec17a4d00a321bdcc27bbc385, type: 2} m_fontSharedMaterials: [] m_fontMaterial: {fileID: 0} m_fontMaterials: [] @@ -93,8 +325,8 @@ MonoBehaviour: m_faceColor: serializedVersion: 2 rgba: 4294967295 - m_fontSize: 30 - m_fontSizeBase: 30 + m_fontSize: 26 + m_fontSizeBase: 26 m_fontWeight: 400 m_enableAutoSizing: 0 m_fontSizeMin: 18 @@ -135,7 +367,7 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} ---- !u!1 &365169721995589967 +--- !u!1 &3954843106050839986 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -143,50 +375,50 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 4576865718705642150} - - component: {fileID: 1507486190926840033} - - component: {fileID: 1195648864383830166} + - component: {fileID: 3541660964508758112} + - component: {fileID: 7849836590017996126} + - component: {fileID: 6263302416817975913} m_Layer: 5 - m_Name: Text + m_Name: Type Icon m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &4576865718705642150 +--- !u!224 &3541660964508758112 RectTransform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 365169721995589967} - m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_GameObject: {fileID: 3954843106050839986} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] - m_Father: {fileID: 6956213211243388673} + m_Father: {fileID: 6107984335030005991} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 10.505001, y: -3} - m_SizeDelta: {x: -121.01, y: -66} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 45, y: 2.5499973} + m_SizeDelta: {x: 25, y: -72.899994} m_Pivot: {x: 0.5, y: 0.5} ---- !u!222 &1507486190926840033 +--- !u!222 &7849836590017996126 CanvasRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 365169721995589967} + m_GameObject: {fileID: 3954843106050839986} m_CullTransparentMesh: 1 ---- !u!114 &1195648864383830166 +--- !u!114 &6263302416817975913 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 365169721995589967} + m_GameObject: {fileID: 3954843106050839986} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} @@ -200,10 +432,11 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: This is an item + m_text: \uf1b2 m_isRightToLeft: 0 - m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} - m_sharedMaterial: {fileID: 2100000, guid: 79459efec17a4d00a321bdcc27bbc385, type: 2} + m_fontAsset: {fileID: 11400000, guid: 4ebb98a3c87fa521a888029274c92b79, type: 2} + m_sharedMaterial: {fileID: -8620075009897487826, guid: 4ebb98a3c87fa521a888029274c92b79, + type: 2} m_fontSharedMaterials: [] m_fontMaterial: {fileID: 0} m_fontMaterials: [] @@ -227,8 +460,8 @@ MonoBehaviour: m_faceColor: serializedVersion: 2 rgba: 4294967295 - m_fontSize: 26 - m_fontSizeBase: 26 + m_fontSize: 30 + m_fontSizeBase: 30 m_fontWeight: 400 m_enableAutoSizing: 0 m_fontSizeMin: 18 @@ -269,192 +502,7 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} ---- !u!1 &1609622311853266229 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 462118327595233664} - - component: {fileID: 2708726142911790895} - - component: {fileID: 3554488115888097478} - m_Layer: 5 - m_Name: Background - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!224 &462118327595233664 -RectTransform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1609622311853266229} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 6804734348468291106} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0.5} - m_AnchorMax: {x: 1, y: 0.5} - m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 0, y: 50} - m_Pivot: {x: 0.5, y: 0.5} ---- !u!222 &2708726142911790895 -CanvasRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1609622311853266229} - m_CullTransparentMesh: 1 ---- !u!114 &3554488115888097478 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1609622311853266229} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3} - m_Name: - m_EditorClassIdentifier: - m_Material: {fileID: 0} - m_Color: {r: 0.1764706, g: 0.20392157, b: 0.7921569, a: 1} - m_RaycastTarget: 1 - m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} - m_Maskable: 1 - m_OnCullStateChanged: - m_PersistentCalls: - m_Calls: [] - m_Texture: {fileID: 0} - m_UVRect: - serializedVersion: 2 - x: 0 - y: 0 - width: 1 - height: 1 ---- !u!1 &2273156958345819773 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 6956213211243388673} - m_Layer: 5 - m_Name: Foreground - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!224 &6956213211243388673 -RectTransform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2273156958345819773} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: - - {fileID: 6031676558965308395} - - {fileID: 390693360110591878} - - {fileID: 4576865718705642150} - m_Father: {fileID: 6804734348468291106} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0.5} - m_AnchorMax: {x: 1, y: 0.5} - m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 0, y: 100} - m_Pivot: {x: 0.5, y: 0.5} ---- !u!1 &3529401454261779529 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 6031676558965308395} - - component: {fileID: 4753967162191152502} - - component: {fileID: 1359938426298818270} - m_Layer: 5 - m_Name: Expand Icon - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!224 &6031676558965308395 -RectTransform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3529401454261779529} - m_LocalRotation: {x: 0, y: 0, z: -0.7071068, w: 0.7071068} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 6956213211243388673} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: -90} - m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 15, y: 0} - m_SizeDelta: {x: 25, y: -78} - m_Pivot: {x: 0.5, y: 0.5} ---- !u!222 &4753967162191152502 -CanvasRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3529401454261779529} - m_CullTransparentMesh: 1 ---- !u!114 &1359938426298818270 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3529401454261779529} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} - m_Name: - m_EditorClassIdentifier: - m_Material: {fileID: 0} - m_Color: {r: 0.6901961, g: 0.6901961, b: 0.6901961, a: 1} - m_RaycastTarget: 1 - m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} - m_Maskable: 1 - m_OnCullStateChanged: - m_PersistentCalls: - m_Calls: [] - m_Sprite: {fileID: 21300000, guid: 4b835e6972939d148aa7acfed964b9a6, type: 3} - m_Type: 0 - m_PreserveAspect: 1 - m_FillCenter: 1 - m_FillMethod: 4 - m_FillAmount: 1 - m_FillClockwise: 1 - m_FillOrigin: 0 - m_UseSpriteMesh: 0 - m_PixelsPerUnitMultiplier: 1 ---- !u!1 &5088984669082115910 +--- !u!1 &8274573712563105952 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -462,9 +510,9 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 6804734348468291106} - - component: {fileID: 809286346028970807} - - component: {fileID: 8422174230029475794} + - component: {fileID: 7648732560831130052} + - component: {fileID: 4571626204040715473} + - component: {fileID: 4659843242736267316} m_Layer: 5 m_Name: TreeViewItem m_TagString: Untagged @@ -472,20 +520,20 @@ GameObject: m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &6804734348468291106 +--- !u!224 &7648732560831130052 RectTransform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 5088984669082115910} + m_GameObject: {fileID: 8274573712563105952} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: - - {fileID: 462118327595233664} - - {fileID: 6956213211243388673} + - {fileID: 3612804172242837094} + - {fileID: 6107984335030005991} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} @@ -493,13 +541,13 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 50} m_Pivot: {x: 0, y: 1} ---- !u!114 &809286346028970807 +--- !u!114 &4571626204040715473 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 5088984669082115910} + m_GameObject: {fileID: 8274573712563105952} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} @@ -533,17 +581,17 @@ MonoBehaviour: m_SelectedTrigger: Selected m_DisabledTrigger: Disabled m_Interactable: 1 - m_TargetGraphic: {fileID: 3554488115888097478} + m_TargetGraphic: {fileID: 368891032765212448} m_OnClick: m_PersistentCalls: m_Calls: [] ---- !u!114 &8422174230029475794 +--- !u!114 &4659843242736267316 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 5088984669082115910} + m_GameObject: {fileID: 8274573712563105952} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: d0b148fe25e99eb48b9724523833bab1, type: 3} diff --git a/Assets/Resources/Prefabs/UI/TreeViewItem.prefab.meta b/Assets/Resources/Prefabs/UI/TreeViewItem.prefab.meta index 2f02a713f1..529c04efdd 100644 --- a/Assets/Resources/Prefabs/UI/TreeViewItem.prefab.meta +++ b/Assets/Resources/Prefabs/UI/TreeViewItem.prefab.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ff3d61db192296643b633658972fbad1 +guid: 95c92362614cf8553b978ce83c848d22 PrefabImporter: externalObjects: {} userData: diff --git a/Assets/SEE/Game/Operator/GraphElementOperator.cs b/Assets/SEE/Game/Operator/GraphElementOperator.cs index 9b449064cd..e7dba19361 100644 --- a/Assets/SEE/Game/Operator/GraphElementOperator.cs +++ b/Assets/SEE/Game/Operator/GraphElementOperator.cs @@ -58,6 +58,11 @@ public AbstractSEECity City protected override float BaseAnimationDuration => City.BaseAnimationDuration; + /// + /// The current color of the element. + /// + public C TargetColor => Color.TargetValue; + #region Abstract Methods /// diff --git a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs index d886937bba..69e07577e4 100644 --- a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs +++ b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using Michsky.UI.ModernUIPack; using SEE.DataModel.DG; using SEE.Game; using SEE.Game.Operator; using SEE.GO; using SEE.Utils; using UnityEngine; +using UnityEngine.Assertions; using UnityEngine.EventSystems; using UnityEngine.UI; using Color = UnityEngine.Color; @@ -43,6 +45,11 @@ public partial class TreeWindow /// The cleaned up ID. private static string CleanupID(string id) => id?.Replace('/', '\\'); + /// + /// The alpha keys for the gradient of a menu item (fully opaque). + /// + private static readonly GradientAlphaKey[] alphaKeys = { new(1, 0), new(1, 1) }; + /// /// Adds the given to the bottom of the tree window. /// @@ -66,17 +73,19 @@ private void AddNode(Node node) /// The text of the item to be added. /// The level of the item to be added. /// The icon of the item to be added, given as a unicode character. - /// The game object of the element represented by the item. May be null. + /// The game object of the element represented by the item. May be null. /// A function that collapses the item. /// A function that expands the item. private void AddItem(string id, string parentId, int children, string text, int level, - char icon, GameObject nodeGameObject, Action collapseItem, Action expandItem) + char icon, GameObject itemGameObject, Action collapseItem, Action expandItem) { GameObject item = PrefabInstantiator.InstantiatePrefab(treeItemPrefab, content, false); Transform foreground = item.transform.Find("Foreground"); GameObject expandIcon = foreground.Find("Expand Icon").gameObject; TMPro.TextMeshProUGUI textMesh = foreground.Find("Text").gameObject.GetComponent(); TMPro.TextMeshProUGUI iconMesh = foreground.Find("Type Icon").gameObject.GetComponent(); + Color[] gradient = null; + Transform parent = null; textMesh.text = text; iconMesh.text = icon.ToString(); @@ -84,27 +93,42 @@ private void AddItem(string id, string parentId, int children, string text, int if (parentId != null) { // Position the item below its parent. - Transform parent = content.Find(parentId); + parent = content.Find(parentId); item.transform.SetSiblingIndex(parent.GetSiblingIndex() + 1); foreground.localPosition += new Vector3(indentShift * level, 0, 0); // TODO: Include number badge in title. } - if (nodeGameObject != null) + if (itemGameObject != null) { - Color nodeColor = nodeGameObject.GetComponent().material.color; - item.transform.Find("Background").GetComponent().color = nodeColor; - - // We also need to set the text color to a color that is readable on the background color. - Color foregroundColor = nodeColor.IdealTextColor(); - textMesh.color = foregroundColor; - iconMesh.color = foregroundColor; - expandIcon.GetComponent().color = foregroundColor; + if (itemGameObject.CompareTag(Tags.Node)) + { + // We add a slight gradient to make it look nicer. + Color color = itemGameObject.GetComponent().material.color; + gradient = new[] { color, color.Darker(0.3f) }; + } + else if (itemGameObject.CompareTag(Tags.Edge)) + { + (Color start, Color end) = itemGameObject.AddOrGetComponent().TargetColor; + gradient = new[] { start, end }; + } } - else + + if (gradient == null) { - // TODO: If there is no color, inherit it from the parent. + // If there is no color, inherit it from the parent. + Assert.IsTrue(parent != null, "Parent must not be null if color is null."); + gradient = parent.Find("Background").GetComponent().EffectGradient.colorKeys.ToColors().ToArray(); } + + item.transform.Find("Background").GetComponent().EffectGradient.SetKeys(gradient.ToGradientColorKeys().ToArray(), alphaKeys); + + // We also need to set the text color to a color that is readable on the background color. + Color foregroundColor = gradient.Aggregate((x, y) => (x + y)/2).IdealTextColor(); + textMesh.color = foregroundColor; + iconMesh.color = foregroundColor; + expandIcon.GetComponent().color = foregroundColor; + // Slashes will cause problems later on, so we replace them with backslashes. // NOTE: This becomes a problem if two nodes A and B exist where node A's name contains slashes and node B // has an identical name, except for all slashes being replaced by backslashes. @@ -114,6 +138,7 @@ private void AddItem(string id, string parentId, int children, string text, int { expandIcon.SetActive(false); } + // FIXME: There's a bug here which makes scrolling impossible. The EventTrigger is at fault. else if (item.TryGetComponentOrLog(out EventTrigger eventTrigger)) { eventTrigger.triggers.Single().callback.AddListener(e => @@ -121,9 +146,9 @@ private void AddItem(string id, string parentId, int children, string text, int // TODO: In the future, highlighting the node should be one available option in a right-click menu. if (((PointerEventData)e).button == PointerEventData.InputButton.Right) { - if (nodeGameObject != null) + if (itemGameObject != null) { - nodeGameObject.AddOrGetComponent().Highlight(duration: 10); + itemGameObject.AddOrGetComponent().Highlight(duration: 10); } } else @@ -290,8 +315,8 @@ void AddEdgeButton(string edgesType, char icon, ICollection edges) ExpandItem(i); foreach (Edge edge in edges) { - // TODO: Pass in edge - AddItem($"{id}#{CleanupID(edge.ID)}", id, 0, edge.ToShortString(), node.Level + 2, edgeTypeUnicode, null, null, null); + GameObject edgeObject = GraphElementIDMap.Find(edge.ID, mustFindElement: true); + AddItem($"{id}#{CleanupID(edge.ID)}", id, 0, edge.ToShortString(), node.Level + 2, edgeTypeUnicode, edgeObject, null, null); } }); } diff --git a/Assets/SEE/Utils/ColorExtensions.cs b/Assets/SEE/Utils/ColorExtensions.cs index d50ea094f0..dcada9a6f7 100644 --- a/Assets/SEE/Utils/ColorExtensions.cs +++ b/Assets/SEE/Utils/ColorExtensions.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using UnityEngine; namespace SEE.Utils @@ -9,18 +11,20 @@ namespace SEE.Utils public static class ColorExtensions { /// - /// Returns given lightened by 50%. + /// Returns given lightened by the given . /// /// base color to be lightened - /// given lightened by 50% - public static Color Lighter(this Color color) => Color.Lerp(color, Color.white, 0.5f); // To lighten by 50 % + /// lightening factor + /// given lightened by + public static Color Lighter(this Color color, float factor = 0.5f) => Color.Lerp(color, Color.white, factor); /// - /// Returns given darkened by 50%. + /// Returns given darkened by the given . /// /// base color to be darkened - /// given darkened by 50% - public static Color Darker(this Color color) => Color.Lerp(color, Color.black, 0.5f); // To darken by 50 % + /// darkening factor + /// given darkened by + public static Color Darker(this Color color, float factor = 0.5f) => Color.Lerp(color, Color.black, factor); /// /// Returns this color with the given value. @@ -69,5 +73,26 @@ public static Color Invert(this Color color) // Color.RGBToHSV(color, out float H, out float S, out float V); // return Color.HSVToRGB((H + 0.5f) % 1f, S, V); } + + /// + /// Converts the given to linearly distributed gradient color keys. + /// + /// The colors to convert + /// The converted colors as gradient color keys. + public static IEnumerable ToGradientColorKeys(this ICollection colors) + { + return colors.Select((c, i) => new GradientColorKey(c, (float)i / colors.Count)); + } + + /// + /// Converts the given to a list of colors. + /// This simply extracts the colors from the keys. + /// + /// The keys to convert + /// The converted keys as colors. + public static IEnumerable ToColors(this IEnumerable keys) + { + return keys.Select(c => c.color); + } } } From da9619e2245b94ec76e5a479b95ae27f4adb40ee Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Thu, 12 Oct 2023 20:09:08 +0200 Subject: [PATCH 08/25] Add ClickEvent to PointerHelper --- .../UI/Menu/Desktop/SimpleListMenuDesktop.cs | 4 +-- .../SEE/UI/PropertyDialog/ButtonProperty.cs | 8 ++--- .../UI/PropertyDialog/SelectionProperty.cs | 4 +-- .../SEE/UI/PropertyDialog/StringProperty.cs | 4 +-- .../UI/StateIndicator/HideStateIndicator.cs | 16 ++++----- Assets/SEE/Utils/PointerHelper.cs | 34 +++++++++++-------- 6 files changed, 38 insertions(+), 32 deletions(-) diff --git a/Assets/SEE/UI/Menu/Desktop/SimpleListMenuDesktop.cs b/Assets/SEE/UI/Menu/Desktop/SimpleListMenuDesktop.cs index 5302765761..449a2510bc 100644 --- a/Assets/SEE/UI/Menu/Desktop/SimpleListMenuDesktop.cs +++ b/Assets/SEE/UI/Menu/Desktop/SimpleListMenuDesktop.cs @@ -116,8 +116,8 @@ protected virtual void AddButton(T entry) // hover listeners PointerHelper pointerHelper = button.GetComponent(); - pointerHelper.EnterEvent.AddListener(() => MenuTooltip.Show(entry.Description)); - pointerHelper.ExitEvent.AddListener(MenuTooltip.Hide); + pointerHelper.EnterEvent.AddListener(_ => MenuTooltip.Show(entry.Description)); + pointerHelper.ExitEvent.AddListener(_ => MenuTooltip.Hide()); // adds clickEvent listener or show that button is disabled if (entry.Enabled) diff --git a/Assets/SEE/UI/PropertyDialog/ButtonProperty.cs b/Assets/SEE/UI/PropertyDialog/ButtonProperty.cs index 7ee370d288..cf8d0d4755 100644 --- a/Assets/SEE/UI/PropertyDialog/ButtonProperty.cs +++ b/Assets/SEE/UI/PropertyDialog/ButtonProperty.cs @@ -110,8 +110,8 @@ void SetUpButton() buttonManager.buttonText = Name; buttonManager.clickEvent.AddListener(Clicked); - pointerHelper.EnterEvent.AddListener(() => tooltip.Show(Description)); - pointerHelper.ExitEvent.AddListener(() => tooltip.Hide()); + pointerHelper.EnterEvent.AddListener(_ => tooltip.Show(Description)); + pointerHelper.ExitEvent.AddListener(_ => tooltip.Hide()); } void Clicked() @@ -130,8 +130,8 @@ private void SetupTooltip() if (button.TryGetComponentOrLog(out PointerHelper pointerHelper)) { // Register listeners on entry and exit events, respectively - pointerHelper.EnterEvent.AddListener(() => tooltip.Show(Description)); - pointerHelper.ExitEvent.AddListener(tooltip.Hide); + pointerHelper.EnterEvent.AddListener(_ => tooltip.Show(Description)); + pointerHelper.ExitEvent.AddListener(_ => tooltip.Hide()); } } diff --git a/Assets/SEE/UI/PropertyDialog/SelectionProperty.cs b/Assets/SEE/UI/PropertyDialog/SelectionProperty.cs index bc09166be9..df21f1e67f 100644 --- a/Assets/SEE/UI/PropertyDialog/SelectionProperty.cs +++ b/Assets/SEE/UI/PropertyDialog/SelectionProperty.cs @@ -88,8 +88,8 @@ void SetupTooltip(GameObject field) pointerHelper = field.AddComponent(); } // Register listeners on entry and exit events, respectively - pointerHelper.EnterEvent.AddListener(() => tooltip.Show(Description)); - pointerHelper.ExitEvent.AddListener(tooltip.Hide); + pointerHelper.EnterEvent.AddListener(_ => tooltip.Show(Description)); + pointerHelper.ExitEvent.AddListener(_ => tooltip.Hide()); // FIXME scrolling doesn't work while hovering above the field, because // the Modern UI Pack uses an Event Trigger (see Utils/PointerHelper for an explanation.) // It is unclear how to resolve this without either abstaining from using the Modern UI Pack diff --git a/Assets/SEE/UI/PropertyDialog/StringProperty.cs b/Assets/SEE/UI/PropertyDialog/StringProperty.cs index a7581bbd75..b8983715e8 100644 --- a/Assets/SEE/UI/PropertyDialog/StringProperty.cs +++ b/Assets/SEE/UI/PropertyDialog/StringProperty.cs @@ -69,8 +69,8 @@ void SetupTooltip(GameObject field) if (field.TryGetComponentOrLog(out PointerHelper pointerHelper)) { // Register listeners on entry and exit events, respectively - pointerHelper.EnterEvent.AddListener(() => tooltip.Show(Description)); - pointerHelper.ExitEvent.AddListener(tooltip.Hide); + pointerHelper.EnterEvent.AddListener(_ => tooltip.Show(Description)); + pointerHelper.ExitEvent.AddListener(_ => tooltip.Hide()); // FIXME scrolling doesn't work while hovering above the field, because // the Modern UI Pack uses an Event Trigger (see Utils/PointerHelper for an explanation.) // It is unclear how to resolve this without either abstaining from using the Modern UI Pack diff --git a/Assets/SEE/UI/StateIndicator/HideStateIndicator.cs b/Assets/SEE/UI/StateIndicator/HideStateIndicator.cs index 0605c5c865..8758a462c1 100644 --- a/Assets/SEE/UI/StateIndicator/HideStateIndicator.cs +++ b/Assets/SEE/UI/StateIndicator/HideStateIndicator.cs @@ -146,8 +146,8 @@ private void SetUpButtonDone(GameObject indicator) buttonManager.buttonText = ButtonNameDone; buttonManager.clickEvent.AddListener(() => SetSelectionType(SelectionTypeDone)); - pointerHelper.EnterEvent.AddListener(() => tooltipDone.Show(DescriptionDone)); - pointerHelper.ExitEvent.AddListener(() => tooltipDone.Hide()); + pointerHelper.EnterEvent.AddListener(_ => tooltipDone.Show(DescriptionDone)); + pointerHelper.ExitEvent.AddListener(_ => tooltipDone.Hide()); } /// @@ -181,8 +181,8 @@ private void SetUpButtonBack(GameObject indicator) buttonManager.buttonText = ButtonNameBack; buttonManager.clickEvent.AddListener(() => SetSelectionType(SelectionTypeBack)); - pointerHelper.EnterEvent.AddListener(() => tooltipBack.Show(DescriptionBack)); - pointerHelper.ExitEvent.AddListener(() => tooltipBack.Hide()); + pointerHelper.EnterEvent.AddListener(_ => tooltipBack.Show(DescriptionBack)); + pointerHelper.ExitEvent.AddListener(_ => tooltipBack.Hide()); } /// @@ -196,8 +196,8 @@ private void SetupTooltipDone(GameObject indicator) if (button.TryGetComponentOrLog(out PointerHelper pointerHelper)) { // Register listeners on entry and exit events, respectively - pointerHelper.EnterEvent.AddListener(() => tooltipDone.Show(DescriptionDone)); - pointerHelper.ExitEvent.AddListener(tooltipDone.Hide); + pointerHelper.EnterEvent.AddListener(_ => tooltipDone.Show(DescriptionDone)); + pointerHelper.ExitEvent.AddListener(_ => tooltipDone.Hide()); } } @@ -212,8 +212,8 @@ private void SetupTooltipBack(GameObject indicator) if (button.TryGetComponentOrLog(out PointerHelper pointerHelper)) { // Register listeners on entry and exit events, respectively - pointerHelper.EnterEvent.AddListener(() => tooltipBack.Show(DescriptionBack)); - pointerHelper.ExitEvent.AddListener(tooltipBack.Hide); + pointerHelper.EnterEvent.AddListener(_ => tooltipBack.Show(DescriptionBack)); + pointerHelper.ExitEvent.AddListener(_ => tooltipBack.Hide()); } } diff --git a/Assets/SEE/Utils/PointerHelper.cs b/Assets/SEE/Utils/PointerHelper.cs index 1f97b9c491..90d850f9a5 100644 --- a/Assets/SEE/Utils/PointerHelper.cs +++ b/Assets/SEE/Utils/PointerHelper.cs @@ -11,36 +11,42 @@ namespace SEE.Utils /// While an could also be used, this leads to bugs with scrolling in certain cases. /// See http://answers.unity.com/answers/1024120/view.html (https://archive.vn/ZScfd) for more information. /// - public class PointerHelper: MonoBehaviour, IPointerExitHandler, IPointerEnterHandler + public class PointerHelper: MonoBehaviour, IPointerExitHandler, IPointerEnterHandler, IPointerClickHandler { /// /// This event will be triggered whenever the mouse pointer leaves the area over the GameObject /// this component is attached to. /// - public readonly UnityEvent ExitEvent = new UnityEvent(); + public readonly UnityEvent ExitEvent = new(); /// /// This event will be triggered whenever the mouse pointer enters the area over the GameObject /// this component is attached to. /// - public readonly UnityEvent EnterEvent = new UnityEvent(); + public readonly UnityEvent EnterEvent = new(); + + /// + /// This event will be triggered whenever the mouse pointer clicks the GameObject + /// this component is attached to. + /// + public readonly UnityEvent ClickEvent = new(); /// /// Invokes the . /// - /// Data about the pointer. Currently unused. - public void OnPointerExit(PointerEventData eventData) - { - ExitEvent.Invoke(); - } + /// Data about the pointer. + public void OnPointerExit(PointerEventData eventData) => ExitEvent.Invoke(eventData); /// /// Invokes the . /// - /// Data about the pointer. Currently unused. - public void OnPointerEnter(PointerEventData eventData) - { - EnterEvent.Invoke(); - } + /// Data about the pointer. + public void OnPointerEnter(PointerEventData eventData) => EnterEvent.Invoke(eventData); + + /// + /// Invokes the . + /// + /// Data about the pointer. + public void OnPointerClick(PointerEventData eventData) => ClickEvent.Invoke(eventData); } -} \ No newline at end of file +} From b005afb077316c62812206ffec4022f2b8809b94 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Thu, 12 Oct 2023 20:28:12 +0200 Subject: [PATCH 09/25] Implement indefinite blinking --- Assets/SEE/Game/Operator/NodeOperator.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Assets/SEE/Game/Operator/NodeOperator.cs b/Assets/SEE/Game/Operator/NodeOperator.cs index d11f029620..75cb579fd7 100644 --- a/Assets/SEE/Game/Operator/NodeOperator.cs +++ b/Assets/SEE/Game/Operator/NodeOperator.cs @@ -115,10 +115,12 @@ public Node Node #region Public API /// - /// Displays a marker above the node and makes it blink times - /// within the given . + /// Displays a marker above the node and makes it blink for seconds. /// - /// The amount of time in seconds the node should be highlighted. + /// The amount of time in seconds the node should be highlighted. + /// If this is set to a negative value, the node will be highlighted indefinitely, with a blink rate + /// proportional to the absolute value of . + /// /// An operation callback for the requested animation public IOperationCallback Highlight(float duration) { @@ -130,8 +132,8 @@ public IOperationCallback Highlight(float duration) marker.MarkBorn(gameObject); // The factor of 1.3 causes the node to blink slightly more than once per second, // which seems visually fitting. - int blinkCount = Mathf.RoundToInt(duration * 1.3f); - return Blink(blinkCount: blinkCount, ToDuration(duration)).OnComplete(() => marker.Clear()); + int blinkCount = duration >= 0 ? Mathf.RoundToInt(duration * 1.3f) : -1; + return Blink(blinkCount: blinkCount, ToFactor(Mathf.Abs(duration))).OnComplete(() => marker.Clear()); } /// @@ -263,7 +265,9 @@ public IOperationCallback ScaleTo(Vector3 newLocalScale, float factor = /// /// Makes the node blink times. /// - /// The number of times the node should blink. + /// The number of times the node should blink. + /// If set to -1, the node will blink indefinitely. + /// /// Factor to apply to the /// that controls the blinking duration. /// If set to 0, will execute directly, that is, the blinking is stopped @@ -436,7 +440,7 @@ protected override IEnumerable AsEnumerable(Color color) return new[] { color }; } - protected void OnEnable() + protected override void OnEnable() { base.OnEnable(); Node = GetNode(gameObject); @@ -474,7 +478,7 @@ Tween[] BlinkAction(int count, float duration) return new Tween[] { - material.DOColor(Color.TargetValue.Invert(), duration / (2 * count)).SetEase(Ease.Linear).SetLoops(2 * count, LoopType.Yoyo).Play() + material.DOColor(Color.TargetValue.Invert(), duration / (2 * Mathf.Abs(count))).SetEase(Ease.Linear).SetLoops(2 * count, LoopType.Yoyo).Play() }; } From b54bf2309ea919fa53896a65cf78fa2395fcf10c Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Thu, 12 Oct 2023 22:07:29 +0200 Subject: [PATCH 10/25] Make conflicting operations bidirectional --- Assets/SEE/Game/Operator/AbstractOperator.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Assets/SEE/Game/Operator/AbstractOperator.cs b/Assets/SEE/Game/Operator/AbstractOperator.cs index f7269044cc..af193bb37a 100644 --- a/Assets/SEE/Game/Operator/AbstractOperator.cs +++ b/Assets/SEE/Game/Operator/AbstractOperator.cs @@ -70,6 +70,13 @@ protected interface IOperation /// Whether the operation is currently running. /// bool IsRunning { get; } + + /// + /// A list of all operations that are conflicting with this one. + /// If an operation in this list is running at the time this operation is started, + /// the conflicting operation will be killed. + /// + public IList ConflictingOperations { get; } } /// @@ -97,7 +104,7 @@ protected abstract class Operation : IOperation where C : MulticastDele /// If an operation in this set is running at the time this operation is started, /// the conflicting operation will be killed. /// - protected readonly IList ConflictingOperations; + public IList ConflictingOperations { get; } /// /// The animator that is controlling the current animation. @@ -131,6 +138,8 @@ protected abstract class Operation : IOperation where C : MulticastDele /// /// /// The operations that are conflicting with this one. + /// Note that this operation will also be added to the conflicting operations of the given operations. + /// Hence, this is always a bidirectional relationship. /// protected Operation(Func animateToAction, V targetValue, IEqualityComparer equalityComparer = null, @@ -140,6 +149,10 @@ protected Operation(Func animateToAction, V targetValue, TargetValue = targetValue; EqualityComparer = equalityComparer ?? EqualityComparer.Default; ConflictingOperations = conflictingOperations?.ToList() ?? new List(); + foreach (IOperation conflictingOperation in ConflictingOperations) + { + conflictingOperation.ConflictingOperations.Add(this); + } } /// From 2725ce2bafa36d5b9165b68a5c14696b6d315df2 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Thu, 12 Oct 2023 22:14:10 +0200 Subject: [PATCH 11/25] Extract common behavior into GraphElementOperator This refactors GraphElementOperator to a major extent: It splits it off into a non-generic class, which handles all non-color operations, and one generic part which handles the rest. This makes it possible for callers to access GraphElementOperator without needing to know of what type the underlying element is. Additionally, this allows the following previously node-exclusive operations to be used on edges as well: * Blink() * Highlight() --- Assets/SEE/Game/Operator/EdgeOperator.cs | 18 ++ .../SEE/Game/Operator/GraphElementOperator.cs | 277 ++++++++++++------ Assets/SEE/Game/Operator/NodeOperator.cs | 81 ++--- 3 files changed, 222 insertions(+), 154 deletions(-) diff --git a/Assets/SEE/Game/Operator/EdgeOperator.cs b/Assets/SEE/Game/Operator/EdgeOperator.cs index 8d77068252..a0036379b2 100644 --- a/Assets/SEE/Game/Operator/EdgeOperator.cs +++ b/Assets/SEE/Game/Operator/EdgeOperator.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using DG.Tweening; using SEE.Game.City; using SEE.GO; @@ -214,6 +215,23 @@ Tween[] AnimateToColorAction((Color start, Color end) colors, float d) return new TweenOperation<(Color start, Color end)>(AnimateToColorAction, spline.GradientColors); } + protected override Tween[] BlinkAction(int count, float duration) + { + // If we're interrupting another blinking, we need to make sure the color still has the correct value. + spline.GradientColors = Color.TargetValue; + Color newStart = Color.TargetValue.start.Invert(); + Color newEnd = Color.TargetValue.end.Invert(); + float loopDuration = duration / (2 * Mathf.Abs(count)); + + Tween startTween = DOTween.To(() => spline.GradientColors.start, + c => spline.GradientColors = (c, spline.GradientColors.end), + newStart, loopDuration); + Tween endTween = DOTween.To(() => spline.GradientColors.end, + c => spline.GradientColors = (spline.GradientColors.start, c), + newEnd, loopDuration); + return new[] { startTween, endTween }.Select(x => x.SetEase(Ease.Linear).SetLoops(2 * count, LoopType.Yoyo).Play()).ToArray(); + } + protected override (Color start, Color end) ModifyColor((Color start, Color end) color, Func modifier) { return (modifier(color.start), modifier(color.end)); diff --git a/Assets/SEE/Game/Operator/GraphElementOperator.cs b/Assets/SEE/Game/Operator/GraphElementOperator.cs index e7dba19361..1ed90958f9 100644 --- a/Assets/SEE/Game/Operator/GraphElementOperator.cs +++ b/Assets/SEE/Game/Operator/GraphElementOperator.cs @@ -7,45 +7,44 @@ using SEE.DataModel; using SEE.Game.City; using SEE.GO; +using SEE.UI.Notification; using SEE.Utils; using UnityEngine; using ArgumentException = System.ArgumentException; namespace SEE.Game.Operator { + /// /// A component managing operations done on the graph element (i.e., node or edge) it is attached to. /// Available operations consist of the public methods exported by this class. /// Operations can be animated or executed directly, by setting the duration to 0. + /// Note that this only contains non-color operations. Color operations are handled by + /// the generic class. /// - /// The type of the color of the graph element - public abstract class GraphElementOperator : AbstractOperator, IObserver where C : struct + public abstract class GraphElementOperator : AbstractOperator { /// - /// Operation handling changes to the color of the element. + /// Operation handling the blinking of the element. + /// The parameter specifies the number of blinks. /// - protected TweenOperation Color { get; private set; } + protected TweenOperation blinking; /// /// Operation handling the glow effect around the element. /// - private TweenOperation glow; + protected TweenOperation glow; /// /// Amount of glow that should be animated towards. /// /// Its value must be greater than 0 and not greater than 5. - private const float fullGlow = 2; + protected const float fullGlow = 2; /// /// Whether the glow effect is currently (supposed to be) enabled. /// - private bool glowEnabled; - - /// - /// The highlight effect of the element. - /// - private HighlightEffect highlightEffect; + protected bool glowEnabled; /// /// The city to which the element belongs. @@ -53,11 +52,119 @@ public abstract class GraphElementOperator : AbstractOperator, IObserver City.BaseAnimationDuration; + protected abstract float GetTargetGlow(); + + /// + /// Returns an array of tweens that animate the element to blink times + /// for the given . + /// + /// the number of times the element should blink + /// the duration of the blinking animation + /// an array of tweens that animate the element to blink times + /// for the given + protected abstract Tween[] BlinkAction(int blinkCount, float duration); + + /// + /// Makes the element blink times. + /// + /// The number of times the element should blink. + /// If set to -1, the element will blink indefinitely. + /// + /// Factor to apply to the + /// that controls the blinking duration. + /// If set to 0, will execute directly, that is, the blinking is stopped + /// before control is returned to the caller. + /// + /// An operation callback for the requested animation + public IOperationCallback Blink(int blinkCount, float factor = 1) + { + return blinking.AnimateTo(blinkCount, ToDuration(factor)); + } + + /// + /// Fade in the glow effect on this element. + /// + /// Factor to apply to the + /// that controls the animation duration. + /// If set to 0, will execute directly, that is, the value is set before control is returned to the caller. + /// + /// An operation callback for the requested animation + public virtual IOperationCallback GlowIn(float factor = 1) + { + float targetGlow = GetTargetGlow(); + glowEnabled = true; + return glow.AnimateTo(targetGlow, ToDuration(factor)); + } + + /// + /// Fade out the glow effect on this element. + /// + /// Factor to apply to the + /// that controls the animation duration. + /// If set to 0, will execute directly, that is, the value is set before control is returned to the caller. + /// + /// An operation callback for the requested animation + public IOperationCallback GlowOut(float factor = 1) + { + glowEnabled = false; + return glow.AnimateTo(0, ToDuration(factor)); + } + + /// + /// Displays a marker above the element and makes it blink and glow for seconds. + /// + /// The amount of time in seconds the element should be highlighted. + /// If this is set to a negative value, the element will be highlighted indefinitely, with a blink rate + /// proportional to the absolute value of . + /// + /// An operation callback for the requested animation + public IOperationCallback Highlight(float duration) + { + ShowNotification.Info($"Highlighting '{name}'", + $"The selected element will be blinking and marked by a spear for {duration} seconds.", + log: false); + // Display marker above the element + // FIXME: marker is not displayed above edge. + MarkerFactory marker = new(markerWidth: 0.01f, markerHeight: 1f, 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. + int blinkCount = duration >= 0 ? Mathf.RoundToInt(duration * 1.3f) : -1; + return new AndCombinedOperationCallback(new[] + { + GlowIn(ToFactor(Mathf.Abs(duration/blinkCount))), + Blink(blinkCount: blinkCount, ToFactor(Mathf.Abs(duration))) + }).OnComplete(() => + { + GlowOut(ToFactor(0.5f)); + marker.Clear(); + }); + } + } + + /// + /// A component managing operations done on the graph element (i.e., node or edge) it is attached to. + /// Available operations consist of the public methods exported by this class. + /// Operations can be animated or executed directly, by setting the duration to 0. + /// + /// The type of the color of the graph element + public abstract class GraphElementOperator : GraphElementOperator, IObserver where C : struct + { + /// + /// Operation handling changes to the color of the element. + /// + protected TweenOperation Color { get; private set; } + + /// + /// The highlight effect of the element. + /// + private HighlightEffect highlightEffect; + /// /// The current color of the element. /// @@ -150,35 +257,6 @@ public IOperationCallback FadeTo(float alpha, float factor = 1) }); } - /// - /// Fade in the glow effect on this element. - /// - /// Factor to apply to the - /// that controls the animation duration. - /// If set to 0, will execute directly, that is, the value is set before control is returned to the caller. - /// - /// An operation callback for the requested animation - public IOperationCallback GlowIn(float factor = 1) - { - float targetGlow = GetTargetGlow(fullGlow, AsEnumerable(Color.TargetValue).Max(x => x.a)); - glowEnabled = true; - return glow.AnimateTo(targetGlow, ToDuration(factor)); - } - - /// - /// Fade out the glow effect on this element. - /// - /// Factor to apply to the - /// that controls the animation duration. - /// If set to 0, will execute directly, that is, the value is set before control is returned to the caller. - /// - /// An operation callback for the requested animation - public IOperationCallback GlowOut(float factor = 1) - { - glowEnabled = false; - return glow.AnimateTo(0, ToDuration(factor)); - } - /// /// Flashes an element in its inverted color for a short time based on the given . /// Note that this animation is not controlled by an operation and thus not necessarily synchronized. @@ -200,30 +278,6 @@ public void HitEffect(float factor = 0.5f) #endregion - /// - /// Refreshes the glow effect properties. - /// - /// Needs to be called whenever the material changes. Hierarchy changes are handled automatically. - /// - public async UniTaskVoid RefreshGlow(bool fullRefresh = false) - { - if (highlightEffect != null && glow != null) - { - if (fullRefresh) - { - glow.KillAnimator(); - Destroyer.Destroy(highlightEffect); - await UniTask.WaitForEndOfFrame(); // component is only destroyed by the end of the frame. - highlightEffect = Highlighter.GetHighlightEffect(gameObject); - SetupGlow(); - } - else - { - highlightEffect.Refresh(); - } - } - } - /// /// Sets up the , assuming it has been assigned /// a new instance of . @@ -239,6 +293,9 @@ private void SetupGlow() highlightEffect.outline = 0; + glow = new TweenOperation(AnimateToGlowAction, highlightEffect.glow); + return; + Tween[] AnimateToGlowAction(float endGlow, float duration) => new Tween[] { DOTween.To(() => highlightEffect.glow, g => @@ -250,8 +307,6 @@ private void SetupGlow() highlightEffect.Refresh(); }).Play() }; - - glow = new TweenOperation(AnimateToGlowAction, highlightEffect.glow); } /// @@ -269,6 +324,16 @@ private static float GetTargetGlow(float glowTarget, float alphaTarget) return Mathf.Min(glowTarget / fullGlow, alphaTarget) * fullGlow; } + /// + /// Calculates a value for the operation according to the following formula: + /// min(1, colorAlpha) * fullGlow + /// + /// Glow value which doesn't exceed alpha value + protected override float GetTargetGlow() + { + return GetTargetGlow(fullGlow, AsEnumerable(Color.TargetValue).Max(x => x.a)); + } + /// /// Determines the this belongs to and returns it. /// @@ -293,11 +358,63 @@ static string CodeCityName(GameObject codeCityObject) } } + /// + /// Handles hierarchy changes by refreshing the glow effect. + /// + /// The event that was triggered + public void OnNext(ChangeEvent value) + { + // As stated in the documentation of Highlight Plus, whenever the hierarchy of an object changes, + // we need to call Refresh() on it or it will stop working. + if (value is HierarchyEvent) + { + RefreshGlow().Forget(); + } + } + + /// + /// Refreshes the glow effect properties. + /// + /// Needs to be called whenever the material changes. Hierarchy changes are handled automatically. + /// + public async UniTaskVoid RefreshGlow(bool fullRefresh = false) + { + if (highlightEffect != null && glow != null) + { + if (fullRefresh) + { + glow.KillAnimator(); + Destroyer.Destroy(highlightEffect); + await UniTask.WaitForEndOfFrame(); // component is only destroyed by the end of the frame. + highlightEffect = Highlighter.GetHighlightEffect(gameObject); + SetupGlow(); + } + else + { + highlightEffect.Refresh(); + } + } + } + + public void OnCompleted() + { + // Nothing to be done. + } + + public void OnError(Exception error) + { + throw error; + } + + protected virtual void OnEnable() { City = GetCity(gameObject); Color = InitializeColorOperation(); + blinking = new TweenOperation(BlinkAction, 0, equalityComparer: new AlwaysFalseEqualityComparer(), + conflictingOperations: new[] { Color }); + if (TryGetComponent(out highlightEffect)) { // If the component already exists, we need to rebuild it to be sure it fits our material. @@ -325,29 +442,5 @@ protected virtual void OnDisable() glow.KillAnimator(); glow = null; } - - /// - /// Handles hierarchy changes by refreshing the glow effect. - /// - /// The event that was triggered - public void OnNext(ChangeEvent value) - { - // As stated in the documentation of Highlight Plus, whenever the hierarchy of an object changes, - // we need to call Refresh() on it or it will stop working. - if (value is HierarchyEvent) - { - RefreshGlow().Forget(); - } - } - - public void OnCompleted() - { - // Nothing to be done. - } - - public void OnError(Exception error) - { - throw error; - } } } diff --git a/Assets/SEE/Game/Operator/NodeOperator.cs b/Assets/SEE/Game/Operator/NodeOperator.cs index 75cb579fd7..fe17742ad5 100644 --- a/Assets/SEE/Game/Operator/NodeOperator.cs +++ b/Assets/SEE/Game/Operator/NodeOperator.cs @@ -51,12 +51,6 @@ public partial class NodeOperator : GraphElementOperator /// private TweenOperation labelAlpha; - /// - /// Operation handling the blinking of the node. - /// The parameter specifies the number of blinks. - /// - private TweenOperation blinking; - /// /// Operation handling the starting position of the label's line. /// @@ -100,6 +94,11 @@ public Node Node /// private bool updateEdges; + /// + /// The material of the node. + /// + private Material material; + /// /// The position this node is supposed to be at. /// @@ -114,28 +113,6 @@ public Node Node #region Public API - /// - /// Displays a marker above the node and makes it blink for seconds. - /// - /// The amount of time in seconds the node should be highlighted. - /// If this is set to a negative value, the node will be highlighted indefinitely, with a blink rate - /// proportional to the absolute value of . - /// - /// An operation callback for the requested animation - public IOperationCallback Highlight(float duration) - { - ShowNotification.Info($"Highlighting '{name}'", - $"The selected node will be blinking and marked by a spear for {duration} seconds.", - log: false); - // Display marker above the node - MarkerFactory marker = new(markerWidth: 0.01f, markerHeight: 1f, UnityEngine.Color.red, default, default); - marker.MarkBorn(gameObject); - // The factor of 1.3 causes the node to blink slightly more than once per second, - // which seems visually fitting. - int blinkCount = duration >= 0 ? Mathf.RoundToInt(duration * 1.3f) : -1; - return Blink(blinkCount: blinkCount, ToFactor(Mathf.Abs(duration))).OnComplete(() => marker.Clear()); - } - /// /// Moves the node to the in world space. /// @@ -262,23 +239,6 @@ public IOperationCallback ScaleTo(Vector3 newLocalScale, float factor = return scale.AnimateTo(newLocalScale, duration); } - /// - /// Makes the node blink times. - /// - /// The number of times the node should blink. - /// If set to -1, the node will blink indefinitely. - /// - /// Factor to apply to the - /// that controls the blinking duration. - /// If set to 0, will execute directly, that is, the blinking is stopped - /// before control is returned to the caller. - /// - /// An operation callback for the requested animation - public IOperationCallback Blink(int blinkCount, float factor = 1) - { - return blinking.AnimateTo(blinkCount, ToDuration(factor)); - } - /// /// Shows the label with given value if it is greater than zero. /// Otherwise, hides the label. @@ -417,7 +377,7 @@ private void MorphEdges(IEnumerable edges, float duration) protected override TweenOperation InitializeColorOperation() { - Material material = GetRenderer(gameObject).material; + return new TweenOperation(AnimateToColorAction, material.color); Tween[] AnimateToColorAction(Color color, float d) { @@ -426,8 +386,17 @@ Tween[] AnimateToColorAction(Color color, float d) material.DOColor(color, d).Play() }; } + } - return new TweenOperation(AnimateToColorAction, material.color); + protected override Tween[] BlinkAction(int count, float duration) + { + // If we're interrupting another blinking, we need to make sure the color still has the correct value. + material.color = Color.TargetValue; + + return new Tween[] + { + material.DOColor(Color.TargetValue.Invert(), duration / (2 * Mathf.Abs(count))).SetEase(Ease.Linear).SetLoops(2 * count, LoopType.Yoyo).Play() + }; } protected override Color ModifyColor(Color color, Func modifier) @@ -442,9 +411,11 @@ protected override IEnumerable AsEnumerable(Color color) protected override void OnEnable() { + // Material needs to be assigned before calling base.OnEnable() + // because it is used in InitializeColorOperation(). + material = GetRenderer(gameObject).material; base.OnEnable(); Node = GetNode(gameObject); - Material material = GetRenderer(gameObject).material; Vector3 currentPosition = transform.position; Vector3 currentScale = transform.localScale; Quaternion currentRotation = transform.rotation; @@ -460,9 +431,6 @@ protected override void OnEnable() labelTextPosition = new TweenOperation(AnimateLabelTextPositionAction, DesiredLabelTextPosition); labelStartLinePosition = new TweenOperation(AnimateLabelStartLinePositionAction, DesiredLabelStartLinePosition); labelEndLinePosition = new TweenOperation(AnimateLabelEndLinePositionAction, DesiredLabelEndLinePosition); - - blinking = new TweenOperation(BlinkAction, 0, equalityComparer: new AlwaysFalseEqualityComparer(), - conflictingOperations: new[] { Color }); return; Tween[] AnimateToXAction(float x, float d) => new Tween[] { transform.DOMoveX(x, d).Play() }; @@ -471,17 +439,6 @@ protected override void OnEnable() Tween[] AnimateToRotationAction(Quaternion r, float d) => new Tween[] { transform.DORotateQuaternion(r, d).Play() }; Tween[] AnimateToScaleAction(Vector3 s, float d) => new Tween[] { transform.DOScale(s, d).Play() }; - Tween[] BlinkAction(int count, float duration) - { - // If we're interrupting another blinking, we need to make sure the color still has the correct value. - material.color = Color.TargetValue; - - return new Tween[] - { - material.DOColor(Color.TargetValue.Invert(), duration / (2 * Mathf.Abs(count))).SetEase(Ease.Linear).SetLoops(2 * count, LoopType.Yoyo).Play() - }; - } - static Node GetNode(GameObject gameObject) { // We allow a null value for artificial nodes, but at least a NodeRef must be attached. From 1065c5bd305e21ae632dac28b8c4d299317a3902 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Thu, 12 Oct 2023 22:15:29 +0200 Subject: [PATCH 12/25] Add utility methods to retrieve operators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, these were only available on GraphElements – now they were made available for GameObjects too. --- Assets/SEE/Utils/GameObjectExtensions.cs | 84 +++++++++++++++++++ Assets/SEE/Utils/GameObjectExtensions.cs.meta | 3 + Assets/SEE/Utils/GraphElementExtensions.cs | 18 ++-- 3 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 Assets/SEE/Utils/GameObjectExtensions.cs create mode 100644 Assets/SEE/Utils/GameObjectExtensions.cs.meta diff --git a/Assets/SEE/Utils/GameObjectExtensions.cs b/Assets/SEE/Utils/GameObjectExtensions.cs new file mode 100644 index 0000000000..df46b54883 --- /dev/null +++ b/Assets/SEE/Utils/GameObjectExtensions.cs @@ -0,0 +1,84 @@ +using System; +using SEE.Game; +using SEE.Game.Operator; +using SEE.GO; +using UnityEngine; + +namespace SEE.Utils +{ + /// + /// Contains utility extension methods for game objects. + /// + public static class GameObjectExtensions + { + /// + /// Returns the for this . + /// If no operator exists yet, it will be added. + /// If the game object is not a node, an exception will be thrown. + /// + /// The game object whose operator to retrieve. + /// The responsible for this . + public static NodeOperator NodeOperator(this GameObject gameObject) + { + if (gameObject.CompareTag(Tags.Node)) + { + return gameObject.AddOrGetComponent(); + } + else + { + throw new InvalidOperationException($"Cannot get NodeOperator for game object {gameObject.name} because it is not a node."); + } + } + + /// + /// Returns the for this . + /// If no operator exists yet, it will be added. + /// If the game object is not an edge, an exception will be thrown. + /// + /// The game object whose operator to retrieve. + /// The responsible for this . + public static EdgeOperator EdgeOperator(this GameObject gameObject) + { + if (gameObject.CompareTag(Tags.Edge)) + { + return gameObject.AddOrGetComponent(); + } + else + { + throw new InvalidOperationException($"Cannot get EdgeOperator for game object {gameObject.name} because it is not an edge."); + } + } + + /// + /// Returns the for this . + /// If no operator exists yet, a fitting operator will be added. + /// If the game object is neither a node nor an edge, an exception will be thrown. + /// + /// The game object whose operator to retrieve. + /// The responsible for this . + public static GraphElementOperator Operator(this GameObject gameObject) + { + if (gameObject.TryGetComponent(out GraphElementOperator elementOperator)) + { + return elementOperator; + } + else + { + // We may need to add the appropriate operator first. + if (gameObject.CompareTag(Tags.Node)) + { + return gameObject.AddComponent(); + } + else if (gameObject.CompareTag(Tags.Edge)) + { + return gameObject.AddComponent(); + } + else + { + throw new InvalidOperationException("Cannot get GraphElementOperator for game object " + + $"{gameObject.name} because it is neither a node nor an edge."); + } + } + } + } +} diff --git a/Assets/SEE/Utils/GameObjectExtensions.cs.meta b/Assets/SEE/Utils/GameObjectExtensions.cs.meta new file mode 100644 index 0000000000..8273f2c8d9 --- /dev/null +++ b/Assets/SEE/Utils/GameObjectExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9fda120e3dbf4b4da1fbe2423e0b8c42 +timeCreated: 1697138401 \ No newline at end of file diff --git a/Assets/SEE/Utils/GraphElementExtensions.cs b/Assets/SEE/Utils/GraphElementExtensions.cs index 2250e6bf42..9ebfb8f06e 100644 --- a/Assets/SEE/Utils/GraphElementExtensions.cs +++ b/Assets/SEE/Utils/GraphElementExtensions.cs @@ -19,19 +19,23 @@ public static class GraphElementExtensions /// Whether to throw an exception /// if no corresponding game object was found. /// The responsible for this . - public static NodeOperator Operator(this Node node, bool mustFindGameObject = true) - => node.GameObject(mustFindGameObject).AddOrGetComponent(); + public static NodeOperator Operator(this Node node) => node.GameObject().NodeOperator(); /// /// Returns the for this . /// If no operator exists yet, it will be added. /// /// The edge whose operator to retrieve. - /// Whether to throw an exception - /// if no corresponding game object was found. /// The responsible for this . - public static EdgeOperator Operator(this Edge edge, bool mustFindGameObject = true) - => edge.GameObject(mustFindGameObject).AddOrGetComponent(); + public static EdgeOperator Operator(this Edge edge) => edge.GameObject().EdgeOperator(); + + /// + /// Returns the for this graph . + /// If no operator exists yet, it will be added. + /// + /// The graph element whose operator to retrieve. + /// The responsible for this . + public static GraphElementOperator Operator(this GraphElement element) => element.GameObject().Operator(); /// /// Returns the for this graph . @@ -44,4 +48,4 @@ public static EdgeOperator Operator(this Edge edge, bool mustFindGameObject = tr public static GameObject GameObject(this GraphElement element, bool mustFind = true) => GraphElementIDMap.Find(element.ID, mustFind); } -} \ No newline at end of file +} From 3d3f89fd94f2e1093e2e454ae34233289650121c Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Thu, 12 Oct 2023 22:15:43 +0200 Subject: [PATCH 13/25] Use new-style new in InteractableObject --- .../Interactables/InteractableObject.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Assets/SEE/Controls/Interactables/InteractableObject.cs b/Assets/SEE/Controls/Interactables/InteractableObject.cs index 14e1fac504..ce9c0e1fc3 100644 --- a/Assets/SEE/Controls/Interactables/InteractableObject.cs +++ b/Assets/SEE/Controls/Interactables/InteractableObject.cs @@ -54,36 +54,34 @@ public sealed class InteractableObject : MonoBehaviour /// /// The interactable objects. /// - private static readonly Dictionary idToInteractableObjectDict = - new Dictionary(); + private static readonly Dictionary idToInteractableObjectDict = new(); /// /// The hovered objects. /// - public static readonly HashSet HoveredObjects = new HashSet(); + public static readonly HashSet HoveredObjects = new(); /// /// The object, that is currently hovered by this player. There is always only ever /// one object hovered by this player with the flag /// set. /// - public static InteractableObject HoveredObjectWithWorldFlag { get; private set; } = null; + public static InteractableObject HoveredObjectWithWorldFlag { get; private set; } /// /// The selected objects. /// - public static readonly HashSet SelectedObjects = new HashSet(); + public static readonly HashSet SelectedObjects = new(); /// /// The grabbed objects. /// - public static readonly HashSet GrabbedObjects = new HashSet(); + public static readonly HashSet GrabbedObjects = new(); /// /// The selected objects per graph. /// - private static readonly Dictionary> graphToSelectedIOs = - new Dictionary>(); + private static readonly Dictionary> graphToSelectedIOs = new(); /// /// The graph element, this interactable object is attached to. @@ -517,7 +515,7 @@ public void SetSelect(bool select, bool isInitiator) private static void UnselectAllInternal(bool isInitiator, bool invokeReplaceEvent) { List replaced = SelectedObjects.ToList(); - List by = new List(); + List by = new(); if (replaced.Count > 0 || by.Count > 0) { // Note: This is no endless loop because SetSelect will remove this @@ -543,7 +541,7 @@ private static void UnselectAllInternal(bool isInitiator, bool invokeReplaceEven public static void ReplaceSelection(InteractableObject interactableObject, bool isInitiator) { List replaced = SelectedObjects.ToList(); - List by = new List(1); + List by = new(1); if (interactableObject) { by.Add(interactableObject); From fd314f992b7206547c4febac7e626a21067cdfd5 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Thu, 12 Oct 2023 22:16:22 +0200 Subject: [PATCH 14/25] TreeView: Highlight edges on click This also fixes a bug in which scrolling no longer worked. --- .../Resources/Prefabs/UI/TreeViewItem.prefab | 11 +++------ .../UI/Window/TreeWindow/DesktopTreeWindow.cs | 24 +++++++++---------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/Assets/Resources/Prefabs/UI/TreeViewItem.prefab b/Assets/Resources/Prefabs/UI/TreeViewItem.prefab index 271655e772..a31ed02041 100644 --- a/Assets/Resources/Prefabs/UI/TreeViewItem.prefab +++ b/Assets/Resources/Prefabs/UI/TreeViewItem.prefab @@ -512,7 +512,7 @@ GameObject: m_Component: - component: {fileID: 7648732560831130052} - component: {fileID: 4571626204040715473} - - component: {fileID: 4659843242736267316} + - component: {fileID: 3602532499758286776} m_Layer: 5 m_Name: TreeViewItem m_TagString: Untagged @@ -585,7 +585,7 @@ MonoBehaviour: m_OnClick: m_PersistentCalls: m_Calls: [] ---- !u!114 &4659843242736267316 +--- !u!114 &3602532499758286776 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -594,11 +594,6 @@ MonoBehaviour: m_GameObject: {fileID: 8274573712563105952} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: d0b148fe25e99eb48b9724523833bab1, type: 3} + m_Script: {fileID: 11500000, guid: 52dd8aaa3b5d4058ac1b1e9242d35f57, type: 3} m_Name: m_EditorClassIdentifier: - m_Delegates: - - eventID: 4 - callback: - m_PersistentCalls: - m_Calls: [] diff --git a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs index 69e07577e4..1effcd378b 100644 --- a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs +++ b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs @@ -138,20 +138,26 @@ private void AddItem(string id, string parentId, int children, string text, int { expandIcon.SetActive(false); } - // FIXME: There's a bug here which makes scrolling impossible. The EventTrigger is at fault. - else if (item.TryGetComponentOrLog(out EventTrigger eventTrigger)) + else if (expandedItems.Contains(id)) { - eventTrigger.triggers.Single().callback.AddListener(e => + // If this item was previously expanded, we need to expand it again. + expandItem(item); + } + + if (item.TryGetComponentOrLog(out PointerHelper pointerHelper)) + { + // Right click highlights the node, left/middle click expands/collapses it. + pointerHelper.ClickEvent.AddListener(e => { // TODO: In the future, highlighting the node should be one available option in a right-click menu. - if (((PointerEventData)e).button == PointerEventData.InputButton.Right) + if (e.button == PointerEventData.InputButton.Right) { if (itemGameObject != null) { - itemGameObject.AddOrGetComponent().Highlight(duration: 10); + itemGameObject.Operator().Highlight(duration: 10); } } - else + else if (children > 0) { if (expandedItems.Contains(id)) { @@ -163,12 +169,6 @@ private void AddItem(string id, string parentId, int children, string text, int } } }); - - // If this item was previously expanded, we need to expand it again. - if (expandedItems.Contains(id)) - { - expandItem(item); - } } } From a960b431df019db2bfb3b000342657eb2bbeb14c Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Oct 2023 13:30:52 +0200 Subject: [PATCH 15/25] Replace operator retrievals with new extension methods This way, if we decide to change how we would like to retrieve operators in the future, we can simply change the extension method rather than having to change every instance in the code. --- Assets/SEE/Controls/Actions/MoveAction.cs | 4 +- .../Actions/NodeManipulationAction.cs | 2 +- .../SEE/Controls/Actions/ShowEdgesAction.cs | 2 +- Assets/SEE/Controls/Actions/ShowLabel.cs | 2 +- Assets/SEE/Controls/Actions/ShuffleAction.cs | 2 +- Assets/SEE/Controls/Actions/ZoomAction.cs | 2 +- .../DG}/GraphElementExtensions.cs | 10 +-- .../DG}/GraphElementExtensions.cs.meta | 0 .../SEE/Game/City/ReflexionVisualization.cs | 12 +-- Assets/SEE/Game/CityRendering/NodeRenderer.cs | 2 +- .../Evolution/EvolutionRendererAddEdges.cs | 2 +- .../Evolution/EvolutionRendererAddNodes.cs | 7 +- .../Game/Evolution/EvolutionRendererAdjust.cs | 6 +- .../Game/Evolution/EvolutionRendererMove.cs | 10 +-- .../Game/Evolution/EvolutionRendererRemove.cs | 8 +- Assets/SEE/Game/Operator/NodeOperator.cs | 3 +- .../Game/SceneManipulation/GameNodeMover.cs | 9 ++- .../SEE/GameObjects/GameObjectExtensions.cs | 77 ++++++++++++++++++- Assets/SEE/GameObjects/Menu/SearchMenu.cs | 2 +- Assets/SEE/Net/Actions/RotateNodeNetAction.cs | 5 +- Assets/SEE/Net/Actions/ScaleNodeNetAction.cs | 5 +- Assets/SEE/Net/Actions/ShuffleNetAction.cs | 3 +- Assets/SEE/Net/Actions/ZoomNetAction.cs | 3 +- .../UI/Window/TreeWindow/DesktopTreeWindow.cs | 3 +- Assets/SEE/Utils/GameObjectExtensions.cs | 72 +---------------- 25 files changed, 128 insertions(+), 125 deletions(-) rename Assets/SEE/{Utils => DataModel/DG}/GraphElementExtensions.cs (86%) rename Assets/SEE/{Utils => DataModel/DG}/GraphElementExtensions.cs.meta (100%) diff --git a/Assets/SEE/Controls/Actions/MoveAction.cs b/Assets/SEE/Controls/Actions/MoveAction.cs index 7f2d3e2468..48e8a89db6 100644 --- a/Assets/SEE/Controls/Actions/MoveAction.cs +++ b/Assets/SEE/Controls/Actions/MoveAction.cs @@ -485,7 +485,7 @@ private void RestoreOriginalAppearance() { UnmarkAsTarget(); MoveToOrigin(); - GrabbedGameObject.AddOrGetComponent().ScaleTo(originalLocalScale); + GrabbedGameObject.NodeOperator().ScaleTo(originalLocalScale); new ScaleNodeNetAction(GrabbedGameObject.name, originalLocalScale).Execute(); } } @@ -622,4 +622,4 @@ public override void Redo() grabbedObject.Redo(); } } -} \ No newline at end of file +} diff --git a/Assets/SEE/Controls/Actions/NodeManipulationAction.cs b/Assets/SEE/Controls/Actions/NodeManipulationAction.cs index 7aca0893db..e0754dc2e3 100644 --- a/Assets/SEE/Controls/Actions/NodeManipulationAction.cs +++ b/Assets/SEE/Controls/Actions/NodeManipulationAction.cs @@ -250,7 +250,7 @@ protected abstract class Memento /// game object to be transformed public Memento(GameObject gameObject) { - NodeOperator = gameObject.AddOrGetComponent(); + NodeOperator = gameObject.NodeOperator(); } /// diff --git a/Assets/SEE/Controls/Actions/ShowEdgesAction.cs b/Assets/SEE/Controls/Actions/ShowEdgesAction.cs index 8c57c0de3a..39b3612b68 100644 --- a/Assets/SEE/Controls/Actions/ShowEdgesAction.cs +++ b/Assets/SEE/Controls/Actions/ShowEdgesAction.cs @@ -40,7 +40,7 @@ private static void ShowOrHide(GameObject hoveredGraphElement) { if (gameEdge.TryGetEdge(out Edge edge)) { - EdgeOperator edgeOperator = gameEdge.AddOrGetComponent(); + EdgeOperator edgeOperator = gameEdge.EdgeOperator(); if (edge.HasToggle(Edge.IsHiddenToggle)) { edge.UnsetToggle(Edge.IsHiddenToggle); diff --git a/Assets/SEE/Controls/Actions/ShowLabel.cs b/Assets/SEE/Controls/Actions/ShowLabel.cs index 0aba08e383..b26ce9e483 100644 --- a/Assets/SEE/Controls/Actions/ShowLabel.cs +++ b/Assets/SEE/Controls/Actions/ShowLabel.cs @@ -160,7 +160,7 @@ private void On() { if (nodeOperator == null) { - nodeOperator = gameObject.AddOrGetComponent(); + nodeOperator = gameObject.NodeOperator(); } if (nodeOperator.Node != null) diff --git a/Assets/SEE/Controls/Actions/ShuffleAction.cs b/Assets/SEE/Controls/Actions/ShuffleAction.cs index a21978ad81..4b30192c70 100644 --- a/Assets/SEE/Controls/Actions/ShuffleAction.cs +++ b/Assets/SEE/Controls/Actions/ShuffleAction.cs @@ -114,7 +114,7 @@ private void Update() // to its original position. originalPosition = cityRootNode.position; // The node operator that is going to be used to move the city-root node - nodeOperator = cityRootNode.gameObject.AddOrGetComponent(); + nodeOperator = cityRootNode.gameObject.NodeOperator(); // Where exactly have we hit the plane containing cideRootNode (if at all)? if (Raycasting.RaycastPlane(new UnityEngine.Plane(Vector3.up, cityRootNode.position), out Vector3 cityPlaneHitPoint)) diff --git a/Assets/SEE/Controls/Actions/ZoomAction.cs b/Assets/SEE/Controls/Actions/ZoomAction.cs index c73eec4f2f..71c53d39f3 100644 --- a/Assets/SEE/Controls/Actions/ZoomAction.cs +++ b/Assets/SEE/Controls/Actions/ZoomAction.cs @@ -242,7 +242,7 @@ protected ZoomState GetZoomStateCopy(Transform transform) protected void UpdateZoomState(Transform transform, ZoomState zoomState) { rootTransformToZoomStates[transform] = zoomState; - rootTransformToOperator[transform] = transform.gameObject.AddOrGetComponent(); + rootTransformToOperator[transform] = transform.gameObject.NodeOperator(); } /// diff --git a/Assets/SEE/Utils/GraphElementExtensions.cs b/Assets/SEE/DataModel/DG/GraphElementExtensions.cs similarity index 86% rename from Assets/SEE/Utils/GraphElementExtensions.cs rename to Assets/SEE/DataModel/DG/GraphElementExtensions.cs index 9ebfb8f06e..339122dad9 100644 --- a/Assets/SEE/Utils/GraphElementExtensions.cs +++ b/Assets/SEE/DataModel/DG/GraphElementExtensions.cs @@ -16,10 +16,8 @@ public static class GraphElementExtensions /// If no operator exists yet, it will be added. /// /// The node whose operator to retrieve. - /// Whether to throw an exception - /// if no corresponding game object was found. /// The responsible for this . - public static NodeOperator Operator(this Node node) => node.GameObject().NodeOperator(); + public static NodeOperator Operator(this Node node) => node.GameObject(true).NodeOperator(); /// /// Returns the for this . @@ -27,7 +25,7 @@ public static class GraphElementExtensions /// /// The edge whose operator to retrieve. /// The responsible for this . - public static EdgeOperator Operator(this Edge edge) => edge.GameObject().EdgeOperator(); + public static EdgeOperator Operator(this Edge edge, bool mustFind = true) => edge.GameObject(mustFind).EdgeOperator(); /// /// Returns the for this graph . @@ -35,7 +33,7 @@ public static class GraphElementExtensions /// /// The graph element whose operator to retrieve. /// The responsible for this . - public static GraphElementOperator Operator(this GraphElement element) => element.GameObject().Operator(); + public static GraphElementOperator Operator(this GraphElement element) => element.GameObject(true).Operator(); /// /// Returns the for this graph . @@ -45,7 +43,7 @@ public static class GraphElementExtensions /// The graph element whose to retrieve. /// Whether to throw an exception if no corresponding game object was found. /// The for this graph . - public static GameObject GameObject(this GraphElement element, bool mustFind = true) + public static GameObject GameObject(this GraphElement element, bool mustFind = false) => GraphElementIDMap.Find(element.ID, mustFind); } } diff --git a/Assets/SEE/Utils/GraphElementExtensions.cs.meta b/Assets/SEE/DataModel/DG/GraphElementExtensions.cs.meta similarity index 100% rename from Assets/SEE/Utils/GraphElementExtensions.cs.meta rename to Assets/SEE/DataModel/DG/GraphElementExtensions.cs.meta diff --git a/Assets/SEE/Game/City/ReflexionVisualization.cs b/Assets/SEE/Game/City/ReflexionVisualization.cs index a7c518cd9f..215685bb35 100644 --- a/Assets/SEE/Game/City/ReflexionVisualization.cs +++ b/Assets/SEE/Game/City/ReflexionVisualization.cs @@ -98,7 +98,7 @@ public void InitializeEdges() if (edge.HasToggle(Edge.IsHiddenToggle)) { // We will instantly hide this edge. It should not show up yet. - edgeObject.AddOrGetComponent().Hide(animationKind, 0f); + edgeObject.EdgeOperator().Hide(animationKind, 0f); } } else @@ -149,10 +149,10 @@ private void ShowAllDivergences(bool show) && edge.IsInImplementation() && edge.State() == State.Divergent) { - GameObject gameEdge = GraphElementIDMap.Find(edge.ID); + GameObject gameEdge = edge.GameObject(); if (gameEdge != null) { - EdgeOperator edgeOperator = gameEdge.AddOrGetComponent(); + EdgeOperator edgeOperator = gameEdge.EdgeOperator(); edgeOperator.ShowOrHide(allDivergencesAreShown, city.EdgeLayoutSettings.AnimationKind); } } @@ -291,7 +291,7 @@ private async UniTaskVoid HandleEdgeChange(EdgeChange edgeChange) edgeChange.Edge.UnsetToggle(Edge.IsHiddenToggle); } - GameObject edge = GraphElementIDMap.Find(edgeChange.Edge.ID); + GameObject edge = edgeChange.Edge.GameObject(); if (edge == null) { @@ -300,13 +300,13 @@ private async UniTaskVoid HandleEdgeChange(EdgeChange edgeChange) // TODO: In the future, the GraphRenderer should be an observer to the Graph, // so that these cases are handled properly. await UniTask.WaitForEndOfFrame(); - edge = GraphElementIDMap.Find(edgeChange.Edge.ID); + edge = edgeChange.Edge.GameObject(); } if (edge != null) { (Color start, Color end) newColors = GetEdgeGradient(edgeChange.Edge); - EdgeOperator edgeOperator = edge.AddOrGetComponent(); + EdgeOperator edgeOperator = edge.EdgeOperator(); edgeOperator.ShowOrHide(!edgeChange.Edge.HasToggle(Edge.IsHiddenToggle), city.EdgeLayoutSettings.AnimationKind); edgeOperator.ChangeColorsTo((newColors.start, newColors.end), useAlpha: false); diff --git a/Assets/SEE/Game/CityRendering/NodeRenderer.cs b/Assets/SEE/Game/CityRendering/NodeRenderer.cs index 84b7828f73..04418a96bc 100644 --- a/Assets/SEE/Game/CityRendering/NodeRenderer.cs +++ b/Assets/SEE/Game/CityRendering/NodeRenderer.cs @@ -413,7 +413,7 @@ internal void LoadLayout(ICollection gameNodes, float groundLevel) if (node != null) { NodeTransform nodeTransform = item.Value; - NodeOperator nodeOperator = node.AddOrGetComponent(); + NodeOperator nodeOperator = node.NodeOperator(); // nodeTransform.position.y relates to the ground of the node; // the node operator's y co-ordinate is meant to be the center nodeTransform.Position.y += nodeTransform.Scale.y / 2; diff --git a/Assets/SEE/Game/Evolution/EvolutionRendererAddEdges.cs b/Assets/SEE/Game/Evolution/EvolutionRendererAddEdges.cs index 161b4d4937..e100966cf1 100644 --- a/Assets/SEE/Game/Evolution/EvolutionRendererAddEdges.cs +++ b/Assets/SEE/Game/Evolution/EvolutionRendererAddEdges.cs @@ -32,7 +32,7 @@ private void Phase5AddNewEdges() { spline.Spline = nextCity.EdgeLayout[edge.ID].Spline; } - edgeObject.AddOrGetComponent() + edgeObject.EdgeOperator() .Show(animationKind, AnimationLagFactor) .OnComplete(animationWatchDog.Finished); } diff --git a/Assets/SEE/Game/Evolution/EvolutionRendererAddNodes.cs b/Assets/SEE/Game/Evolution/EvolutionRendererAddNodes.cs index 004aad7362..15044b3717 100644 --- a/Assets/SEE/Game/Evolution/EvolutionRendererAddNodes.cs +++ b/Assets/SEE/Game/Evolution/EvolutionRendererAddNodes.cs @@ -2,6 +2,7 @@ using SEE.Game.Operator; using SEE.GO; using SEE.Layout; +using SEE.Utils; using Sirenix.Utilities; using UnityEngine; using UnityEngine.Assertions; @@ -62,9 +63,9 @@ void Add(GameObject gameNode, ILayoutNode layoutNode) // in a code city though; otherwise the NodeOperator would not work. gameNode.transform.SetParent(gameObject.transform); - gameNode.AddOrGetComponent() - .MoveTo(layoutNode.CenterPosition, AnimationLagFactor, updateEdges: false) - .OnComplete(animationWatchDog.Finished); + gameNode.NodeOperator() + .MoveTo(layoutNode.CenterPosition, AnimationLagFactor, updateEdges: false) + .OnComplete(animationWatchDog.Finished); } } } diff --git a/Assets/SEE/Game/Evolution/EvolutionRendererAdjust.cs b/Assets/SEE/Game/Evolution/EvolutionRendererAdjust.cs index 8cb414eb93..73ced3e21c 100644 --- a/Assets/SEE/Game/Evolution/EvolutionRendererAdjust.cs +++ b/Assets/SEE/Game/Evolution/EvolutionRendererAdjust.cs @@ -72,9 +72,9 @@ void ScaleTo(GameObject gameNode, ILayoutNode layoutNode) layoutNode.LocalScale : gameNode.transform.parent.InverseTransformVector(layoutNode.LocalScale); - gameNode.AddOrGetComponent() - .ScaleTo(localScale, AnimationLagFactor, updateEdges: false) - .OnComplete(() => OnScalingFinished(gameNode)); + gameNode.NodeOperator() + .ScaleTo(localScale, AnimationLagFactor, updateEdges: false) + .OnComplete(() => OnScalingFinished(gameNode)); } } diff --git a/Assets/SEE/Game/Evolution/EvolutionRendererMove.cs b/Assets/SEE/Game/Evolution/EvolutionRendererMove.cs index 7f8fa200fb..9768d9e698 100644 --- a/Assets/SEE/Game/Evolution/EvolutionRendererMove.cs +++ b/Assets/SEE/Game/Evolution/EvolutionRendererMove.cs @@ -84,8 +84,8 @@ void SetUpEdgeAnimation(ISet edges) if (next.EdgeLayout.TryGetValue(edge.ID, out ILayoutEdge newLayoutEdge)) { objectManager.GetEdge(edge, out GameObject gameEdge); - gameEdge.AddOrGetComponent().MorphTo(newLayoutEdge.Spline, AnimationLagFactor) - .OnComplete(animationWatchDog.Finished); + gameEdge.EdgeOperator().MorphTo(newLayoutEdge.Spline, AnimationLagFactor) + .OnComplete(animationWatchDog.Finished); } else { @@ -125,9 +125,9 @@ private void RenderExistingNode(Node graphNode) void MoveTo(GameObject gameNode, ILayoutNode layoutNode) { // currentGameNode is shifted to its new position through the animator. - gameNode.AddOrGetComponent() - .MoveTo(layoutNode.CenterPosition, AnimationLagFactor, updateEdges: false) - .OnComplete(animationWatchDog.Finished); + gameNode.NodeOperator() + .MoveTo(layoutNode.CenterPosition, AnimationLagFactor, updateEdges: false) + .OnComplete(animationWatchDog.Finished); } } } diff --git a/Assets/SEE/Game/Evolution/EvolutionRendererRemove.cs b/Assets/SEE/Game/Evolution/EvolutionRendererRemove.cs index a4ffaf42ac..df5173b118 100644 --- a/Assets/SEE/Game/Evolution/EvolutionRendererRemove.cs +++ b/Assets/SEE/Game/Evolution/EvolutionRendererRemove.cs @@ -94,15 +94,15 @@ void MoveTo(GameObject gameObject, Vector3 newPosition) { if (gameObject.IsNode()) { - gameObject.AddOrGetComponent() + gameObject.NodeOperator() .MoveTo(newPosition, AnimationLagFactor, updateEdges: false) .OnComplete(() => OnRemoveFinishedAnimation(gameObject)); } else if (gameObject.IsEdge()) { - gameObject.AddOrGetComponent() - .FadeTo(0, AnimationLagFactor) - .OnComplete(() => OnRemoveFinishedAnimation(gameObject)); + gameObject.EdgeOperator() + .FadeTo(0, AnimationLagFactor) + .OnComplete(() => OnRemoveFinishedAnimation(gameObject)); } else { diff --git a/Assets/SEE/Game/Operator/NodeOperator.cs b/Assets/SEE/Game/Operator/NodeOperator.cs index fe17742ad5..7c974cab2f 100644 --- a/Assets/SEE/Game/Operator/NodeOperator.cs +++ b/Assets/SEE/Game/Operator/NodeOperator.cs @@ -370,8 +370,7 @@ private void MorphEdges(IEnumerable edges, float duration) // Let each game edge morph to the splines according to the layout. foreach (GameObject gameEdge in gameEdges) { - EdgeOperator edgeOperator = gameEdge.AddOrGetComponent(); - edgeOperator.MorphTo(layoutEdges[gameEdge.name].Spline, ToFactor(duration)); + gameEdge.EdgeOperator().MorphTo(layoutEdges[gameEdge.name].Spline, ToFactor(duration)); } } diff --git a/Assets/SEE/Game/SceneManipulation/GameNodeMover.cs b/Assets/SEE/Game/SceneManipulation/GameNodeMover.cs index 5b5e41c11e..20ca29a3d9 100644 --- a/Assets/SEE/Game/SceneManipulation/GameNodeMover.cs +++ b/Assets/SEE/Game/SceneManipulation/GameNodeMover.cs @@ -1,5 +1,6 @@ using SEE.Game.Operator; using SEE.GO; +using SEE.Utils; using UnityEngine; using UnityEngine.Assertions; @@ -71,7 +72,7 @@ public static void PutOn(Transform child, GameObject parent, { // This assignment must take place before we set the parent of child to null // because a newly created node operator attempts to derive its code city. - NodeOperator nodeOperator = child.gameObject.AddOrGetComponent(); + NodeOperator nodeOperator = child.gameObject.NodeOperator(); // Release child from its current parent so that local position and scale // and world-space position and scale are the same, respectively. @@ -169,7 +170,7 @@ private static void PutOn2(Transform child, GameObject parent, bool scaleDown = targetPosition = child.position; } targetPosition.y = parent.GetRoof() + childExtent.y + topPadding; - NodeOperator nodeOperator = child.gameObject.AddOrGetComponent(); + NodeOperator nodeOperator = child.gameObject.NodeOperator(); nodeOperator.MoveTo(targetPosition, 0); // From now on, we assume that child's center is contained in the @@ -260,7 +261,7 @@ public static void PutOnAndFit(Transform child, GameObject newParent, { // The gameObject may have already been scaled down, hence, // we need to restore its original scale. - child.gameObject.AddOrGetComponent().ScaleTo(originalLocalScale, 0); + child.gameObject.NodeOperator().ScaleTo(originalLocalScale, 0); } PutOn(child, newParent, scaleDown: scaleDown); } @@ -274,7 +275,7 @@ public static void PutOnAndFit(Transform child, GameObject newParent, /// the factor by which the animation duration is multiplied internal static void MoveTo(GameObject gameObject, Vector3 targetPosition, float factor) { - gameObject.AddOrGetComponent().MoveTo(targetPosition, factor); + gameObject.NodeOperator().MoveTo(targetPosition, factor); } } } diff --git a/Assets/SEE/GameObjects/GameObjectExtensions.cs b/Assets/SEE/GameObjects/GameObjectExtensions.cs index f3dfc4cc36..ad55f5c9dc 100644 --- a/Assets/SEE/GameObjects/GameObjectExtensions.cs +++ b/Assets/SEE/GameObjects/GameObjectExtensions.cs @@ -324,7 +324,7 @@ public static void SetVisibility(this GameObject gameObject, bool show, bool inc /// object whose scale should be set /// the new scale in world space /// if true and is a graph node, - /// a will be used to animate the scaling; otherwise the + /// a will be used to animate the scaling; otherwise the /// scale of is set immediately without any animation public static void SetAbsoluteScale(this GameObject gameObject, Vector3 worldScale, bool animate = true) { @@ -332,7 +332,7 @@ public static void SetAbsoluteScale(this GameObject gameObject, Vector3 worldSca gameObject.transform.parent = null; if (animate && gameObject.HasNodeRef()) { - NodeOperator @operator = gameObject.AddOrGetComponent(); + NodeOperator @operator = gameObject.NodeOperator(); @operator.ScaleTo(worldScale, 0f); } else @@ -592,6 +592,7 @@ public static bool HasNodeRef(this GameObject gameObject) /// component attached to it public static bool IsNode(this GameObject gameObject) { + // TODO: Does comparing the tag work too? It would be a lot more efficient. return gameObject.TryGetComponent(out NodeRef _); } @@ -878,5 +879,75 @@ public static void SetChildActive(this GameObject gameObject, string childName, Debug.LogError($"Game object '{gameObject.FullName()}' does not have child with name '{childName}'.\n"); } } + + /// + /// Returns the for this . + /// If no operator exists yet, it will be added. + /// If the game object is not a node, an exception will be thrown. + /// + /// The game object whose operator to retrieve. + /// The responsible for this . + public static NodeOperator NodeOperator(this GameObject gameObject) + { + if (gameObject.CompareTag(Tags.Node)) + { + return gameObject.AddOrGetComponent(); + } + else + { + throw new InvalidOperationException($"Cannot get NodeOperator for game object {gameObject.name} because it is not a node."); + } + } + + /// + /// Returns the for this . + /// If no operator exists yet, it will be added. + /// If the game object is not an edge, an exception will be thrown. + /// + /// The game object whose operator to retrieve. + /// The responsible for this . + public static EdgeOperator EdgeOperator(this GameObject gameObject) + { + if (gameObject.CompareTag(Tags.Edge)) + { + return gameObject.AddOrGetComponent(); + } + else + { + throw new InvalidOperationException($"Cannot get EdgeOperator for game object {gameObject.name} because it is not an edge."); + } + } + + /// + /// Returns the for this . + /// If no operator exists yet, a fitting operator will be added. + /// If the game object is neither a node nor an edge, an exception will be thrown. + /// + /// The game object whose operator to retrieve. + /// The responsible for this . + public static GraphElementOperator Operator(this GameObject gameObject) + { + if (gameObject.TryGetComponent(out GraphElementOperator elementOperator)) + { + return elementOperator; + } + else + { + // We may need to add the appropriate operator first. + if (gameObject.IsNode()) + { + return gameObject.AddComponent(); + } + else if (gameObject.IsEdge()) + { + return gameObject.AddComponent(); + } + else + { + throw new InvalidOperationException("Cannot get GraphElementOperator for game object " + + $"{gameObject.name} because it is neither a node nor an edge."); + } + } + } } -} \ No newline at end of file +} diff --git a/Assets/SEE/GameObjects/Menu/SearchMenu.cs b/Assets/SEE/GameObjects/Menu/SearchMenu.cs index dbeae257b1..36342ec304 100644 --- a/Assets/SEE/GameObjects/Menu/SearchMenu.cs +++ b/Assets/SEE/GameObjects/Menu/SearchMenu.cs @@ -156,7 +156,7 @@ void MenuEntryAction(GameObject chosen) /// The name of the node which shall be highlighted. private static void HighlightNode(GameObject result) { - NodeOperator nodeOperator = result.AddOrGetComponent(); + NodeOperator nodeOperator = result.NodeOperator(); nodeOperator.Highlight(blinkSeconds); } diff --git a/Assets/SEE/Net/Actions/RotateNodeNetAction.cs b/Assets/SEE/Net/Actions/RotateNodeNetAction.cs index fb56cfcda1..2a72e33a8a 100644 --- a/Assets/SEE/Net/Actions/RotateNodeNetAction.cs +++ b/Assets/SEE/Net/Actions/RotateNodeNetAction.cs @@ -1,5 +1,6 @@ using SEE.Game.Operator; using SEE.GO; +using SEE.Utils; using UnityEngine; namespace SEE.Net.Actions @@ -40,7 +41,7 @@ protected override void ExecuteOnClient() if (!IsRequester()) { GameObject gameObject = Find(GameObjectID); - NodeOperator nodeOperator = gameObject.AddOrGetComponent(); + NodeOperator nodeOperator = gameObject.NodeOperator(); nodeOperator.RotateTo(Rotation, 0); } } @@ -50,4 +51,4 @@ protected override void ExecuteOnServer() // Intentionally left blank. } } -} \ No newline at end of file +} diff --git a/Assets/SEE/Net/Actions/ScaleNodeNetAction.cs b/Assets/SEE/Net/Actions/ScaleNodeNetAction.cs index 446ccc04ca..7bf406d041 100644 --- a/Assets/SEE/Net/Actions/ScaleNodeNetAction.cs +++ b/Assets/SEE/Net/Actions/ScaleNodeNetAction.cs @@ -1,5 +1,6 @@ using SEE.Game.Operator; using SEE.GO; +using SEE.Utils; using UnityEngine; namespace SEE.Net.Actions @@ -53,8 +54,8 @@ protected override void ExecuteOnClient() { if (!IsRequester()) { - Find(GameObjectID).AddOrGetComponent().ScaleTo(LocalScale, AnimationFactor); + Find(GameObjectID).NodeOperator().ScaleTo(LocalScale, AnimationFactor); } } } -} \ No newline at end of file +} diff --git a/Assets/SEE/Net/Actions/ShuffleNetAction.cs b/Assets/SEE/Net/Actions/ShuffleNetAction.cs index 8778e6a0b4..6df674a4c1 100644 --- a/Assets/SEE/Net/Actions/ShuffleNetAction.cs +++ b/Assets/SEE/Net/Actions/ShuffleNetAction.cs @@ -1,6 +1,7 @@ using SEE.Game; using SEE.Game.Operator; using SEE.GO; +using SEE.Utils; using UnityEngine; namespace SEE.Net.Actions @@ -41,7 +42,7 @@ protected override void ExecuteOnClient() GameObject gameObject = GraphElementIDMap.Find(GameObjectID); if (gameObject != null) { - gameObject.AddOrGetComponent().MoveTo(Position, 0); + gameObject.NodeOperator().MoveTo(Position, 0); } else { diff --git a/Assets/SEE/Net/Actions/ZoomNetAction.cs b/Assets/SEE/Net/Actions/ZoomNetAction.cs index 74b1b87e10..3718b8547a 100644 --- a/Assets/SEE/Net/Actions/ZoomNetAction.cs +++ b/Assets/SEE/Net/Actions/ZoomNetAction.cs @@ -2,6 +2,7 @@ using SEE.Game; using SEE.Game.Operator; using SEE.GO; +using SEE.Utils; using UnityEngine; namespace SEE.Net.Actions @@ -49,7 +50,7 @@ protected override void ExecuteOnClient() GameObject gameObject = GraphElementIDMap.Find(GameObjectID); if (gameObject != null) { - NodeOperator nodeOperator = gameObject.AddOrGetComponent(); + NodeOperator nodeOperator = gameObject.NodeOperator(); nodeOperator.MoveTo(Position, ZoomAction.AnimationFactor); nodeOperator.ScaleTo(LocalScale, ZoomAction.AnimationFactor); } diff --git a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs index 1effcd378b..e40eee6151 100644 --- a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs +++ b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs @@ -96,7 +96,6 @@ private void AddItem(string id, string parentId, int children, string text, int parent = content.Find(parentId); item.transform.SetSiblingIndex(parent.GetSiblingIndex() + 1); foreground.localPosition += new Vector3(indentShift * level, 0, 0); - // TODO: Include number badge in title. } if (itemGameObject != null) @@ -109,7 +108,7 @@ private void AddItem(string id, string parentId, int children, string text, int } else if (itemGameObject.CompareTag(Tags.Edge)) { - (Color start, Color end) = itemGameObject.AddOrGetComponent().TargetColor; + (Color start, Color end) = itemGameObject.EdgeOperator().TargetColor; gradient = new[] { start, end }; } } diff --git a/Assets/SEE/Utils/GameObjectExtensions.cs b/Assets/SEE/Utils/GameObjectExtensions.cs index df46b54883..17b121d381 100644 --- a/Assets/SEE/Utils/GameObjectExtensions.cs +++ b/Assets/SEE/Utils/GameObjectExtensions.cs @@ -10,75 +10,5 @@ namespace SEE.Utils /// Contains utility extension methods for game objects. /// public static class GameObjectExtensions - { - /// - /// Returns the for this . - /// If no operator exists yet, it will be added. - /// If the game object is not a node, an exception will be thrown. - /// - /// The game object whose operator to retrieve. - /// The responsible for this . - public static NodeOperator NodeOperator(this GameObject gameObject) - { - if (gameObject.CompareTag(Tags.Node)) - { - return gameObject.AddOrGetComponent(); - } - else - { - throw new InvalidOperationException($"Cannot get NodeOperator for game object {gameObject.name} because it is not a node."); - } - } - - /// - /// Returns the for this . - /// If no operator exists yet, it will be added. - /// If the game object is not an edge, an exception will be thrown. - /// - /// The game object whose operator to retrieve. - /// The responsible for this . - public static EdgeOperator EdgeOperator(this GameObject gameObject) - { - if (gameObject.CompareTag(Tags.Edge)) - { - return gameObject.AddOrGetComponent(); - } - else - { - throw new InvalidOperationException($"Cannot get EdgeOperator for game object {gameObject.name} because it is not an edge."); - } - } - - /// - /// Returns the for this . - /// If no operator exists yet, a fitting operator will be added. - /// If the game object is neither a node nor an edge, an exception will be thrown. - /// - /// The game object whose operator to retrieve. - /// The responsible for this . - public static GraphElementOperator Operator(this GameObject gameObject) - { - if (gameObject.TryGetComponent(out GraphElementOperator elementOperator)) - { - return elementOperator; - } - else - { - // We may need to add the appropriate operator first. - if (gameObject.CompareTag(Tags.Node)) - { - return gameObject.AddComponent(); - } - else if (gameObject.CompareTag(Tags.Edge)) - { - return gameObject.AddComponent(); - } - else - { - throw new InvalidOperationException("Cannot get GraphElementOperator for game object " - + $"{gameObject.name} because it is neither a node nor an edge."); - } - } - } - } + { } } From ce41319a35579bc9f2059e7e6cb949ecedd41806 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Oct 2023 13:55:51 +0200 Subject: [PATCH 16/25] Make MustGetComponent return component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, the component was stored in an out variable. The new approach has the advantage that calls can be chained on the return value – returning the component in an out variable mainly makes sense when the method itself has a different return value, which was not the case here. --- .../SEE/Controls/Actions/ZoomActionDesktop.cs | 2 +- Assets/SEE/Game/Operator/EdgeOperator.cs | 4 ++-- .../SEE/GameObjects/GameObjectExtensions.cs | 23 ++++++++----------- Assets/SEE/UI/ConfigMenu/ConfigMenu.cs | 10 ++++---- Assets/SEE/UI/ConfigMenu/ConfigMenuFactory.cs | 2 +- .../SEE/UI/ConfigMenu/DynamicUIBehaviour.cs | 4 ++-- Assets/SEE/UI/Notification/Notification.cs | 4 ++-- 7 files changed, 22 insertions(+), 27 deletions(-) diff --git a/Assets/SEE/Controls/Actions/ZoomActionDesktop.cs b/Assets/SEE/Controls/Actions/ZoomActionDesktop.cs index ce80af6450..d087d05160 100644 --- a/Assets/SEE/Controls/Actions/ZoomActionDesktop.cs +++ b/Assets/SEE/Controls/Actions/ZoomActionDesktop.cs @@ -67,7 +67,7 @@ private void Update() // request of SEEInput.ZoomInto(). if (zoomInto) { - rootTransform.parent.gameObject.MustGetComponent(out CityCursor cursor); + CityCursor cursor = rootTransform.parent.gameObject.MustGetComponent(); if (cursor.E.HasFocus()) { float optimalTargetZoomFactor = clippingPlane.MinLengthXZ / (cursor.E.ComputeDiameterXZ() / zoomState.CurrentZoomFactor); diff --git a/Assets/SEE/Game/Operator/EdgeOperator.cs b/Assets/SEE/Game/Operator/EdgeOperator.cs index a0036379b2..92384a8d75 100644 --- a/Assets/SEE/Game/Operator/EdgeOperator.cs +++ b/Assets/SEE/Game/Operator/EdgeOperator.cs @@ -155,7 +155,7 @@ protected override void OnEnable() { // Assigned so that the expensive getter isn't called everytime. GameObject go = gameObject; - go.MustGetComponent(out spline); + spline = go.MustGetComponent(); base.OnEnable(); morphism = new MorphismOperation(AnimateToMorphismAction, spline.Spline, null); @@ -173,7 +173,7 @@ SplineMorphism AnimateToMorphismAction((BSpline targetSpline, GameObject tempora } else { - go.MustGetComponent(out SEESpline sourceSpline); + SEESpline sourceSpline = go.MustGetComponent(); animator.CreateTween(sourceSpline.Spline, s.targetSpline, d) .OnComplete(() => { diff --git a/Assets/SEE/GameObjects/GameObjectExtensions.cs b/Assets/SEE/GameObjects/GameObjectExtensions.cs index ad55f5c9dc..61bcec92e3 100644 --- a/Assets/SEE/GameObjects/GameObjectExtensions.cs +++ b/Assets/SEE/GameObjects/GameObjectExtensions.cs @@ -239,12 +239,7 @@ public static void SetColor(this GameObject gameObject, Color color) /// public static Color GetColor(this GameObject gameObject) { - if (gameObject.TryGetComponent(out Renderer renderer)) - { - return renderer.sharedMaterial.color; - } - - throw new InvalidOperationException($"GameObject {gameObject.name} has no renderer component."); + return gameObject.MustGetComponent().sharedMaterial.color; } /// @@ -474,11 +469,11 @@ public static Vector3 WorldSpaceScale(this GameObject gameObject) public static bool IsInArea(this GameObject block, GameObject parentBlock, float outerEdgeMargin) { // FIXME: Support node types other than cubes - block.MustGetComponent(out Collider collider); + Collider collider = block.MustGetComponent(); Vector3 blockCenter = collider.bounds.center; // We only care about the XZ-plane. Setting z to zero here makes it consistent with the bounds setup below. Bounds blockBounds = new(new Vector3(blockCenter.x, blockCenter.z, 0), collider.bounds.extents); - parentBlock.MustGetComponent(out Collider parentCollider); + Collider parentCollider = parentBlock.MustGetComponent(); Bounds parentBlockBounds = parentCollider.bounds; Vector2 topRight = parentBlockBounds.max.XZ(); @@ -552,21 +547,21 @@ public static T AddOrGetComponent(this GameObject gameObject) where T: Compon /// /// Tries to get the component of the given type of this . - /// If the component was found, it will be stored in . - /// If it wasn't found, will be null and - /// will be thrown. + /// If the component was found, it will be returned. + /// If it wasn't found, will be thrown. /// /// The game object the component should be gotten from. Must not be null. - /// The variable in which to save the component. /// The type of the component. /// thrown if has no /// component of type - public static void MustGetComponent(this GameObject gameObject, out T component) + public static T MustGetComponent(this GameObject gameObject) { - if (!gameObject.TryGetComponent(out component)) + if (!gameObject.TryGetComponent(out T component)) { throw new InvalidOperationException($"Couldn't find component '{typeof(T).GetNiceName()}' on game object '{gameObject.name}'"); } + + return component; } /// diff --git a/Assets/SEE/UI/ConfigMenu/ConfigMenu.cs b/Assets/SEE/UI/ConfigMenu/ConfigMenu.cs index 12811a3c16..76404b49f1 100644 --- a/Assets/SEE/UI/ConfigMenu/ConfigMenu.cs +++ b/Assets/SEE/UI/ConfigMenu/ConfigMenu.cs @@ -125,7 +125,7 @@ private void Awake() colorPickerControl.gameObject.SetActive(false); // Reset (hide) the color picker on page changes. - tabButtons.MustGetComponent(out TabGroup tabGroupController); + TabGroup tabGroupController = tabButtons.MustGetComponent(); tabGroupController.SubscribeToUpdates(colorPickerControl.Reset); } @@ -168,7 +168,7 @@ private void SetupCity(EditableInstance instanceToEdit) GameObject instanceGameObject = GameObject.Find(instanceToEdit.GameObjectName); if (instanceGameObject != null) { - instanceGameObject.MustGetComponent(out city); + city = instanceGameObject.MustGetComponent(); } else { @@ -219,7 +219,7 @@ private void SetupActions() private void CreateActionButton(string buttonText, UnityAction onClick) { GameObject deleteGraphButtonGo = Instantiate(actionButtonPrefab, actions.transform, false); - deleteGraphButtonGo.MustGetComponent(out ButtonManagerBasic deleteGraphButton); + ButtonManagerBasic deleteGraphButton = deleteGraphButtonGo.MustGetComponent(); deleteGraphButton.buttonText = buttonText; deleteGraphButton.clickEvent.AddListener(onClick); } @@ -586,7 +586,7 @@ private void SetupMiscellaneousPage() private GameObject CreateAndInsertPage(string headline) { GameObject page = Instantiate(pagePrefab, tabOutlet.transform, false); - page.MustGetComponent(out PageController pageController); + PageController pageController = page.MustGetComponent(); pageController.HeadlineText = headline; return page; } @@ -596,7 +596,7 @@ private void CreateAndInsertTabButton(string label, { GameObject tabButton = Instantiate(tabButtonPrefab, tabButtons.transform, false); tabButton.name = $"{label}Button"; - tabButton.MustGetComponent(out TabButton button); + TabButton button = tabButton.MustGetComponent(); button.ButtonText = label; if (initialState == TabButtonState.InitialActive) { diff --git a/Assets/SEE/UI/ConfigMenu/ConfigMenuFactory.cs b/Assets/SEE/UI/ConfigMenu/ConfigMenuFactory.cs index 6bc18c3d88..151214e244 100644 --- a/Assets/SEE/UI/ConfigMenu/ConfigMenuFactory.cs +++ b/Assets/SEE/UI/ConfigMenu/ConfigMenuFactory.cs @@ -82,7 +82,7 @@ private void BuildConfigMenu(EditableInstance instanceToEdit, bool turnMenuOn) GameObject configMenuGo = Instantiate(configMenuPrefab); configMenuGo.name = configMenuPrefab.name; configMenuGo.transform.SetSiblingIndex(0); - configMenuGo.MustGetComponent(out configMenu); + configMenu = configMenuGo.MustGetComponent(); configMenu.CurrentlyEditing = instanceToEdit; configMenu.OnInstanceChangeRequest.AddListener(ReplaceMenu); if (turnMenuOn) diff --git a/Assets/SEE/UI/ConfigMenu/DynamicUIBehaviour.cs b/Assets/SEE/UI/ConfigMenu/DynamicUIBehaviour.cs index 1398a82eb2..27740ed52a 100644 --- a/Assets/SEE/UI/ConfigMenu/DynamicUIBehaviour.cs +++ b/Assets/SEE/UI/ConfigMenu/DynamicUIBehaviour.cs @@ -70,7 +70,7 @@ protected void MustGetChild(string path, out GameObject target) protected void MustGetComponentInChild(string pathToChild, out T component) { MustGetChild(pathToChild, out GameObject child); - child.MustGetComponent(out component); + component = child.MustGetComponent(); } /// @@ -80,7 +80,7 @@ protected void MustGetComponentInChild(string pathToChild, out T component) /// The type of the component to search for. protected void MustGetComponent(out T component) { - gameObject.MustGetComponent(out component); + component = gameObject.MustGetComponent(); } /// diff --git a/Assets/SEE/UI/Notification/Notification.cs b/Assets/SEE/UI/Notification/Notification.cs index a480712f66..89b3d2b809 100644 --- a/Assets/SEE/UI/Notification/Notification.cs +++ b/Assets/SEE/UI/Notification/Notification.cs @@ -181,7 +181,7 @@ public IOperationCallback MoveDown(float y) protected override void StartDesktop() { ManagedNotification = PrefabInstantiator.InstantiatePrefab(notificationPrefab, Canvas.transform, false); - ManagedNotification.MustGetComponent(out notificationOperator); + notificationOperator = ManagedNotification.MustGetComponent(); // Setup anchoring RectTransform rectTransform = (RectTransform)ManagedNotification.transform; @@ -239,4 +239,4 @@ protected override void UpdateDesktop() } } } -} \ No newline at end of file +} From ba31b4663984845c16a8c0fc4517dabf07d99c72 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Oct 2023 14:09:21 +0200 Subject: [PATCH 17/25] Infer node/edge status by tag presence --- .../SEE/GameObjects/GameObjectExtensions.cs | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/Assets/SEE/GameObjects/GameObjectExtensions.cs b/Assets/SEE/GameObjects/GameObjectExtensions.cs index 61bcec92e3..1b497af121 100644 --- a/Assets/SEE/GameObjects/GameObjectExtensions.cs +++ b/Assets/SEE/GameObjects/GameObjectExtensions.cs @@ -578,17 +578,13 @@ public static bool HasNodeRef(this GameObject gameObject) } /// - /// Returns true if has a - /// component attached to it. Unlike , the - /// node reference may be null. + /// Returns true if is tagged by . /// - /// the game object whose NodeRef is checked - /// true if has a - /// component attached to it + /// the game object to check + /// true if is tagged by public static bool IsNode(this GameObject gameObject) { - // TODO: Does comparing the tag work too? It would be a lot more efficient. - return gameObject.TryGetComponent(out NodeRef _); + return gameObject.CompareTag(Tags.Node); } /// @@ -673,16 +669,13 @@ public static bool HasEdgeRef(this GameObject gameObject) } /// - /// Returns true if has an - /// component attached to it. Unlike the - /// value of this edge reference may be null + /// Returns true if is tagged by . /// - /// the game object whose EdgeRef is checked - /// true if has an - /// component attached to it + /// the game object to check + /// true if is tagged by public static bool IsEdge(this GameObject gameObject) { - return gameObject.TryGetComponent(out EdgeRef _); + return gameObject.CompareTag(Tags.Edge); } /// From 7ee74500c21f7a1a155385b5edf177d708526dbd Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Oct 2023 14:22:03 +0200 Subject: [PATCH 18/25] Animate TreeView expand/collapse --- .../UI/Window/TreeWindow/DesktopTreeWindow.cs | 115 ++++++++++-------- Assets/SEE/Utils/ColorExtensions.cs | 2 +- 2 files changed, 67 insertions(+), 50 deletions(-) diff --git a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs index e40eee6151..6249f05774 100644 --- a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs +++ b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using DG.Tweening; using Michsky.UI.ModernUIPack; using SEE.DataModel.DG; using SEE.Game; -using SEE.Game.Operator; using SEE.GO; using SEE.Utils; using UnityEngine; @@ -98,35 +98,7 @@ private void AddItem(string id, string parentId, int children, string text, int foreground.localPosition += new Vector3(indentShift * level, 0, 0); } - if (itemGameObject != null) - { - if (itemGameObject.CompareTag(Tags.Node)) - { - // We add a slight gradient to make it look nicer. - Color color = itemGameObject.GetComponent().material.color; - gradient = new[] { color, color.Darker(0.3f) }; - } - else if (itemGameObject.CompareTag(Tags.Edge)) - { - (Color start, Color end) = itemGameObject.EdgeOperator().TargetColor; - gradient = new[] { start, end }; - } - } - - if (gradient == null) - { - // If there is no color, inherit it from the parent. - Assert.IsTrue(parent != null, "Parent must not be null if color is null."); - gradient = parent.Find("Background").GetComponent().EffectGradient.colorKeys.ToColors().ToArray(); - } - - item.transform.Find("Background").GetComponent().EffectGradient.SetKeys(gradient.ToGradientColorKeys().ToArray(), alphaKeys); - - // We also need to set the text color to a color that is readable on the background color. - Color foregroundColor = gradient.Aggregate((x, y) => (x + y)/2).IdealTextColor(); - textMesh.color = foregroundColor; - iconMesh.color = foregroundColor; - expandIcon.GetComponent().color = foregroundColor; + ColorItem(); // Slashes will cause problems later on, so we replace them with backslashes. // NOTE: This becomes a problem if two nodes A and B exist where node A's name contains slashes and node B @@ -143,31 +115,77 @@ private void AddItem(string id, string parentId, int children, string text, int expandItem(item); } - if (item.TryGetComponentOrLog(out PointerHelper pointerHelper)) + RegisterClickHandler(); + AnimateIn(); + return; + + void ColorItem() { - // Right click highlights the node, left/middle click expands/collapses it. - pointerHelper.ClickEvent.AddListener(e => + if (itemGameObject != null) { - // TODO: In the future, highlighting the node should be one available option in a right-click menu. - if (e.button == PointerEventData.InputButton.Right) + if (itemGameObject.IsNode()) { - if (itemGameObject != null) - { - itemGameObject.Operator().Highlight(duration: 10); - } + // We add a slight gradient to make it look nicer. + Color color = itemGameObject.GetComponent().material.color; + gradient = new[] { color, color.Darker(0.3f) }; } - else if (children > 0) + else if (itemGameObject.IsEdge()) + { + (Color start, Color end) = itemGameObject.EdgeOperator().TargetColor; + gradient = new[] { start, end }; + } + } + + if (gradient == null) + { + // If there is no color, inherit it from the parent. + Assert.IsTrue(parent != null, "Parent must not be null if color is null."); + gradient = parent.Find("Background").GetComponent().EffectGradient.colorKeys.ToColors().ToArray(); + } + + item.transform.Find("Background").GetComponent().EffectGradient.SetKeys(gradient.ToGradientColorKeys().ToArray(), alphaKeys); + + // We also need to set the text color to a color that is readable on the background color. + Color foregroundColor = gradient.Aggregate((x, y) => (x + y) / 2).IdealTextColor(); + textMesh.color = foregroundColor; + iconMesh.color = foregroundColor; + expandIcon.GetComponent().color = foregroundColor; + } + + void AnimateIn() + { + item.transform.localScale = new Vector3(1, 0, 1); + item.transform.DOScaleY(1, duration: 0.5f); + } + + void RegisterClickHandler() + { + if (item.TryGetComponentOrLog(out PointerHelper pointerHelper)) + { + // Right click highlights the node, left/middle click expands/collapses it. + pointerHelper.ClickEvent.AddListener(e => { - if (expandedItems.Contains(id)) + // TODO: In the future, highlighting the node should be one available option in a right-click menu. + if (e.button == PointerEventData.InputButton.Right) { - collapseItem(item); + if (itemGameObject != null) + { + itemGameObject.Operator().Highlight(duration: 10); + } } - else + else if (children > 0) { - expandItem(item); + if (expandedItems.Contains(id)) + { + collapseItem(item); + } + else + { + expandItem(item); + } } - } - }); + }); + } } } @@ -249,8 +267,7 @@ private void ExpandItem(GameObject item) expandedItems.Add(item.name); if (item.transform.Find("Foreground/Expand Icon").gameObject.TryGetComponentOrLog(out RectTransform rectTransform)) { - // TODO: Animate this. - rectTransform.Rotate(0, 0, -90); + rectTransform.DORotate(new Vector3(0, 0, -180), duration: 0.5f); } } @@ -264,7 +281,7 @@ private void CollapseItem(GameObject item) expandedItems.Remove(item.name); if (item.transform.Find("Foreground/Expand Icon").gameObject.TryGetComponentOrLog(out RectTransform rectTransform)) { - rectTransform.Rotate(0, 0, 90); + rectTransform.DORotate(new Vector3(0, 0, -90), duration: 0.5f); } } diff --git a/Assets/SEE/Utils/ColorExtensions.cs b/Assets/SEE/Utils/ColorExtensions.cs index dcada9a6f7..3825e76761 100644 --- a/Assets/SEE/Utils/ColorExtensions.cs +++ b/Assets/SEE/Utils/ColorExtensions.cs @@ -54,7 +54,7 @@ public static Color WithAlpha(this Color color, float alpha) /// the optimal text color for the given . public static Color IdealTextColor(this Color backgroundColor) { - const int nThreshold = 105; + const int nThreshold = 110; int bgDelta = Convert.ToInt32((backgroundColor.r * 255 * 0.299) + (backgroundColor.g * 255 * 0.587) + (backgroundColor.b * 255 * 0.114)); return (255 - bgDelta < nThreshold) ? Color.black : Color.white; From 65ac2ee78414f17a0f3f94164d6d75ed0f35c405 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Oct 2023 14:27:41 +0200 Subject: [PATCH 19/25] Improve text rendering on colored backgrounds --- Assets/SEE/Utils/ColorExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/SEE/Utils/ColorExtensions.cs b/Assets/SEE/Utils/ColorExtensions.cs index 3825e76761..7194cd9dde 100644 --- a/Assets/SEE/Utils/ColorExtensions.cs +++ b/Assets/SEE/Utils/ColorExtensions.cs @@ -54,7 +54,7 @@ public static Color WithAlpha(this Color color, float alpha) /// the optimal text color for the given . public static Color IdealTextColor(this Color backgroundColor) { - const int nThreshold = 110; + const int nThreshold = 130; int bgDelta = Convert.ToInt32((backgroundColor.r * 255 * 0.299) + (backgroundColor.g * 255 * 0.587) + (backgroundColor.b * 255 * 0.114)); return (255 - bgDelta < nThreshold) ? Color.black : Color.white; From 966e79bc522b9770434812b122b9910ae7afad7c Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Oct 2023 16:02:17 +0200 Subject: [PATCH 20/25] Remove unused code in InteractableObject --- .../Interactables/InteractableObject.cs | 81 ------------------- 1 file changed, 81 deletions(-) diff --git a/Assets/SEE/Controls/Interactables/InteractableObject.cs b/Assets/SEE/Controls/Interactables/InteractableObject.cs index ce9c0e1fc3..d434d45164 100644 --- a/Assets/SEE/Controls/Interactables/InteractableObject.cs +++ b/Assets/SEE/Controls/Interactables/InteractableObject.cs @@ -88,87 +88,6 @@ public sealed class InteractableObject : MonoBehaviour /// public GraphElementRef GraphElemRef { get; private set; } - public bool TryGetNodeRef(out NodeRef nodeRef) - { - bool result = false; - nodeRef = null; - if (GraphElemRef is NodeRef @ref) - { - result = true; - nodeRef = @ref; - } - - return result; - } - - public bool TryGetEdgeRef(out EdgeRef edgeRef) - { - bool result = false; - edgeRef = null; - if (GraphElemRef is EdgeRef @ref) - { - result = true; - edgeRef = @ref; - } - - return result; - } - - public bool TryGetNode(out Node node) - { - bool result = false; - node = null; - if (GraphElemRef is NodeRef nodeRef) - { - node = nodeRef.Value; - result = node != null; - } - - return result; - } - - public bool TryGetEdge(out Edge edge) - { - bool result = false; - edge = null; - if (GraphElemRef is EdgeRef edgeRef) - { - edge = edgeRef.Value; - result = edge != null; - } - - return result; - } - - public NodeRef GetNodeRef() - { - Assert.IsTrue(GraphElemRef is NodeRef); - return (NodeRef)GraphElemRef; - } - - public EdgeRef GetEdgeRef() - { - Assert.IsTrue(GraphElemRef is EdgeRef); - return (EdgeRef)GraphElemRef; - } - - public Node GetNode() - { - Assert.IsTrue(GraphElemRef is NodeRef); - return (Node)GraphElemRef.Elem; - } - - public Edge GetEdge() - { - Assert.IsTrue(GraphElemRef is EdgeRef); - return (Edge)GraphElemRef.Elem; - } - - /// - /// - /// - public Graph ItsGraph => GraphElemRef.Elem.ItsGraph; - /// /// A bit vector for hovering flags. Each flag is a bit as defined in . /// If the bit is set, this is to be considered hovered over for interaction From 1bc058a712c390441ea3a17ef9b1b4b999d720ff Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Oct 2023 16:22:39 +0200 Subject: [PATCH 21/25] Stop blinking when count parameter is zero --- Assets/SEE/Game/Operator/EdgeOperator.cs | 30 ++++++++++++------- .../SEE/Game/Operator/GraphElementOperator.cs | 4 +-- Assets/SEE/Game/Operator/NodeOperator.cs | 15 +++++++--- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/Assets/SEE/Game/Operator/EdgeOperator.cs b/Assets/SEE/Game/Operator/EdgeOperator.cs index 92384a8d75..818f327011 100644 --- a/Assets/SEE/Game/Operator/EdgeOperator.cs +++ b/Assets/SEE/Game/Operator/EdgeOperator.cs @@ -219,17 +219,25 @@ protected override Tween[] BlinkAction(int count, float duration) { // If we're interrupting another blinking, we need to make sure the color still has the correct value. spline.GradientColors = Color.TargetValue; - Color newStart = Color.TargetValue.start.Invert(); - Color newEnd = Color.TargetValue.end.Invert(); - float loopDuration = duration / (2 * Mathf.Abs(count)); - - Tween startTween = DOTween.To(() => spline.GradientColors.start, - c => spline.GradientColors = (c, spline.GradientColors.end), - newStart, loopDuration); - Tween endTween = DOTween.To(() => spline.GradientColors.end, - c => spline.GradientColors = (spline.GradientColors.start, c), - newEnd, loopDuration); - return new[] { startTween, endTween }.Select(x => x.SetEase(Ease.Linear).SetLoops(2 * count, LoopType.Yoyo).Play()).ToArray(); + + if (count != 0) + { + Color newStart = Color.TargetValue.start.Invert(); + Color newEnd = Color.TargetValue.end.Invert(); + float loopDuration = duration / (2 * Mathf.Abs(count)); + + Tween startTween = DOTween.To(() => spline.GradientColors.start, + c => spline.GradientColors = (c, spline.GradientColors.end), + newStart, loopDuration); + Tween endTween = DOTween.To(() => spline.GradientColors.end, + c => spline.GradientColors = (spline.GradientColors.start, c), + newEnd, loopDuration); + return new[] { startTween, endTween }.Select(x => x.SetEase(Ease.Linear).SetLoops(2 * count, LoopType.Yoyo).Play()).ToArray(); + } + else + { + return new Tween[] { }; + } } protected override (Color start, Color end) ModifyColor((Color start, Color end) color, Func modifier) diff --git a/Assets/SEE/Game/Operator/GraphElementOperator.cs b/Assets/SEE/Game/Operator/GraphElementOperator.cs index 1ed90958f9..a4c7011489 100644 --- a/Assets/SEE/Game/Operator/GraphElementOperator.cs +++ b/Assets/SEE/Game/Operator/GraphElementOperator.cs @@ -14,7 +14,6 @@ namespace SEE.Game.Operator { - /// /// A component managing operations done on the graph element (i.e., node or edge) it is attached to. /// Available operations consist of the public methods exported by this class. @@ -74,6 +73,7 @@ public AbstractSEECity City /// /// The number of times the element should blink. /// If set to -1, the element will blink indefinitely. + /// If set to 0, the element will not blink at all. /// /// Factor to apply to the /// that controls the blinking duration. @@ -137,7 +137,7 @@ public IOperationCallback Highlight(float duration) int blinkCount = duration >= 0 ? Mathf.RoundToInt(duration * 1.3f) : -1; return new AndCombinedOperationCallback(new[] { - GlowIn(ToFactor(Mathf.Abs(duration/blinkCount))), + GlowIn(ToFactor(Mathf.Abs(duration / blinkCount))), Blink(blinkCount: blinkCount, ToFactor(Mathf.Abs(duration))) }).OnComplete(() => { diff --git a/Assets/SEE/Game/Operator/NodeOperator.cs b/Assets/SEE/Game/Operator/NodeOperator.cs index 7c974cab2f..b2282dbe9e 100644 --- a/Assets/SEE/Game/Operator/NodeOperator.cs +++ b/Assets/SEE/Game/Operator/NodeOperator.cs @@ -159,7 +159,7 @@ public IOperationCallback MoveYTo(float newYPosition, float factor = 1, /// /// if true, the connecting edges will be moved along with the node /// An operation callback for the requested animation - public IOperationCallback MoveZTo(float newZPosition, float factor =1, bool updateEdges = true) + public IOperationCallback MoveZTo(float newZPosition, float factor = 1, bool updateEdges = true) { float duration = ToDuration(factor); updateLayoutDuration = duration; @@ -392,10 +392,17 @@ protected override Tween[] BlinkAction(int count, float duration) // If we're interrupting another blinking, we need to make sure the color still has the correct value. material.color = Color.TargetValue; - return new Tween[] + if (count != 0) { - material.DOColor(Color.TargetValue.Invert(), duration / (2 * Mathf.Abs(count))).SetEase(Ease.Linear).SetLoops(2 * count, LoopType.Yoyo).Play() - }; + return new Tween[] + { + material.DOColor(Color.TargetValue.Invert(), duration / (2 * Mathf.Abs(count))).SetEase(Ease.Linear).SetLoops(2 * count, LoopType.Yoyo).Play() + }; + } + else + { + return new Tween[] { }; + } } protected override Color ModifyColor(Color color, Func modifier) From 71b76f38f71ee8d9c58421aa0d77a1cca914e660 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Oct 2023 16:23:01 +0200 Subject: [PATCH 22/25] Replace flasher with operator in InteractableObject --- .../Controls/Interactables/InteractableObject.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Assets/SEE/Controls/Interactables/InteractableObject.cs b/Assets/SEE/Controls/Interactables/InteractableObject.cs index d434d45164..5df6f7c01b 100644 --- a/Assets/SEE/Controls/Interactables/InteractableObject.cs +++ b/Assets/SEE/Controls/Interactables/InteractableObject.cs @@ -9,7 +9,6 @@ #if INCLUDE_STEAM_VR using Valve.VR.InteractionSystem; #endif -using SEE.Game.SceneManipulation; using SEE.Net.Actions; using SEE.Audio; @@ -142,18 +141,12 @@ public sealed class InteractableObject : MonoBehaviour /// public Synchronizer InteractableSynchronizer { get; private set; } - /// - /// Will be used to flash the selected object while it is selected. - /// - private GameObjectFlasher flasher; - private void Awake() { #if INCLUDE_STEAM_VR gameObject.TryGetComponentOrLog(out interactable); #endif GraphElemRef = GetComponent(); - flasher = new GameObjectFlasher(gameObject); } private void OnDestroy() @@ -377,7 +370,8 @@ public void SetSelect(bool select, bool isInitiator) graphToSelectedIOs[graph].Add(this); - flasher.StartFlashing(); + // Start blinking indefinitely. + gameObject.Operator().Blink(-1); // Invoke events SelectIn?.Invoke(this, isInitiator); @@ -398,7 +392,8 @@ public void SetSelect(bool select, bool isInitiator) // Update all selected object list per graph graphToSelectedIOs[GraphElemRef.Elem.ItsGraph].Remove(this); - flasher.StopFlashing(); + // Stop blinking. + gameObject.Operator().Blink(0); // Invoke events SelectOut?.Invoke(this, isInitiator); From 7d3a7844ea5c56dfa97e7fc56f53d172264dccea Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Oct 2023 16:23:14 +0200 Subject: [PATCH 23/25] Remove obsolete GameObjectFlasher class --- .../SceneManipulation/GameObjectFlasher.cs | 67 ------------------- .../GameObjectFlasher.cs.meta | 11 --- 2 files changed, 78 deletions(-) delete mode 100644 Assets/SEE/Game/SceneManipulation/GameObjectFlasher.cs delete mode 100644 Assets/SEE/Game/SceneManipulation/GameObjectFlasher.cs.meta diff --git a/Assets/SEE/Game/SceneManipulation/GameObjectFlasher.cs b/Assets/SEE/Game/SceneManipulation/GameObjectFlasher.cs deleted file mode 100644 index ca53ce9c17..0000000000 --- a/Assets/SEE/Game/SceneManipulation/GameObjectFlasher.cs +++ /dev/null @@ -1,67 +0,0 @@ -using UnityEngine; -using DG.Tweening; -using SEE.Utils; - -namespace SEE.Game.SceneManipulation -{ - /// - /// Flashes a game object, that is, animates its color pulsating from its - /// original color to its inverted color. - /// - [System.Obsolete("Use NodeOperator.Blink() instead.")] - internal class GameObjectFlasher // TODO: Usages of this class should be replaced with NodeOperator.Blink() - { - /// - /// Constructor. - /// - /// the object to be flashed - public GameObjectFlasher(GameObject gameObject) - { - this.gameObject = gameObject; - } - - /// - /// The object to be flashed. - /// - private readonly GameObject gameObject; - - /// - /// The tween to animate . - /// - private Tween selectionTween; - - /// - /// The original color of . - /// - private Color originalColor; - - /// - /// The duration of the color animation in seconds. - /// - private const float animationDuration = 3; - - /// - /// Starts the flashing animation. - /// - public void StartFlashing() - { - Material material = gameObject.GetComponent().sharedMaterial; - originalColor = material.color; - selectionTween = material.DOColor(material.color.Invert(), animationDuration).SetEase(Ease.Flash).SetLoops(-1, LoopType.Yoyo); - } - - /// - /// Stops the flashing animation immediately and restores the original color. - /// - public void StopFlashing() - { - if (selectionTween != null) - { - selectionTween.SetLoops(0); - selectionTween.Kill(); - gameObject.GetComponent().sharedMaterial.color = originalColor; - selectionTween = null; - } - } - } -} diff --git a/Assets/SEE/Game/SceneManipulation/GameObjectFlasher.cs.meta b/Assets/SEE/Game/SceneManipulation/GameObjectFlasher.cs.meta deleted file mode 100644 index 276e302514..0000000000 --- a/Assets/SEE/Game/SceneManipulation/GameObjectFlasher.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 9218e6cea2dee9344a5d45ff3676939c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: From 2133f0889812f3e02d52ee21e4c141a025b9112e Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 13 Oct 2023 16:27:24 +0200 Subject: [PATCH 24/25] Remove empty Utils.GameObjectExtensions class --- Assets/SEE/Utils/GameObjectExtensions.cs | 14 -------------- Assets/SEE/Utils/GameObjectExtensions.cs.meta | 3 --- 2 files changed, 17 deletions(-) delete mode 100644 Assets/SEE/Utils/GameObjectExtensions.cs delete mode 100644 Assets/SEE/Utils/GameObjectExtensions.cs.meta diff --git a/Assets/SEE/Utils/GameObjectExtensions.cs b/Assets/SEE/Utils/GameObjectExtensions.cs deleted file mode 100644 index 17b121d381..0000000000 --- a/Assets/SEE/Utils/GameObjectExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using SEE.Game; -using SEE.Game.Operator; -using SEE.GO; -using UnityEngine; - -namespace SEE.Utils -{ - /// - /// Contains utility extension methods for game objects. - /// - public static class GameObjectExtensions - { } -} diff --git a/Assets/SEE/Utils/GameObjectExtensions.cs.meta b/Assets/SEE/Utils/GameObjectExtensions.cs.meta deleted file mode 100644 index 8273f2c8d9..0000000000 --- a/Assets/SEE/Utils/GameObjectExtensions.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 9fda120e3dbf4b4da1fbe2423e0b8c42 -timeCreated: 1697138401 \ No newline at end of file From 3549a1e1b876245622a293850fb2feb1c2367844 Mon Sep 17 00:00:00 2001 From: Rainer Koschke Date: Tue, 17 Oct 2023 11:26:46 +0200 Subject: [PATCH 25/25] Use of nameof instead of literal name. --- Assets/SEE/GameObjects/GameObjectExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Assets/SEE/GameObjects/GameObjectExtensions.cs b/Assets/SEE/GameObjects/GameObjectExtensions.cs index 1b497af121..ec73ed45d1 100644 --- a/Assets/SEE/GameObjects/GameObjectExtensions.cs +++ b/Assets/SEE/GameObjects/GameObjectExtensions.cs @@ -883,7 +883,7 @@ public static NodeOperator NodeOperator(this GameObject gameObject) } else { - throw new InvalidOperationException($"Cannot get NodeOperator for game object {gameObject.name} because it is not a node."); + throw new InvalidOperationException($"Cannot get {nameof(NodeOperator)} for game object {gameObject.name} because it is not a node."); } } @@ -902,7 +902,7 @@ public static EdgeOperator EdgeOperator(this GameObject gameObject) } else { - throw new InvalidOperationException($"Cannot get EdgeOperator for game object {gameObject.name} because it is not an edge."); + throw new InvalidOperationException($"Cannot get {nameof(EdgeOperator)} for game object {gameObject.name} because it is not an edge."); } } @@ -932,7 +932,7 @@ public static GraphElementOperator Operator(this GameObject gameObject) } else { - throw new InvalidOperationException("Cannot get GraphElementOperator for game object " + throw new InvalidOperationException($"Cannot get {nameof(GraphElementOperator)} for game object " + $"{gameObject.name} because it is neither a node nor an edge."); } }