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