namespace Fusion.Editor { #if FUSION_WEAVER && UNITY_EDITOR using System; using System.Collections.Generic; using Photon.Realtime; using UnityEditor; using UnityEngine; using EditorUtility = UnityEditor.EditorUtility; public partial class FusionHubWindow : EditorWindow { private const int NavWidth = 256 + 2; private static bool? _ready; // true after InitContent(), reset onDestroy, onEnable, etc. private static Vector2 _windowSize; private static Vector2 _windowPosition = new Vector2(100, 100); private int _currentSection; [MenuItem("Tools/Fusion/Fusion Hub &f", false, 0)] public static void Open() { if (Application.isPlaying) { return; } var window = GetWindow(true, Constants.WindowTitle, true); window.position = new Rect(_windowPosition, _windowSize); window.Show(); } private static void ReOpen() { if (_ready.HasValue && _ready.Value == false) { Open(); } EditorApplication.update -= ReOpen; } private void OnEnable() { _ready = false; _windowSize = new Vector2(800, 540); this.minSize = _windowSize; // Pre-load Release History this.PrepareReleaseHistoryText(); wantsMouseMove = true; } private void OnDestroy() { _ready = false; } private void OnGUI() { // skip until ready if (InitContent() == false) { return; } GUI.skin = FusionHubSkin; _windowPosition = this.position.position; // full window wrapper using (new EditorGUILayout.HorizontalScope(GUI.skin.window)) { // Left Nav menu using (new EditorGUILayout.VerticalScope(GUILayout.MaxWidth(NavWidth), GUILayout.MinWidth(NavWidth))) { DrawHeader(); DrawLeftNavMenu(); } // Right Main Content using (new EditorGUILayout.VerticalScope()) { DrawContent(); } } DrawFooter(); // Force repaints while mouse is over the window, to keep Hover graphics working (Unity quirk) var timeSinceStartup = Time.realtimeSinceStartupAsDouble; if (Event.current.type == EventType.MouseMove && timeSinceStartup > _nextForceRepaint) { // Cap the repaint rate a bit since we are forcing repaint on mouse move _nextForceRepaint = timeSinceStartup + .05f; Repaint(); } } private double _nextForceRepaint; private Vector2 _scrollRect; private void DrawContent() { var section = _sections[_currentSection]; GUILayout.Label(section.Description, headerTextStyle); using (new EditorGUILayout.VerticalScope(FusionHubSkin.box)) { using (var scrollScope = new EditorGUILayout.ScrollViewScope(_scrollRect)) { _scrollRect = scrollScope.scrollPosition; section.DrawMethod.Invoke(); } } } private void DrawWelcomeSection() { // Top Welcome content box GUILayout.Label(Constants.WelcomeText); GUILayout.Space(16); DrawSetupAppIdBox(); } private void DrawSetupSection() { DrawSetupAppIdBox(); DrawButtonAction(Icon.FusionIcon, "Fusion Network Project Settings", "Network settings specific to Fusion.", callback: () => NetworkProjectConfigUtilities.PingGlobalConfigAsset(true)); DrawButtonAction(Icon.PhotonCloud, "Photon App Settings", "Network settings specific to the Photon transport.", callback: () => { EditorGUIUtility.PingObject(Photon.Realtime.PhotonAppSettings.Global); Selection.activeObject = Photon.Realtime.PhotonAppSettings.Global; }); } private void DrawDocumentationSection() { DrawButtonAction(Icon.Documentation, "Fusion Introduction", "The Fusion Introduction web page.", callback: OpenURL(Constants.UrlFusionIntro)); DrawButtonAction(Icon.Documentation, "SDK and Release Notes", "Link to the latest Fusion version SDK.", callback: OpenURL(Constants.UrlFusionSDK)); DrawButtonAction(Icon.Documentation, "API Reference", "The API library reference documentation.", callback: OpenURL(Constants.UrlFusionDocApi)); } private void DrawSamplesSection() { GUILayout.Label("Tutorials", headerLabelStyle); DrawButtonAction(Icon.Samples, "Fusion 100 Tutorial", "Fusion Fundamentals Tutorial", callback: OpenURL(Constants.UrlFusion100)); } private void DrawFusionReleaseSection() { GUILayout.Label(fusionReleaseHistory, releaseNotesStyle); } private void DrawReleaseHistoryItem(string label, List items) { if (items != null && items.Count > 0) { GUILayout.BeginVertical(); { GUILayout.Space(5); foreach (string text in items) { GUILayout.Label(string.Format("- {0}.", text), textLabelStyle); } } GUILayout.EndVertical(); } } private void DrawSupportSection() { GUILayout.BeginVertical(); GUILayout.Space(5); GUILayout.Label(Constants.Support, textLabelStyle); GUILayout.EndVertical(); GUILayout.Space(15); DrawButtonAction(Icon.Community, Constants.DiscordHeader, Constants.DiscordText, callback: OpenURL(Constants.UrlDashboardProfile)); DrawButtonAction(Icon.Documentation, Constants.DocumentationHeader, Constants.DocumentationText, callback: OpenURL(Constants.UrlFusionDocsOnline)); } private void DrawSetupAppIdBox() { var realtimeSettings = Photon.Realtime.PhotonAppSettings.Global; var realtimeAppId = realtimeSettings.AppSettings.AppIdFusion; // Setting up AppId content box. using (new EditorGUILayout.VerticalScope(FusionHubSkin.GetStyle("SteelBox") /*contentBoxStyle*/)) { GUILayout.Label(Constants.RealtimeAppidSetupInstructions); DrawButtonAction(Icon.PhotonCloud, "Open the Photon Dashboard", callback: OpenURL(Constants.UrlDashboard)); EditorGUILayout.Space(4); using (new EditorGUILayout.HorizontalScope(FusionHubSkin.GetStyle("SteelBox"))) { EditorGUI.BeginChangeCheck(); GUILayout.Label("Fusion App Id:", GUILayout.Width(120)); var icon = IsAppIdValid() ? CorrectIcon : EditorGUIUtility.FindTexture("console.erroricon.sml"); GUILayout.Label(icon, GUILayout.Width(24), GUILayout.Height(24)); var editedAppId = EditorGUILayout.TextField("", realtimeAppId, FusionHubSkin.textField, GUILayout.Height(24)); // Check for changes and validate the AppId if (EditorGUI.EndChangeCheck() && Guid.TryParse(editedAppId, out _)) { // Update the AppId realtimeSettings.AppSettings.AppIdFusion = editedAppId; EditorUtility.SetDirty(realtimeSettings); AssetDatabase.SaveAssets(); } } } } private void DrawLeftNavMenu() { for (var i = 0; i < _sections.Length; ++i) { var section = _sections[i]; if (DrawNavButton(section, _currentSection == i)) { _currentSection = i; } } } private void DrawHeader() { GUILayout.Label(GetIcon(Icon.ProductLogo), _navbarHeaderGraphicStyle); } private void DrawFooter() { GUILayout.BeginHorizontal(FusionHubSkin.window); { GUILayout.Label("\u00A9 2022, Exit Games GmbH. All rights reserved."); } GUILayout.EndHorizontal(); } private bool DrawNavButton(Section section, bool currentSection) { var content = new GUIContent() { text = " " + section.Title, image = GetIcon(section.Icon), }; var renderStyle = currentSection ? buttonActiveStyle : GUI.skin.button; return GUILayout.Button(content, renderStyle); } private void DrawButtonAction(Icon icon, string header, string description = null, bool? active = null, Action callback = null, int? width = null) { DrawButtonAction(GetIcon(icon), header, description, active, callback, width); } private static void DrawButtonAction(Texture2D icon, string header, string description = null, bool? active = null, Action callback = null, int? width = null) { var padding = GUI.skin.button.padding.top + GUI.skin.button.padding.bottom; var height = icon.height + padding; var renderStyle = active.HasValue && active.Value == true ? buttonActiveStyle : GUI.skin.button; // Draw text separately (not part of button guiconent) to have control over the space between the icon and the text. var rect = EditorGUILayout.GetControlRect(false, height, width.HasValue ? GUILayout.Width(width.Value) : GUILayout.ExpandWidth(true)); var clicked = GUI.Button(rect, icon, renderStyle); GUI.Label(new Rect(rect) { xMin = rect.xMin + icon.width + 20 }, description == null ? "" + header + "" : string.Format("{0}\n{1}", header, "" + description + "")); if (clicked && callback != null) { callback.Invoke(); } } /// /// Unity handling for post asset processing callback. Checks existence of settings assets every time assets change. /// private class FusionHubPostProcessor : AssetPostprocessor { private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { EnsureAssetExists(); } /// /// Attempts enforce existence of singleton. If Editor is not ready, this method will be deferred one editor update and try again until it succeeds. /// [UnityEditor.Callbacks.DidReloadScripts] private static void EnsureAssetExists() { if (!PhotonAppSettings.TryGetGlobal(out var global) || global.AppSettings.AppIdFusion == null) { FusionEditorLog.Trace($"Opening HUB due to missing settings."); EditorApplication.delayCall += () => { Open(); }; } } } } #endif }