From a0a9f1ca1c3c67b8a1d55bdc046bdecab985ded9 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 20 Oct 2023 01:16:41 +0200 Subject: [PATCH 01/11] Implement GraphSearch class --- Assets/SEE/DataModel/DG/GraphSearch.cs | 140 ++++++++++++++++++++ Assets/SEE/DataModel/DG/GraphSearch.cs.meta | 3 + 2 files changed, 143 insertions(+) create mode 100644 Assets/SEE/DataModel/DG/GraphSearch.cs create mode 100644 Assets/SEE/DataModel/DG/GraphSearch.cs.meta diff --git a/Assets/SEE/DataModel/DG/GraphSearch.cs b/Assets/SEE/DataModel/DG/GraphSearch.cs new file mode 100644 index 0000000000..b486ff1466 --- /dev/null +++ b/Assets/SEE/DataModel/DG/GraphSearch.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FuzzySharp; + +namespace SEE.DataModel.DG +{ + /// + /// Allows searching for nodes by their string representation. + /// The graph associated to this search may be dynamic – that is, when the graph changes + /// (for example, when a node is added), the search index will be updated accordingly. + /// Searches are fuzzy, i.e., they will return results even if the query does not match the + /// element's name exactly. + /// + /// To perform a search on the associated graph, call . + /// + public class GraphSearch : IObserver + { + /// + /// A mapping from names to a list of nodes with that name. + /// Is constructed in the constructor in order not to have to descend into the graph every + /// time a search is executed. + /// + private readonly IDictionary> elements; + + /// + /// The graph to be searched. + /// + private readonly Graph graph; + + /// + /// Creates a new instance of for the given . + /// + /// The graph to be searched. + public GraphSearch(Graph graph) + { + this.graph = graph; + elements = graph.Nodes().GroupBy(ElementToString).ToDictionary(g => g.Key, g => g.ToList()); + graph.Subscribe(this); + } + + /// + /// Performs a fuzzy search for the given in the graph. + /// + /// The query to be searched for. + /// A list of nodes which match the query. + public IEnumerable Search(string query, int limit = 10, int cutoff = 40) + { + return Process.ExtractTop(FilterString(query), elements.Keys, limit: limit, cutoff: cutoff) + .SelectMany(x => elements[x.Value].Select(Element => (x.Score, Element))) + .OrderByDescending(x => x.Score) + .Select(x => x.Element); + } + + /// + /// Removes zero-width-spaces from the given , as well as whitespace at the + /// beginning and end. + /// + /// The string which shall be filtered. + /// The filtered string. + public static string FilterString(string input) + { + const string zeroWidthSpace = "\u200B"; + return input.Trim().Replace(zeroWidthSpace, string.Empty); + } + + /// + /// Adds the given to the dictionary. + /// + /// The element to be added. + private void AddElement(Node element) + { + string elementString = ElementToString(element); + if (elements.TryGetValue(elementString, out List list)) + { + list.Add(element); + } + else + { + elements.Add(elementString, new List { element }); + } + } + + /// + /// Removes the given from the dictionary. + /// + /// The element to be removed. + private void RemoveElement(Node element) + { + string elementString = ElementToString(element); + if (elements.TryGetValue(elementString, out List list)) + { + list.Remove(element); + if (list.Count == 0) + { + elements.Remove(elementString); + } + } + } + + /// + /// Converts the given to a searchable string. + /// + /// The element to be converted. + /// The string representation of the element. + private static string ElementToString(Node element) + { + return element.SourceName; + } + + public void OnCompleted() + { + // Nothing to be done. + } + + public void OnError(Exception error) + { + throw error; + } + + public void OnNext(ChangeEvent changeEvent) + { + // We want to update our mapping of names to nodes whenever a node is added or removed. + switch (changeEvent) + { + case NodeEvent { Change: ChangeType.Addition } nodeEvent: + AddElement(nodeEvent.Node); + break; + case NodeEvent { Change: ChangeType.Removal } nodeEvent: + RemoveElement(nodeEvent.Node); + break; + case IAttributeEvent { AttributeName: Node.SourceNameAttribute, Attributable: Node node }: + // If the source name of a node changes, we need to update the mapping. + RemoveElement(node); + AddElement(node); + break; + } + } + } +} diff --git a/Assets/SEE/DataModel/DG/GraphSearch.cs.meta b/Assets/SEE/DataModel/DG/GraphSearch.cs.meta new file mode 100644 index 0000000000..e655afaa6b --- /dev/null +++ b/Assets/SEE/DataModel/DG/GraphSearch.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 15f8fbf868d74f6d940b2d0d614f5dd7 +timeCreated: 1697754642 \ No newline at end of file From 1553f75888b25c8c3c7270ae757f259d84f85132 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 20 Oct 2023 14:57:30 +0200 Subject: [PATCH 02/11] Implement search for TreeView --- Assets/Resources/Prefabs/UI/TreeView.prefab | 709 +++++++++++++++++- Assets/SEE/UI/PlatformDependentComponent.cs | 6 +- .../UI/Window/TreeWindow/DesktopTreeWindow.cs | 90 ++- Assets/SEE/UI/Window/TreeWindow/TreeWindow.cs | 18 +- Data/GXL/SEE/SEE.cfg | 6 +- 5 files changed, 805 insertions(+), 24 deletions(-) diff --git a/Assets/Resources/Prefabs/UI/TreeView.prefab b/Assets/Resources/Prefabs/UI/TreeView.prefab index c23e08612d..34d358bb48 100644 --- a/Assets/Resources/Prefabs/UI/TreeView.prefab +++ b/Assets/Resources/Prefabs/UI/TreeView.prefab @@ -1,5 +1,209 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!1 &842951385601223108 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6206375982450128193} + - component: {fileID: 6756889094794557856} + - component: {fileID: 6078913256362592785} + - component: {fileID: 5081112725215650259} + - component: {fileID: 8918530655449257547} + - component: {fileID: 3444143981090016800} + m_Layer: 5 + m_Name: SearchField + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6206375982450128193 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 842951385601223108} + 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: 8357808458188078163} + - {fileID: 1175549163707059561} + - {fileID: 3073758193502664746} + m_Father: {fileID: 7868881898829307179} + 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: -3.7499342, y: 0} + m_SizeDelta: {x: -7.500131, y: 55} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &6756889094794557856 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 842951385601223108} + m_CullTransparentMesh: 0 +--- !u!114 &6078913256362592785 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 842951385601223108} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2da0c512f12947e489f739169773d7ca, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 0 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 0} + m_HighlightedColor: {r: 1, g: 1, b: 1, a: 0.039215688} + m_PressedColor: {r: 1, g: 1, b: 1, a: 0.05882353} + m_SelectedColor: {r: 1, g: 1, b: 1, a: 0.039215688} + m_DisabledColor: {r: 1, g: 1, b: 1, a: 0} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Highlighted + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1299946610580397705} + m_TextViewport: {fileID: 3073758193502664746} + m_TextComponent: {fileID: 4963036195758837251} + m_Placeholder: {fileID: 0} + m_VerticalScrollbar: {fileID: 0} + m_VerticalScrollbarEventHandler: {fileID: 0} + m_LayoutGroup: {fileID: 0} + m_ScrollSensitivity: 1 + m_ContentType: 0 + m_InputType: 0 + m_AsteriskChar: 42 + m_KeyboardType: 0 + m_LineType: 0 + m_HideMobileInput: 0 + m_HideSoftKeyboard: 0 + m_CharacterValidation: 0 + m_RegexValue: + m_GlobalPointSize: 14 + m_CharacterLimit: 0 + m_OnEndEdit: + m_PersistentCalls: + m_Calls: [] + m_OnSubmit: + m_PersistentCalls: + m_Calls: [] + m_OnSelect: + m_PersistentCalls: + m_Calls: [] + m_OnDeselect: + m_PersistentCalls: + m_Calls: [] + m_OnTextSelection: + m_PersistentCalls: + m_Calls: [] + m_OnEndTextSelection: + m_PersistentCalls: + m_Calls: [] + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] + m_OnTouchScreenKeyboardStatusChanged: + m_PersistentCalls: + m_Calls: [] + m_CaretColor: {r: 1, g: 1, b: 1, a: 1} + m_CustomCaretColor: 1 + m_SelectionColor: {r: 1, g: 1, b: 1, a: 0.09803922} + m_Text: + m_CaretBlinkRate: 0.85 + m_CaretWidth: 2 + m_ReadOnly: 0 + m_RichText: 1 + m_GlobalFontAsset: {fileID: 11400000, guid: 4bd810f1cbcb0f446a8f5a31453e243f, type: 2} + m_OnFocusSelectAll: 1 + m_ResetOnDeActivation: 1 + m_RestoreOriginalTextOnEscape: 1 + m_isRichTextEditingAllowed: 1 + m_LineLimit: 0 + m_InputValidator: {fileID: 0} +--- !u!95 &5081112725215650259 +Animator: + serializedVersion: 5 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 842951385601223108} + m_Enabled: 1 + m_Avatar: {fileID: 0} + m_Controller: {fileID: 9100000, guid: f02ff10900044744b851159f375542e2, type: 2} + m_CullingMode: 0 + m_UpdateMode: 2 + m_ApplyRootMotion: 0 + m_LinearVelocityBlending: 0 + m_StabilizeFeet: 0 + m_WarningMessage: + m_HasTransformHierarchy: 1 + m_AllowConstantClipSamplingOptimization: 1 + m_KeepAnimatorStateOnDisable: 0 + m_WriteDefaultValuesOnDisable: 0 +--- !u!114 &8918530655449257547 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 842951385601223108} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c65c7917835d8a04b94c8b906234b09e, type: 3} + m_Name: + m_EditorClassIdentifier: + inputText: {fileID: 0} + inputFieldAnimator: {fileID: 0} +--- !u!114 &3444143981090016800 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 842951385601223108} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d170bc6b162fcce46a456b2011fd50b4, type: 3} + m_Name: + m_EditorClassIdentifier: + UIManagerAsset: {fileID: 11400000, guid: 2a619a9609984be49b53b928dd94e61b, type: 2} + webglMode: 0 + images: + - {fileID: 2940933265865494054} + - {fileID: 4431518105055718632} + texts: + - {fileID: 3548788532928508177} + - {fileID: 1512550248131604402} --- !u!1 &894369973855324406 GameObject: m_ObjectHideFlags: 0 @@ -75,6 +279,141 @@ MonoBehaviour: m_FillOrigin: 0 m_UseSpriteMesh: 0 m_PixelsPerUnitMultiplier: 15 +--- !u!1 &1512550248131604402 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3777093290825092734} + - component: {fileID: 8242539107127619044} + - component: {fileID: 4963036195758837251} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3777093290825092734 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1512550248131604402} + 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: 3073758193502664746} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0.5} + m_SizeDelta: {x: -30, y: -1} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &8242539107127619044 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1512550248131604402} + m_CullTransparentMesh: 0 +--- !u!114 &4963036195758837251 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1512550248131604402} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, 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_text: "\u200B" + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 4bd810f1cbcb0f446a8f5a31453e243f, type: 2} + m_sharedMaterial: {fileID: 21539420542967178, guid: 4bd810f1cbcb0f446a8f5a31453e243f, + type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 4294967295 + m_fontColor: {r: 1, g: 1, b: 1, a: 1} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 28 + m_fontSizeBase: 28 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_enableWordWrapping: 0 + m_wordWrappingRatios: 0.4 + m_overflowMode: 1 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 1 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &2851445155489688032 GameObject: m_ObjectHideFlags: 0 @@ -150,6 +489,217 @@ MonoBehaviour: m_FillOrigin: 0 m_UseSpriteMesh: 0 m_PixelsPerUnitMultiplier: 15 +--- !u!1 &2940933265865494054 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8357808458188078163} + - component: {fileID: 8155224562833598069} + - component: {fileID: 1108056371345408637} + m_Layer: 5 + m_Name: Background + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8357808458188078163 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2940933265865494054} + 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: 5241878067270971924} + m_Father: {fileID: 6206375982450128193} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &8155224562833598069 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2940933265865494054} + m_CullTransparentMesh: 0 +--- !u!114 &1108056371345408637 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2940933265865494054} + 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: 1, g: 1, b: 1, a: 0.019607844} + 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: 5e16c7aea118d68498053518146c9cf9, type: 3} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 10 +--- !u!1 &3548788532928508177 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1175549163707059561} + - component: {fileID: 2420396502281564817} + - component: {fileID: 1534017100348806313} + m_Layer: 5 + m_Name: Placeholder + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1175549163707059561 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3548788532928508177} + 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: 6206375982450128193} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 15, y: 0.5} + m_SizeDelta: {x: -30, y: -1} + m_Pivot: {x: 0, y: 0.5} +--- !u!222 &2420396502281564817 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3548788532928508177} + m_CullTransparentMesh: 0 +--- !u!114 &1534017100348806313 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3548788532928508177} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, 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_text: Search... + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 4bd810f1cbcb0f446a8f5a31453e243f, type: 2} + m_sharedMaterial: {fileID: 21539420542967178, guid: 4bd810f1cbcb0f446a8f5a31453e243f, + type: 2} + m_fontSharedMaterials: [] + m_fontMaterial: {fileID: 0} + m_fontMaterials: [] + m_fontColor32: + serializedVersion: 2 + rgba: 2952790015 + m_fontColor: {r: 1, g: 1, b: 1, a: 0.6862745} + m_enableVertexGradient: 0 + m_colorMode: 3 + m_fontColorGradient: + topLeft: {r: 1, g: 1, b: 1, a: 1} + topRight: {r: 1, g: 1, b: 1, a: 1} + bottomLeft: {r: 1, g: 1, b: 1, a: 1} + bottomRight: {r: 1, g: 1, b: 1, a: 1} + m_fontColorGradientPreset: {fileID: 0} + m_spriteAsset: {fileID: 0} + m_tintAllSprites: 0 + m_StyleSheet: {fileID: 0} + m_TextStyleHashCode: -1183493901 + m_overrideHtmlColors: 0 + m_faceColor: + serializedVersion: 2 + rgba: 4294967295 + m_fontSize: 28 + m_fontSizeBase: 28 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 1 + m_VerticalAlignment: 512 + m_textAlignment: 65535 + m_characterSpacing: 0 + m_wordSpacing: 0 + m_lineSpacing: 0 + m_lineSpacingMax: 0 + m_paragraphSpacing: 0 + m_charWidthMaxAdj: 0 + m_enableWordWrapping: 1 + m_wordWrappingRatios: 0.4 + m_overflowMode: 1 + m_linkedTextComponent: {fileID: 0} + parentLinkedComponent: {fileID: 0} + m_enableKerning: 1 + m_enableExtraPadding: 0 + checkPaddingRequired: 0 + m_isRichText: 1 + m_parseCtrlCharacters: 1 + m_isOrthographic: 1 + m_isCullingEnabled: 0 + m_horizontalMapping: 0 + m_verticalMapping: 0 + m_uvLineOffset: 0 + m_geometrySortingOrder: 0 + m_IsTextObjectScaleStatic: 0 + m_VertexBufferAutoSizeReduction: 1 + m_useMaxVisibleDescender: 1 + m_pageToDisplay: 1 + m_margin: {x: 0, y: 0, z: 0, w: 0} + m_isUsingLegacyAnimationComponent: 0 + m_isVolumetricText: 0 + m_hasFontAssetChanged: 0 + m_baseMaterial: {fileID: 0} + m_maskOffset: {x: 0, y: 0, z: 0, w: 0} --- !u!1 &3708138963226508037 GameObject: m_ObjectHideFlags: 0 @@ -186,6 +736,162 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: -10, y: -10} m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &4431518105055718632 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5241878067270971924} + - component: {fileID: 1834334288966421307} + - component: {fileID: 1299946610580397705} + m_Layer: 5 + m_Name: Filled + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &5241878067270971924 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4431518105055718632} + 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: 8357808458188078163} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1834334288966421307 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4431518105055718632} + m_CullTransparentMesh: 0 +--- !u!114 &1299946610580397705 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4431518105055718632} + 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: 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_Sprite: {fileID: 21300000, guid: 5e16c7aea118d68498053518146c9cf9, type: 3} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 0 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 10 +--- !u!1 &5181310319957536929 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7868881898829307179} + m_Layer: 5 + m_Name: Search + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7868881898829307179 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5181310319957536929} + 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: 6206375982450128193} + m_Father: {fileID: 2472446831714888479} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &5756927395396884001 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3073758193502664746} + - component: {fileID: 5446393462633531186} + m_Layer: 5 + m_Name: Text Area + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3073758193502664746 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5756927395396884001} + 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: 3777093290825092734} + m_Father: {fileID: 6206375982450128193} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &5446393462633531186 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5756927395396884001} + m_CullTransparentMesh: 0 --- !u!1 &6265431863627329327 GameObject: m_ObjectHideFlags: 0 @@ -328,7 +1034,8 @@ RectTransform: m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 - m_Children: [] + m_Children: + - {fileID: 7868881898829307179} m_Father: {fileID: 8823517661499080595} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} diff --git a/Assets/SEE/UI/PlatformDependentComponent.cs b/Assets/SEE/UI/PlatformDependentComponent.cs index 43e51eb787..943fa3551a 100644 --- a/Assets/SEE/UI/PlatformDependentComponent.cs +++ b/Assets/SEE/UI/PlatformDependentComponent.cs @@ -54,7 +54,11 @@ public abstract class PlatformDependentComponent : MonoBehaviour /// /// Initializes the component for the current platform. /// - protected void Start() + /// + /// Only override this if you need to execute platform-independent code on startup. + /// Otherwise, use , or . + /// + protected virtual void Start() { // initializes the Canvas if necessary if (Canvas == null) diff --git a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs index 6249f05774..890bafc284 100644 --- a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs +++ b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs @@ -3,16 +3,17 @@ using System.Linq; using DG.Tweening; using Michsky.UI.ModernUIPack; +using SEE.Controls; using SEE.DataModel.DG; using SEE.Game; using SEE.GO; +using SEE.UI.Notification; using SEE.Utils; +using TMPro; using UnityEngine; using UnityEngine.Assertions; using UnityEngine.EventSystems; using UnityEngine.UI; -using Color = UnityEngine.Color; -using Transform = UnityEngine.Transform; namespace SEE.UI.Window.TreeWindow { @@ -77,13 +78,14 @@ private void AddNode(Node node) /// 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 itemGameObject, 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(); + TextMeshProUGUI textMesh = foreground.Find("Text").gameObject.MustGetComponent(); + TextMeshProUGUI iconMesh = foreground.Find("Type Icon").gameObject.MustGetComponent(); Color[] gradient = null; Transform parent = null; @@ -173,15 +175,15 @@ void RegisterClickHandler() itemGameObject.Operator().Highlight(duration: 10); } } - else if (children > 0) + else { if (expandedItems.Contains(id)) { - collapseItem(item); + collapseItem?.Invoke(item); } else { - expandItem(item); + expandItem?.Invoke(item); } } }); @@ -294,6 +296,11 @@ private void CollapseItem(GameObject item) /// The game object of the element represented by the node. private void ExpandNode(Node node, GameObject item, GameObject nodeGameObject) { + if (node.NumberOfChildren() == 0) + { + return; + } + ExpandItem(item); foreach (Node child in node.Children()) @@ -350,18 +357,49 @@ private void CollapseNode(Node node, GameObject item) RemoveNodeChildren(node); } - protected override void StartDesktop() + /// + /// Searches for the given in the graph + /// and displays the results in the tree window. + /// + /// The search term to be searched for. + private void SearchFor(string searchTerm) { - if (Graph == null) + ClearTree(); + if (searchTerm == null || searchTerm.Trim().Length == 0) { - Debug.LogError("Graph must be set before starting the tree window."); - return; + AddRoots(); } - Title = $"{Graph.Name} – Tree View"; - base.StartDesktop(); - content = PrefabInstantiator.InstantiatePrefab(treeWindowPrefab, Window.transform.Find("Content"), false).transform.Find("Content"); + foreach (Node node in searcher.Search(searchTerm)) + { + GameObject nodeGameObject = GraphElementIDMap.Find(node.ID, mustFindElement: true); + AddItem(CleanupID(node.ID), null, + 0, node.ToShortString(), 0, nodeTypeUnicode, nodeGameObject, + null, null); // TODO: Reveal in hierarchy on click (& clear search field) + } + } + /// + /// Clears the tree view of all items. + /// + private void ClearTree() + { + foreach (Transform child in content) + { + if (child.name == "Search") + { + continue; + } + Destroyer.Destroy(child.gameObject); + expandedItems.Clear(); + } + } + + /// + /// Adds the roots of the graph to the tree view. + /// + private void AddRoots() + { // We will traverse the graph and add each node to the tree view. IList roots = Graph.GetRoots(); foreach (Node root in roots) @@ -371,8 +409,28 @@ protected override void StartDesktop() if (roots.Count == 0) { - Debug.LogWarning("Graph has no roots. TreeView will be empty."); + ShowNotification.Warn("Empty graph", "Graph has no roots. TreeView will be empty."); } } + + protected override void StartDesktop() + { + if (Graph == null) + { + Debug.LogError("Graph must be set before starting the tree window."); + return; + } + + Title = $"{Graph.Name} – Tree View"; + base.StartDesktop(); + content = PrefabInstantiator.InstantiatePrefab(treeWindowPrefab, Window.transform.Find("Content"), false).transform.Find("Content"); + + TMP_InputField search = content.Find("Search/SearchField").gameObject.MustGetComponent(); + search.onSelect.AddListener(_ => SEEInput.KeyboardShortcutsEnabled = false); + search.onDeselect.AddListener(_ => SEEInput.KeyboardShortcutsEnabled = true); + search.onValueChanged.AddListener(SearchFor); + + AddRoots(); + } } } diff --git a/Assets/SEE/UI/Window/TreeWindow/TreeWindow.cs b/Assets/SEE/UI/Window/TreeWindow/TreeWindow.cs index 92e4669942..6a16f0763d 100644 --- a/Assets/SEE/UI/Window/TreeWindow/TreeWindow.cs +++ b/Assets/SEE/UI/Window/TreeWindow/TreeWindow.cs @@ -1,3 +1,4 @@ +using System; using SEE.DataModel.DG; namespace SEE.UI.Window.TreeWindow @@ -48,6 +49,17 @@ public partial class TreeWindow : BaseWindow /// public Graph Graph; + /// + /// The search helper used to search for elements in the graph. + /// + private GraphSearch searcher; + + protected override void Start() + { + searcher = new GraphSearch(Graph); + base.Start(); + } + public override void RebuildLayout() { // Nothing needs to be done. @@ -56,17 +68,17 @@ public override void RebuildLayout() protected override void InitializeFromValueObject(WindowValues valueObject) { // TODO: Should tree windows be sent over the network? - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public override void UpdateFromNetworkValueObject(WindowValues valueObject) { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } public override WindowValues ToValueObject() { - throw new System.NotImplementedException(); + throw new NotImplementedException(); } } } diff --git a/Data/GXL/SEE/SEE.cfg b/Data/GXL/SEE/SEE.cfg index 90de99dc13..cae5935c70 100644 --- a/Data/GXL/SEE/SEE.cfg +++ b/Data/GXL/SEE/SEE.cfg @@ -638,7 +638,7 @@ NodeLayout : { }; }; EdgeLayout : { - EdgeLayout : "Bundling"; + EdgeLayout : "None"; AnimationKind : "Fading"; AnimateInnerEdges : True; EdgeWidth : 0.01000000; @@ -674,8 +674,8 @@ CoseGraph : { }; GXLPath : { Root : "ProjectFolder"; - RelativePath : "/Data/GXL/SEE/SEE.gxl"; - AbsolutePath : "CodeFacts.gxl"; + RelativePath : "/Data/GXL/SEE/CodeFacts.gxl.xz"; + AbsolutePath : "CodeFacts.gxl.xz"; }; CSVPath : { Root : "AssetsFolder"; From 1d1cc6484440920547edb41ff4347068655924d2 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 27 Oct 2023 16:14:17 +0200 Subject: [PATCH 03/11] Fix color gradient calculation --- 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 7194cd9dde..db9a9aa769 100644 --- a/Assets/SEE/Utils/ColorExtensions.cs +++ b/Assets/SEE/Utils/ColorExtensions.cs @@ -81,7 +81,7 @@ public static Color Invert(this Color color) /// 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)); + return colors.Select((c, i) => new GradientColorKey(c, (float) i / (colors.Count-1))); } /// From b5ed6783b70085ccffb433d72180f06dfb0b162f Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 27 Oct 2023 16:15:16 +0200 Subject: [PATCH 04/11] Implement `HasGameObject` and `WithGameObject` extension methods --- .../DataModel/DG/GraphElementExtensions.cs | 21 +++++++++++++++++++ Assets/SEE/Game/GraphElementIDMap.cs | 14 ++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Assets/SEE/DataModel/DG/GraphElementExtensions.cs b/Assets/SEE/DataModel/DG/GraphElementExtensions.cs index 339122dad9..ddb873093c 100644 --- a/Assets/SEE/DataModel/DG/GraphElementExtensions.cs +++ b/Assets/SEE/DataModel/DG/GraphElementExtensions.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Linq; using SEE.DataModel.DG; using SEE.Game; using SEE.Game.Operator; @@ -45,5 +47,24 @@ public static class GraphElementExtensions /// The for this graph . public static GameObject GameObject(this GraphElement element, bool mustFind = false) => GraphElementIDMap.Find(element.ID, mustFind); + + /// + /// Returns true iff the given has a game object associated to it. + /// + /// The element to check. + /// True iff the given has a game object associated to it. + public static bool HasGameObject(this GraphElement element) + => GraphElementIDMap.Has(element.ID); + + /// + /// Returns all s in the given which have a + /// game object associated to them. + /// + /// The elements to filter. + /// The type of the elements. + /// All s in the given which have a + /// game object associated to them. + public static IEnumerable WithGameObject(this IEnumerable elements) where T : GraphElement + => elements.Where(HasGameObject); } } diff --git a/Assets/SEE/Game/GraphElementIDMap.cs b/Assets/SEE/Game/GraphElementIDMap.cs index 26754eb3cd..6a30ceee8f 100644 --- a/Assets/SEE/Game/GraphElementIDMap.cs +++ b/Assets/SEE/Game/GraphElementIDMap.cs @@ -47,6 +47,18 @@ internal static GameObject Find(string id, bool mustFindElement = false) } } + /// + /// Returns whether there is a game object with the given + /// in this map. + /// + /// the ID of the game object to be looked up + /// whether there is a game object with the given in this map + internal static bool Has(string id) + { + Assert.IsFalse(string.IsNullOrEmpty(id)); + return mapping.ContainsKey(id); + } + /// /// Emits the and all current entries of the map. /// @@ -168,4 +180,4 @@ internal static void Clear() mapping.Clear(); } } -} \ No newline at end of file +} From 97b1e2651a09aa1909b816b0908786be8ac12193 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 27 Oct 2023 16:15:32 +0200 Subject: [PATCH 05/11] Make graph search case-invariant --- Assets/SEE/DataModel/DG/GraphSearch.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Assets/SEE/DataModel/DG/GraphSearch.cs b/Assets/SEE/DataModel/DG/GraphSearch.cs index b486ff1466..e27878459d 100644 --- a/Assets/SEE/DataModel/DG/GraphSearch.cs +++ b/Assets/SEE/DataModel/DG/GraphSearch.cs @@ -54,14 +54,14 @@ public IEnumerable Search(string query, int limit = 10, int cutoff = 40) /// /// Removes zero-width-spaces from the given , as well as whitespace at the - /// beginning and end. + /// beginning and end, and converts the string to lowercase. /// /// The string which shall be filtered. /// The filtered string. public static string FilterString(string input) { const string zeroWidthSpace = "\u200B"; - return input.Trim().Replace(zeroWidthSpace, string.Empty); + return input.Trim().Replace(zeroWidthSpace, string.Empty).ToLowerInvariant(); } /// @@ -105,7 +105,7 @@ private void RemoveElement(Node element) /// The string representation of the element. private static string ElementToString(Node element) { - return element.SourceName; + return element.SourceName.ToLowerInvariant(); } public void OnCompleted() From 46b3f1e7964fa8f55014abe43766e4a276a970d6 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 27 Oct 2023 16:15:52 +0200 Subject: [PATCH 06/11] Do not blink if the material is null --- Assets/SEE/Game/Operator/NodeOperator.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Assets/SEE/Game/Operator/NodeOperator.cs b/Assets/SEE/Game/Operator/NodeOperator.cs index b2282dbe9e..e6c57fa1b4 100644 --- a/Assets/SEE/Game/Operator/NodeOperator.cs +++ b/Assets/SEE/Game/Operator/NodeOperator.cs @@ -390,6 +390,11 @@ Tween[] AnimateToColorAction(Color color, float d) 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. + if (material == null) + { + return new Tween[] { }; + } + material.color = Color.TargetValue; if (count != 0) From 8f5871e12b55d1bf5ea315f0fed3a509345b8611 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 27 Oct 2023 16:18:31 +0200 Subject: [PATCH 07/11] Add UIExtensions static class --- Assets/SEE/Utils/UIExtensions.cs | 60 +++++++++++++++++++++++++++ Assets/SEE/Utils/UIExtensions.cs.meta | 3 ++ 2 files changed, 63 insertions(+) create mode 100644 Assets/SEE/Utils/UIExtensions.cs create mode 100644 Assets/SEE/Utils/UIExtensions.cs.meta diff --git a/Assets/SEE/Utils/UIExtensions.cs b/Assets/SEE/Utils/UIExtensions.cs new file mode 100644 index 0000000000..637ece0df2 --- /dev/null +++ b/Assets/SEE/Utils/UIExtensions.cs @@ -0,0 +1,60 @@ +using DG.Tweening; +using SEE.Game.Operator; +using UnityEngine; +using UnityEngine.UI; + +namespace SEE.Utils +{ + /// + /// Contains extension methods for (2D) Unity UI elements. + /// + public static class UIExtensions + { + /// + /// Scrolls this to the given . + /// If a is given, the scrolling will be animated. + /// + /// the scroll rect to scroll + /// the item to scroll to + /// the duration of the animation, if any + /// a callback which can be used to react to the end of the animation + public static IOperationCallback ScrollTo(this ScrollRect scrollRect, RectTransform item, float? duration = null) + { + Canvas.ForceUpdateCanvases(); + // Adapted from https://gist.github.com/yasirkula/75ca350fb83ddcc1558d33a8ecf1483f + Vector2 contentSize = scrollRect.content.rect.size; + Vector2 contentScale = scrollRect.content.localScale; + + // Calculate the focus point relative to the content's pivot. + Vector2 itemCenterPoint = scrollRect.content.InverseTransformPoint(item.transform.TransformPoint(item.rect.center)); + Vector2 contentSizeOffset = contentSize * scrollRect.content.pivot; + + // Scale position and size according to scroll rect's local scale. + Vector2 position = (itemCenterPoint + contentSizeOffset) * contentScale; + contentSize *= contentScale; + + Vector2 viewportSize = ((RectTransform) scrollRect.content.parent).rect.size; + + // Calculate the new scroll position which centers the item. + Vector2 scrollPosition = scrollRect.normalizedPosition; + if (scrollRect.horizontal && contentSize.x > viewportSize.x) + { + scrollPosition.x = Mathf.Clamp01((position.x - viewportSize.x * 0.5f) / (contentSize.x - viewportSize.x)); + } + if (scrollRect.vertical && contentSize.y > viewportSize.y) + { + scrollPosition.y = Mathf.Clamp01((position.y - viewportSize.y * 0.5f) / (contentSize.y - viewportSize.y)); + } + + if (!duration.HasValue) + { + scrollRect.normalizedPosition = scrollPosition; + return new DummyOperationCallback(); + } + else + { + return new TweenOperationCallback(scrollRect.DONormalizedPos(scrollPosition, duration.Value).Play()); + } + } + } +} diff --git a/Assets/SEE/Utils/UIExtensions.cs.meta b/Assets/SEE/Utils/UIExtensions.cs.meta new file mode 100644 index 0000000000..53c3be85c9 --- /dev/null +++ b/Assets/SEE/Utils/UIExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f8325884e51a4ae0907c16d41f40554f +timeCreated: 1698235700 \ No newline at end of file From cf945e80342c8f1e438a0a31ac8f031666c19ab1 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Fri, 27 Oct 2023 16:21:02 +0200 Subject: [PATCH 08/11] Scroll to found element in TreeView search --- .../256px/Rounded Filled 256px Dark.asset | 3 + .../Rounded Filled 256px Dark.asset.meta | 3 + .../256px/Rounded Filled 256px Dark.png | 3 + .../256px/Rounded Filled 256px Dark.png.meta | 3 + Assets/Resources/Prefabs/UI/TreeView.prefab | 128 ++++- .../Resources/Prefabs/UI/TreeViewItem.prefab | 496 +++++++++--------- .../Prefabs/UI/TreeViewItem.prefab.meta | 2 +- .../UI/Window/TreeWindow/DesktopTreeWindow.cs | 119 +++-- 8 files changed, 458 insertions(+), 299 deletions(-) create mode 100644 Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.asset create mode 100644 Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.asset.meta create mode 100644 Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.png create mode 100644 Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.png.meta diff --git a/Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.asset b/Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.asset new file mode 100644 index 0000000000..fedafffb71 --- /dev/null +++ b/Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f0ecb7001b9f486675b110915cd311c1e4d62a3993f02ebbc6c1212675f214a +size 6897 diff --git a/Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.asset.meta b/Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.asset.meta new file mode 100644 index 0000000000..484022d1e4 --- /dev/null +++ b/Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.asset.meta @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:822dc409d5f1374b96792431988303cfc39eb67eff14dee3d6f1339c29627b36 +size 189 diff --git a/Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.png b/Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.png new file mode 100644 index 0000000000..2fd19bd7b3 --- /dev/null +++ b/Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91976320ee004dd9355b81627047f5f70d22f30f5b1689d057982d0d51321f76 +size 9556 diff --git a/Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.png.meta b/Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.png.meta new file mode 100644 index 0000000000..cbaa36b50c --- /dev/null +++ b/Assets/Plugins/Modern UI Pack/Textures/Border/Rounded/256px/Rounded Filled 256px Dark.png.meta @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14c5f726b34e2611eb6b7078d5cd4bb4d0e5ced4b9eeb0669b6b09db053cd09c +size 3196 diff --git a/Assets/Resources/Prefabs/UI/TreeView.prefab b/Assets/Resources/Prefabs/UI/TreeView.prefab index 34d358bb48..dddc220848 100644 --- a/Assets/Resources/Prefabs/UI/TreeView.prefab +++ b/Assets/Resources/Prefabs/UI/TreeView.prefab @@ -40,7 +40,7 @@ RectTransform: 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: -3.7499342, y: 0} + m_AnchoredPosition: {x: -3.749939, y: 0} m_SizeDelta: {x: -7.500131, y: 55} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &6756889094794557856 @@ -834,18 +834,18 @@ RectTransform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 5181310319957536929} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + 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: 6206375982450128193} - m_Father: {fileID: 2472446831714888479} + m_Father: {fileID: 8823517661499080595} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 0, y: 0} - m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 0, y: 50} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 482.25, y: -30} + m_SizeDelta: {x: 954.5, y: 50} m_Pivot: {x: 0.5, y: 0.5} --- !u!1 &5756927395396884001 GameObject: @@ -925,12 +925,12 @@ RectTransform: m_Children: - {fileID: 8846550913904393515} - {fileID: 866037333387126464} - m_Father: {fileID: 8823517661499080595} + m_Father: {fileID: 6104944427379572325} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 0} m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 0, y: 20} + m_SizeDelta: {x: 0.000018119812, y: 20} m_Pivot: {x: 0.5, y: 0} --- !u!222 &5165653685777371019 CanvasRenderer: @@ -1005,6 +1005,98 @@ MonoBehaviour: webglMode: 0 background: {fileID: 2881518792493254415} bar: {fileID: 7370997065605906984} +--- !u!1 &6821221533684094758 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6104944427379572325} + - component: {fileID: 7777609248601255341} + - component: {fileID: 1683213145217237807} + - component: {fileID: 3334699822168737793} + m_Layer: 5 + m_Name: Content + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6104944427379572325 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6821221533684094758} + 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: 2472446831714888479} + - {fileID: 8823517661645426698} + - {fileID: 2059283171776054673} + m_Father: {fileID: 8823517661499080595} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -30.570019} + m_SizeDelta: {x: 0, y: -61.13996} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &7777609248601255341 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6821221533684094758} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ShowMaskGraphic: 0 +--- !u!222 &1683213145217237807 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6821221533684094758} + m_CullTransparentMesh: 1 +--- !u!114 &3334699822168737793 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6821221533684094758} + 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: 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_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 --- !u!1 &8513057685923210505 GameObject: m_ObjectHideFlags: 0 @@ -1017,7 +1109,7 @@ GameObject: - component: {fileID: 7442591805894148481} - component: {fileID: 8843005544103412409} m_Layer: 5 - m_Name: Content + m_Name: Items m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -1030,13 +1122,12 @@ RectTransform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 8513057685923210505} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + 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: 7868881898829307179} - m_Father: {fileID: 8823517661499080595} + m_Children: [] + m_Father: {fileID: 6104944427379572325} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 1, y: 1} @@ -1302,9 +1393,8 @@ RectTransform: m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: - - {fileID: 2472446831714888479} - - {fileID: 8823517661645426698} - - {fileID: 2059283171776054673} + - {fileID: 6104944427379572325} + - {fileID: 7868881898829307179} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} @@ -1340,7 +1430,7 @@ MonoBehaviour: m_Inertia: 1 m_DecelerationRate: 0.135 m_ScrollSensitivity: 20 - m_Viewport: {fileID: 0} + m_Viewport: {fileID: 6104944427379572325} m_HorizontalScrollbar: {fileID: 7161509444652221816} m_VerticalScrollbar: {fileID: 8823517661645426696} m_HorizontalScrollbarVisibility: 1 @@ -1448,7 +1538,7 @@ RectTransform: m_Children: - {fileID: 8823517661321162181} - {fileID: 8823517660650887623} - m_Father: {fileID: 8823517661499080595} + m_Father: {fileID: 6104944427379572325} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 1, y: 0} m_AnchorMax: {x: 1, y: 1} diff --git a/Assets/Resources/Prefabs/UI/TreeViewItem.prefab b/Assets/Resources/Prefabs/UI/TreeViewItem.prefab index a31ed02041..ecdfaa14e9 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 &337901394664264623 +--- !u!1 &3221561598132462901 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -8,74 +8,94 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 7493521065603945485} - - component: {fileID: 8481403556691258000} - - component: {fileID: 2780406110822494520} + - component: {fileID: 3766619498664164433} + - component: {fileID: 6994604829827172676} + - component: {fileID: 8043074890402026029} m_Layer: 5 - m_Name: Expand Icon + m_Name: TreeViewItem m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &7493521065603945485 +--- !u!224 &3766619498664164433 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_GameObject: {fileID: 3221561598132462901} + 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: 6107984335030005991} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: -90} + m_Children: + - {fileID: 7801144434069277683} + - {fileID: 766617590976963954} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 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_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0, y: 1} +--- !u!114 &6994604829827172676 +MonoBehaviour: 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 + m_GameObject: {fileID: 3221561598132462901} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 6592963879312978613} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &8043074890402026029 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 337901394664264623} + m_GameObject: {fileID: 3221561598132462901} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Script: {fileID: 11500000, guid: 52dd8aaa3b5d4058ac1b1e9242d35f57, 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 +--- !u!1 &6543745927510804026 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -83,119 +103,74 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 3612804172242837094} - - component: {fileID: 1287414283101376713} - - component: {fileID: 368891032765212448} - - component: {fileID: 8528920679602411433} + - component: {fileID: 4151804783001895320} + - component: {fileID: 3158015502856716037} + - component: {fileID: 8716019332972553389} m_Layer: 5 - m_Name: Background + m_Name: Expand Icon m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &3612804172242837094 +--- !u!224 &4151804783001895320 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_GameObject: {fileID: 6543745927510804026} + 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: 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_Father: {fileID: 766617590976963954} + 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 &1287414283101376713 +--- !u!222 &3158015502856716037 CanvasRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2457842372413307603} + m_GameObject: {fileID: 6543745927510804026} m_CullTransparentMesh: 1 ---- !u!114 &368891032765212448 +--- !u!114 &8716019332972553389 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2457842372413307603} + m_GameObject: {fileID: 6543745927510804026} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3} + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 1, g: 1, b: 1, a: 1} + 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_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 + 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 &7530604749489586215 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -203,88 +178,50 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 6107984335030005991} + - component: {fileID: 8018369032896587253} + - component: {fileID: 3643483630539419851} + - component: {fileID: 615798641653032956} m_Layer: 5 - m_Name: Foreground + m_Name: Type Icon m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &6107984335030005991 +--- !u!224 &8018369032896587253 RectTransform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3152911572347607451} + m_GameObject: {fileID: 7530604749489586215} 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: 6107984335030005991} + m_Father: {fileID: 766617590976963954} 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 &2351212017304989447 +--- !u!222 &3643483630539419851 CanvasRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3556668903542347433} + m_GameObject: {fileID: 7530604749489586215} m_CullTransparentMesh: 1 ---- !u!114 &2656358682537043824 +--- !u!114 &615798641653032956 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3556668903542347433} + m_GameObject: {fileID: 7530604749489586215} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} @@ -298,10 +235,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: [] @@ -325,8 +263,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 @@ -367,7 +305,7 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} ---- !u!1 &3954843106050839986 +--- !u!1 &8015187158269920060 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -375,50 +313,50 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 3541660964508758112} - - component: {fileID: 7849836590017996126} - - component: {fileID: 6263302416817975913} + - component: {fileID: 6174073020406636757} + - component: {fileID: 9133677281363212946} + - component: {fileID: 8844280093565481701} m_Layer: 5 - m_Name: Type Icon + m_Name: Text m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &3541660964508758112 +--- !u!224 &6174073020406636757 RectTransform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3954843106050839986} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_GameObject: {fileID: 8015187158269920060} + 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: 6107984335030005991} + m_Father: {fileID: 766617590976963954} 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.505005, y: 1.2000017} + m_SizeDelta: {x: -121.01, y: -57.600006} m_Pivot: {x: 0.5, y: 0.5} ---- !u!222 &7849836590017996126 +--- !u!222 &9133677281363212946 CanvasRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3954843106050839986} + m_GameObject: {fileID: 8015187158269920060} m_CullTransparentMesh: 1 ---- !u!114 &6263302416817975913 +--- !u!114 &8844280093565481701 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3954843106050839986} + m_GameObject: {fileID: 8015187158269920060} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: f4688fdb7df04437aeb418b961361dc5, type: 3} @@ -432,11 +370,10 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: \uf1b2 + m_text: IfYouCanSeeThisIDidSomethingWrongThisIsJustAVeryLongStringOfTextToTestTheOverflowCapabilitiesOfThisTextMeshProObjectSoTheHopeIsThatTheTextDoesNotOverflowBeyondTheBoundsOfTheItem 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: [] @@ -460,15 +397,15 @@ MonoBehaviour: m_faceColor: serializedVersion: 2 rgba: 4294967295 - m_fontSize: 30 - m_fontSizeBase: 30 + m_fontSize: 18 + m_fontSizeBase: 26 m_fontWeight: 400 - m_enableAutoSizing: 0 + m_enableAutoSizing: 1 m_fontSizeMin: 18 - m_fontSizeMax: 72 + m_fontSizeMax: 30 m_fontStyle: 0 m_HorizontalAlignment: 1 - m_VerticalAlignment: 256 + m_VerticalAlignment: 512 m_textAlignment: 65535 m_characterSpacing: 0 m_wordSpacing: 0 @@ -478,7 +415,7 @@ MonoBehaviour: m_charWidthMaxAdj: 0 m_enableWordWrapping: 1 m_wordWrappingRatios: 0.4 - m_overflowMode: 0 + m_overflowMode: 1 m_linkedTextComponent: {fileID: 0} parentLinkedComponent: {fileID: 0} m_enableKerning: 1 @@ -502,7 +439,7 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} ---- !u!1 &8274573712563105952 +--- !u!1 &8476088519983172622 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -510,90 +447,153 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 7648732560831130052} - - component: {fileID: 4571626204040715473} - - component: {fileID: 3602532499758286776} + - component: {fileID: 766617590976963954} m_Layer: 5 - m_Name: TreeViewItem + m_Name: Foreground m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &7648732560831130052 +--- !u!224 &766617590976963954 RectTransform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 8274573712563105952} + m_GameObject: {fileID: 8476088519983172622} 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: 3612804172242837094} - - {fileID: 6107984335030005991} - m_Father: {fileID: 0} + - {fileID: 4151804783001895320} + - {fileID: 8018369032896587253} + - {fileID: 6174073020406636757} + m_Father: {fileID: 3766619498664164433} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 0, y: 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 &8970180186745827142 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7801144434069277683} + - component: {fileID: 5745935338147661148} + - component: {fileID: 6592963879312978613} + - component: {fileID: 2899379093387412028} + m_Layer: 5 + m_Name: Background + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7801144434069277683 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8970180186745827142} + 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: 3766619498664164433} + 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, y: 1} ---- !u!114 &4571626204040715473 + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &5745935338147661148 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8970180186745827142} + m_CullTransparentMesh: 1 +--- !u!114 &6592963879312978613 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 8274573712563105952} + m_GameObject: {fileID: 8970180186745827142} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3} m_Name: m_EditorClassIdentifier: - m_Navigation: - m_Mode: 3 - m_WrapAround: 0 - m_SelectOnUp: {fileID: 0} - m_SelectOnDown: {fileID: 0} - m_SelectOnLeft: {fileID: 0} - m_SelectOnRight: {fileID: 0} - m_Transition: 1 - m_Colors: - m_NormalColor: {r: 1, g: 1, b: 1, a: 1} - m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} - m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} - m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} - m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} - m_ColorMultiplier: 1 - m_FadeDuration: 0.1 - m_SpriteState: - m_HighlightedSprite: {fileID: 0} - m_PressedSprite: {fileID: 0} - m_SelectedSprite: {fileID: 0} - m_DisabledSprite: {fileID: 0} - m_AnimationTriggers: - m_NormalTrigger: Normal - m_HighlightedTrigger: Highlighted - m_PressedTrigger: Pressed - m_SelectedTrigger: Selected - m_DisabledTrigger: Disabled - m_Interactable: 1 - m_TargetGraphic: {fileID: 368891032765212448} - m_OnClick: + 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: [] ---- !u!114 &3602532499758286776 + m_Texture: {fileID: 0} + m_UVRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 +--- !u!114 &2899379093387412028 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 8274573712563105952} + m_GameObject: {fileID: 8970180186745827142} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 52dd8aaa3b5d4058ac1b1e9242d35f57, type: 3} + 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 diff --git a/Assets/Resources/Prefabs/UI/TreeViewItem.prefab.meta b/Assets/Resources/Prefabs/UI/TreeViewItem.prefab.meta index 529c04efdd..0d99f2d9a0 100644 --- a/Assets/Resources/Prefabs/UI/TreeViewItem.prefab.meta +++ b/Assets/Resources/Prefabs/UI/TreeViewItem.prefab.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 95c92362614cf8553b978ce83c848d22 +guid: 4e7329bce1107abf6a795e345a0b2270 PrefabImporter: externalObjects: {} userData: diff --git a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs index 890bafc284..a74a813014 100644 --- a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs +++ b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Cysharp.Threading.Tasks; using DG.Tweening; using Michsky.UI.ModernUIPack; using SEE.Controls; @@ -11,7 +12,6 @@ using SEE.Utils; using TMPro; using UnityEngine; -using UnityEngine.Assertions; using UnityEngine.EventSystems; using UnityEngine.UI; @@ -23,9 +23,14 @@ namespace SEE.UI.Window.TreeWindow public partial class TreeWindow { /// - /// Transform of the content of the tree window. + /// Transform of the object containing the items of the tree window. /// - private Transform content; + private RectTransform items; + + /// + /// Component that allows scrolling through the items of the tree window. + /// + private ScrollRect scrollRect; /// /// A set of all items (node IDs) that have been expanded. @@ -51,13 +56,18 @@ public partial class TreeWindow /// private static readonly GradientAlphaKey[] alphaKeys = { new(1, 0), new(1, 1) }; + /// + /// The input field in which the user can enter a search term. + /// + private TMP_InputField SearchField; + /// /// Adds the given to the bottom of the tree window. /// /// The node to be added. private void AddNode(Node node) { - GameObject nodeGameObject = GraphElementIDMap.Find(node.ID, mustFindElement: true); + GameObject nodeGameObject = GraphElementIDMap.Find(node.ID); int children = node.NumberOfChildren() + Mathf.Min(node.Outgoings.Count, 1) + Mathf.Min(node.Incomings.Count, 1); AddItem(CleanupID(node.ID), CleanupID(node.Parent?.ID), @@ -81,7 +91,7 @@ private void AddItem(string id, string parentId, int children, string text, int char icon, GameObject itemGameObject, Action collapseItem, Action expandItem) { - GameObject item = PrefabInstantiator.InstantiatePrefab(treeItemPrefab, content, false); + GameObject item = PrefabInstantiator.InstantiatePrefab(treeItemPrefab, items, false); Transform foreground = item.transform.Find("Foreground"); GameObject expandIcon = foreground.Find("Expand Icon").gameObject; TextMeshProUGUI textMesh = foreground.Find("Text").gameObject.MustGetComponent(); @@ -95,7 +105,7 @@ private void AddItem(string id, string parentId, int children, string text, int if (parentId != null) { // Position the item below its parent. - parent = content.Find(parentId); + parent = items.Find(parentId); item.transform.SetSiblingIndex(parent.GetSiblingIndex() + 1); foreground.localPosition += new Vector3(indentShift * level, 0, 0); } @@ -136,13 +146,14 @@ void ColorItem() (Color start, Color end) = itemGameObject.EdgeOperator().TargetColor; gradient = new[] { start, end }; } + else + { + throw new ArgumentException("Item must be either a node or an edge."); + } } - - if (gradient == null) + else { - // 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(); + gradient = new[] { Color.gray, Color.gray.Darker() }; } item.transform.Find("Background").GetComponent().EffectGradient.SetKeys(gradient.ToGradientColorKeys().ToArray(), alphaKeys); @@ -174,6 +185,10 @@ void RegisterClickHandler() { itemGameObject.Operator().Highlight(duration: 10); } + else + { + ShowNotification.Warn("No game object", "There is nothing to highlight for this item."); + } } else { @@ -236,7 +251,7 @@ private void RemoveNodeChildren(Node node) /// The type of the item. private void RemoveItem(string id, T initial, Func> getChildItems) { - GameObject item = content.Find(id)?.gameObject; + GameObject item = items.Find(id)?.gameObject; if (item == null) { Debug.LogWarning($"Item {id} not found."); @@ -296,11 +311,6 @@ private void CollapseItem(GameObject item) /// The game object of the element represented by the node. private void ExpandNode(Node node, GameObject item, GameObject nodeGameObject) { - if (node.NumberOfChildren() == 0) - { - return; - } - ExpandItem(item); foreach (Node child in node.Children()) @@ -338,7 +348,7 @@ void AddEdgeButton(string edgesType, char icon, ICollection edges) ExpandItem(i); foreach (Edge edge in edges) { - GameObject edgeObject = GraphElementIDMap.Find(edge.ID, mustFindElement: true); + GameObject edgeObject = GraphElementIDMap.Find(edge.ID); AddItem($"{id}#{CleanupID(edge.ID)}", id, 0, edge.ToShortString(), node.Level + 2, edgeTypeUnicode, edgeObject, null, null); } }); @@ -375,8 +385,58 @@ private void SearchFor(string searchTerm) GameObject nodeGameObject = GraphElementIDMap.Find(node.ID, mustFindElement: true); AddItem(CleanupID(node.ID), null, 0, node.ToShortString(), 0, nodeTypeUnicode, nodeGameObject, - null, null); // TODO: Reveal in hierarchy on click (& clear search field) + null, _ => MakeVisible(node).Forget()); } + + items.position = items.position.WithXYZ(y: 0); + } + + /// + /// Makes the given visible in the tree window by expanding all its parents + /// and scrolling to it. + /// + /// The node to be made visible. + private async UniTaskVoid MakeVisible(Node node) + { + SearchField.onValueChanged.RemoveListener(SearchFor); + SearchField.text = string.Empty; + SearchField.ReleaseSelection(); + SearchField.onValueChanged.AddListener(SearchFor); + ClearTree(); + + // We need to find a path from the root to the node, which we do by working our way up the hierarchy. + // We then expand all nodes on the path. + Node current = node; + while (current.Parent != null) + { + current = current.Parent; + expandedItems.Add(CleanupID(current.ID)); + } + + // FIXME: Node sometimes gets placed at wrong spot. This seems to be a very annoying racing condition. + // The only fix I can think of involves creating a data structure that represents the tree view + // and then using that to set the sibling indices of the items all at once. + AddRoots(); + + // We need to wait until the transform actually exists. + await UniTask.Yield(); + RectTransform item = (RectTransform)items.Find(CleanupID(node.ID)); + scrollRect.ScrollTo(item, duration: 1f); + + // Make element blink. + UIGradient uiGradient = item.Find("Background").GetComponent(); + Gradient gradient = uiGradient.EffectGradient; + DOTween.To(() => uiGradient.EffectGradient.colorKeys[0].color, x => + { + gradient.SetKeys(new[] + { + new GradientColorKey(x, 0), new GradientColorKey(x.Darker(), 1) + }, alphaKeys); + uiGradient.EffectGradient = gradient; + }, + gradient.colorKeys[0].color.Invert(), duration: 0.5f) + .SetEase(Ease.Linear) + .SetLoops(6, LoopType.Yoyo).Play(); } /// @@ -384,14 +444,9 @@ private void SearchFor(string searchTerm) /// private void ClearTree() { - foreach (Transform child in content) + foreach (Transform child in items) { - if (child.name == "Search") - { - continue; - } Destroyer.Destroy(child.gameObject); - expandedItems.Clear(); } } @@ -423,12 +478,14 @@ protected override void StartDesktop() Title = $"{Graph.Name} – Tree View"; base.StartDesktop(); - content = PrefabInstantiator.InstantiatePrefab(treeWindowPrefab, Window.transform.Find("Content"), false).transform.Find("Content"); - - TMP_InputField search = content.Find("Search/SearchField").gameObject.MustGetComponent(); - search.onSelect.AddListener(_ => SEEInput.KeyboardShortcutsEnabled = false); - search.onDeselect.AddListener(_ => SEEInput.KeyboardShortcutsEnabled = true); - search.onValueChanged.AddListener(SearchFor); + Transform root = PrefabInstantiator.InstantiatePrefab(treeWindowPrefab, Window.transform.Find("Content"), false).transform; + items = (RectTransform)root.Find("Content/Items"); + scrollRect = root.gameObject.MustGetComponent(); + + SearchField = root.Find("Search/SearchField").gameObject.MustGetComponent(); + SearchField.onSelect.AddListener(_ => SEEInput.KeyboardShortcutsEnabled = false); + SearchField.onDeselect.AddListener(_ => SEEInput.KeyboardShortcutsEnabled = true); + SearchField.onValueChanged.AddListener(SearchFor); AddRoots(); } From c287b5f69e4390ffb7fab9d78a6458a134c79361 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sat, 28 Oct 2023 01:19:57 +0200 Subject: [PATCH 09/11] Reorder TreeView after item creation This fixes any ordering problems which e.g. occurred during search, and also allows for more fine-grained control over the order in which objects appear. --- .../UI/Window/TreeWindow/DesktopTreeWindow.cs | 147 +++++++++++++----- 1 file changed, 109 insertions(+), 38 deletions(-) diff --git a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs index a74a813014..134b322321 100644 --- a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs +++ b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs @@ -61,6 +61,67 @@ public partial class TreeWindow /// private TMP_InputField SearchField; + /// + /// Orders the tree below the given node according to the graph hierarchy. + /// This needs to be called whenever the tree is expanded. + /// + /// The node below which the tree should be ordered. + private void OrderTree(Node orderBelow) + { + Debug.Log("Ordering tree."); + int index = items.Find(CleanupID(orderBelow.ID)).GetSiblingIndex(); + OrderTreeRecursive(orderBelow); + + return; + + // Orders the item with the given id to the current index and increments the index. + void OrderItemHere(string id) + { + Transform item = items.Find(id); + if (item == null) + { + Debug.LogError($"Item {id} not found."); + } + else + { + item.SetSiblingIndex(index++); + } + } + + // Recurses over the tree in pre-order and assigns indices to each node. + void OrderTreeRecursive(Node node) + { + string id = CleanupID(node.ID); + OrderItemHere(id); + if (expandedItems.Contains(id)) + { + foreach (Node child in node.Children().OrderBy(x => x.SourceName)) + { + OrderTreeRecursive(child); + } + + HandleEdges($"{id}#Outgoing", node.Outgoings); + HandleEdges($"{id}#Incoming", node.Incomings); + } + } + + // Orders the edges under the given id (outgoing/incoming) to the current index and increments the index. + void HandleEdges(string edgesId, ICollection edges) + { + if (edges.Count > 0) + { + OrderItemHere(edgesId); + if (expandedItems.Contains(edgesId)) + { + foreach (Edge edge in edges) + { + OrderItemHere($"{edgesId}#{CleanupID(edge.ID)}"); + } + } + } + } + } + /// /// Adds the given to the bottom of the tree window. /// @@ -70,45 +131,41 @@ private void AddNode(Node node) GameObject nodeGameObject = GraphElementIDMap.Find(node.ID); 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, nodeGameObject, - i => CollapseNode(node, i), i => ExpandNode(node, i, nodeGameObject)); + AddItem(CleanupID(node.ID), children, node.ToShortString(), node.Level, nodeTypeUnicode, nodeGameObject, + item => CollapseNode(node, item), + (item, order) => ExpandNode(node, item, orderTree: order)); } /// - /// Adds the given item beneath its parent to the tree window. + /// Adds the given item to the tree window. /// /// The ID of the item to be added. - /// The ID of the parent of the item to be added. /// The number of children of the item to be added. /// 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. - /// 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, + /// A function that collapses the item. + /// It takes the item that was collapsed as an argument. + /// A function that expands the item. + /// It takes the item that was expanded and a boolean indicating whether the + /// tree should be ordered after expanding the item as arguments. + private void AddItem(string id, int children, string text, int level, char icon, GameObject itemGameObject, - Action collapseItem, Action expandItem) + Action collapseItem, Action expandItem) { GameObject item = PrefabInstantiator.InstantiatePrefab(treeItemPrefab, items, false); + Transform background = item.transform.Find("Background"); Transform foreground = item.transform.Find("Foreground"); GameObject expandIcon = foreground.Find("Expand Icon").gameObject; TextMeshProUGUI textMesh = foreground.Find("Text").gameObject.MustGetComponent(); TextMeshProUGUI iconMesh = foreground.Find("Type Icon").gameObject.MustGetComponent(); - Color[] gradient = null; - Transform parent = null; textMesh.text = text; iconMesh.text = icon.ToString(); - if (parentId != null) - { - // Position the item below its parent. - parent = items.Find(parentId); - item.transform.SetSiblingIndex(parent.GetSiblingIndex() + 1); - foreground.localPosition += new Vector3(indentShift * level, 0, 0); - } + foreground.localPosition += new Vector3(indentShift * level, 0, 0); + background.localPosition += new Vector3(indentShift * level, 0, 0); ColorItem(); @@ -124,15 +181,19 @@ private void AddItem(string id, string parentId, int children, string text, int else if (expandedItems.Contains(id)) { // If this item was previously expanded, we need to expand it again. - expandItem(item); + // The tree should not be reordered after this – this should only happen at the end of the expansion, + // and thus needs to be done at the originating call. + expandItem(item, false); } RegisterClickHandler(); AnimateIn(); return; + // Colors the item according to its game object. void ColorItem() { + Color[] gradient; if (itemGameObject != null) { if (itemGameObject.IsNode()) @@ -156,7 +217,7 @@ void ColorItem() gradient = new[] { Color.gray, Color.gray.Darker() }; } - item.transform.Find("Background").GetComponent().EffectGradient.SetKeys(gradient.ToGradientColorKeys().ToArray(), alphaKeys); + 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(); @@ -165,12 +226,14 @@ void ColorItem() expandIcon.GetComponent().color = foregroundColor; } + // Expands the item by animating its scale. void AnimateIn() { item.transform.localScale = new Vector3(1, 0, 1); item.transform.DOScaleY(1, duration: 0.5f); } + // Registers a click handler for the item. void RegisterClickHandler() { if (item.TryGetComponentOrLog(out PointerHelper pointerHelper)) @@ -198,7 +261,9 @@ void RegisterClickHandler() } else { - expandItem?.Invoke(item); + // Tree should be reordered after this, since the expansion + // originated from the user here. + expandItem?.Invoke(item, true); } } }); @@ -225,15 +290,15 @@ private void RemoveNodeChildren(Node node) // We need to remove the "Outgoing" and "Incoming" buttons if they exist, along with their children. if (n.Outgoings.Count > 0) { - children = appendEdgeChildren("Outgoing", n.Outgoings); + children = AppendEdgeChildren("Outgoing", n.Outgoings); } if (n.Incomings.Count > 0) { - children = appendEdgeChildren("Incoming", n.Incomings); + children = AppendEdgeChildren("Incoming", n.Incomings); } return children; - IEnumerable<(string, Node)> appendEdgeChildren(string edgeType, IEnumerable edges) + IEnumerable<(string, Node)> AppendEdgeChildren(string edgeType, IEnumerable edges) { return children.Append((cleanId + "#" + edgeType, null)) .Concat(edges.Select(x => ($"{cleanId}#{edgeType}#{CleanupID(x.ID)}", null))); @@ -308,8 +373,8 @@ private void CollapseItem(GameObject item) /// /// The node represented by the item. /// The item to be expanded. - /// The game object of the element represented by the node. - private void ExpandNode(Node node, GameObject item, GameObject nodeGameObject) + /// Whether to order the tree after expanding the node. + private void ExpandNode(Node node, GameObject item, bool orderTree = false) { ExpandItem(item); @@ -326,6 +391,10 @@ private void ExpandNode(Node node, GameObject item, GameObject nodeGameObject) { AddEdgeButton("Incoming", incomingEdgeUnicode, node.Incomings); } + if (orderTree) + { + OrderTree(node); + } return; void AddEdgeButton(string edgesType, char icon, ICollection edges) @@ -335,21 +404,25 @@ 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, nodeGameObject, - i => + AddItem(id, edges.Count, $"{edgesType} Edges", node.Level + 1, icon, itemGameObject: null, + collapsedItem => { - CollapseItem(i); + CollapseItem(collapsedItem); foreach (Edge edge in edges) { RemoveItem($"{id}#{CleanupID(edge.ID)}"); } - }, i => + }, (expandedItem, order) => { - ExpandItem(i); + ExpandItem(expandedItem); foreach (Edge edge in edges) { GameObject edgeObject = GraphElementIDMap.Find(edge.ID); - AddItem($"{id}#{CleanupID(edge.ID)}", id, 0, edge.ToShortString(), node.Level + 2, edgeTypeUnicode, edgeObject, null, null); + AddItem($"{id}#{CleanupID(edge.ID)}", 0, edge.ToShortString(), node.Level + 2, edgeTypeUnicode, edgeObject, null, null); + } + if (order) + { + OrderTree(node); } }); } @@ -378,14 +451,15 @@ private void SearchFor(string searchTerm) if (searchTerm == null || searchTerm.Trim().Length == 0) { AddRoots(); + return; } foreach (Node node in searcher.Search(searchTerm)) { GameObject nodeGameObject = GraphElementIDMap.Find(node.ID, mustFindElement: true); - AddItem(CleanupID(node.ID), null, + AddItem(CleanupID(node.ID), 0, node.ToShortString(), 0, nodeTypeUnicode, nodeGameObject, - null, _ => MakeVisible(node).Forget()); + null, (_, _) => MakeVisible(node).Forget()); } items.position = items.position.WithXYZ(y: 0); @@ -413,12 +487,9 @@ private async UniTaskVoid MakeVisible(Node node) expandedItems.Add(CleanupID(current.ID)); } - // FIXME: Node sometimes gets placed at wrong spot. This seems to be a very annoying racing condition. - // The only fix I can think of involves creating a data structure that represents the tree view - // and then using that to set the sibling indices of the items all at once. AddRoots(); - // We need to wait until the transform actually exists. + // We need to wait until the transform actually exists, hence the yield. await UniTask.Yield(); RectTransform item = (RectTransform)items.Find(CleanupID(node.ID)); scrollRect.ScrollTo(item, duration: 1f); From 93c51522a60fedd4ad98076c41d77aa64fd6e5d7 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sat, 28 Oct 2023 01:57:46 +0200 Subject: [PATCH 10/11] Remove SearchMenu --- .../Prefabs/Players/DesktopPlayer.prefab | 19 +- Assets/SEE/GameObjects/Menu/SearchMenu.cs | 225 ------------------ .../SEE/GameObjects/Menu/SearchMenu.cs.meta | 3 - Assets/SEE/UI/Menu/NestedMenu.cs | 3 +- 4 files changed, 5 insertions(+), 245 deletions(-) delete mode 100644 Assets/SEE/GameObjects/Menu/SearchMenu.cs delete mode 100644 Assets/SEE/GameObjects/Menu/SearchMenu.cs.meta diff --git a/Assets/Resources/Prefabs/Players/DesktopPlayer.prefab b/Assets/Resources/Prefabs/Players/DesktopPlayer.prefab index a02138142b..402ec8f478 100644 --- a/Assets/Resources/Prefabs/Players/DesktopPlayer.prefab +++ b/Assets/Resources/Prefabs/Players/DesktopPlayer.prefab @@ -16,7 +16,6 @@ GameObject: - component: {fileID: -1556465120740209359} - component: {fileID: 2429369898342928831} - component: {fileID: 6840467278216287030} - - component: {fileID: 6800333804276665222} - component: {fileID: 182833635018827598} - component: {fileID: 9190471872809942182} - component: {fileID: 403608537903628731} @@ -122,9 +121,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 49626a155a935004f985ad777a29244b, type: 3} m_Name: m_EditorClassIdentifier: - move: 0 - clickDown: 0 - clickUp: 0 + Move: 0 + ClickDown: 0 + ClickUp: 0 --- !u!114 &637028239384079898 MonoBehaviour: m_ObjectHideFlags: 0 @@ -169,18 +168,6 @@ AudioListener: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6839967699279661599} m_Enabled: 1 ---- !u!114 &6800333804276665222 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 6839967699279661599} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 3e7d657fc8c84c81b2d2918eac9c228e, type: 3} - m_Name: - m_EditorClassIdentifier: --- !u!114 &182833635018827598 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/SEE/GameObjects/Menu/SearchMenu.cs b/Assets/SEE/GameObjects/Menu/SearchMenu.cs deleted file mode 100644 index 36342ec304..0000000000 --- a/Assets/SEE/GameObjects/Menu/SearchMenu.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using FuzzySharp; -using SEE.Controls; -using SEE.Game; -using SEE.Game.Operator; -using SEE.UI.Menu; -using SEE.UI.Notification; -using SEE.UI.PropertyDialog; -using UnityEngine; - -namespace SEE.GO.Menu -{ - /// - /// A menu which allows its user to fuzzy search for nodes by entering the - /// source name of a node. - /// - public class SearchMenu : MonoBehaviour - { - /// - /// The time (in seconds) the found node will blink. - /// - private const float blinkSeconds = 15; - - /// - /// The dialog in which the search query can be entered. - /// - private PropertyDialog searchDialog; - - /// - /// The property which contains the searched query. - /// - private StringProperty searchString; - - /// - /// The menu in which the search results are listed. - /// The user can select the desired node here. - /// - private SimpleListMenu resultMenu; - - /// - /// 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 - /// time a search is executed. Note that this implies that changes to the cities while the game is running - /// will not be reflected in the search. - /// - private readonly IDictionary> cachedNodes = new Dictionary>(); - - /// - /// A list containing all entries in the . - /// - /// This is not an because the ForEach function is not defined for the - /// interface, which is used in . - private readonly List resultMenuEntries = new(); - - /// - /// Executes the search with the values entered in the . - /// - private void ExecuteSearch() - { - SEEInput.KeyboardShortcutsEnabled = true; - // Format: (score, name, found game object) - IEnumerable<(int, string, GameObject)> results = - Process.ExtractTop(FilterString(searchString.Value), cachedNodes.Keys) - .Where(x => x.Score > 0) // results with score 0 are usually garbage - .SelectMany(x => cachedNodes[x.Value].Select(y => (x.Score, x.Value, y))) - .ToList(); - - // In cases there are duplicates in the result, we append the filename too - HashSet encounteredNames = new(); - results = results.GroupBy(x => x.Item2) - .SelectMany(x => x.Select((entry, i) => (entry, index: i))) - .Select(x => - { - ((int score, string name, GameObject gameObject) entry, int index) = x; - if (index <= 0) - { - return entry; - } - else - { - string newName = $"{entry.name} ({entry.gameObject.GetNode().SourceFile ?? index.ToString()})"; - if (!encounteredNames.Contains(newName)) - { - encounteredNames.Add(newName); - } - else - { - // If this node exists multiple times within this filename, - // we append the index to it. - newName += $" ({index.ToString()})"; - } - return (entry.score, newName, entry.gameObject); - } - }).ToList(); - - switch (results.Count()) - { - case 0: - ShowNotification.Warn("No nodes found", "No nodes found for the search term " - + $"'{FilterString(searchString.Value)}'."); - break; - case 1: - HighlightNode(results.First().Item3); - break; - default: - ShowResultsMenu(results); - break; - } - } - - /// - /// This will show a menu with search results to the user in case more than one node was found. - /// - /// A list of found nodes represented by 3-tuples in the format - /// [score (from fuzzy search), name, node game object]. - private void ShowResultsMenu(IEnumerable<(int, string, GameObject)> results) - { - if (resultMenu == null) - { - // Initialize result menu - resultMenu = gameObject.AddComponent(); - resultMenu.Title = "Search Results"; - resultMenu.Description = "Please select the node you wish to highlight."; - resultMenu.Icon = Resources.Load("Materials/ModernUIPack/Search"); - } - - // 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), 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) - { - HighlightNode(chosen); - resultMenu.ShowMenu = false; - } - } - - /// - /// Returns a color between black and gray, the higher the given score the grayer it is. - /// - /// score for the shade of gray - /// shade of gray - private static Color ScoreColor(int score) => Color.Lerp(Color.gray, Color.white, score / 100f); - - /// - /// Highlights the given > node with the name - /// by displaying a marker above it and making it blink. - /// - /// 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) - { - NodeOperator nodeOperator = result.NodeOperator(); - nodeOperator.Highlight(blinkSeconds); - } - - /// - /// Removes the zero-width-space from the given , as well as whitespace at the - /// beginning and end. - /// - /// The string which shall be filtered. - /// The filtered string. - public static string FilterString(string input) - { - const string zeroWidthSpace = "\u200B"; - return input.Trim().Replace(zeroWidthSpace, string.Empty); - } - - /// - /// Constructs the and the . - /// - private void Start() - { - // Save all nodes in the scene to quickly search them later on - foreach (GameObject node in SceneQueries.AllGameNodesInScene(true, true)) - { - string sourceName = node.GetNode().SourceName; - if (!cachedNodes.ContainsKey(sourceName)) - { - cachedNodes[sourceName] = new List(); - } - cachedNodes[sourceName].Add(node); - } - - searchString = gameObject.AddComponent(); - searchString.Name = "Source name"; - searchString.Description = "The name of the source code component to search for."; - - PropertyGroup group = gameObject.AddComponent(); - group.Name = "Search parameters"; - group.AddProperty(searchString); - - searchDialog = gameObject.AddComponent(); - searchDialog.Title = "Search node"; - searchDialog.Description = "Enter the node name you wish to search for."; - searchDialog.Icon = Resources.Load("Materials/ModernUIPack/Search"); - searchDialog.AddGroup(group); - - // Re-enable keyboard shortcuts on cancel - searchDialog.OnCancel.AddListener(() => SEEInput.KeyboardShortcutsEnabled = true); - searchDialog.OnConfirm.AddListener(ExecuteSearch); - - //TODO: Bool properties (leaves, nodes) - //TODO: Selection property (select city) - } - - /// - /// Checks whether the shall be opened. - /// - private void Update() - { - if (SEEInput.ToggleSearch()) - { - searchDialog.DialogShouldBeShown = true; - SEEInput.KeyboardShortcutsEnabled = false; - } - } - } -} diff --git a/Assets/SEE/GameObjects/Menu/SearchMenu.cs.meta b/Assets/SEE/GameObjects/Menu/SearchMenu.cs.meta deleted file mode 100644 index 7ddc3ff609..0000000000 --- a/Assets/SEE/GameObjects/Menu/SearchMenu.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 3e7d657fc8c84c81b2d2918eac9c228e -timeCreated: 1619441931 \ No newline at end of file diff --git a/Assets/SEE/UI/Menu/NestedMenu.cs b/Assets/SEE/UI/Menu/NestedMenu.cs index 050c6844e1..dfd57f1297 100644 --- a/Assets/SEE/UI/Menu/NestedMenu.cs +++ b/Assets/SEE/UI/Menu/NestedMenu.cs @@ -3,6 +3,7 @@ using System.Linq; using FuzzySharp; using SEE.Controls; +using SEE.DataModel.DG; using SEE.GO; using SEE.GO.Menu; using TMPro; @@ -256,7 +257,7 @@ private void SearchTextEntered(string text) } allEntries ??= GetAllEntries().ToDictionary(x => x.Title, x => x); - IEnumerable results = Process.ExtractTop(SearchMenu.FilterString(text), allEntries.Keys, cutoff: 10) + IEnumerable results = Process.ExtractTop(GraphSearch.FilterString(text), allEntries.Keys, cutoff: 10) .OrderByDescending(x => x.Score) .Select(x => allEntries[x.Value]) .ToList(); From 6900a90eab63d92162e836507af66454c7072fa9 Mon Sep 17 00:00:00 2001 From: Falko Galperin Date: Sat, 28 Oct 2023 11:43:27 +0200 Subject: [PATCH 11/11] Fix issues noted in code review by @koschke --- Assets/SEE/DataModel/DG/GraphSearch.cs | 17 +++++++++++++++-- .../UI/Window/TreeWindow/DesktopTreeWindow.cs | 1 - Assets/SEE/Utils/ColorExtensions.cs | 9 +++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Assets/SEE/DataModel/DG/GraphSearch.cs b/Assets/SEE/DataModel/DG/GraphSearch.cs index e27878459d..44fb8300b8 100644 --- a/Assets/SEE/DataModel/DG/GraphSearch.cs +++ b/Assets/SEE/DataModel/DG/GraphSearch.cs @@ -6,7 +6,7 @@ namespace SEE.DataModel.DG { /// - /// Allows searching for nodes by their string representation. + /// Allows searching for nodes by their source name. /// The graph associated to this search may be dynamic – that is, when the graph changes /// (for example, when a node is added), the search index will be updated accordingly. /// Searches are fuzzy, i.e., they will return results even if the query does not match the @@ -40,7 +40,9 @@ public GraphSearch(Graph graph) } /// - /// Performs a fuzzy search for the given in the graph. + /// Performs a fuzzy search for the given in the graph, + /// by comparing it to the source name of the nodes. + /// Case will be ignored, and the query may be a substring of the source name (this is a fuzzy search). /// /// The query to be searched for. /// A list of nodes which match the query. @@ -108,16 +110,27 @@ private static string ElementToString(Node element) return element.SourceName.ToLowerInvariant(); } + /// + /// Called when no more events will be fired from the graph. + /// public void OnCompleted() { // Nothing to be done. } + /// + /// Called when an error occurs in the graph. + /// + /// The error which occurred. public void OnError(Exception error) { throw error; } + /// + /// Called when a new event is fired from the graph. + /// + /// The event which was fired. public void OnNext(ChangeEvent changeEvent) { // We want to update our mapping of names to nodes whenever a node is added or removed. diff --git a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs index 134b322321..d7319460df 100644 --- a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs +++ b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs @@ -68,7 +68,6 @@ public partial class TreeWindow /// The node below which the tree should be ordered. private void OrderTree(Node orderBelow) { - Debug.Log("Ordering tree."); int index = items.Find(CleanupID(orderBelow.ID)).GetSiblingIndex(); OrderTreeRecursive(orderBelow); diff --git a/Assets/SEE/Utils/ColorExtensions.cs b/Assets/SEE/Utils/ColorExtensions.cs index db9a9aa769..2b2d4031d8 100644 --- a/Assets/SEE/Utils/ColorExtensions.cs +++ b/Assets/SEE/Utils/ColorExtensions.cs @@ -56,7 +56,7 @@ public static Color IdealTextColor(this Color backgroundColor) { const int nThreshold = 130; int bgDelta = Convert.ToInt32((backgroundColor.r * 255 * 0.299) + (backgroundColor.g * 255 * 0.587) - + (backgroundColor.b * 255 * 0.114)); + + (backgroundColor.b * 255 * 0.114)); return (255 - bgDelta < nThreshold) ? Color.black : Color.white; } @@ -81,7 +81,12 @@ public static Color Invert(this Color color) /// 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-1))); + return colors.Count switch + { + 0 => new List(), + 1 => new List { new(colors.First(), 0f) }, + var n => colors.Select((c, i) => new GradientColorKey(c, (float)i / (n - 1))) + }; } ///