226 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			226 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | ||
|  | using Unity.Multiplayer.Center.Analytics; | ||
|  | using UnityEditor; | ||
|  | using UnityEngine; | ||
|  | using UnityEngine.UIElements; | ||
|  | 
 | ||
|  | namespace Unity.Multiplayer.Center.Window | ||
|  | { | ||
|  |     internal class MultiplayerCenterWindow : EditorWindow, ISerializationCallbackReceiver | ||
|  |     { | ||
|  |         const string k_PathInPackage = "Packages/com.unity.multiplayer.center/Editor/MultiplayerCenterWindow"; | ||
|  |         const string k_SpinnerClassName = "processing"; | ||
|  |         const string k_SessionStateDomainReloadKey = "MultiplayerCenter.InDomainReload"; | ||
|  | 
 | ||
|  |         VisualElement m_SpinningIcon; | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Nest the main container in a VisualElement to allow for easy enabling/disabling of the entire window but | ||
|  |         /// without the spinning icon. | ||
|  |         /// </summary> | ||
|  |         VisualElement m_MainContainer; | ||
|  | 
 | ||
|  |         Vector2 m_WindowSize = new(350, 300); | ||
|  | 
 | ||
|  |         public int CurrentTab => m_TabGroup.CurrentTab; | ||
|  | 
 | ||
|  |         // Testing purposes only. We don't want to set CurrentTab from window | ||
|  |         internal int CurrentTabTest | ||
|  |         { | ||
|  |             get => m_TabGroup.CurrentTab; | ||
|  |             set => m_TabGroup.SetSelected(value); | ||
|  |         } | ||
|  | 
 | ||
|  |         [SerializeField] | ||
|  |         bool m_RequestGettingStartedTabAfterDomainReload = false; | ||
|  | 
 | ||
|  |         [SerializeField] | ||
|  |         TabGroup m_TabGroup; | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// This is the reference Multiplayer Center analytics implementation. This class owns it. | ||
|  |         /// </summary> | ||
|  |         IMultiplayerCenterAnalytics m_MultiplayerCenterAnalytics; | ||
|  | 
 | ||
|  |         IMultiplayerCenterAnalytics MultiplayerCenterAnalytics => m_MultiplayerCenterAnalytics ??= MultiplayerCenterAnalyticsFactory.Create(); | ||
|  | 
 | ||
|  |         [MenuItem("Window/Multiplayer/Multiplayer Center")] | ||
|  |         public static void OpenWindow() | ||
|  |         { | ||
|  |             var showUtility = false; // TODO: figure out if it would be a good idea to have a utility window (always on top, cannot be tabbed) | ||
|  |             GetWindow<MultiplayerCenterWindow>(showUtility, "Multiplayer Center", true); | ||
|  |         } | ||
|  | 
 | ||
|  |         void OnEnable() | ||
|  |         { | ||
|  |             // Adjust window size based on dpi scaling | ||
|  |             var dpiScale = EditorGUIUtility.pixelsPerPoint; | ||
|  |             minSize = new Vector2(m_WindowSize.x * dpiScale, m_WindowSize.y * dpiScale); | ||
|  | 
 | ||
|  |             AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeDomainReload; | ||
|  |             AssemblyReloadEvents.afterAssemblyReload -= OnAfterDomainReload; | ||
|  |             AssemblyReloadEvents.beforeAssemblyReload += OnBeforeDomainReload; | ||
|  |             AssemblyReloadEvents.afterAssemblyReload += OnAfterDomainReload; | ||
|  |         } | ||
|  | 
 | ||
|  |         void OnDisable() | ||
|  |         { | ||
|  |             AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeDomainReload; | ||
|  |             AssemblyReloadEvents.afterAssemblyReload -= OnAfterDomainReload; | ||
|  |         } | ||
|  | 
 | ||
|  |         /// <summary> | ||
|  |         /// Changes Tab from Recommendation to the Quickstart tab. | ||
|  |         /// </summary> | ||
|  |         public void RequestShowGettingStartedTabAfterDomainReload() | ||
|  |         { | ||
|  |             m_RequestGettingStartedTabAfterDomainReload = true; | ||
|  | 
 | ||
|  |             // If no domain reload is necessary, this will be called. | ||
|  |             // If domain reload is necessary, the delay call will be forgotten, but CreateGUI will be called like after any domain reload | ||
|  |             // An extra delay is added to make sure that the visibility conditions of the Quickstart tab have been | ||
|  |             // fully evaluated. This solves MTT-8939. | ||
|  |             EditorApplication.delayCall += () => | ||
|  |             { | ||
|  |                 rootVisualElement.schedule.Execute(CallCreateGuiWithQuickstartRequest).ExecuteLater(300); | ||
|  |             }; | ||
|  |         } | ||
|  | 
 | ||
|  |         internal void DisableUiForInstallation() | ||
|  |         { | ||
|  |             SetSpinnerIconRotating(); | ||
|  |             m_MainContainer.SetEnabled(false); | ||
|  |         } | ||
|  | 
 | ||
|  |         internal void ReenableUiAfterInstallation() | ||
|  |         { | ||
|  |             RemoveSpinnerIconRotating(); | ||
|  |             m_MainContainer.SetEnabled(true); | ||
|  |         } | ||
|  | 
 | ||
|  |         void Update() | ||
|  |         { | ||
|  |             // Restore the GUI if it was cleared in OnBeforeSerialize. | ||
|  |             if (m_TabGroup == null || m_TabGroup.ViewCount < 1) | ||
|  |             { | ||
|  |                 CreateGUI(); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void CreateGUI() | ||
|  |         { | ||
|  |             rootVisualElement.name = "root"; | ||
|  |             m_MainContainer ??= new VisualElement(); | ||
|  |             m_MainContainer.name = "recommendation-tab-container"; | ||
|  |             m_MainContainer.Clear(); | ||
|  |             rootVisualElement.Add(m_MainContainer); | ||
|  |             m_SpinningIcon = new VisualElement(); | ||
|  |             var theme = EditorGUIUtility.isProSkin ? "dark" : "light"; | ||
|  |             rootVisualElement.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>($"{k_PathInPackage}/UI/{theme}.uss")); | ||
|  |             rootVisualElement.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>($"{k_PathInPackage}/UI/MultiplayerCenterWindow.uss")); | ||
|  | 
 | ||
|  |             if (m_TabGroup == null || m_TabGroup.ViewCount < 1 || !m_TabGroup.TabsAreValid()) | ||
|  |                 m_TabGroup = new TabGroup(MultiplayerCenterAnalytics, new ITabView[] {new RecommendationTabView(), new GettingStartedTabView()}); | ||
|  |             else // since we are not serializing the analytics provider, we need to set it again | ||
|  |                 m_TabGroup.MultiplayerCenterAnalytics = MultiplayerCenterAnalytics; | ||
|  | 
 | ||
|  |             m_TabGroup.CreateTabs(); | ||
|  |             m_MainContainer.Add(m_TabGroup.Root); | ||
|  | 
 | ||
|  |             var installationInProgress = !PackageManagement.IsInstallationFinished(); | ||
|  |             SetWindowContentEnabled(installationInProgress, m_RequestGettingStartedTabAfterDomainReload); | ||
|  |             ShowAppropriateTab(installationInProgress); | ||
|  |         } | ||
|  | 
 | ||
|  |         void ShowAppropriateTab(bool installationInProgress) | ||
|  |         { | ||
|  |             if (installationInProgress) | ||
|  |             { | ||
|  |                 PackageManagement.RegisterToExistingInstaller(b => RequestShowGettingStartedTabAfterDomainReload()); | ||
|  |                 m_TabGroup.SetSelected(0, force: true); | ||
|  |                 return; | ||
|  |             } | ||
|  | 
 | ||
|  |             if (m_RequestGettingStartedTabAfterDomainReload) | ||
|  |             { | ||
|  |                 m_RequestGettingStartedTabAfterDomainReload = false; | ||
|  |                 m_TabGroup.SetSelected(1, force: true); | ||
|  |             } | ||
|  |             else | ||
|  |             { | ||
|  |                 m_TabGroup.SetSelected(m_TabGroup.CurrentTab, force: true); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         void SetWindowContentEnabled(bool installationInProgress, bool quickstartRequested) | ||
|  |         { | ||
|  |             m_MainContainer.SetEnabled(!installationInProgress || quickstartRequested); | ||
|  | 
 | ||
|  |             // if we are current already processing an installation, show the spinning icon | ||
|  |             if (installationInProgress) | ||
|  |             { | ||
|  |                 // Wait a bit because the animation does not trigger when we call this in CreateGUI | ||
|  |                 EditorApplication.delayCall += SetSpinnerIconRotating; | ||
|  |             } | ||
|  | 
 | ||
|  |             rootVisualElement.Add(m_SpinningIcon); | ||
|  |         } | ||
|  | 
 | ||
|  |         void CallCreateGuiWithQuickstartRequest() | ||
|  |         { | ||
|  |             // Interestingly, setting this before registering the delay call sometimes results in the value | ||
|  |             // being false when CreateGUI starts, so we set it again here. | ||
|  |             m_RequestGettingStartedTabAfterDomainReload = true; | ||
|  |             CreateGUI(); | ||
|  |         } | ||
|  | 
 | ||
|  |         void SetSpinnerIconRotating() | ||
|  |         { | ||
|  |             m_SpinningIcon.AddToClassList(k_SpinnerClassName); | ||
|  |         } | ||
|  | 
 | ||
|  |         void RemoveSpinnerIconRotating() | ||
|  |         { | ||
|  |             m_SpinningIcon?.RemoveFromClassList(k_SpinnerClassName); | ||
|  |         } | ||
|  | 
 | ||
|  |         void ClearTabs() | ||
|  |         { | ||
|  |             m_TabGroup?.Clear(); | ||
|  |             m_TabGroup = null; | ||
|  |         } | ||
|  | 
 | ||
|  |         // This will not get called when the Editor is closed. | ||
|  |         void OnDestroy() | ||
|  |         { | ||
|  |             ClearTabs(); | ||
|  |         } | ||
|  | 
 | ||
|  |         static void OnBeforeDomainReload() | ||
|  |         { | ||
|  |             SessionState.SetBool(k_SessionStateDomainReloadKey, true); | ||
|  |         } | ||
|  | 
 | ||
|  |         static void OnAfterDomainReload() | ||
|  |         { | ||
|  |             SessionState.SetBool(k_SessionStateDomainReloadKey, false); | ||
|  |         } | ||
|  | 
 | ||
|  |         public void OnBeforeSerialize() | ||
|  |         { | ||
|  |             // ClearTabs if the Window gets serialized, but we are not in DomainReload | ||
|  |             // This happens when the Editor closes or the WindowLayout is saved by the user. | ||
|  |             // This ensures that the State of the Tabs is not serialized into the WindowLayout of the User. | ||
|  |             if (SessionState.GetBool(k_SessionStateDomainReloadKey, false) == false) | ||
|  |             { | ||
|  |                 ClearTabs(); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         public void OnAfterDeserialize() | ||
|  |         { | ||
|  |             // Empty on purpose. | ||
|  |         } | ||
|  |     } | ||
|  | } |