A game about forced loneliness, made by TACStudios
at master 225 lines 8.6 kB view raw
1using System; 2using Unity.Multiplayer.Center.Analytics; 3using UnityEditor; 4using UnityEngine; 5using UnityEngine.UIElements; 6 7namespace Unity.Multiplayer.Center.Window 8{ 9 internal class MultiplayerCenterWindow : EditorWindow, ISerializationCallbackReceiver 10 { 11 const string k_PathInPackage = "Packages/com.unity.multiplayer.center/Editor/MultiplayerCenterWindow"; 12 const string k_SpinnerClassName = "processing"; 13 const string k_SessionStateDomainReloadKey = "MultiplayerCenter.InDomainReload"; 14 15 VisualElement m_SpinningIcon; 16 17 /// <summary> 18 /// Nest the main container in a VisualElement to allow for easy enabling/disabling of the entire window but 19 /// without the spinning icon. 20 /// </summary> 21 VisualElement m_MainContainer; 22 23 Vector2 m_WindowSize = new(350, 300); 24 25 public int CurrentTab => m_TabGroup.CurrentTab; 26 27 // Testing purposes only. We don't want to set CurrentTab from window 28 internal int CurrentTabTest 29 { 30 get => m_TabGroup.CurrentTab; 31 set => m_TabGroup.SetSelected(value); 32 } 33 34 [SerializeField] 35 bool m_RequestGettingStartedTabAfterDomainReload = false; 36 37 [SerializeField] 38 TabGroup m_TabGroup; 39 40 /// <summary> 41 /// This is the reference Multiplayer Center analytics implementation. This class owns it. 42 /// </summary> 43 IMultiplayerCenterAnalytics m_MultiplayerCenterAnalytics; 44 45 IMultiplayerCenterAnalytics MultiplayerCenterAnalytics => m_MultiplayerCenterAnalytics ??= MultiplayerCenterAnalyticsFactory.Create(); 46 47 [MenuItem("Window/Multiplayer/Multiplayer Center")] 48 public static void OpenWindow() 49 { 50 var showUtility = false; // TODO: figure out if it would be a good idea to have a utility window (always on top, cannot be tabbed) 51 GetWindow<MultiplayerCenterWindow>(showUtility, "Multiplayer Center", true); 52 } 53 54 void OnEnable() 55 { 56 // Adjust window size based on dpi scaling 57 var dpiScale = EditorGUIUtility.pixelsPerPoint; 58 minSize = new Vector2(m_WindowSize.x * dpiScale, m_WindowSize.y * dpiScale); 59 60 AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeDomainReload; 61 AssemblyReloadEvents.afterAssemblyReload -= OnAfterDomainReload; 62 AssemblyReloadEvents.beforeAssemblyReload += OnBeforeDomainReload; 63 AssemblyReloadEvents.afterAssemblyReload += OnAfterDomainReload; 64 } 65 66 void OnDisable() 67 { 68 AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeDomainReload; 69 AssemblyReloadEvents.afterAssemblyReload -= OnAfterDomainReload; 70 } 71 72 /// <summary> 73 /// Changes Tab from Recommendation to the Quickstart tab. 74 /// </summary> 75 public void RequestShowGettingStartedTabAfterDomainReload() 76 { 77 m_RequestGettingStartedTabAfterDomainReload = true; 78 79 // If no domain reload is necessary, this will be called. 80 // If domain reload is necessary, the delay call will be forgotten, but CreateGUI will be called like after any domain reload 81 // An extra delay is added to make sure that the visibility conditions of the Quickstart tab have been 82 // fully evaluated. This solves MTT-8939. 83 EditorApplication.delayCall += () => 84 { 85 rootVisualElement.schedule.Execute(CallCreateGuiWithQuickstartRequest).ExecuteLater(300); 86 }; 87 } 88 89 internal void DisableUiForInstallation() 90 { 91 SetSpinnerIconRotating(); 92 m_MainContainer.SetEnabled(false); 93 } 94 95 internal void ReenableUiAfterInstallation() 96 { 97 RemoveSpinnerIconRotating(); 98 m_MainContainer.SetEnabled(true); 99 } 100 101 void Update() 102 { 103 // Restore the GUI if it was cleared in OnBeforeSerialize. 104 if (m_TabGroup == null || m_TabGroup.ViewCount < 1) 105 { 106 CreateGUI(); 107 } 108 } 109 110 void CreateGUI() 111 { 112 rootVisualElement.name = "root"; 113 m_MainContainer ??= new VisualElement(); 114 m_MainContainer.name = "recommendation-tab-container"; 115 m_MainContainer.Clear(); 116 rootVisualElement.Add(m_MainContainer); 117 m_SpinningIcon = new VisualElement(); 118 var theme = EditorGUIUtility.isProSkin ? "dark" : "light"; 119 rootVisualElement.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>($"{k_PathInPackage}/UI/{theme}.uss")); 120 rootVisualElement.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>($"{k_PathInPackage}/UI/MultiplayerCenterWindow.uss")); 121 122 if (m_TabGroup == null || m_TabGroup.ViewCount < 1 || !m_TabGroup.TabsAreValid()) 123 m_TabGroup = new TabGroup(MultiplayerCenterAnalytics, new ITabView[] {new RecommendationTabView(), new GettingStartedTabView()}); 124 else // since we are not serializing the analytics provider, we need to set it again 125 m_TabGroup.MultiplayerCenterAnalytics = MultiplayerCenterAnalytics; 126 127 m_TabGroup.CreateTabs(); 128 m_MainContainer.Add(m_TabGroup.Root); 129 130 var installationInProgress = !PackageManagement.IsInstallationFinished(); 131 SetWindowContentEnabled(installationInProgress, m_RequestGettingStartedTabAfterDomainReload); 132 ShowAppropriateTab(installationInProgress); 133 } 134 135 void ShowAppropriateTab(bool installationInProgress) 136 { 137 if (installationInProgress) 138 { 139 PackageManagement.RegisterToExistingInstaller(b => RequestShowGettingStartedTabAfterDomainReload()); 140 m_TabGroup.SetSelected(0, force: true); 141 return; 142 } 143 144 if (m_RequestGettingStartedTabAfterDomainReload) 145 { 146 m_RequestGettingStartedTabAfterDomainReload = false; 147 m_TabGroup.SetSelected(1, force: true); 148 } 149 else 150 { 151 m_TabGroup.SetSelected(m_TabGroup.CurrentTab, force: true); 152 } 153 } 154 155 void SetWindowContentEnabled(bool installationInProgress, bool quickstartRequested) 156 { 157 m_MainContainer.SetEnabled(!installationInProgress || quickstartRequested); 158 159 // if we are current already processing an installation, show the spinning icon 160 if (installationInProgress) 161 { 162 // Wait a bit because the animation does not trigger when we call this in CreateGUI 163 EditorApplication.delayCall += SetSpinnerIconRotating; 164 } 165 166 rootVisualElement.Add(m_SpinningIcon); 167 } 168 169 void CallCreateGuiWithQuickstartRequest() 170 { 171 // Interestingly, setting this before registering the delay call sometimes results in the value 172 // being false when CreateGUI starts, so we set it again here. 173 m_RequestGettingStartedTabAfterDomainReload = true; 174 CreateGUI(); 175 } 176 177 void SetSpinnerIconRotating() 178 { 179 m_SpinningIcon.AddToClassList(k_SpinnerClassName); 180 } 181 182 void RemoveSpinnerIconRotating() 183 { 184 m_SpinningIcon?.RemoveFromClassList(k_SpinnerClassName); 185 } 186 187 void ClearTabs() 188 { 189 m_TabGroup?.Clear(); 190 m_TabGroup = null; 191 } 192 193 // This will not get called when the Editor is closed. 194 void OnDestroy() 195 { 196 ClearTabs(); 197 } 198 199 static void OnBeforeDomainReload() 200 { 201 SessionState.SetBool(k_SessionStateDomainReloadKey, true); 202 } 203 204 static void OnAfterDomainReload() 205 { 206 SessionState.SetBool(k_SessionStateDomainReloadKey, false); 207 } 208 209 public void OnBeforeSerialize() 210 { 211 // ClearTabs if the Window gets serialized, but we are not in DomainReload 212 // This happens when the Editor closes or the WindowLayout is saved by the user. 213 // This ensures that the State of the Tabs is not serialized into the WindowLayout of the User. 214 if (SessionState.GetBool(k_SessionStateDomainReloadKey, false) == false) 215 { 216 ClearTabs(); 217 } 218 } 219 220 public void OnAfterDeserialize() 221 { 222 // Empty on purpose. 223 } 224 } 225}