diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2e519a614d..e0f4039567 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -71,14 +71,17 @@ jobs: steps: - name: Collect bad patterns run: | - if [ -n "$GITHUB_BASE_REF" ] && ! PATTERNS=$(curl -H 'Accept: application/vnd.github.v3.diff' -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -f 'https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.number }}' | ./GitScripts/check_for_bad_patterns.py); then - DELIMITER=7MApgggGyx6C0 - echo "PATTERNS<<$DELIMITER" >> $GITHUB_ENV - echo "$PATTERNS" >> $GITHUB_ENV - echo "$DELIMITER" >> $GITHUB_ENV - else - echo "PATTERNS=none" >> $GITHUB_ENV + if [ -n "$GITHUB_BASE_REF" ]; then + PATTERNS=$(curl -H 'Accept: application/vnd.github.v3.diff' -H 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -f 'https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.number }}' | ./GitScripts/check_for_bad_patterns.py || true) + if [ "$PATTERNS" != "[]" ]; then + DELIMITER=7MApgggGyx6C0 + echo "PATTERNS<<$DELIMITER" >> $GITHUB_ENV + echo "$PATTERNS" >> $GITHUB_ENV + echo "$DELIMITER" >> $GITHUB_ENV + exit 0 + fi fi + echo "PATTERNS=none" >> $GITHUB_ENV - uses: actions/github-script@v6 name: Check for bad patterns with: diff --git a/Assets/Resources/Prefabs/UI/PopupMenu.prefab b/Assets/Resources/Prefabs/UI/PopupMenu.prefab index 2865898c28..dd03a134c3 100644 --- a/Assets/Resources/Prefabs/UI/PopupMenu.prefab +++ b/Assets/Resources/Prefabs/UI/PopupMenu.prefab @@ -160,7 +160,7 @@ MonoBehaviour: m_Top: 0 m_Bottom: 0 m_ChildAlignment: 0 - m_Spacing: 3 + m_Spacing: 0 m_ChildForceExpandWidth: 1 m_ChildForceExpandHeight: 0 m_ChildControlWidth: 1 diff --git a/Assets/Resources/Prefabs/UI/PopupMenuHeading.prefab b/Assets/Resources/Prefabs/UI/PopupMenuHeading.prefab new file mode 100644 index 0000000000..b2c04b9731 --- /dev/null +++ b/Assets/Resources/Prefabs/UI/PopupMenuHeading.prefab @@ -0,0 +1,137 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &4296809060982174892 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8706819387747019311} + - component: {fileID: 2060764360537592436} + - component: {fileID: 3611351358325543688} + m_Layer: 5 + m_Name: PopupMenuHeading + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8706819387747019311 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4296809060982174892} + 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: 0} + 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: 25} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &2060764360537592436 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4296809060982174892} + m_CullTransparentMesh: 1 +--- !u!114 &3611351358325543688 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4296809060982174892} + 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: Node Properties + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: d10d3fbe67cb68d41930a013bc4e2e43, type: 2} + m_sharedMaterial: {fileID: 21041790971390992, guid: d10d3fbe67cb68d41930a013bc4e2e43, + 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: 18 + m_fontSizeBase: 18 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + 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: 0 + 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: 0 + 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} diff --git a/Assets/Resources/Prefabs/UI/PopupMenuHeading.prefab.meta b/Assets/Resources/Prefabs/UI/PopupMenuHeading.prefab.meta new file mode 100644 index 0000000000..c147ed91f6 --- /dev/null +++ b/Assets/Resources/Prefabs/UI/PopupMenuHeading.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3ea654c8c3f555028adf5c7eb6451e83 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/Prefabs/UI/TreeView.prefab b/Assets/Resources/Prefabs/UI/TreeView.prefab index dddc220848..d49f18321b 100644 --- a/Assets/Resources/Prefabs/UI/TreeView.prefab +++ b/Assets/Resources/Prefabs/UI/TreeView.prefab @@ -1,5 +1,187 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: +--- !u!1 &429727636846351680 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5000712621006847538} + - component: {fileID: 8552963848894541773} + - component: {fileID: 2697686805765614399} + - component: {fileID: 2678860440501321340} + - component: {fileID: 6642898023618199802} + - component: {fileID: 3052445492809702230} + m_Layer: 5 + m_Name: Group + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &5000712621006847538 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 429727636846351680} + 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: 8967932099786957271} + - {fileID: 8680346654750948472} + m_Father: {fileID: 7868881898829307179} + 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: 55} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &8552963848894541773 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 429727636846351680} + m_CullTransparentMesh: 0 +--- !u!114 &2697686805765614399 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 429727636846351680} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, 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: 1} + m_HighlightedColor: {r: 0.84313726, g: 0.84313726, b: 0.84313726, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.84313726, g: 0.84313726, b: 0.84313726, a: 1} + m_DisabledColor: {r: 1, g: 1, b: 1, a: 0.39215687} + 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: 2678860440501321340} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &2678860440501321340 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 429727636846351680} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.33333334, g: 0.37254903, b: 0.4509804, 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: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 13 +--- !u!114 &6642898023618199802 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 429727636846351680} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1a12ddbc47b17cd478cb447d1113a22b, type: 3} + m_Name: + m_EditorClassIdentifier: + buttonText: "\uF5FD" + clickEvent: + m_PersistentCalls: + m_Calls: [] + hoverEvent: + m_PersistentCalls: + m_Calls: [] + hoverSound: {fileID: 0} + clickSound: {fileID: 0} + buttonVar: {fileID: 0} + normalText: {fileID: 8361415602960380083} + soundSource: {fileID: 0} + rippleParent: {fileID: 6267150986429446833} + useCustomContent: 0 + enableButtonSounds: 0 + useHoverSound: 1 + useClickSound: 1 + useRipple: 1 + rippleUpdateMode: 1 + rippleShape: {fileID: 0} + speed: 1 + maxSize: 4 + startColor: {r: 1, g: 1, b: 1, a: 1} + transitionColor: {r: 1, g: 1, b: 1, a: 0} + renderOnTop: 0 + centered: 0 +--- !u!114 &3052445492809702230 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 429727636846351680} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreLayout: 0 + m_MinWidth: 55 + m_MinHeight: -1 + m_PreferredWidth: 55 + m_PreferredHeight: -1 + m_FlexibleWidth: -1 + m_FlexibleHeight: -1 + m_LayoutPriority: 1 --- !u!1 &842951385601223108 GameObject: m_ObjectHideFlags: 0 @@ -14,6 +196,7 @@ GameObject: - component: {fileID: 5081112725215650259} - component: {fileID: 8918530655449257547} - component: {fileID: 3444143981090016800} + - component: {fileID: 4789464308854986259} m_Layer: 5 m_Name: SearchField m_TagString: Untagged @@ -38,10 +221,10 @@ RectTransform: - {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.749939, y: 0} - m_SizeDelta: {x: -7.500131, y: 55} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 55} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &6756889094794557856 CanvasRenderer: @@ -204,6 +387,26 @@ MonoBehaviour: texts: - {fileID: 3548788532928508177} - {fileID: 1512550248131604402} +--- !u!114 &4789464308854986259 +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: 306cc8c2b49d7114eaa3623786fc2126, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreLayout: 0 + m_MinWidth: 100 + m_MinHeight: -1 + m_PreferredWidth: -1 + m_PreferredHeight: -1 + m_FlexibleWidth: 100 + m_FlexibleHeight: -1 + m_LayoutPriority: 3 --- !u!1 &894369973855324406 GameObject: m_ObjectHideFlags: 0 @@ -279,6 +482,141 @@ MonoBehaviour: m_FillOrigin: 0 m_UseSpriteMesh: 0 m_PixelsPerUnitMultiplier: 15 +--- !u!1 &1391077163789461073 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5693542113043864410} + - component: {fileID: 4202743844568988407} + - component: {fileID: 2494380858770498350} + m_Layer: 5 + m_Name: Icon + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &5693542113043864410 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1391077163789461073} + 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: 7683108501662694733} + 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: -25, y: -25} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &4202743844568988407 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1391077163789461073} + m_CullTransparentMesh: 0 +--- !u!114 &2494380858770498350 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1391077163789461073} + 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: "\uF0B0" + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 4ebb98a3c87fa521a888029274c92b79, type: 2} + m_sharedMaterial: {fileID: -8620075009897487826, guid: 4ebb98a3c87fa521a888029274c92b79, + 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: 36 + m_fontSizeBase: 36 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + 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: 0 + 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: 0 + 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 &1512550248131604402 GameObject: m_ObjectHideFlags: 0 @@ -414,7 +752,7 @@ MonoBehaviour: m_hasFontAssetChanged: 0 m_baseMaterial: {fileID: 0} m_maskOffset: {x: 0, y: 0, z: 0, w: 0} ---- !u!1 &2851445155489688032 +--- !u!1 &2476908023509198667 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -422,34 +760,169 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 8846550913904393515} - - component: {fileID: 115155201325811548} - - component: {fileID: 2881518792493254415} + - component: {fileID: 9195632984993411493} + - component: {fileID: 7822887819180324640} + - component: {fileID: 4605941648409354567} m_Layer: 5 - m_Name: Background + m_Name: Icon m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &8846550913904393515 +--- !u!224 &9195632984993411493 RectTransform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2851445155489688032} - m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_GameObject: {fileID: 2476908023509198667} + 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: 2059283171776054673} + m_Father: {fileID: 3416948009511138372} 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: -10, y: -10} + m_SizeDelta: {x: -25, y: -25} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7822887819180324640 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2476908023509198667} + m_CullTransparentMesh: 0 +--- !u!114 &4605941648409354567 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2476908023509198667} + 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: "\uF0DC" + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 4ebb98a3c87fa521a888029274c92b79, type: 2} + m_sharedMaterial: {fileID: -8620075009897487826, guid: 4ebb98a3c87fa521a888029274c92b79, + 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: 36 + m_fontSizeBase: 36 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + 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: 0 + 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: 0 + 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 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8846550913904393515} + - component: {fileID: 115155201325811548} + - component: {fileID: 2881518792493254415} + m_Layer: 5 + m_Name: Background + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8846550913904393515 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2851445155489688032} + 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: 2059283171776054673} + 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: -10, y: -10} m_Pivot: {x: 0.5, y: 0.5} --- !u!222 &115155201325811548 CanvasRenderer: @@ -736,6 +1209,141 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: -10, y: -10} m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &4280109293398571001 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8967932099786957271} + - component: {fileID: 177693816970757469} + - component: {fileID: 8361415602960380083} + m_Layer: 5 + m_Name: Icon + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8967932099786957271 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4280109293398571001} + 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: 5000712621006847538} + 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: -25, y: -25} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &177693816970757469 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4280109293398571001} + m_CullTransparentMesh: 0 +--- !u!114 &8361415602960380083 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4280109293398571001} + 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: "\uF5FD" + m_isRightToLeft: 0 + m_fontAsset: {fileID: 11400000, guid: 4ebb98a3c87fa521a888029274c92b79, type: 2} + m_sharedMaterial: {fileID: -8620075009897487826, guid: 4ebb98a3c87fa521a888029274c92b79, + 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: 36 + m_fontSizeBase: 36 + m_fontWeight: 400 + m_enableAutoSizing: 0 + m_fontSizeMin: 18 + m_fontSizeMax: 72 + m_fontStyle: 0 + m_HorizontalAlignment: 2 + 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: 0 + 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: 0 + 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 &4431518105055718632 GameObject: m_ObjectHideFlags: 0 @@ -811,6 +1419,188 @@ MonoBehaviour: m_FillOrigin: 0 m_UseSpriteMesh: 0 m_PixelsPerUnitMultiplier: 10 +--- !u!1 &5043279403120338259 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7683108501662694733} + - component: {fileID: 1994657440880389828} + - component: {fileID: 4745195452347766396} + - component: {fileID: 479753717333530281} + - component: {fileID: 6777758793146567997} + - component: {fileID: 3913614832241006454} + m_Layer: 5 + m_Name: Filter + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7683108501662694733 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5043279403120338259} + 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: 5693542113043864410} + - {fileID: 3512017188549587436} + m_Father: {fileID: 7868881898829307179} + 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: 55} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1994657440880389828 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5043279403120338259} + m_CullTransparentMesh: 0 +--- !u!114 &4745195452347766396 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5043279403120338259} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, 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: 1} + m_HighlightedColor: {r: 0.84313726, g: 0.84313726, b: 0.84313726, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.84313726, g: 0.84313726, b: 0.84313726, a: 1} + m_DisabledColor: {r: 1, g: 1, b: 1, a: 0.39215687} + 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: 479753717333530281} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &479753717333530281 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5043279403120338259} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.33333334, g: 0.37254903, b: 0.4509804, 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: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 13 +--- !u!114 &6777758793146567997 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5043279403120338259} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1a12ddbc47b17cd478cb447d1113a22b, type: 3} + m_Name: + m_EditorClassIdentifier: + buttonText: "\uF0B0" + clickEvent: + m_PersistentCalls: + m_Calls: [] + hoverEvent: + m_PersistentCalls: + m_Calls: [] + hoverSound: {fileID: 0} + clickSound: {fileID: 0} + buttonVar: {fileID: 0} + normalText: {fileID: 2494380858770498350} + soundSource: {fileID: 0} + rippleParent: {fileID: 9139761773161578503} + useCustomContent: 0 + enableButtonSounds: 0 + useHoverSound: 1 + useClickSound: 1 + useRipple: 1 + rippleUpdateMode: 1 + rippleShape: {fileID: 0} + speed: 1 + maxSize: 4 + startColor: {r: 1, g: 1, b: 1, a: 1} + transitionColor: {r: 1, g: 1, b: 1, a: 0} + renderOnTop: 0 + centered: 0 +--- !u!114 &3913614832241006454 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5043279403120338259} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreLayout: 0 + m_MinWidth: 55 + m_MinHeight: -1 + m_PreferredWidth: 55 + m_PreferredHeight: -1 + m_FlexibleWidth: -1 + m_FlexibleHeight: -1 + m_LayoutPriority: 1 --- !u!1 &5181310319957536929 GameObject: m_ObjectHideFlags: 0 @@ -820,6 +1610,7 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 7868881898829307179} + - component: {fileID: 3493787011031423823} m_Layer: 5 m_Name: Search m_TagString: Untagged @@ -840,14 +1631,177 @@ RectTransform: m_ConstrainProportionsScale: 0 m_Children: - {fileID: 6206375982450128193} + - {fileID: 5000712621006847538} + - {fileID: 7683108501662694733} + - {fileID: 3416948009511138372} m_Father: {fileID: 8823517661499080595} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 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_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -10} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &3493787011031423823 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5181310319957536929} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 30649d3a9faa99c48a7b1166b86bf2a0, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 5 + m_Right: 5 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 3 + m_Spacing: 5 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 1 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!1 &5673117406039939321 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5705055065613871648} + - component: {fileID: 1794437172805276588} + - component: {fileID: 6681791915537918917} + - component: {fileID: 6091450123876866387} + m_Layer: 5 + m_Name: Ripple + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &5705055065613871648 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5673117406039939321} + 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: 3416948009511138372} + 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 &1794437172805276588 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5673117406039939321} + m_CullTransparentMesh: 0 +--- !u!114 &6681791915537918917 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5673117406039939321} + 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: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 951352f31055aae46b6e9786313c632d, 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: 12 +--- !u!114 &6091450123876866387 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5673117406039939321} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ShowMaskGraphic: 0 +--- !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!1 &5756927395396884001 +--- !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 &6148420533256328260 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -855,43 +1809,180 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 3073758193502664746} - - component: {fileID: 5446393462633531186} + - component: {fileID: 3416948009511138372} + - component: {fileID: 3163452583473062481} + - component: {fileID: 6425330908644344478} + - component: {fileID: 4311322798690598591} + - component: {fileID: 3761970443406164293} + - component: {fileID: 3784183319226496380} m_Layer: 5 - m_Name: Text Area + m_Name: Sort m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!224 &3073758193502664746 +--- !u!224 &3416948009511138372 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_GameObject: {fileID: 6148420533256328260} + 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} + - {fileID: 9195632984993411493} + - {fileID: 5705055065613871648} + m_Father: {fileID: 7868881898829307179} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 1, y: 1} + m_AnchorMax: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 55} m_Pivot: {x: 0.5, y: 0.5} ---- !u!222 &5446393462633531186 +--- !u!222 &3163452583473062481 CanvasRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 5756927395396884001} + m_GameObject: {fileID: 6148420533256328260} m_CullTransparentMesh: 0 +--- !u!114 &6425330908644344478 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6148420533256328260} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, 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: 1} + m_HighlightedColor: {r: 0.84313726, g: 0.84313726, b: 0.84313726, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.84313726, g: 0.84313726, b: 0.84313726, a: 1} + m_DisabledColor: {r: 1, g: 1, b: 1, a: 0.39215687} + 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: 4311322798690598591} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &4311322798690598591 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6148420533256328260} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.33333334, g: 0.37254903, b: 0.4509804, 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: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 13 +--- !u!114 &3761970443406164293 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6148420533256328260} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1a12ddbc47b17cd478cb447d1113a22b, type: 3} + m_Name: + m_EditorClassIdentifier: + buttonText: "\uF0DC" + clickEvent: + m_PersistentCalls: + m_Calls: [] + hoverEvent: + m_PersistentCalls: + m_Calls: [] + hoverSound: {fileID: 0} + clickSound: {fileID: 0} + buttonVar: {fileID: 0} + normalText: {fileID: 4605941648409354567} + soundSource: {fileID: 0} + rippleParent: {fileID: 5673117406039939321} + useCustomContent: 0 + enableButtonSounds: 0 + useHoverSound: 1 + useClickSound: 1 + useRipple: 1 + rippleUpdateMode: 1 + rippleShape: {fileID: 0} + speed: 1 + maxSize: 4 + startColor: {r: 1, g: 1, b: 1, a: 1} + transitionColor: {r: 1, g: 1, b: 1, a: 0} + renderOnTop: 0 + centered: 0 +--- !u!114 &3784183319226496380 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6148420533256328260} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 306cc8c2b49d7114eaa3623786fc2126, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreLayout: 0 + m_MinWidth: 55 + m_MinHeight: -1 + m_PreferredWidth: 55 + m_PreferredHeight: -1 + m_FlexibleWidth: -1 + m_FlexibleHeight: -1 + m_LayoutPriority: 1 --- !u!1 &6265431863627329327 GameObject: m_ObjectHideFlags: 0 @@ -1005,6 +2096,95 @@ MonoBehaviour: webglMode: 0 background: {fileID: 2881518792493254415} bar: {fileID: 7370997065605906984} +--- !u!1 &6267150986429446833 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8680346654750948472} + - component: {fileID: 6840524763941592153} + - component: {fileID: 8535146521443516864} + - component: {fileID: 7576912764321325546} + m_Layer: 5 + m_Name: Ripple + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8680346654750948472 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6267150986429446833} + 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: 5000712621006847538} + 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 &6840524763941592153 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6267150986429446833} + m_CullTransparentMesh: 0 +--- !u!114 &8535146521443516864 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6267150986429446833} + 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: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 951352f31055aae46b6e9786313c632d, 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: 12 +--- !u!114 &7576912764321325546 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6267150986429446833} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ShowMaskGraphic: 0 --- !u!1 &6821221533684094758 GameObject: m_ObjectHideFlags: 0 @@ -1126,7 +2306,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: 7041327802215014214} m_Father: {fileID: 6104944427379572325} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} @@ -1639,3 +2820,221 @@ MonoBehaviour: callback: m_PersistentCalls: m_Calls: [] +--- !u!1 &9139761773161578503 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3512017188549587436} + - component: {fileID: 4731213525127919656} + - component: {fileID: 588219030636976623} + - component: {fileID: 521566120197899752} + m_Layer: 5 + m_Name: Ripple + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3512017188549587436 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9139761773161578503} + 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: 7683108501662694733} + 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 &4731213525127919656 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9139761773161578503} + m_CullTransparentMesh: 0 +--- !u!114 &588219030636976623 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9139761773161578503} + 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: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 951352f31055aae46b6e9786313c632d, 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: 12 +--- !u!114 &521566120197899752 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9139761773161578503} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ShowMaskGraphic: 0 +--- !u!1001 &6193129183857894167 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 2472446831714888479} + m_Modifications: + - target: {fileID: 3221561598132462901, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_Name + value: TreeViewItem + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_Pivot.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_Pivot.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_AnchorMax.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_AnchorMax.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_AnchorMin.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_AnchorMin.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_SizeDelta.x + value: 797 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_SizeDelta.y + value: 50 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_LocalRotation.x + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_LocalRotation.y + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_AnchoredPosition.x + value: 5 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_AnchoredPosition.y + value: -5 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8844280093565481701, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + propertyPath: m_fontStyle + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: 4e7329bce1107abf6a795e345a0b2270, type: 3} +--- !u!224 &7041327802215014214 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 3766619498664164433, guid: 4e7329bce1107abf6a795e345a0b2270, + type: 3} + m_PrefabInstance: {fileID: 6193129183857894167} + m_PrefabAsset: {fileID: 0} diff --git a/Assets/SEE/Controls/Actions/ContextMenuAction.cs b/Assets/SEE/Controls/Actions/ContextMenuAction.cs index f301c1a5c0..9e8b785df1 100644 --- a/Assets/SEE/Controls/Actions/ContextMenuAction.cs +++ b/Assets/SEE/Controls/Actions/ContextMenuAction.cs @@ -42,13 +42,7 @@ private void Update() } IEnumerable actions = GetApplicableOptions(o.GraphElemRef.Elem, o.gameObject); - - PopupMenu.ClearActions(); - PopupMenu.AddActions(actions); - - // We want to move the popup menu to the cursor position before showing it. - PopupMenu.MoveTo(Input.mousePosition); - PopupMenu.ShowMenu().Forget(); + PopupMenu.ShowWith(actions, Input.mousePosition); } } @@ -81,19 +75,19 @@ private static IEnumerable GetCommonOptions(GraphElement graphE IList actions = new List { // TODO (#665): Ask for confirmation or allow undo. - new("Delete", DeleteElement, '\uF1F8'), + new("Delete", DeleteElement, Icons.Trash), // TODO (#666): Better properties view - new("Properties", ShowProperties, '\uF05A'), + new("Properties", ShowProperties, Icons.Info), }; if (gameObject != null) { - actions.Add(new("Highlight", Highlight, '\uF0EB')); + actions.Add(new("Highlight", Highlight, Icons.LightBulb)); } if (graphElement.Filename() != null) { - actions.Add(new("Show Code", ShowCode, '\uF121')); + actions.Add(new("Show Code", ShowCode, Icons.Code)); } return actions; @@ -179,7 +173,7 @@ private static IEnumerable GetNodeOptions(Node node, GameObject { IList actions = new List { - new("Show in TreeView", RevealInTreeView, '\uF802'), + new("Show in TreeView", RevealInTreeView, Icons.TreeView), }; return actions; @@ -200,18 +194,18 @@ private static IEnumerable GetEdgeOptions(Edge edge, GameObject { IList actions = new List { - new("Show at Source (TreeView)", RevealAtSource, '\uF802'), - new("Show at Target (TreeView)", RevealAtTarget, '\uF802'), + new("Show at Source (TreeView)", RevealAtSource, Icons.TreeView), + new("Show at Target (TreeView)", RevealAtTarget, Icons.TreeView), }; if (edge.Type == "Clone") { - actions.Add(new("Show Unified Diff", ShowUnifiedDiff, '\uE13A')); + actions.Add(new("Show Unified Diff", ShowUnifiedDiff, Icons.Compare)); } if (edge.IsInImplementation() && ReflexionGraph.IsDivergent(edge)) { - actions.Add(new("Accept Divergence", AcceptDivergence, '\uF00C')); + actions.Add(new("Accept Divergence", AcceptDivergence, Icons.Checkmark)); } return actions; diff --git a/Assets/SEE/Controls/Actions/MoveAction.cs b/Assets/SEE/Controls/Actions/MoveAction.cs index 9ae50c3fa8..919b1cf2db 100644 --- a/Assets/SEE/Controls/Actions/MoveAction.cs +++ b/Assets/SEE/Controls/Actions/MoveAction.cs @@ -588,17 +588,14 @@ private void UpdateHierarchy() if (Raycasting.RaycastLowestNode(out RaycastHit? raycastHit, out Node _, grabbedObject.Node)) { // Note: the root node can never be grabbed. See above. + // We need to undo the reparenting of the grabbed node if it is + // currently reparented onto another node. + grabbedObject.UnReparent(); if (raycastHit.HasValue) { // The user is currently aiming at a node. The grabbed node is reparented onto this aimed node. grabbedObject.Reparent(raycastHit.Value.transform.gameObject); } - else - { - // The user is currently not aiming at a node. The reparenting - // of the grabbed must be reverted. - grabbedObject.UnReparent(); - } } } } diff --git a/Assets/SEE/DataModel/DG/Attributable.cs b/Assets/SEE/DataModel/DG/Attributable.cs index af3451de34..2de26ba4ec 100644 --- a/Assets/SEE/DataModel/DG/Attributable.cs +++ b/Assets/SEE/DataModel/DG/Attributable.cs @@ -198,7 +198,7 @@ public bool TryGetNumeric(string attributeName, out float value) /// /// Returns the value of a numeric (integer or float) attribute for the - /// attributed named if it exists. + /// attribute named if it exists. /// Otherwise an exception is thrown. /// /// Note: It could happen that the same name is given to a float and @@ -222,6 +222,85 @@ public float GetNumeric(string attributeName) } } + /// + /// Returns the value of an attribute of any type (integer, float, string, or toggle) + /// for the attribute named if it exists. + /// Otherwise an exception is thrown. + /// + /// In case of a toggle attribute, the value is always + /// if the attribute exists. + /// + /// Note: It could happen that the same name is given to different attribute types, + /// in which case the preference is as follows: float, integer, string, toggle. + /// + /// name of an attribute + /// value of attribute + /// if is not an attribute of this node + public object GetAny(string attributeName) + { + if (FloatAttributes.TryGetValue(attributeName, out float floatValue)) + { + return floatValue; + } + else if (IntAttributes.TryGetValue(attributeName, out int intValue)) + { + return intValue; + } + else if (StringAttributes.TryGetValue(attributeName, out string stringValue)) + { + return stringValue; + } + else if (toggleAttributes.Contains(attributeName)) + { + return UnitType.Unit; + } + else + { + throw new UnknownAttribute(attributeName); + } + } + + /// + /// Returns true if is the name of an attribute + /// of any type (integer, float, string, or toggle) of this node, and if so, + /// sets to the value of that attribute. + /// Otherwise is set to null and false is returned. + /// + /// Note: It could happen that the same name is given to different attribute types, + /// in which case the preference is as follows: float, integer, string, toggle. + /// + /// name of an attribute + /// value of attribute + /// whether is the name of an attribute of this node + public bool TryGetAny(string attributeName, out object value) + { + if (FloatAttributes.TryGetValue(attributeName, out float floatValue)) + { + value = floatValue; + return true; + } + else if (IntAttributes.TryGetValue(attributeName, out int intValue)) + { + value = intValue; + return true; + } + else if (StringAttributes.TryGetValue(attributeName, out string stringValue)) + { + value = stringValue; + return true; + } + else if (toggleAttributes.Contains(attributeName)) + { + value = UnitType.Unit; + return true; + } + else + { + value = null; + return false; + } + } + /// /// Returns the values of all numeric (int and float) attributes of this node. /// @@ -455,4 +534,4 @@ protected virtual void HandleCloned(object clone) target.IntAttributes = new Dictionary(IntAttributes); } } -} \ No newline at end of file +} diff --git a/Assets/SEE/DataModel/DG/Graph.cs b/Assets/SEE/DataModel/DG/Graph.cs index 70493424cb..d40a2cc406 100644 --- a/Assets/SEE/DataModel/DG/Graph.cs +++ b/Assets/SEE/DataModel/DG/Graph.cs @@ -483,18 +483,22 @@ public void RemoveElement(GraphElement element) } /// - /// Returns the names of all node types of this graph + /// Returns the names of all node types of this graph. /// /// node types of this graph - internal HashSet AllNodeTypes() - { - HashSet result = new(); - foreach (Node node in Nodes()) - { - result.Add(node.Type); - } - return result; - } + internal HashSet AllNodeTypes() => Nodes().Select(n => n.Type).ToHashSet(); + + /// + /// Returns the names of all edge types of this graph. + /// + /// edge types of this graph + internal HashSet AllEdgeTypes() => Edges().Select(e => e.Type).ToHashSet(); + + /// + /// Returns the names of all element types of this graph. + /// + /// element types of this graph + internal HashSet AllElementTypes() => Elements().Select(e => e.Type).ToHashSet(); /// /// The number of nodes of the graph. diff --git a/Assets/SEE/DataModel/DG/GraphElementsAttributes.cs b/Assets/SEE/DataModel/DG/GraphElementsAttributes.cs index 2dde2deaaa..94ebd13654 100644 --- a/Assets/SEE/DataModel/DG/GraphElementsAttributes.cs +++ b/Assets/SEE/DataModel/DG/GraphElementsAttributes.cs @@ -173,7 +173,7 @@ public ISet AllNumericEdgeAttributes() /// Returns the union of /// and . /// - /// names of all numeric (int or float) node attributes + /// names of all numeric (int or float) attributes public ISet AllNumericAttributes() { ISet result = AllNumericNodeAttributes(); @@ -181,6 +181,18 @@ public ISet AllNumericAttributes() return result; } + /// + /// Returns the union of + /// and . + /// + /// names of all string attributes + public ISet AllStringAttributes() + { + ISet result = AllStringNodeAttributes(); + result.UnionWith(AllStringEdgeAttributes()); + return result; + } + /// /// Returns the union of the names of all numeric node attributes of the given . /// diff --git a/Assets/SEE/DataModel/DG/GraphExtensions.cs b/Assets/SEE/DataModel/DG/GraphExtensions.cs index 4ef91a2f4f..bb0be090e9 100644 --- a/Assets/SEE/DataModel/DG/GraphExtensions.cs +++ b/Assets/SEE/DataModel/DG/GraphExtensions.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; +using SEE.DataModel.GraphSearch; namespace SEE.DataModel.DG { /// - /// Provides extensions to . + /// Provides extensions to and related classes. /// public static class GraphExtensions { @@ -125,5 +126,18 @@ public static IGraphElementDiff AttributeDiff(params Graph[] graphs) }); return new AttributeDiff(floatAttributes, intAttributes, stringAttributes, toggleAttributes); } + + /// + /// Applies all to the given . + /// + /// graph modifiers to apply to the graph elements + /// the graph elements to modify + /// the type of the graph elements + /// the modified graph elements + public static IEnumerable ApplyAll(this IEnumerable modifiers, IEnumerable elements) + where T : GraphElement + { + return modifiers.Aggregate(elements, (current, modifier) => modifier.Apply(current)); + } } } diff --git a/Assets/SEE/DataModel/DG/GraphSearch.cs.meta b/Assets/SEE/DataModel/DG/GraphSearch.cs.meta deleted file mode 100644 index e655afaa6b..0000000000 --- a/Assets/SEE/DataModel/DG/GraphSearch.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 15f8fbf868d74f6d940b2d0d614f5dd7 -timeCreated: 1697754642 \ No newline at end of file diff --git a/Assets/SEE/DataModel/GraphSearch.meta b/Assets/SEE/DataModel/GraphSearch.meta new file mode 100644 index 0000000000..3dc0c05a73 --- /dev/null +++ b/Assets/SEE/DataModel/GraphSearch.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5576bb27322b498189cc83975680c415 +timeCreated: 1700171649 \ No newline at end of file diff --git a/Assets/SEE/DataModel/GraphSearch/GraphFilter.cs b/Assets/SEE/DataModel/GraphSearch/GraphFilter.cs new file mode 100644 index 0000000000..4879a206bb --- /dev/null +++ b/Assets/SEE/DataModel/GraphSearch/GraphFilter.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Linq; +using SEE.DataModel.DG; + +namespace SEE.DataModel.GraphSearch +{ + /// + /// A configurable filter for graph elements, mainly intended for use with . + /// + public record GraphFilter : IGraphModifier + { + /// + /// A set of toggle attributes of which at least one must be present in a graph element for it to be included. + /// + public readonly ISet IncludeToggleAttributes = new HashSet(); + + /// + /// A set of toggle attributes of which none must be present in a graph element for it to be included. + /// + public readonly ISet ExcludeToggleAttributes = new HashSet(); + + /// + /// Elements that should always be excluded. + /// + public readonly ISet ExcludeElements = new HashSet(); + + /// + /// Whether to include nodes. + /// + public bool IncludeNodes = true; + + /// + /// Whether to include edges. + /// + public bool IncludeEdges = true; + + /// + /// Returns whether the given element should be included in the search results. + /// + /// The element to check. + /// The type of the element. + /// Whether the element should be included. + public bool Includes(T element) where T : GraphElement + { + return element switch + { + Node => IncludeNodes, + Edge => IncludeEdges, + _ => false + } + && (IncludeToggleAttributes.Count == 0 || IncludeToggleAttributes.Overlaps(element.ToggleAttributes)) + && !ExcludeElements.Contains(element) + && !ExcludeToggleAttributes.Overlaps(element.ToggleAttributes); + } + + /// + /// Applies the filter to the given elements, that is, returns only those elements that are included. + /// + /// The elements to filter. + /// The type of the elements. + /// The filtered elements. + public IEnumerable Apply(IEnumerable elements) where T : GraphElement + { + return elements.Where(Includes); + } + + /// + /// Implements . + /// + public bool IsActive() => !IncludeNodes || !IncludeEdges + || IncludeToggleAttributes.Count > 0 || ExcludeToggleAttributes.Count > 0 || ExcludeElements.Count > 0; + + /// + /// Implements . + /// + public void Reset() + { + IncludeToggleAttributes.Clear(); + ExcludeToggleAttributes.Clear(); + ExcludeElements.Clear(); + IncludeNodes = true; + IncludeEdges = true; + } + } +} diff --git a/Assets/SEE/DataModel/GraphSearch/GraphFilter.cs.meta b/Assets/SEE/DataModel/GraphSearch/GraphFilter.cs.meta new file mode 100644 index 0000000000..c6e41a8396 --- /dev/null +++ b/Assets/SEE/DataModel/GraphSearch/GraphFilter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f40d8a0e41f0452d833e5d9df6c481b2 +timeCreated: 1700171708 \ No newline at end of file diff --git a/Assets/SEE/DataModel/DG/GraphSearch.cs b/Assets/SEE/DataModel/GraphSearch/GraphSearch.cs similarity index 81% rename from Assets/SEE/DataModel/DG/GraphSearch.cs rename to Assets/SEE/DataModel/GraphSearch/GraphSearch.cs index 44fb8300b8..1c5c29ee96 100644 --- a/Assets/SEE/DataModel/DG/GraphSearch.cs +++ b/Assets/SEE/DataModel/GraphSearch/GraphSearch.cs @@ -2,8 +2,9 @@ using System.Collections.Generic; using System.Linq; using FuzzySharp; +using SEE.DataModel.DG; -namespace SEE.DataModel.DG +namespace SEE.DataModel.GraphSearch { /// /// Allows searching for nodes by their source name. @@ -26,7 +27,22 @@ public class GraphSearch : IObserver /// /// The graph to be searched. /// - private readonly Graph graph; + public readonly Graph Graph; + + /// + /// The filter that is applied to the graph elements when they are searched. + /// + public GraphFilter Filter { get; } = new(); + + /// + /// The sorter that is applied to the graph elements when they are searched. + /// + public GraphSorter Sorter { get; } = new(); + + /// + /// Returns all graph modifiers that shall be applied to the search results. + /// + private IEnumerable Modifiers => new IGraphModifier[] { Filter, Sorter }; /// /// Creates a new instance of for the given . @@ -34,7 +50,7 @@ public class GraphSearch : IObserver /// The graph to be searched. public GraphSearch(Graph graph) { - this.graph = graph; + this.Graph = graph; elements = graph.Nodes().GroupBy(ElementToString).ToDictionary(g => g.Key, g => g.ToList()); graph.Subscribe(this); } @@ -48,10 +64,14 @@ public GraphSearch(Graph graph) /// 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); + IEnumerable<(int Score, Node Element)> results = Process.ExtractTop(FilterString(query), elements.Keys, limit: limit, cutoff: cutoff) + .SelectMany(x => Modifiers.ApplyAll(elements[x.Value]).Select(Element => (x.Score, Element))); + if (!Sorter.IsActive()) + { + // If we don't sort by any custom attribute, we sort by the fuzzy score. + results = results.OrderByDescending(x => x.Score); + } + return results.Select(x => x.Element); } /// diff --git a/Assets/SEE/DataModel/GraphSearch/GraphSearch.cs.meta b/Assets/SEE/DataModel/GraphSearch/GraphSearch.cs.meta new file mode 100644 index 0000000000..eb6e0e3de4 --- /dev/null +++ b/Assets/SEE/DataModel/GraphSearch/GraphSearch.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e2bc5cd2ba5f498dbb66dc802641b47d +timeCreated: 1700171671 \ No newline at end of file diff --git a/Assets/SEE/DataModel/GraphSearch/GraphSorter.cs b/Assets/SEE/DataModel/GraphSearch/GraphSorter.cs new file mode 100644 index 0000000000..830297dda9 --- /dev/null +++ b/Assets/SEE/DataModel/GraphSearch/GraphSorter.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SEE.DataModel.DG; + +namespace SEE.DataModel.GraphSearch +{ + /// + /// A configurable sorter for graph elements, mainly intended for use with . + /// + public class GraphSorter : IGraphModifier + { + /// + /// The attributes to sort by along with whether to sort descending, in the order of precedence. + /// + private readonly List<(string Name, Func GetKey, bool Descending)> SortAttributes = new(); + + /// + /// Add an attribute to sort by. + /// + /// The name of the attribute to sort by. + /// A function that returns the value to sort by for the given element. + /// Whether to sort descending. + public void AddSortAttribute(string attributeName, Func getAttribute, bool descending) + { + SortAttributes.Add((attributeName, getAttribute, descending)); + } + + /// + /// Removes the sort attribute with the given name. + /// If there are multiple attributes with the given name, all of them are removed. + /// + /// The name of the attribute to remove. + public void RemoveSortAttribute(string attributeName) + { + SortAttributes.RemoveAll(a => a.Name == attributeName); + } + + /// + /// Implements . + /// + public IEnumerable Apply(IEnumerable elements) where T : GraphElement + { + return SortAttributes.Count == 0 + ? elements + // The first `OrderBy` call is to get an IOrderedEnumerable that we can repeatedly pass to `ThenBy`. + // `OrderBy` is stable, so the order is preserved, as we are passing in a constant key. + : SortAttributes.Aggregate(elements.OrderBy(_ => 0), + (current, sortAttribute) => + { + (_, Func GetKey, bool Descending) = sortAttribute; + return Descending + ? current.ThenByDescending(x => GetKey(x)) + : current.ThenBy(x => GetKey(x)); + }); + } + + /// + /// Whether the given attribute is sorted descending. + /// Note that this returns null if the attribute is not sorted at all. + /// + /// The attribute to check. + /// Whether the attribute is sorted descending, or null if it is not sorted at all. + /// + /// If there is more than one attribute with the given name, the first one is returned. + /// + public bool? IsAttributeDescending(string attributeName) + { + (string, Func, bool Descending) result = SortAttributes.FirstOrDefault(a => a.Name == attributeName); + if (result == default) + { + return null; + } + else + { + return result.Descending; + } + } + + /// + /// Implements . + /// + public bool IsActive() => SortAttributes.Count > 0; + + /// + /// Implements . + /// + public void Reset() => SortAttributes.Clear(); + } +} diff --git a/Assets/SEE/DataModel/GraphSearch/GraphSorter.cs.meta b/Assets/SEE/DataModel/GraphSearch/GraphSorter.cs.meta new file mode 100644 index 0000000000..92f54b7599 --- /dev/null +++ b/Assets/SEE/DataModel/GraphSearch/GraphSorter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 79b7ddb4545b4a6f9518f4771ce28166 +timeCreated: 1700743470 \ No newline at end of file diff --git a/Assets/SEE/DataModel/GraphSearch/IGraphModifier.cs b/Assets/SEE/DataModel/GraphSearch/IGraphModifier.cs new file mode 100644 index 0000000000..ecfd226496 --- /dev/null +++ b/Assets/SEE/DataModel/GraphSearch/IGraphModifier.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using SEE.DataModel.DG; + +namespace SEE.DataModel.GraphSearch +{ + /// + /// Modifies a collection of graph elements by filtering, sorting, or otherwise transforming it. + /// Intended for use with . + /// + public interface IGraphModifier + { + /// + /// Applies the modifier to the given collection of graph elements. + /// + /// The graph elements to modify. + /// The type of the graph elements. + /// The modified collection. + IEnumerable Apply(IEnumerable elements) where T : GraphElement; + + /// + /// Returns whether the modifier is active, that is, + /// whether it would modify the collection in its current configuration. + /// + bool IsActive(); + + /// + /// Resets the modifier to its default configuration. + /// + void Reset(); + } +} diff --git a/Assets/SEE/DataModel/GraphSearch/IGraphModifier.cs.meta b/Assets/SEE/DataModel/GraphSearch/IGraphModifier.cs.meta new file mode 100644 index 0000000000..ae7d2f6c7d --- /dev/null +++ b/Assets/SEE/DataModel/GraphSearch/IGraphModifier.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 61d4574687ca49b781ef2a1698ca6b5f +timeCreated: 1700745751 \ No newline at end of file diff --git a/Assets/SEE/Game/Avatars/PersonalAssistantSpeechInput.cs b/Assets/SEE/Game/Avatars/PersonalAssistantSpeechInput.cs index 1bff50abcc..a40b0a90ad 100644 --- a/Assets/SEE/Game/Avatars/PersonalAssistantSpeechInput.cs +++ b/Assets/SEE/Game/Avatars/PersonalAssistantSpeechInput.cs @@ -228,7 +228,7 @@ async UniTaskVoid SendChatMessage(ChatRequest request, Notification notification { ChatResponse result = await openAiClient.ChatEndpoint.GetCompletionAsync(request); notification.Close(); - string message = result.FirstChoice.Message.Content; + string message = result.FirstChoice.Message.Content.ToString(); chatGptHistory.Add(new Message(Role.Assistant, message)); // We need to stop listening before we start speaking, else we will hear our own voice. StopListening(); @@ -319,4 +319,4 @@ private void Update() } } } -} \ No newline at end of file +} diff --git a/Assets/SEE/Game/City/ReflexionVisualization.cs b/Assets/SEE/Game/City/ReflexionVisualization.cs index 215685bb35..291f341789 100644 --- a/Assets/SEE/Game/City/ReflexionVisualization.cs +++ b/Assets/SEE/Game/City/ReflexionVisualization.cs @@ -93,7 +93,7 @@ public void InitializeEdges() GameObject edgeObject = edge.GameObject(); if (edgeObject != null && edgeObject.TryGetComponent(out SEESpline spline)) { - spline.GradientColors = GetEdgeGradient(edge); + spline.GradientColors = GetEdgeGradient(edge.State()); if (edge.HasToggle(Edge.IsHiddenToggle)) { @@ -176,14 +176,13 @@ public void StartFromScratch(ReflexionGraph graph, SEEReflexionCity city) } /// - /// Returns a fitting color gradient from the first to the second color for the given edge by examining - /// its state. + /// Returns a fitting color gradient from the first to the second color for the given edge state. /// - /// edge for which to yield a color gradient + /// edge state for which to yield a color gradient /// color gradient - private static (Color, Color) GetEdgeGradient(Edge edge) + public static (Color, Color) GetEdgeGradient(State edgeState) { - (Color, Color) gradient = edge.State() switch + (Color, Color) gradient = edgeState switch { State.Undefined => (Color.black, Color.Lerp(Color.gray, Color.black, edgeGradientFactor)), State.Specified => (Color.gray, Color.Lerp(Color.gray, Color.black, edgeGradientFactor)), @@ -194,7 +193,7 @@ private static (Color, Color) GetEdgeGradient(Edge edge) State.Divergent => (Color.red, Color.Lerp(Color.red, Color.black, edgeGradientFactor)), State.Absent => (Color.yellow, Color.Lerp(Color.yellow, Color.black, edgeGradientFactor)), State.Convergent => (Color.green, Color.Lerp(Color.green, Color.black, edgeGradientFactor)), - _ => throw new ArgumentOutOfRangeException(nameof(edge), edge.State(), "Unknown state of given edge!") + _ => throw new ArgumentOutOfRangeException(nameof(edgeState), edgeState, "Unknown state of given edge!") }; return gradient; @@ -305,7 +304,7 @@ private async UniTaskVoid HandleEdgeChange(EdgeChange edgeChange) if (edge != null) { - (Color start, Color end) newColors = GetEdgeGradient(edgeChange.Edge); + (Color start, Color end) newColors = GetEdgeGradient(edgeChange.Edge.State()); EdgeOperator edgeOperator = edge.EdgeOperator(); edgeOperator.ShowOrHide(!edgeChange.Edge.HasToggle(Edge.IsHiddenToggle), city.EdgeLayoutSettings.AnimationKind); edgeOperator.ChangeColorsTo((newColors.start, newColors.end), useAlpha: false); diff --git a/Assets/SEE/Game/CityRendering/AbstractLayoutNode.cs b/Assets/SEE/Game/CityRendering/AbstractLayoutNode.cs index 0798a02cfa..8dcc11e059 100644 --- a/Assets/SEE/Game/CityRendering/AbstractLayoutNode.cs +++ b/Assets/SEE/Game/CityRendering/AbstractLayoutNode.cs @@ -36,9 +36,9 @@ public abstract class AbstractLayoutNode : ILayoutNode /// the mapping of graph nodes onto LayoutNodes this node should be added to protected AbstractLayoutNode(Node node, IDictionary toLayoutNode) { - this.Node = node; - this.ToLayoutNode = toLayoutNode; - this.ToLayoutNode[node] = this; + Node = node; + ToLayoutNode = toLayoutNode; + ToLayoutNode[node] = this; } /// @@ -179,8 +179,8 @@ public ICollection Successors public abstract Vector3 Ground { get; } private Vector3 relativePosition; - private bool isSublayoutNode = false; - private bool isSublayoutRoot = false; + private bool isSublayoutNode; + private bool isSublayoutRoot; private Sublayout sublayout; private ILayoutNode sublayoutRoot; @@ -199,4 +199,4 @@ public override string ToString() } } -} \ No newline at end of file +} diff --git a/Assets/SEE/Net/Dashboard/DashboardRetriever.cs b/Assets/SEE/Net/Dashboard/DashboardRetriever.cs index 554adae3b9..3f0ad4d587 100644 --- a/Assets/SEE/Net/Dashboard/DashboardRetriever.cs +++ b/Assets/SEE/Net/Dashboard/DashboardRetriever.cs @@ -36,18 +36,15 @@ public partial class DashboardRetriever : MonoBehaviour [EnvironmentVariable("DASHBOARD_PUBLIC_KEY")] [TextArea] [Tooltip("The public key for the X.509 certificate authority from the dashboard's certificate.")] - public string PublicKey = "3082018A0282018100D5EB3F1B3AB0ABCE827FA70BA32FCF8C834B1206464B785B7AE1" - + "3D962F19887D6733B0A555651F130BCDB04D8BF6F199EF76DB7932C001B6916D0E3F0C" - + "84C9A7DDB7BC62C93590E49C5DD97109B01B2CFAFD183A45DD8E02F86EBAFB4C74DF24" - + "449D582DFC03D8E783DA035BB2985907EC00774D748AFC9A0195E05E2B992A7877F437" - + "DB6088E2490A53D83D8F729482A383142AE5FBAFA4F2C3112E5A5C16D520ADFCF5E0F0" - + "C9FF865126AFC8B97EAEFD77CE31A431F0E66C2200CDF70BC56B478FB7B56858CE605E" - + "3313A876E4B719529DB929D9D7A966B16A9656FF639AE7382B6B7591E19D05A0B35468" - + "03007DCE8354FDFDFC0DAB4E5103C0ED67A6BFD42E810C78A649DD419A0E4C1BB15267" - + "A85DB4336D101F3799B71A654C5A8422875EE4ADCF7FD7D684D5B71AC4C0E392A533DE" - + "143AACC68CCDD77F3FB47AFCF59F058E3873FCF454CED0EF1B5DF8A18A14C4D56A4C81" - + "E6F3D3D8246BDF2E402C78AB50DCA8CC603E2681B9E28A032BFE156DDC04C266986E31" - + "10112A86CC01C5150203010001"; + public string PublicKey = "3082018A028201810084BA2FF29AB0282BEE362EA659FE9C5A90CC7C6E6AED7743847C2" + + "CE12FCAE85963CE613C4DA2B1685EB8B355A95072FF7FBC4D08D5545573A3BB8C21667D7FEE766DA410B22681AC" + + "CD028DB46CE5EAE8E9D455B350BA3E6867480F2990799F3D3130F87EAAC7B8AD4226634A28C99922C43C8CC8984" + + "EA92FB25FDC7510AEFA7793AF0042DF30498BC1F0507613ABB1A30F3954FF21A6631EB83A40A4DD7B5EB89B5CA1" + + "B2982605453A0B1B2A8D4064917E8C6582A6DBE4A032E3EB84B9B2A4500C8ADEA236787CBC709E30D893D08DC8B" + + "824D309C0AA4CFE976A4645B8EBDA797E618A5A22A775078C84BC536486D6F45E0659A1FEB03FEEC6944393025E" + + "1EFF3948E42ADF05A803770D327F85B900B1D7ADFB9B7BBE4E01E23E7653578B28917D4683DA4EC538758EF6A02" + + "532CD74CAA6B644D0FFCE5AA096A6CFE76E5C0BD30DA19DF4187E1E358077CD771B0B470441A934B8F991FE78EA" + + "A90B35AD7CF600B573272D9E2888DB0BB34FA374F29FDA92E2D3E2D36A1E1A1E40164648D63F930203010001"; /// /// The URL to the Axivion Dashboard, up to the project name. @@ -373,7 +370,13 @@ protected override bool ValidateCertificate(byte[] certificateData) // https://docs.unity3d.com/ScriptReference/Networking.CertificateHandler.ValidateCertificate.html X509Certificate2 certificate = new(certificateData); string certPublicKey = certificate.GetPublicKeyString(); - return certPublicKey?.Equals(acceptKey) ?? false; + + bool result = certPublicKey?.Equals(acceptKey) ?? false; + if (!result) + { + Debug.LogError($"Public keys do not match:\nOurs: {acceptKey}\nServer's: {certPublicKey}\n"); + } + return result; } } } diff --git a/Assets/SEE/Net/Dashboard/DashboardVersion.cs b/Assets/SEE/Net/Dashboard/DashboardVersion.cs index 2f5677b385..2a44c280d4 100644 --- a/Assets/SEE/Net/Dashboard/DashboardVersion.cs +++ b/Assets/SEE/Net/Dashboard/DashboardVersion.cs @@ -35,7 +35,7 @@ namespace SEE.Net.Dashboard /// Latest supported version of the Axivion Dashboard. /// Should be updated when new (supported and tested) versions come out. /// - public static readonly DashboardVersion SupportedVersion = new(7, 6, 3, 12797); + public static readonly DashboardVersion SupportedVersion = new(7, 7, 1, 13682); /// /// Represents the difference of another version in comparison to this one. diff --git a/Assets/SEE/UI/Menu/NestedMenu.cs b/Assets/SEE/UI/Menu/NestedMenu.cs index dfd57f1297..eb4cfb5eb6 100644 --- a/Assets/SEE/UI/Menu/NestedMenu.cs +++ b/Assets/SEE/UI/Menu/NestedMenu.cs @@ -3,9 +3,8 @@ using System.Linq; using FuzzySharp; using SEE.Controls; -using SEE.DataModel.DG; +using SEE.DataModel.GraphSearch; using SEE.GO; -using SEE.GO.Menu; using TMPro; using UnityEngine; using UnityEngine.Windows.Speech; diff --git a/Assets/SEE/UI/PopupMenu/PopupMenu.cs b/Assets/SEE/UI/PopupMenu/PopupMenu.cs index 8976b6f7d8..b2e9a08b70 100644 --- a/Assets/SEE/UI/PopupMenu/PopupMenu.cs +++ b/Assets/SEE/UI/PopupMenu/PopupMenu.cs @@ -6,6 +6,7 @@ using SEE.Utils; using TMPro; using UnityEngine; +using UnityEngine.UI; namespace SEE.UI.PopupMenu { @@ -30,15 +31,20 @@ public class PopupMenu : PlatformDependentComponent private CanvasGroup MenuCanvasGroup; /// - /// The transform under which the actions are listed. + /// The transform under which the entries are listed. /// - private RectTransform ActionList; + private RectTransform EntryList; /// - /// A queue of actions that were added before the menu was started. - /// These actions will be added to the menu once it is started. + /// The content size fitter of the popup menu. /// - private readonly Queue ActionsBeforeStart = new(); + private ContentSizeFitter contentSizeFitter; + + /// + /// A queue of entries that were added before the menu was started. + /// These entries will be added to the menu once it is started. + /// + private readonly Queue EntriesBeforeStart = new(); /// /// Whether the menu should currently be shown. @@ -59,76 +65,123 @@ protected override void StartDesktop() { // Instantiate the menu. Menu = (RectTransform)PrefabInstantiator.InstantiatePrefab(MenuPrefabPath, Canvas.transform, false).transform; + contentSizeFitter = Menu.gameObject.MustGetComponent(); MenuCanvasGroup = Menu.gameObject.MustGetComponent(); - ActionList = (RectTransform)Menu.Find("Action List"); + EntryList = (RectTransform)Menu.Find("Action List"); // The menu should be hidden when the user moves the mouse away from it. PointerHelper pointerHelper = Menu.gameObject.MustGetComponent(); - pointerHelper.ExitEvent.AddListener(_ => HideMenu().Forget()); + pointerHelper.ExitEvent.AddListener(x => + { + // If the mouse is not moving, this may indicate that the trigger has just been + // menu entries being rebuilt instead of the mouse moving outside of the menu. + if (x.IsPointerMoving()) + { + HideMenu().Forget(); + } + }); - // We add all actions that were added before the menu was started. - while (ActionsBeforeStart.Count > 0) + // We add all entries that were added before the menu was started. + while (EntriesBeforeStart.Count > 0) { - AddAction(ActionsBeforeStart.Dequeue()); + AddEntry(EntriesBeforeStart.Dequeue()); } - // FIXME (#632): On the first appearance of the menu, it lacks a background. - // We hide the menu by default. Menu.gameObject.SetActive(false); + + // TODO (#679): Make this scrollable once it gets too big. } /// - /// Adds a new to the menu. + /// Adds a new to the menu. /// - /// The action to be added. - public void AddAction(PopupMenuAction action) + /// The entry to be added. + public void AddEntry(PopupMenuEntry entry) { if (Menu is null) { - ActionsBeforeStart.Enqueue(action); + EntriesBeforeStart.Enqueue(entry); return; } + switch (entry) + { + case PopupMenuAction action: + AddAction(action); + break; + case PopupMenuHeading heading: + AddHeading(heading); + break; + default: + throw new System.ArgumentException($"Unknown entry type: {entry.GetType()}"); + } + // TODO (#668): Respect priority - GameObject actionItem = PrefabInstantiator.InstantiatePrefab("Prefabs/UI/PopupMenuButton", ActionList, false); + } + + /// + /// Adds a new to the menu. + /// + /// The action to be added. + private void AddAction(PopupMenuAction action) + { + GameObject actionItem = PrefabInstantiator.InstantiatePrefab("Prefabs/UI/PopupMenuButton", EntryList, false); ButtonManagerBasic button = actionItem.MustGetComponent(); button.buttonText = action.Name; - button.clickEvent.AddListener(() => - { - action.Action(); - HideMenu().Forget(); - }); + + button.clickEvent.AddListener(OnClick); if (action.IconGlyph != default) { actionItem.transform.Find("Icon").gameObject.MustGetComponent().text = action.IconGlyph.ToString(); } + return; + + void OnClick() + { + action.Action(); + if (action.CloseAfterClick) + { + HideMenu().Forget(); + } + } + } + + /// + /// Adds a new to the menu. + /// + /// The heading to be added. + private void AddHeading(PopupMenuHeading heading) + { + GameObject headingItem = PrefabInstantiator.InstantiatePrefab("Prefabs/UI/PopupMenuHeading", EntryList, false); + TextMeshProUGUI text = headingItem.MustGetComponent(); + text.text = heading.Text; } /// - /// Adds all given to the menu. + /// Adds all given to the menu. /// - /// The actions to be added. - public void AddActions(IEnumerable actions) + /// The entries to be added. + public void AddEntries(IEnumerable entries) { - foreach (PopupMenuAction action in actions) + foreach (PopupMenuEntry entry in entries) { - AddAction(action); + AddEntry(entry); } } /// - /// Removes all actions from the menu. + /// Removes all entries from the menu. /// - public void ClearActions() + public void ClearEntries() { if (Menu is null) { - ActionsBeforeStart.Clear(); + EntriesBeforeStart.Clear(); return; } - foreach (Transform child in ActionList) + foreach (Transform child in EntryList) { Destroyer.Destroy(child.gameObject); } @@ -162,11 +215,17 @@ public void MoveTo(Vector2 position) /// Activates the menu and fades it in. /// This asynchronous method will return once the menu is fully shown. /// - public async UniTaskVoid ShowMenu() + public async UniTask ShowMenu() { ShouldShowMenu = true; Menu.gameObject.SetActive(true); Menu.localScale = Vector3.zero; + // This may seem stupid, but unfortunately, due to a Unity bug, + // this appears to be the only way to make the content size fitter update. + // See https://forum.unity.com/threads/content-size-fitter-refresh-problem.498536/ + contentSizeFitter.enabled = false; + await UniTask.WaitForEndOfFrame(); + contentSizeFitter.enabled = true; await UniTask.WhenAll(Menu.DOScale(1, AnimationDuration).AsyncWaitForCompletion().AsUniTask(), MenuCanvasGroup.DOFade(1, AnimationDuration / 2).AsyncWaitForCompletion().AsUniTask()); } @@ -175,7 +234,7 @@ await UniTask.WhenAll(Menu.DOScale(1, AnimationDuration).AsyncWaitForCompletion( /// Hides the menu and fades it out. /// This asynchronous method will return once the menu is fully hidden and deactivated. /// - public async UniTaskVoid HideMenu() + public async UniTask HideMenu() { ShouldShowMenu = false; // We use a fade effect rather than DOScale because it looks better. @@ -185,5 +244,27 @@ public async UniTaskVoid HideMenu() Menu.gameObject.SetActive(false); } } + + /// + /// Convenience method that shows the menu with the given + /// at the given . + /// + /// The entries to be shown in the menu. + /// If null, entries will not be modified. + /// The position at which the menu should be shown. + /// If null, menu will not be moved. + public void ShowWith(IEnumerable entries = null, Vector2? position = null) + { + if (entries != null) + { + ClearEntries(); + AddEntries(entries); + } + if (position.HasValue) + { + MoveTo(position.Value); + } + ShowMenu().Forget(); + } } } diff --git a/Assets/SEE/UI/PopupMenu/PopupMenuAction.cs b/Assets/SEE/UI/PopupMenu/PopupMenuAction.cs deleted file mode 100644 index f7c45953e1..0000000000 --- a/Assets/SEE/UI/PopupMenu/PopupMenuAction.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using UnityEngine.Events; - -namespace SEE.UI.PopupMenu -{ - /// - /// An action that can be added to a . - /// - /// The name of the action. - /// The action to be executed when the user clicks on the action. - /// The unicode glyph of the FontAwesome v6 icon - /// that should be displayed next to the action. - /// The priority of the action. Actions with a higher priority - /// are displayed first. - public record PopupMenuAction(string Name, Action Action, char IconGlyph, int priority = default); -} diff --git a/Assets/SEE/UI/PopupMenu/PopupMenuEntry.cs b/Assets/SEE/UI/PopupMenu/PopupMenuEntry.cs new file mode 100644 index 0000000000..a40808e7d2 --- /dev/null +++ b/Assets/SEE/UI/PopupMenu/PopupMenuEntry.cs @@ -0,0 +1,33 @@ +using System; +using UnityEngine.Events; + +namespace SEE.UI.PopupMenu +{ + /// + /// An entry in a . + /// The priority of the entry. Entries with a higher priority + /// are displayed first. + /// + public abstract record PopupMenuEntry(int Priority); + + /// + /// An action that can be added to a . + /// + /// The name of the action. + /// The action to be executed when the user clicks on the action. + /// The unicode glyph of the FontAwesome v6 icon + /// that should be displayed next to the action. + /// Whether the menu should be closed after the action is executed. + /// The priority of the entry. Entries with a higher priority + /// are displayed first. + public record PopupMenuAction(string Name, Action Action, char IconGlyph, bool CloseAfterClick = true, + int Priority = default) : PopupMenuEntry(Priority); + + /// + /// A heading that can be added to a . + /// + /// The text of the heading. + /// The priority of the entry. Entries with a higher priority + /// are displayed first. + public record PopupMenuHeading(string Text, int Priority = default) : PopupMenuEntry(Priority); +} diff --git a/Assets/SEE/UI/PopupMenu/PopupMenuAction.cs.meta b/Assets/SEE/UI/PopupMenu/PopupMenuEntry.cs.meta similarity index 100% rename from Assets/SEE/UI/PopupMenu/PopupMenuAction.cs.meta rename to Assets/SEE/UI/PopupMenu/PopupMenuEntry.cs.meta diff --git a/Assets/SEE/UI/Window/DesktopWindowSpace.cs b/Assets/SEE/UI/Window/DesktopWindowSpace.cs index 5e5e35317e..d5769bd6af 100644 --- a/Assets/SEE/UI/Window/DesktopWindowSpace.cs +++ b/Assets/SEE/UI/Window/DesktopWindowSpace.cs @@ -154,7 +154,11 @@ void CloseTab(PanelTab panelTab) { if (panelTab.Panel == panel) { - CloseWindow(windows.First(x => x.Window.GetInstanceID() == panelTab.Content.gameObject.GetInstanceID())); + BaseWindow window = windows.FirstOrDefault(x => x.Window.GetInstanceID() == panelTab.Content.gameObject.GetInstanceID()); + if (window != null) + { + CloseWindow(window); + } if (panelTab.Panel.NumberOfTabs <= 1) { // All tabs were closed, so we send out an event diff --git a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs index e1123752be..27266a85d4 100644 --- a/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs +++ b/Assets/SEE/UI/Window/TreeWindow/DesktopTreeWindow.cs @@ -16,6 +16,8 @@ using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; +using ArgumentException = System.ArgumentException; +using Edge = SEE.DataModel.DG.Edge; using Node = SEE.DataModel.DG.Node; namespace SEE.UI.Window.TreeWindow @@ -59,97 +61,256 @@ public partial class TreeWindow /// private TMP_InputField SearchField; + /// + /// The button that opens the filter menu. + /// + private ButtonManagerBasic FilterButton; + + /// + /// The button that opens the grouping menu. + /// + private ButtonManagerBasic GroupButton; + + /// + /// The button that opens the sorting menu. + /// + private ButtonManagerBasic SortButton; + + /// + /// Orders the tree below the given group according to the graph hierarchy. + /// + /// The group below which the tree should be ordered. + private void OrderTree(TreeWindowGroup orderBelow) + { + Transform groupItem = items.Find(CleanupID(orderBelow.Text)); + if (groupItem == null) + { + return; + } + + int groupIndex = groupItem.GetSiblingIndex() + 1; + foreach (Node node in GetRoots(orderBelow)) + { + // The groups are always at the top level of the tree window. + // Thus, we put the root nodes (indented by one level) directly below the group. + items.Find(ElementId(node, orderBelow)).SetSiblingIndex(groupIndex); + // The next root should be added below the last one, which is why we use the current index. + groupIndex = OrderTree(node, 1, orderBelow) ?? groupIndex; + } + } + /// /// 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) + /// The level of the given node. + /// The group in which is contained, if any. + /// The index in the hierarchy of the last node handled by this method. + /// If no nodes were ordered, null is returned. + private int? OrderTree(Node orderBelow, int? nodeLevel = null, TreeWindowGroup inGroup = null) { - int index = items.Find(CleanupID(orderBelow.ID)).GetSiblingIndex(); - OrderTreeRecursive(orderBelow); + Transform nodeItem = items.Find(ElementId(orderBelow, inGroup)); + if (nodeItem == null) + { + return null; + } - return; + // We determine the node level based on the indent of the foreground. + if (!nodeLevel.HasValue) + { + nodeLevel = Mathf.RoundToInt(((RectTransform)nodeItem.Find("Foreground")).offsetMin.x) / indentShift; + } + int index = nodeItem.GetSiblingIndex(); + + OrderTreeRecursive(orderBelow, nodeLevel.Value); + + return index; // Orders the item with the given id to the current index and increments the index. - void OrderItemHere(string id) + void OrderItemHere(string id, int level) { Transform item = items.Find(id); - if (item == null) - { - Debug.LogError($"Item {id} not found."); - } - else + if (item != null) { item.SetSiblingIndex(index++); + RectTransform foreground = (RectTransform)item.Find("Foreground"); + RectTransform background = (RectTransform)item.Find("Background"); + foreground.offsetMin = foreground.offsetMin.WithXY(x: indentShift * level); + background.offsetMin = background.offsetMin.WithXY(x: indentShift * level); } } // Recurses over the tree in pre-order and assigns indices to each node. - void OrderTreeRecursive(Node node) + void OrderTreeRecursive(Node node, int level) { - string id = CleanupID(node.ID); - OrderItemHere(id); + string id = ElementId(node, inGroup); + OrderItemHere(id, level); if (expandedItems.Contains(id)) { - foreach (Node child in node.Children().OrderBy(x => x.SourceName)) + IEnumerable children = Searcher.Sorter.Apply(WithHiddenChildren(node.Children(), inGroup)); + // When grouping is active, we sort by the count of group elements in this node. + if (Grouper.IsActive) { - OrderTreeRecursive(child); + children = children.OrderByDescending(x => Grouper.DescendantsInGroup(x, inGroup)); + } + foreach (Node child in children) + { + OrderTreeRecursive(child, level + 1); } - HandleEdges($"{id}#Outgoing", node.Outgoings); - HandleEdges($"{id}#Incoming", node.Incomings); + foreach ((List edges, string edgesType) in RelevantEdges(node, inGroup)) + { + HandleEdges($"{id}#{edgesType}", edges, level + 1); + } } } // Orders the edges under the given id (outgoing/incoming) to the current index and increments the index. - void HandleEdges(string edgesId, ICollection edges) + void HandleEdges(string edgesId, ICollection edges, int level) { if (edges.Count > 0) { - OrderItemHere(edgesId); + OrderItemHere(edgesId, level); if (expandedItems.Contains(edgesId)) { foreach (Edge edge in edges) { - OrderItemHere($"{edgesId}#{CleanupID(edge.ID)}"); + OrderItemHere($"{edgesId}#{CleanupID(edge.ID)}", level + 1); } } } } } + /// + /// Returns the TreeWindow ID of the given in the given . + /// + /// The element whose ID shall be returned. + /// The group in which the element is contained, if any. + /// The TreeWindow ID of the given in the given . + private static string ElementId(GraphElement element, TreeWindowGroup group) + { + string id = CleanupID(element.ID); + if (group != null) + { + // If it belongs to a group, we will need to append the group name, otherwise the ID will not be unique, + // as the element may be used repeatedly across multiple groups. + id = $"{id}#{CleanupID(group.Text)}"; + } + return id; + } + + /// + /// Adds the given to the bottom of the tree window. + /// + /// The group to be added. + private void AddGroup(TreeWindowGroup group) + { + AddItem(CleanupID(group.Text), true, $"{group.Text} [{Grouper.MembersInGroup(group)}]", + group.IconGlyph, gradient: group.Gradient, + collapseItem: CollapseGroup, expandItem: ExpandGroup); + if (expandedItems.Contains(CleanupID(group.Text))) + { + ExpandGroup(items.Find(CleanupID(group.Text))?.gameObject, order: false); + } + return; + + void CollapseGroup(GameObject item) + { + CollapseItem(item); + foreach (GraphElement element in GetRoots(group)) + { + RemoveItem(ElementId(element, group), element, + x => x is Node node ? GetChildItems(node, group) : Enumerable.Empty<(string, Node)>()); + } + } + + void ExpandGroup(GameObject item, bool order) + { + ExpandItem(item); + foreach (Node element in GetRoots(group)) + { + AddNode(element, group); + } + if (order) + { + OrderTree(group); + } + } + } + + /// + /// Whether the given shall be displayed in the tree window. + /// + /// The element to be checked. + /// The group in which the element is contained, if any. + /// Whether the given shall be displayed in the tree window. + private bool ShouldBeDisplayed(GraphElement element, TreeWindowGroup inGroup = null) + { + return (inGroup != null && Grouper.IsRelevantFor(element, inGroup)) || (inGroup == null && Searcher.Filter.Includes(element)); + } + /// /// Adds the given to the bottom of the tree window. /// /// The node to be added. - private void AddNode(Node node) + /// The group in which the node is contained, if any. + private void AddNode(Node node, TreeWindowGroup inGroup = null) { GameObject nodeGameObject = GraphElementIDMap.Find(node.ID); - int children = node.NumberOfChildren() + Mathf.Min(node.Outgoings.Count, 1) + Mathf.Min(node.Incomings.Count, 1); + int children = node.NumberOfChildren() + node.Edges.Count; - AddItem(CleanupID(node.ID), children, node.ToShortString(), node.Level, nodeTypeUnicode, nodeGameObject, node, - item => CollapseNode(node, item), (item, order) => ExpandNode(node, item, orderTree: order)); + if (ShouldBeDisplayed(node, inGroup)) + { + string text = node.ToShortString(); + string id = ElementId(node, inGroup); + if (inGroup != null) + { + // Not actually the number of direct children, but this doesn't matter, as we only + // need it for the text and to check whether there are any children at all. + children = Grouper.DescendantsInGroup(node, inGroup); + if (Grouper.GetGroupFor(node) != inGroup) + { + // This node is only included because it has relevant descendants. + text = $"{text}"; + } + if (children > 0) + { + text = $"{text} [{children}]"; + } + } + AddItem(id, children > 0, text, Icons.Node, nodeGameObject, node, + collapseItem: item => CollapseNode(node, item, inGroup), + expandItem: (item, order) => ExpandNode(node, item, orderTree: order, inGroup)); + } + else + { + // The node itself may not be included, but its children (or edges) could be. + // Thus, we assume this invisible node to be expanded by default and add its children. + ExpandNode(node, null, inGroup: inGroup); + } } /// /// Adds the given item to the tree window. /// /// The ID of the item to be added. - /// The number of children of the item to be added. + /// Whether the item has children. /// The text of the item to be added. - /// The level of the item to be added. /// The icon of the item to be added, given as a unicode character. /// The game object of the element represented by the item. May be null. /// The graph element represented by the item. May be null. + /// The gradient to be used for the item's background. May be null. /// A function that collapses the item. - /// It takes the item that was collapsed as an argument. + /// It takes the item that was collapsed as an argument. May be null. /// 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 representedGameObject, GraphElement representedGraphElement, - Action collapseItem, Action expandItem) + /// tree should be ordered after expanding the item as arguments. May be null. + private void AddItem(string id, bool hasChildren, string text, char icon, + GameObject representedGameObject = null, GraphElement representedGraphElement = null, + Color[] gradient = null, + Action collapseItem = null, Action expandItem = null) { GameObject item = PrefabInstantiator.InstantiatePrefab(treeItemPrefab, items, false); Transform background = item.transform.Find("Background"); @@ -161,21 +322,18 @@ private void AddItem(string id, int children, string text, int level, textMesh.text = text; iconMesh.text = icon.ToString(); - foreground.localPosition += new Vector3(indentShift * level, 0, 0); - background.localPosition += new Vector3(indentShift * level, 0, 0); - ColorItem(); - // Slashes will cause problems later on, so we replace them with backslashes. + // Slashes will cause problems later on in the `transform.Find` method, so we replace them with backslashes. // NOTE: This becomes a problem if two nodes A and B exist where node A's name contains slashes and node B // has an identical name, except for all slashes being replaced by backslashes. // I hope this is unlikely enough to not be a problem for now. - item.name = CleanupID(id); - if (children <= 0) + item.name = id; + if (!hasChildren) { expandIcon.SetActive(false); } - else if (expandedItems.Contains(id)) + else if (expandedItems.Contains(id) && expandItem != null) { // If this item was previously expanded, we need to expand it again. // The tree should not be reordered after this – this should only happen at the end of the expansion, @@ -190,8 +348,7 @@ private void AddItem(string id, int children, string text, int level, // Colors the item according to its game object. void ColorItem() { - Color[] gradient; - if (representedGameObject != null) + if (gradient == null && representedGameObject != null) { if (representedGameObject.IsNode()) { @@ -209,7 +366,7 @@ void ColorItem() throw new ArgumentException("Item must be either a node or an edge."); } } - else + else if (gradient == null) { gradient = new[] { Color.gray, Color.gray.Darker() }; } @@ -247,15 +404,17 @@ void RegisterClickHandler() } // We want all applicable actions for the element, except ones where the element - // element is shown in the TreeView, since we are already in the TreeView. + // element is shown in the TreeWindow, since we are already in the TreeWindow. IEnumerable actions = ContextMenuAction .GetApplicableOptions(representedGraphElement, representedGameObject) - .Where(x => !x.Name.Contains("TreeView")); - ContextMenu.ClearActions(); - ContextMenu.AddActions(actions); - ContextMenu.MoveTo(e.position); - ContextMenu.ShowMenu().Forget(); + .Where(x => !x.Name.Contains("TreeWindow")); + actions = actions.Append(new PopupMenuAction("Hide in TreeWindow", () => + { + Searcher.Filter.ExcludeElements.Add(representedGraphElement); + Rebuild(); + }, Icons.Hide)); + ContextMenu.ShowWith(actions, e.position); } else { @@ -273,39 +432,63 @@ void RegisterClickHandler() } } + /// + /// Returns those nodes within which are included in the current filter, + /// and transitively adds all children of those nodes within + /// which are not included in the current filter. + /// + /// The nodes to be filtered. + /// The group in which the nodes are contained, if any. + /// The filtered nodes with any hidden transitive children. + private IEnumerable WithHiddenChildren(IList nodes, TreeWindowGroup inGroup) + { + return nodes.Where(x => ShouldBeDisplayed(x, inGroup)) + .Concat(nodes.Where(x => !ShouldBeDisplayed(x, inGroup)) + .SelectMany(x => WithHiddenChildren(x.Children(), inGroup))); + } + + /// + /// Returns those nodes within which are not included in the current filter, + /// transitively including all hidden children of those nodes. + /// + /// The nodes to be filtered. + /// The group in which the nodes are contained, if any. + /// The transitive hidden children of the given nodes. + private IEnumerable HiddenChildren(IEnumerable nodes, TreeWindowGroup inGroup) + { + return nodes.Where(x => !ShouldBeDisplayed(x, inGroup)).SelectMany(x => HiddenChildren(x.Children(), inGroup).Append(x)); + } + /// /// Removes the given 's children from the tree window. /// /// The node to be removed. - private void RemoveNodeChildren(Node node) + /// The group in which the node is contained, if any. + private void RemoveNodeChildren(Node node, TreeWindowGroup inGroup = null) { - foreach ((string childID, Node child) in GetChildItems(node)) + foreach ((string childID, Node child) in GetChildItems(node, inGroup)) { - RemoveItem(childID, child, GetChildItems); + RemoveItem(childID, child, x => GetChildItems(x, inGroup)); } - return; + } - IEnumerable<(string ID, Node child)> GetChildItems(Node n) + /// + /// Returns the child items of the given along with their ID. + /// + /// The node whose child items are requested. + /// The group in which the node is contained, if any. + /// The child items of the given along with their ID. + private IEnumerable<(string ID, Node child)> GetChildItems(Node node, TreeWindowGroup inGroup = null) + { + string cleanId = ElementId(node, inGroup); + IEnumerable<(string, Node)> children = WithHiddenChildren(node.Children(), inGroup).Select(x => (ElementId(x, inGroup), x)); + foreach ((List edges, string edgesType) in RelevantEdges(node, inGroup)) { - string cleanId = CleanupID(n.ID); - IEnumerable<(string, Node)> children = n.Children().Select(x => (CleanupID(x.ID), x)); - // 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); - } - if (n.Incomings.Count > 0) - { - children = AppendEdgeChildren("Incoming", n.Incomings); - } - return children; - - IEnumerable<(string, Node)> AppendEdgeChildren(string edgeType, IEnumerable edges) - { - return children.Append((cleanId + "#" + edgeType, null)) - .Concat(edges.Select(x => ($"{cleanId}#{edgeType}#{CleanupID(x.ID)}", null))); - } + // The "Outgoing" and "Incoming" buttons if they exist, along with their children, belong here too. + children = children.Append((cleanId + "#" + edgesType, null)) + .Concat(edges.Select(x => ($"{cleanId}#{edgesType}#{CleanupID(x.ID)}", null))); } + return children; } /// @@ -316,7 +499,8 @@ private void RemoveNodeChildren(Node node) /// The initial item whose children will be removed. /// A function that returns the children, along with their ID, of an item. /// The type of the item. - private void RemoveItem(string id, T initial, Func> getChildItems) + /// The type of the children of the item. + private void RemoveItem(string id, T initial, Func> getChildItems) where V : T { GameObject item = items.Find(id)?.gameObject; if (item == null) @@ -339,7 +523,7 @@ private void RemoveItem(string id, T initial, Func from the tree window. /// /// The ID of the item to be removed. - private void RemoveItem(string id) => RemoveItem(id, null, null); + private void RemoveItem(string id) => RemoveItem(id, null, null); /// /// Expands the given . @@ -369,78 +553,129 @@ private void CollapseItem(GameObject item) } } + /// + /// Returns connected and lifted edges of the given . + /// The edges are grouped by their type (outgoing, incoming, lifted outgoing, lifted incoming) + /// and each is returned as a list along with its type. Only those types are included that + /// have at least one edge. + /// + /// The node whose edges are requested. + /// The group in which the node is contained, if any. + /// The edges of the node, grouped by their type. + private IEnumerable<(List edges, string edgesType)> RelevantEdges(Node node, TreeWindowGroup inGroup = null) + { + List outgoings = Searcher.Filter.Apply(node.Outgoings).ToList(); + List incomings = Searcher.Filter.Apply(node.Incomings).ToList(); + // We need to lift edges of any hidden children upwards to the first visible parent, which is + // this node. We then need to filter them again, since they may have been hidden by the filter. + List hiddenChildren = HiddenChildren(node.Children(), inGroup).ToList(); + List liftedOutgoings = Searcher.Filter.Apply(hiddenChildren.SelectMany(x => x.Outgoings)).ToList(); + List liftedIncomings = Searcher.Filter.Apply(hiddenChildren.SelectMany(x => x.Incomings)).ToList(); + return new[] + { + (outgoings, "Outgoing"), + (incomings, "Incoming"), + (liftedOutgoings, "Lifted Outgoing"), + (liftedIncomings, "Lifted Incoming") + }.Select(x => inGroup == null ? x : (x.Item1.Where(y => Grouper.IsRelevantFor(y, inGroup)).ToList(), x.Item2)) + .Where(x => x.Item1.Count > 0); + } + /// /// Expands the given . /// Its children will be added to the tree window. /// /// The node represented by the item. - /// The item to be expanded. + /// The item to be expanded. If this is null + /// (i.e., no item actually exists in the TreeWindow) + /// only the children of the node will be added, not its connected edges. /// Whether to order the tree after expanding the node. - private void ExpandNode(Node node, GameObject item, bool orderTree = false) + /// The group in which the node is contained, if any. + private void ExpandNode(Node node, GameObject item, bool orderTree = false, TreeWindowGroup inGroup = null) { - ExpandItem(item); - foreach (Node child in node.Children()) { - AddNode(child); + AddNode(child, inGroup); } - if (node.Outgoings.Count > 0) - { - AddEdgeButton("Outgoing", outgoingEdgeUnicode, node.Outgoings); - } - if (node.Incomings.Count > 0) - { - AddEdgeButton("Incoming", incomingEdgeUnicode, node.Incomings); - } - if (orderTree) + if (item != null) { - OrderTree(node); + ExpandItem(item); + + foreach ((List edges, string edgesType) in RelevantEdges(node, inGroup)) + { + AddEdgeButton(edges, edgesType); + } + + if (orderTree) + { + OrderTree(node, inGroup: inGroup); + } } return; - void AddEdgeButton(string edgesType, char icon, ICollection edges) + void AddEdgeButton(ICollection edges, string edgesType) { - string cleanedId = CleanupID(node.ID); + char icon = edgesType switch + { + "Incoming" => Icons.IncomingEdge, + "Outgoing" => Icons.OutgoingEdge, + "Lifted Incoming" => Icons.LiftedIncomingEdge, + "Lifted Outgoing" => Icons.LiftedOutgoingEdge, + _ => Icons.QuestionMark + }; + string cleanedId = ElementId(node, inGroup); string id = $"{cleanedId}#{edgesType}"; // 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, edges.Count, $"{edgesType} Edges", node.Level + 1, icon, - representedGameObject: null, representedGraphElement: null, - collapsedItem => + AddItem(id, edges.Count > 0, $"{edgesType} Edges", icon, + collapseItem: collapsedItem => { CollapseItem(collapsedItem); foreach (Edge edge in edges) { RemoveItem($"{id}#{CleanupID(edge.ID)}"); } - }, (expandedItem, order) => + }, + expandItem: (expandedItem, order) => { ExpandItem(expandedItem); - foreach (Edge edge in edges) - { - GameObject edgeObject = GraphElementIDMap.Find(edge.ID); - AddItem($"{id}#{CleanupID(edge.ID)}", 0, edge.ToShortString(), node.Level + 2, edgeTypeUnicode, edgeObject, edge, null, null); - } + AddEdges(id, edges); if (order) { - OrderTree(node); + OrderTree(node, inGroup: inGroup); } }); } } + /// + /// Adds the given to the tree window. + /// + /// The ID of the TreeWindow item to which the edges belong. + /// The edges to be added. + private void AddEdges(string id, IEnumerable edges) + { + foreach (Edge edge in edges) + { + GameObject edgeObject = GraphElementIDMap.Find(edge.ID); + string title = edge.ToShortString(); + AddItem($"{id}#{CleanupID(edge.ID)}", false, title, Icons.Edge, edgeObject, edge); + } + } + /// /// Collapses the given . /// Its children will be removed from the tree window. /// /// The node represented by the item. /// The item to be collapsed. - private void CollapseNode(Node node, GameObject item) + /// The group in which the node is contained, if any. + private void CollapseNode(Node node, GameObject item, TreeWindowGroup inGroup = null) { CollapseItem(item); - RemoveNodeChildren(node); + RemoveNodeChildren(node, inGroup); } /// @@ -453,16 +688,16 @@ private void SearchFor(string searchTerm) ClearTree(); if (searchTerm == null || searchTerm.Trim().Length == 0) { - AddRoots(); + AddRoots().Forget(); return; } - foreach (Node node in searcher.Search(searchTerm)) + foreach (Node node in Searcher.Search(searchTerm)) { GameObject nodeGameObject = GraphElementIDMap.Find(node.ID, mustFindElement: true); AddItem(CleanupID(node.ID), - 0, node.ToShortString(), 0, nodeTypeUnicode, nodeGameObject, node, - null, (_, _) => RevealElement(node).Forget()); + false, node.ToShortString(), Icons.Node, nodeGameObject, node, + expandItem: (_, _) => RevealElement(node).Forget()); } items.position = items.position.WithXYZ(y: 0); @@ -478,12 +713,19 @@ private void SearchFor(string searchTerm) /// Whether to make the source or target node of the edge visible. public async UniTaskVoid RevealElement(GraphElement element, bool viaSource = false) { + TreeWindowGroup group = Grouper?.GetGroupFor(element); if (SearchField == null) { // We need to wait until the window is initialized. // This case may occur when the method is called from the outside. await UniTask.WaitUntil(() => SearchField != null); } + if (!ShouldBeDisplayed(element) || (group == null && Grouper != null && Grouper.IsActive)) + { + ShowNotification.Warn("Element filtered out", + "Element is not included in the current filter or group and thus can't be shown."); + return; + } SearchField.onValueChanged.RemoveListener(SearchFor); SearchField.text = string.Empty; SearchField.ReleaseSelection(); @@ -496,13 +738,13 @@ public async UniTaskVoid RevealElement(GraphElement element, bool viaSource = fa Edge edge => viaSource ? edge.Source : edge.Target, _ => throw new ArgumentOutOfRangeException(nameof(element)) }; - string transformID = CleanupID(current.ID); + string transformID = ElementId(current, group); if (element is Edge) { expandedItems.Add(transformID); transformID += viaSource ? "#Outgoing" : "#Incoming"; expandedItems.Add(transformID); - transformID += $"#{CleanupID(element.ID)}"; + transformID += $"#{ElementId(element, group)}"; } // We need to find a path from the root to the node, which we do by working our way up the hierarchy. @@ -510,13 +752,18 @@ public async UniTaskVoid RevealElement(GraphElement element, bool viaSource = fa while (current.Parent != null) { current = current.Parent; - expandedItems.Add(CleanupID(current.ID)); + expandedItems.Add(ElementId(current, group)); } - AddRoots(); + // Finally, if the element is in a group, we need to expand the group. + if (group != null) + { + expandedItems.Add(CleanupID(group.Text)); + } + + // We need to wait until the transform actually exists, hence the await. + await AddRoots(); - // We need to wait until the transform actually exists, hence the yield. - await UniTask.Yield(); RectTransform item = (RectTransform)items.Find(transformID); scrollRect.ScrollTo(item, duration: 1f); @@ -555,7 +802,24 @@ protected override void StartDesktop() SearchField.onDeselect.AddListener(_ => SEEInput.KeyboardShortcutsEnabled = true); SearchField.onValueChanged.AddListener(SearchFor); - AddRoots(); + FilterButton = root.Find("Search/Filter").gameObject.MustGetComponent(); + SortButton = root.Find("Search/Sort").gameObject.MustGetComponent(); + GroupButton = root.Find("Search/Group").gameObject.MustGetComponent(); + PopupMenu.PopupMenu popupMenu = gameObject.AddComponent(); + ContextMenu = new TreeWindowContextMenu(popupMenu, Searcher, Grouper, Rebuild, + FilterButton, SortButton, GroupButton); + + Rebuild(); + } + + /// + /// Rebuilds the tree window. + /// + private void Rebuild() + { + ClearTree(); + Grouper?.RebuildCounts(); + AddRoots().Forget(); } } } diff --git a/Assets/SEE/UI/Window/TreeWindow/TreeWindow.cs b/Assets/SEE/UI/Window/TreeWindow/TreeWindow.cs index b3b79d0e4a..3f03577137 100644 --- a/Assets/SEE/UI/Window/TreeWindow/TreeWindow.cs +++ b/Assets/SEE/UI/Window/TreeWindow/TreeWindow.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; +using Cysharp.Threading.Tasks; using SEE.DataModel; using SEE.DataModel.DG; +using SEE.DataModel.GraphSearch; using SEE.UI.Notification; using SEE.Utils; using UnityEngine; @@ -27,71 +30,100 @@ public partial class TreeWindow : BaseWindow, IObserver /// private const string treeItemPrefab = "Prefabs/UI/TreeViewItem"; - // TODO: In the future, distinguish by node/edge type as well for the icons. /// - /// The unicode character for a node. + /// The graph to be displayed. + /// Must be set before starting the window. /// - private const char nodeTypeUnicode = '\uf1b2'; + public Graph Graph; /// - /// The unicode character for an edge. + /// The search helper used to search for elements in the graph. + /// We also use this to keep track of the current filter, sort, and group settings. /// - private const char edgeTypeUnicode = '\uf542'; + private GraphSearch Searcher; /// - /// The unicode character for outgoing edges. + /// Transform of the object containing the items of the tree window. /// - private const char outgoingEdgeUnicode = '\uf2f5'; + private RectTransform items; /// - /// The unicode character for incoming edges. + /// The context menu that is displayed when the user right-clicks on an item + /// or uses the filter or sort buttons. /// - private const char incomingEdgeUnicode = '\uf2f6'; + private TreeWindowContextMenu ContextMenu; /// - /// The graph to be displayed. - /// Must be set before starting the window. + /// The grouper that is used to group the elements in the tree window. /// - public Graph Graph; + private TreeWindowGrouper Grouper; /// - /// The search helper used to search for elements in the graph. + /// The subscription to the graph observable. /// - private GraphSearch searcher; + private IDisposable subscription; - /// - /// The context menu that is displayed when the user right-clicks on an item. - /// - private PopupMenu.PopupMenu ContextMenu; + protected override void Start() + { + Searcher = new GraphSearch(Graph); + Grouper = new TreeWindowGrouper(Searcher.Filter, Graph); + subscription = Graph.Subscribe(this); + base.Start(); + } + + private void OnDestroy() + { + subscription.Dispose(); + } /// - /// Transform of the object containing the items of the tree window. + /// Returns the roots for the tree view. /// - private RectTransform items; - - protected override void Start() + /// The group to which the roots should belong. + /// The roots for the tree view. + /// + /// The roots for the tree view may differ from the roots of the graph, such as when the graph is grouped. + /// + private IList GetRoots(TreeWindowGroup inGroup = null) { - searcher = new GraphSearch(Graph); - ContextMenu = gameObject.AddComponent(); - Graph.Subscribe(this); - base.Start(); + return WithHiddenChildren(Graph.GetRoots(), inGroup).ToList(); } /// /// Adds the roots of the graph to the tree view. + /// It may take up to a frame to add and reorder all items, hence this method is asynchronous. /// - private void AddRoots() + private async UniTask AddRoots() { - // We will traverse the graph and add each node to the tree view. - IList roots = Graph.GetRoots(); - foreach (Node root in roots) + if (Grouper.IsActive) { - AddNode(root); + // Instead of the roots, we should add the categories as the first level. + foreach (TreeWindowGroup group in Grouper.AllGroups) + { + if (Grouper.MembersInGroup(group) > 0) + { + AddGroup(group); + } + } } - - if (roots.Count == 0) + else { - ShowNotification.Warn("Empty graph", "Graph has no roots. TreeView will be empty."); + IList roots = GetRoots(); + if (roots.Count == 0) + { + ShowNotification.Warn("Empty graph", "Graph has no roots. TreeView will be empty."); + return; + } + + foreach (Node root in roots) + { + AddNode(root); + } + await UniTask.Yield(); + foreach (Node root in roots) + { + OrderTree(root); + } } } @@ -102,7 +134,10 @@ private void ClearTree() { foreach (Transform child in items) { - Destroyer.Destroy(child.gameObject); + if (child != null) + { + Destroyer.Destroy(child.gameObject); + } } } @@ -148,8 +183,7 @@ public void OnNext(ChangeEvent value) case GraphElementTypeEvent: case HierarchyEvent: case NodeEvent: - ClearTree(); - AddRoots(); + Rebuild(); break; } } diff --git a/Assets/SEE/UI/Window/TreeWindow/TreeWindowContextMenu.cs b/Assets/SEE/UI/Window/TreeWindow/TreeWindowContextMenu.cs new file mode 100644 index 0000000000..27d8478b98 --- /dev/null +++ b/Assets/SEE/UI/Window/TreeWindow/TreeWindowContextMenu.cs @@ -0,0 +1,418 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Michsky.UI.ModernUIPack; +using SEE.DataModel.DG; +using SEE.DataModel.GraphSearch; +using SEE.Game.City; +using SEE.Tools.ReflexionAnalysis; +using SEE.UI.PopupMenu; +using SEE.Utils; +using UnityEngine; +using ArgumentOutOfRangeException = System.ArgumentOutOfRangeException; +using State = SEE.Tools.ReflexionAnalysis.State; + +namespace SEE.UI.Window.TreeWindow +{ + /// + /// Manages the context menu for the tree window. + /// + public class TreeWindowContextMenu + { + /// + /// The context menu that this class manages. + /// + private readonly PopupMenu.PopupMenu ContextMenu; + + /// + /// The graph search associated with the tree window. + /// We also retrieve the graph from this. + /// + private readonly GraphSearch Searcher; + + /// + /// The grouper that is used to group the elements in the tree window. + /// + private readonly TreeWindowGrouper Grouper; + + /// + /// The function to call to rebuild the tree window. + /// + private readonly Action Rebuild; + + /// + /// The button that opens the filter menu. + /// + private readonly ButtonManagerBasic FilterButton; + + /// + /// The button that opens the sort menu. + /// + private readonly ButtonManagerBasic SortButton; + + /// + /// The button that opens the group menu. + /// + private readonly ButtonManagerBasic GroupButton; + + /// + /// Constructor. + /// + /// The context menu that this class manages. + /// The graph search associated with the tree window. + /// The grouper that is used to group the elements in the tree window. + /// The function to call to rebuild the tree window. + /// The button that opens the filter menu. + /// The button that opens the sort menu. + /// The button that opens the group menu. + public TreeWindowContextMenu(PopupMenu.PopupMenu contextMenu, GraphSearch searcher, TreeWindowGrouper grouper, + Action rebuild, ButtonManagerBasic filterButton, ButtonManagerBasic sortButton, + ButtonManagerBasic groupButton) + { + ContextMenu = contextMenu; + Searcher = searcher; + Grouper = grouper; + Rebuild = rebuild; + FilterButton = filterButton; + SortButton = sortButton; + GroupButton = groupButton; + + ResetFilter(); + ResetSort(); + ResetGrouping(); + FilterButton.clickEvent.AddListener(ShowFilterMenu); + SortButton.clickEvent.AddListener(ShowSortMenu); + GroupButton.clickEvent.AddListener(ShowGroupMenu); + } + + /// + /// Forwards to . + /// + public void ShowWith(IEnumerable entries, Vector2 position) => ContextMenu.ShowWith(entries, position); + + #region Filter menu + + /// + /// Displays the filter menu. + /// + private void ShowFilterMenu() + { + UpdateFilterMenuEntries(); + ContextMenu.ShowWith(position: FilterButton.transform.position); + } + + /// + /// Updates the filter menu entries. + /// + private void UpdateFilterMenuEntries() + { + ISet nodeToggles = Searcher.Graph.AllToggleNodeAttributes(); + ISet edgeToggles = Searcher.Graph.AllToggleEdgeAttributes(); + ISet commonToggles = nodeToggles.Intersect(edgeToggles).ToHashSet(); + // Don't include common toggles in node/edge toggles. + nodeToggles.ExceptWith(commonToggles); + edgeToggles.ExceptWith(commonToggles); + + List entries = new() + { + new PopupMenuAction("Reset", () => + { + ResetFilter(); + UpdateFilterMenuEntries(); + Rebuild(); + }, Icons.ArrowRotateLeft, CloseAfterClick: false), + new PopupMenuAction("Edges", + () => + { + Searcher.Filter.IncludeEdges = !Searcher.Filter.IncludeEdges; + UpdateFilterMenuEntries(); + Rebuild(); + }, + Checkbox(Searcher.Filter.IncludeEdges), CloseAfterClick: false), + }; + + if (Searcher.Filter.ExcludeElements.Count > 0) + { + entries.Insert(0, new PopupMenuAction("Show hidden elements", + () => + { + Searcher.Filter.ExcludeElements.Clear(); + Rebuild(); + }, + Icons.Show)); + } + + if (commonToggles.Count > 0) + { + entries.Add(new PopupMenuHeading("Common properties")); + entries.AddRange(commonToggles.Select(FilterActionFor)); + } + if (nodeToggles.Count > 0) + { + entries.Add(new PopupMenuHeading("Node properties")); + entries.AddRange(nodeToggles.Select(FilterActionFor)); + } + if (edgeToggles.Count > 0) + { + entries.Add(new PopupMenuHeading("Edge properties")); + entries.AddRange(edgeToggles.Select(FilterActionFor)); + } + + ContextMenu.ClearEntries(); + ContextMenu.AddEntries(entries); + } + + /// + /// Returns the filter action for the given . + /// + /// The toggle attribute to create a filter action for. + /// The filter action for the given . + private PopupMenuAction FilterActionFor(string toggleAttribute) + { + return new PopupMenuAction(toggleAttribute, ToggleFilterAction, + Searcher.Filter.ExcludeToggleAttributes.Contains(toggleAttribute) + ? Icons.MinusCheckbox + : Checkbox(Searcher.Filter.IncludeToggleAttributes.Contains(toggleAttribute)), + CloseAfterClick: false); + + void ToggleFilterAction() + { + // Toggle from include->exclude->none->include. + if (Searcher.Filter.IncludeToggleAttributes.Contains(toggleAttribute)) + { + Searcher.Filter.IncludeToggleAttributes.Remove(toggleAttribute); + Searcher.Filter.ExcludeToggleAttributes.Add(toggleAttribute); + } + else if (Searcher.Filter.ExcludeToggleAttributes.Contains(toggleAttribute)) + { + Searcher.Filter.ExcludeToggleAttributes.Remove(toggleAttribute); + } + else + { + Searcher.Filter.IncludeToggleAttributes.Add(toggleAttribute); + } + UpdateFilterMenuEntries(); + Rebuild(); + } + } + + /// + /// Resets the filter to its default state. + /// + private void ResetFilter() + { + Searcher.Filter.Reset(); + } + + /// + /// Returns the icon for a checkbox. + /// + /// Whether the checkbox is checked. + /// The icon for a checkbox. + private static char Checkbox(bool value) => value ? Icons.CheckedCheckbox : Icons.EmptyCheckbox; + + #endregion + + #region Sort menu + + /// + /// Displays the sort menu. + /// + private void ShowSortMenu() + { + UpdateSortMenuEntries(); + ContextMenu.ShowWith(position: SortButton.transform.position); + } + + /// + /// Updates the sort menu entries. + /// + private void UpdateSortMenuEntries() + { + List entries = new() + { + new PopupMenuAction("Reset", () => + { + ResetSort(); + UpdateSortMenuEntries(); + Rebuild(); + }, Icons.ArrowRotateLeft, CloseAfterClick: false) + }; + + if (Grouper.IsActive) + { + entries.Add(new PopupMenuHeading("Grouping is active!")); + entries.Add(new PopupMenuHeading("Items ordered by group count.")); + } + + // These are the attributes we want to sort by for the time being. We might want to include + // all other attributes in the future, in which case the following code needs to be adapted. + entries.Add(SortActionFor("Source Name", x => x is Node node ? node.SourceName : null, false)); + entries.Add(SortActionFor("Source Line", x => x.SourceLine(), true)); + entries.Add(SortActionFor("Filename", x => x.Filename(), false)); + entries.Add(SortActionFor("Type", x => x.Type, false)); + + ContextMenu.ClearEntries(); + ContextMenu.AddEntries(entries); + } + + /// + /// Returns the sort action for the given and . + /// + /// The name of the sort attribute. + /// The key to sort by. + /// Whether this is for a numeric attribute. + /// The sort action for the given . + private PopupMenuAction SortActionFor(string name, Func key, bool numeric) + { + return new PopupMenuAction(name, ToggleSortAction, + SortIcon(numeric, Searcher.Sorter.IsAttributeDescending(name)), + CloseAfterClick: false); + + void ToggleSortAction() + { + // Switch from ascending->descending->none->ascending. + switch (Searcher.Sorter.IsAttributeDescending(name)) + { + case null: + Searcher.Sorter.AddSortAttribute(name, key, false); + break; + case false: + Searcher.Sorter.RemoveSortAttribute(name); + Searcher.Sorter.AddSortAttribute(name, key, true); + break; + default: + Searcher.Sorter.RemoveSortAttribute(name); + break; + } + UpdateSortMenuEntries(); + Rebuild(); + } + } + + /// + /// Returns the sort icon depending on whether the attribute is + /// and whether it is sorted in order. + /// + /// Whether the attribute is numeric. + /// Whether the attribute is sorted in descending order. + /// The sort icon depending on the given parameters. + private static char SortIcon(bool numeric, bool? descending) + { + return (numeric, descending) switch + { + (_, null) => ' ', + (true, true) => Icons.SortNumericDown, + (true, false) => Icons.SortNumericUp, + (false, true) => Icons.SortAlphabeticalDown, + (false, false) => Icons.SortAlphabeticalUp + }; + } + + /// + /// Resets the sort to its default state. + /// + private void ResetSort() + { + Searcher.Sorter.Reset(); + Searcher.Sorter.AddSortAttribute("Source Name", x => x is Node node ? node.SourceName : null, false); + } + + #endregion + + #region Group menu + + /// + /// Displays the group menu. + /// + private void ShowGroupMenu() + { + UpdateGroupMenuEntries(); + ContextMenu.ShowWith(position: GroupButton.transform.position); + } + + /// + /// Updates the group menu entries. + /// + private void UpdateGroupMenuEntries() + { + ISet currentGroups = Grouper.AllGroups.ToHashSet(); + List entries = new() + { + new PopupMenuAction("None", () => + { + ResetGrouping(); + Rebuild(); + UpdateGroupMenuEntries(); + }, Radio(!Grouper.IsActive), CloseAfterClick: true), + // Here we define the criteria by which a user can group. If new grouping criteria + // arise in the future, they need to be added here. + GroupActionFor("Reflexion State", + new TreeWindowGroupAssigment(Enum.GetValues(typeof(State)).Cast() + .ToDictionary(keySelector: x => x, + elementSelector: ReflexionStateToGroup), + element => element is Edge edge ? edge.State() : null)), + GroupActionFor("Type", + new TreeWindowGroupAssigment(Searcher.Graph.AllElementTypes().ToDictionary(x => x, TypeToGroup), element => element.Type)), + }; + ContextMenu.ClearEntries(); + ContextMenu.AddEntries(entries); + return; + + // Returns the group action for the given and . + PopupMenuAction GroupActionFor(string name, ITreeWindowGroupAssigment assignment) + { + return new PopupMenuAction(name, () => + { + Grouper.Assignment = assignment; + Rebuild(); + UpdateGroupMenuEntries(); + }, Radio(currentGroups.SetEquals(assignment.AllGroups)), + CloseAfterClick: true); + } + + // Returns the group for the given . + TreeWindowGroup ReflexionStateToGroup(State? state) + { + (string text, char icon) = state switch + { + State.Divergent => ("Divergent", Icons.CircleExclamationMark), + State.Absent => ("Absent", Icons.CircleMinus), + State.Allowed => ("Allowed", Icons.CircleCheckmark), + State.Convergent => ("Convergent", Icons.CircleCheckmark), + State.ImplicitlyAllowed => ("Implicitly allowed", Icons.CircleCheckmark), + State.AllowedAbsent => ("Allowed absent", Icons.CircleCheckmark), + State.Specified => ("Specified", Icons.CircleQuestionMark), + State.Unmapped => ("Unmapped", Icons.CircleQuestionMark), + State.Undefined => ("Undefined", Icons.QuestionMark), + null => ("Unknown", Icons.QuestionMark), + _ => throw new ArgumentOutOfRangeException(nameof(state), state, null) + }; + (Color start, Color end) = ReflexionVisualization.GetEdgeGradient(state ?? State.Undefined); + return new TreeWindowGroup(text, icon, start, end); + } + + TreeWindowGroup TypeToGroup(string type) + { + return new TreeWindowGroup(type, Icons.Info, Color.white, Color.white.Darker()); + } + } + + /// + /// Returns a radio button icon for the given . + /// + /// Whether the radio button is checked. + /// A radio button icon for the given . + private static char Radio(bool value) => value ? Icons.CheckedRadio : Icons.EmptyRadio; + + /// + /// Resets the grouping to its default state. + /// + private void ResetGrouping() + { + Grouper.Reset(); + } + + #endregion + } +} diff --git a/Assets/SEE/UI/Window/TreeWindow/TreeWindowContextMenu.cs.meta b/Assets/SEE/UI/Window/TreeWindow/TreeWindowContextMenu.cs.meta new file mode 100644 index 0000000000..ac3feb721e --- /dev/null +++ b/Assets/SEE/UI/Window/TreeWindow/TreeWindowContextMenu.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 44c97c49b245449fa2f08e9c2b3e98a3 +timeCreated: 1700750122 \ No newline at end of file diff --git a/Assets/SEE/UI/Window/TreeWindow/TreeWindowGrouper.cs b/Assets/SEE/UI/Window/TreeWindow/TreeWindowGrouper.cs new file mode 100644 index 0000000000..4207f66f4c --- /dev/null +++ b/Assets/SEE/UI/Window/TreeWindow/TreeWindowGrouper.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SEE.DataModel.DG; +using SEE.DataModel.GraphSearch; +using SEE.Utils; +using UnityEngine; + +namespace SEE.UI.Window.TreeWindow +{ + /// + /// A group of graph elements in the tree window. + /// + /// The text to display for this group. + /// The icon to display for this group. + /// The start color of the gradient to use for this group. + /// The end color of the gradient to use for this group. + public record TreeWindowGroup(string Text, char IconGlyph, Color StartColor, Color EndColor) + { + /// + /// Returns the color gradient to use for this group. + /// + public Color[] Gradient => new[] { StartColor, EndColor }; + } + + /// + /// An assignment of graph elements to s. + /// + public interface ITreeWindowGroupAssigment + { + /// + /// Returns the group the given element belongs to. + /// + /// The element whose group shall be returned. + /// The group the given element belongs to. + public TreeWindowGroup GroupFor(GraphElement element); + + /// + /// Returns all groups that are available, ordered by the number of members (descending). + /// + public IEnumerable AllGroups + { + get; + } + + /// + /// A dummy group assignment that always returns null. + /// + /// A dummy group assignment that always returns null. + public static ITreeWindowGroupAssigment Dummy() + { + return new TreeWindowGroupAssigment(new Dictionary(), _ => null); + } + } + + /// + /// A group assignment that is based on a mapping from group identifiers to the corresponding groups. + /// + /// A mapping from group identifiers to the corresponding groups. + /// This mapping does not need to be injective. + /// A function that returns the group identifier for a given element. + /// The type of the group identifiers. + public record TreeWindowGroupAssigment( + IDictionary Groups, + Func DetermineGroup) : ITreeWindowGroupAssigment + { + public IEnumerable AllGroups => Groups.Values.Distinct(); + + public TreeWindowGroup GroupFor(GraphElement element) + { + T itsGroup = DetermineGroup(element); + if (itsGroup == null) + { + return null; + } + return Groups.TryGetValue(DetermineGroup(element), out TreeWindowGroup group) ? group : null; + } + } + + /// + /// Manages the grouping of graph elements in the tree window. + /// + public class TreeWindowGrouper + { + /// + /// The assignment of graph elements to groups. + /// Note that you will need to invoke after changing this. + /// + public ITreeWindowGroupAssigment Assignment + { + private get; + set; + } + + /// + /// The filter to use for determining which elements are included in the tree window. + /// + private readonly GraphFilter Filter; + + /// + /// The graph on which the tree window is based. + /// + private readonly Graph Graph; + + /// + /// A mapping from node IDs and groups to the number of descendants of that node + /// that are included in the tree window and belong to that group. + /// + private readonly DefaultDictionary<(string nodeId, TreeWindowGroup group), int> DescendantCounts; + + /// + /// Creates a new with the given parameters. + /// + /// The filter to use for determining which elements are included in the tree window. + /// The graph on which the tree window is based. + public TreeWindowGrouper(GraphFilter filter, Graph graph) + { + Filter = filter; + Graph = graph; + DescendantCounts = new DefaultDictionary<(string, TreeWindowGroup), int>(); + Reset(); + } + + /// + /// Resets the grouping of graph elements in the tree window. + /// + public void Reset() + { + Assignment = ITreeWindowGroupAssigment.Dummy(); + DescendantCounts.Clear(); + } + + /// + /// Returns all groups that are available, ordered by the number of members (descending). + /// + public IOrderedEnumerable AllGroups => Assignment.AllGroups.OrderByDescending(MembersInGroup); + + /// + /// Whether there is at least one group. + /// + public bool IsActive => AllGroups.Any(); + + /// + /// Returns the group for the given element. + /// + /// The element whose group shall be returned. + /// The group for the given element. + public TreeWindowGroup GetGroupFor(GraphElement element) + { + return Assignment.GroupFor(element); + } + + /// + /// Returns the number of descendants of the given that are included in the tree window + /// and belong to the given . + /// + /// The node whose descendants shall be counted. + /// The group to which the descendants shall belong. + /// The number of descendants of the given that are included in the tree window + /// and belong to the given . + public int DescendantsInGroup(Node node, TreeWindowGroup group) => DescendantCounts[(node.ID, group)]; + + /// + /// Returns the number of members of the given . + /// + /// The group whose members shall be counted. + /// The number of members of the given . + public int MembersInGroup(TreeWindowGroup group) => Graph.GetRoots().Sum(x => DescendantsInGroup(x, group)); + + /// + /// Rebuilds the descendant counts for all nodes. + /// + /// + /// This method should be called whenever the graph or its filter changes. + /// + public void RebuildCounts() + { + DescendantCounts.Clear(); + foreach (Node root in Graph.GetRoots()) + { + BuildDescendantCounts(root); + } + } + + /// + /// Returns all direct children of the given that are included in the tree window + /// and belong to the given , including any connected edges. + /// + /// The node whose children and edges shall be returned. + /// The group to which the children and edges shall belong. + /// All direct children of the given that are included in the tree window + /// and belong to the given . + public IEnumerable ChildrenInGroup(Node node, TreeWindowGroup group) + { + return node.Children().Concat(node.Edges).Where(x => Filter.Includes(x) && GetGroupFor(x) == group); + } + + /// + /// Returns whether the given is relevant for the given . + /// Specifically, this is the case if the element is included in the tree window and belongs to the group, + /// or if it is an ascendant of such an element. + /// + /// The element whose relevance shall be checked. + /// The group for which the relevance shall be checked. + /// Whether the given is relevant for the given . + public bool IsRelevantFor(GraphElement element, TreeWindowGroup group) + { + return DescendantCounts[(element.ID, group)] > 0 || (Filter.Includes(element) && GetGroupFor(element) == group); + } + + /// + /// Computes the descendant counts for the given + /// and stores them in . + /// + /// The node whose descendant counts shall be computed. + private void BuildDescendantCounts(Node node) + { + foreach (Node descendant in node.PostOrderDescendants()) + { + // Due to post-order traversal, we can assume that the counts for all children have already been computed. + foreach (TreeWindowGroup group in AllGroups) + { + // We add the number of relevant children to the sum of the counts of *all* children. + // The latter uses all children, not just the relevant ones, because we want to include + // relevant descendants of irrelevant children as well. + DescendantCounts[(descendant.ID, group)] = descendant.Children().Sum(x => DescendantCounts[(x.ID, group)]) + ChildrenInGroup(descendant, group).Count(); + } + } + } + } +} diff --git a/Assets/SEE/UI/Window/TreeWindow/TreeWindowGrouper.cs.meta b/Assets/SEE/UI/Window/TreeWindow/TreeWindowGrouper.cs.meta new file mode 100644 index 0000000000..0bd3621140 --- /dev/null +++ b/Assets/SEE/UI/Window/TreeWindow/TreeWindowGrouper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 05795f4b3d7b4b4e8511e1e25d5cc3ca +timeCreated: 1701276810 \ No newline at end of file diff --git a/Assets/SEE/UI/Window/WindowSpace.cs b/Assets/SEE/UI/Window/WindowSpace.cs index f2c9288fcf..52bbb19351 100644 --- a/Assets/SEE/UI/Window/WindowSpace.cs +++ b/Assets/SEE/UI/Window/WindowSpace.cs @@ -88,7 +88,6 @@ public void AddWindow(BaseWindow window) /// Closes a previously opened window. /// /// The window which should be closed. - /// If the given is already closed. /// If the given is null. public void CloseWindow(BaseWindow window) { @@ -96,12 +95,10 @@ public void CloseWindow(BaseWindow window) { throw new ArgumentNullException(nameof(window)); } - else if (!windows.Contains(window)) + else if (windows.Contains(window)) { - throw new ArgumentException("Given window is already closed."); + windows.Remove(window); } - - windows.Remove(window); } /// diff --git a/Assets/SEE/Utils/CollectionExtensions.cs b/Assets/SEE/Utils/CollectionExtensions.cs new file mode 100644 index 0000000000..9f00d38161 --- /dev/null +++ b/Assets/SEE/Utils/CollectionExtensions.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Linq; +using FuzzySharp.Utils; + +namespace SEE.Utils +{ + /// + /// Contains utility extension methods for collections and enumerables. + /// + public static class CollectionExtensions + { + /// + /// Returns all permutations of this . + /// + /// The list whose permutations shall be returned + /// Type of the given list + /// All permutations of this + /// For [1,2,3] this would return {[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]}. + public static ISet> Permutations(this IList inputList) + { + ISet> result = new HashSet>(); + if (inputList.Count <= 1) + { + result.Add(inputList); + } + else + { + foreach (IList permutation in inputList.Skip(1).ToList().Permutations()) + { + result.UnionWith(Enumerable.Range(0, inputList.Count) + .Select(i => permutation.Take(i).Concat(inputList.Take(1)) + .Concat(permutation.Skip(i)).ToList())); + } + } + + return result; + } + + /// + /// Toggles the given in the given , + /// that is, if the set contains the element, it will be removed, otherwise it will be added. + /// + /// The set in which the element shall be toggled. + /// The element which shall be toggled. + /// The type of the elements in the set. + public static void Toggle(this ISet set, T element) + { + if (!set.Add(element)) + { + set.Remove(element); + } + } + + /// + /// Gets the value for the given from the given . + /// If the key is not present in the dictionary, the given + /// will be added to the dictionary and returned. + /// + /// The dictionary from which the value shall be retrieved. + /// The key for which the value shall be retrieved. + /// The default value which shall be added to the dictionary if the key is not present. + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + /// The value for the given from the given . + public static V GetOrAdd(this IDictionary dict, K key, V defaultValue) + { + if (dict.TryGetValue(key, out V value)) + { + return value; + } + else + { + return dict[key] = defaultValue; + } + } + } +} diff --git a/Assets/SEE/Utils/ListExtensions.cs.meta b/Assets/SEE/Utils/CollectionExtensions.cs.meta similarity index 100% rename from Assets/SEE/Utils/ListExtensions.cs.meta rename to Assets/SEE/Utils/CollectionExtensions.cs.meta diff --git a/Assets/SEE/Utils/DefaultDictionary.cs b/Assets/SEE/Utils/DefaultDictionary.cs new file mode 100644 index 0000000000..129b29980a --- /dev/null +++ b/Assets/SEE/Utils/DefaultDictionary.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace SEE.Utils +{ + /// + /// A dictionary which adds and returns a default value if the key is not present. + /// Note that the dictionary must not be downcast to a normal dictionary, as this would + /// remove the default value functionality. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + public class DefaultDictionary : Dictionary where V : new() + { + public new V this[K key] + { + get => this.GetOrAdd(key, new V()); + set => base[key] = value; + } + } +} diff --git a/Assets/SEE/Utils/DefaultDictionary.cs.meta b/Assets/SEE/Utils/DefaultDictionary.cs.meta new file mode 100644 index 0000000000..70a06b8cca --- /dev/null +++ b/Assets/SEE/Utils/DefaultDictionary.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 438efdbfddf247a28fb4f9a2f261f5f9 +timeCreated: 1701353409 \ No newline at end of file diff --git a/Assets/SEE/Utils/Icons.cs b/Assets/SEE/Utils/Icons.cs new file mode 100644 index 0000000000..eed21942d0 --- /dev/null +++ b/Assets/SEE/Utils/Icons.cs @@ -0,0 +1,44 @@ +namespace SEE.Utils +{ + /// + /// Contains unicode characters for icons in the FontAwesome 6 free font. + /// + /// If you need to find out what a given icon looks like, search for the Unicode sequence + /// (e.g. "F1B2") on https://fontawesome.com/icons. The first result should be the icon you're looking for. + /// + /// See https://github.com/uni-bremen-agst/SEE/wiki/Icons for more information. + /// + public static class Icons + { + public const char Node = '\uF1B2'; + public const char Edge = '\uF542'; + public const char OutgoingEdge = '\uF2F5'; + public const char IncomingEdge = '\uF2F6'; + public const char LiftedIncomingEdge = '\uF090'; + public const char LiftedOutgoingEdge = '\uF08B'; + public const char EmptyCheckbox = '\uF0C8'; + public const char CheckedCheckbox = '\uF14A'; + public const char MinusCheckbox = '\uF146'; + public const char Checkmark = '\uF00C'; + public const char Trash = '\uF1F8'; + public const char Info = '\uF05A'; + public const char LightBulb = '\uF0EB'; + public const char Code = '\uF121'; + public const char TreeView = '\uF802'; + public const char Compare = '\uE13A'; + public const char Hide = '\uF070'; + public const char Show = '\uF06E'; + public const char QuestionMark = '?'; + public const char ArrowRotateLeft = '\uF0E2'; + public const char SortAlphabeticalUp = '\uF15E'; + public const char SortAlphabeticalDown = '\uF15D'; + public const char SortNumericUp = '\uF163'; + public const char SortNumericDown = '\uF162'; + public const char EmptyRadio = '\uF111'; + public const char CheckedRadio = '\uF192'; + public const char CircleMinus = '\uF056'; + public const char CircleCheckmark = '\uF058'; + public const char CircleQuestionMark = '\uF059'; + public const char CircleExclamationMark = '\uF06A'; + } +} diff --git a/Assets/SEE/Utils/Icons.cs.meta b/Assets/SEE/Utils/Icons.cs.meta new file mode 100644 index 0000000000..cf3de511cd --- /dev/null +++ b/Assets/SEE/Utils/Icons.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 82bfa0ee71124c189c85ddd900596c50 +timeCreated: 1700173761 \ No newline at end of file diff --git a/Assets/SEE/Utils/ListExtensions.cs b/Assets/SEE/Utils/ListExtensions.cs deleted file mode 100644 index c3bbcb57ac..0000000000 --- a/Assets/SEE/Utils/ListExtensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using FuzzySharp.Utils; - -namespace SEE.Utils -{ - public static class ListExtensions - { - public static void Resize(this List list, int count) - { - if (list.Count < count) - { - if (list.Capacity < count) - { - list.Capacity = count; - } - - int end = count - list.Count; - for (int i = 0; i < end; i++) - { - list.Add(default); - } - } - } - - /// - /// Returns all permutations of this . - /// - /// The list whose permutations shall be returned - /// Type of the given list - /// All permutations of this - /// For [1,2,3] this would return {[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]}. - public static ISet> Permutations(this IList inputList) - { - ISet> result = new HashSet>(); - if (inputList.Count <= 1) - { - result.Add(inputList); - } - else - { - foreach (IList permutation in inputList.Skip(1).ToList().Permutations()) - { - result.UnionWith(Enumerable.Range(0, inputList.Count) - .Select(i => permutation.Take(i).Concat(inputList.Take(1)) - .Concat(permutation.Skip(i)).ToList())); - } - } - - return result; - } - } -} \ No newline at end of file diff --git a/Assets/SEE/Utils/MathExtensions.cs b/Assets/SEE/Utils/MathExtensions.cs index 857a24b583..27166cbed0 100644 --- a/Assets/SEE/Utils/MathExtensions.cs +++ b/Assets/SEE/Utils/MathExtensions.cs @@ -320,32 +320,32 @@ public static void TestCircleAABB(Vector2 center, float radius, Vector2 min, Vec public static Vector2 ZW(this Vector4 a) => new(a.z, a.w); /// - /// Returns the given , replacing any of its components with - /// , , and/or , respectively, if they were given. + /// Returns the given , replacing any of its components with + /// , and/or respectively, if they were given. /// If no parameters are given, this method will be equivalent to the identity function. /// - /// The vector whose components shall be replaced + /// The vector whose components shall be replaced /// New X component /// New Y component - /// New Z component /// with its components replaced - public static Vector3 WithXYZ(this Vector3 vector3, float? x = null, float? y = null, float? z = null) + public static Vector2 WithXY(this Vector2 vector2, float? x = null, float? y = null) { - return new Vector3(x ?? vector3.x, y ?? vector3.y, z ?? vector3.z); + return new Vector2(x ?? vector2.x, y ?? vector2.y); } /// - /// Returns the given , replacing any of its components with - /// , and/or respectively, if they were given. + /// Returns the given , replacing any of its components with + /// , , and/or , respectively, if they were given. /// If no parameters are given, this method will be equivalent to the identity function. /// - /// The vector whose components shall be replaced + /// The vector whose components shall be replaced /// New X component /// New Y component + /// New Z component /// with its components replaced - public static Vector2 WithXY(this Vector2 vector2, float? x = null, float? y = null) + public static Vector3 WithXYZ(this Vector3 vector3, float? x = null, float? y = null, float? z = null) { - return new Vector2(x ?? vector2.x, y ?? vector2.y); + return new Vector3(x ?? vector3.x, y ?? vector3.y, z ?? vector3.z); } /// diff --git a/Assets/Scenes/SEEWorld.unity b/Assets/Scenes/SEEWorld.unity index 8b020abe0f..f9037b8ee3 100644 --- a/Assets/Scenes/SEEWorld.unity +++ b/Assets/Scenes/SEEWorld.unity @@ -1372,6 +1372,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 175ce339391b42b1b31b4db72cbdb2ae, type: 3} m_Name: m_EditorClassIdentifier: + PKey: 3082018A028201810084BA2FF29AB0282BEE362EA659FE9C5A90CC7C6E6AED7743847C2CE12FCAE85963CE613C4DA2B1685EB8B355A95072FF7FBC4D08D5545573A3BB8C21667D7FEE766DA410B22681ACCD028DB46CE5EAE8E9D455B350BA3E6867480F2990799F3D3130F87EAAC7B8AD4226634A28C99922C43C8CC8984EA92FB25FDC7510AEFA7793AF0042DF30498BC1F0507613ABB1A30F3954FF21A6631EB83A40A4DD7B5EB89B5CA1B2982605453A0B1B2A8D4064917E8C6582A6DBE4A032E3EB84B9B2A4500C8ADEA236787CBC709E30D893D08DC8B824D309C0AA4CFE976A4645B8EBDA797E618A5A22A775078C84BC536486D6F45E0659A1FEB03FEEC6944393025E1EFF3948E42ADF05A803770D327F85B900B1D7ADFB9B7BBE4E01E23E7653578B28917D4683DA4EC538758EF6A02532CD74CAA6B644D0FFCE5AA096A6CFE76E5C0BD30DA19DF4187E1E358077CD771B0B470441A934B8F991FE78EAA90B35AD7CF600B573272D9E2888DB0BB34FA374F29FDA92E2D3E2D36A1E1A1E40164648D63F930203010001 PublicKey: 3082018A0282018100D5EB3F1B3AB0ABCE827FA70BA32FCF8C834B1206464B785B7AE13D962F19887D6733B0A555651F130BCDB04D8BF6F199EF76DB7932C001B6916D0E3F0C84C9A7DDB7BC62C93590E49C5DD97109B01B2CFAFD183A45DD8E02F86EBAFB4C74DF24449D582DFC03D8E783DA035BB2985907EC00774D748AFC9A0195E05E2B992A7877F437DB6088E2490A53D83D8F729482A383142AE5FBAFA4F2C3112E5A5C16D520ADFCF5E0F0C9FF865126AFC8B97EAEFD77CE31A431F0E66C2200CDF70BC56B478FB7B56858CE605E3313A876E4B719529DB929D9D7A966B16A9656FF639AE7382B6B7591E19D05A0B3546803007DCE8354FDFDFC0DAB4E5103C0ED67A6BFD42E810C78A649DD419A0E4C1BB15267A85DB4336D101F3799B71A654C5A8422875EE4ADCF7FD7D684D5B71AC4C0E392A533DE143AACC68CCDD77F3FB47AFCF59F058E3873FCF454CED0EF1B5DF8A18A14C4D56A4C81E6F3D3D8246BDF2E402C78AB50DCA8CC603E2681B9E28A032BFE156DDC04C266986E3110112A86CC01C5150203010001 BaseUrl: https://stvr2.informatik.uni-bremen.de:9443/axivion/projects/SEE/ Token: 0.000000000000Q.2Jb6PIgB1pk4g8ss-DtnfdtDp0xlcugYQHFRcRvBRH4 @@ -2723,8 +2724,12 @@ MonoBehaviour: Data: 8|System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Boolean, mscorlib]], mscorlib - Name: comparer - Entry: 9 - Data: 4 + Entry: 7 + Data: 9|System.Collections.Generic.GenericEqualityComparer`1[[System.String, + mscorlib]], mscorlib + - Name: + Entry: 8 + Data: - Name: Entry: 12 Data: 0 @@ -2736,11 +2741,11 @@ MonoBehaviour: Data: - Name: InnerNodeLayout Entry: 7 - Data: 9|System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[SEE.Game.City.NodeLayoutKind, + Data: 10|System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[SEE.Game.City.NodeLayoutKind, SEE]], mscorlib - Name: comparer Entry: 9 - Data: 4 + Data: 9 - Name: Entry: 12 Data: 0 @@ -2752,11 +2757,11 @@ MonoBehaviour: Data: - Name: InnerNodeShape Entry: 7 - Data: 10|System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[SEE.Game.City.NodeShapes, + Data: 11|System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[SEE.Game.City.NodeShapes, SEE]], mscorlib - Name: comparer Entry: 9 - Data: 4 + Data: 9 - Name: Entry: 12 Data: 0 @@ -2768,11 +2773,11 @@ MonoBehaviour: Data: - Name: LoadedForNodeTypes Entry: 7 - Data: 11|System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Boolean, + Data: 12|System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Boolean, mscorlib]], mscorlib - Name: comparer Entry: 9 - Data: 4 + Data: 9 - Name: Entry: 12 Data: 0 diff --git a/Axivion/axivion-jenkins.bat b/Axivion/axivion-jenkins.bat index 14ea8be980..ee4d5db0ad 100644 --- a/Axivion/axivion-jenkins.bat +++ b/Axivion/axivion-jenkins.bat @@ -42,8 +42,8 @@ REM net (start|stop) "axivion_dashboard_service" REM or use the Windows Services Console (services.msc). REM The Visual Studio .csproj files need to be created before we can start the build. -"C:\Program Files\Unity\Hub\Editor\2022.3.13f1\Editor\Unity.exe" -batchmode -nographics -logFile - -executeMethod CITools.SolutionGenerator.Sync -projectPath . -quit -REM "C:\Program Files\Unity\Hub\Editor\2022.3.13f1\Editor\Unity.exe" -batchmode -nographics -logFile - -executeMethod UnityEditor.SyncVS.SyncSolution -projectPath . -quit +"C:\Program Files\Unity\Hub\Editor\2022.3.15f1\Editor\Unity.exe" -batchmode -nographics -logFile - -executeMethod CITools.SolutionGenerator.Sync -projectPath . -quit +REM "C:\Program Files\Unity\Hub\Editor\2022.3.15f1\Editor\Unity.exe" -batchmode -nographics -logFile - -executeMethod UnityEditor.SyncVS.SyncSolution -projectPath . -quit REM Count the number of command-line parameters setlocal enabledelayedexpansion diff --git a/Packages/manifest.json b/Packages/manifest.json index 05b5e45618..9f9fa957ab 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -2,17 +2,17 @@ "dependencies": { "com.cakeslice.outline-effect": "https://github.com/cakeslice/Outline-Effect.git", "com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.2.5", - "com.openai.unity": "5.0.11", + "com.openai.unity": "7.0.3", "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", "com.unity.ai.navigation": "1.1.5", - "com.unity.burst": "1.8.10", + "com.unity.burst": "1.8.11", "com.unity.formats.fbx": "4.2.1", - "com.unity.ide.rider": "3.0.26", + "com.unity.ide.rider": "3.0.27", "com.unity.ide.visualstudio": "2.0.22", "com.unity.ide.vscode": "1.2.5", "com.unity.inputsystem": "1.7.0", - "com.unity.netcode.gameobjects": "1.7.0", + "com.unity.netcode.gameobjects": "1.7.1", "com.unity.postprocessing": "3.2.2", "com.unity.shadergraph": "14.0.9", "com.unity.test-framework": "1.1.33", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index eb153d0dee..d57beeaf10 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -22,12 +22,13 @@ "hash": "72e620d169841f32bc6110ad0e12ee9eae6f1aaf" }, "com.openai.unity": { - "version": "5.0.11", + "version": "7.0.3", "depth": 0, "source": "registry", "dependencies": { - "com.utilities.rest": "2.1.8", - "com.utilities.encoder.wav": "1.0.6" + "com.utilities.rest": "2.3.1", + "com.utilities.encoder.wav": "1.0.8", + "com.utilities.encoder.ogg": "3.0.12" }, "url": "https://package.openupm.com" }, @@ -56,7 +57,7 @@ "url": "https://packages.unity.com" }, "com.unity.burst": { - "version": "1.8.10", + "version": "1.8.11", "depth": 0, "source": "registry", "dependencies": { @@ -99,7 +100,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.26", + "version": "3.0.27", "depth": 0, "source": "registry", "dependencies": { @@ -140,7 +141,7 @@ "url": "https://packages.unity.com" }, "com.unity.netcode.gameobjects": { - "version": "1.7.0", + "version": "1.7.1", "depth": 0, "source": "registry", "dependencies": { @@ -354,7 +355,7 @@ "url": "https://packages.unity.com" }, "com.utilities.async": { - "version": "2.0.1", + "version": "2.1.1", "depth": 2, "source": "registry", "dependencies": { @@ -365,27 +366,36 @@ "url": "https://package.openupm.com" }, "com.utilities.audio": { - "version": "1.0.6", + "version": "1.0.11", "depth": 2, "source": "registry", "dependencies": { "com.unity.modules.audio": "1.0.0", "com.unity.modules.unitywebrequestaudio": "1.0.0", - "com.utilities.async": "2.0.1" + "com.utilities.async": "2.1.1" + }, + "url": "https://package.openupm.com" + }, + "com.utilities.encoder.ogg": { + "version": "3.0.12", + "depth": 1, + "source": "registry", + "dependencies": { + "com.utilities.audio": "1.0.11" }, "url": "https://package.openupm.com" }, "com.utilities.encoder.wav": { - "version": "1.0.6", + "version": "1.0.8", "depth": 1, "source": "registry", "dependencies": { - "com.utilities.audio": "1.0.6" + "com.utilities.audio": "1.0.11" }, "url": "https://package.openupm.com" }, "com.utilities.extensions": { - "version": "1.1.10", + "version": "1.1.13", "depth": 2, "source": "registry", "dependencies": { @@ -395,12 +405,12 @@ "url": "https://package.openupm.com" }, "com.utilities.rest": { - "version": "2.1.8", + "version": "2.3.1", "depth": 1, "source": "registry", "dependencies": { - "com.utilities.async": "2.0.1", - "com.utilities.extensions": "1.1.10", + "com.utilities.async": "2.1.1", + "com.utilities.extensions": "1.1.13", "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.modules.unitywebrequestassetbundle": "1.0.0", "com.unity.modules.unitywebrequestaudio": "1.0.0", diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index 91206fdc40..2e7bb8a620 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2022.3.13f1 -m_EditorVersionWithRevision: 2022.3.13f1 (5f90a5ebde0f) +m_EditorVersion: 2022.3.15f1 +m_EditorVersionWithRevision: 2022.3.15f1 (b58023a2b463) diff --git a/README.md b/README.md index 435b745028..4faa473b76 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Tests](https://github.com/uni-bremen-agst/SEE/actions/workflows/main.yml/badge.svg)](https://github.com/uni-bremen-agst/SEE/actions/workflows/main.yml) SEE visualizes hierarchical dependency graphs of software in 3D/VR based on the city metaphor. -The underlying game engine is Unity 3D (version 2022.3.13f1). +The underlying game engine is Unity 3D (version 2022.3.15f1). ![Screenshot of SEE](Screenshot.png)