// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Unity
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
// GameKit
using AWS.GameKit.Editor.FileStructure;
using AWS.GameKit.Editor.GUILayoutExtensions;
using AWS.GameKit.Editor.Utils;
using AWS.GameKit.Runtime.Utils;
namespace AWS.GameKit.Editor.Windows.Settings
{
///
/// Displays the Settings window and receives its UI events.
///
public class SettingsWindow : EditorWindow
{
private const string WINDOW_TITLE = "AWS GameKit Settings";
private enum InitializationLevel
{
// Initialize() has not been called.
// The GUI is empty and most public method calls are ignored (ex: OpenPage).
Uninitialized,
// Initialize() has been called, but some delayed setup will occur during the next OnGUI() call.
// The GUI is empty and all public method calls are acted on.
PartiallyInitialized,
// Initialize() has been called and all setup is complete.
// The GUI is drawn and all public method calls are acted on.
FullyInitialized
}
// Data
private SettingsModel _model;
// State
private InitializationLevel _initLevel = InitializationLevel.Uninitialized;
// GUI Widgets
private readonly PageContainerWidget _pageContainerWidget = new PageContainerWidget();
private NavigationTreeWidget _navigationTreeWidget;
// Events
public static event OnWindowEnabled Enabled;
public delegate void OnWindowEnabled(SettingsWindow enabledSettingsWindow);
///
/// Open the Settings window and navigate to the specified page.
///
public static void OpenPage(PageType pageType)
{
SettingsWindow window = GetWindow();
window.TryOpenPage(pageType);
}
///
/// Open the Settings window and navigate to the specified tab on the specified page.
///
///
/// If the specified tab does not exist on the page, then an Error will be logged and the page's currently selected tab will not change. The page will still be opened.
///
public static void OpenPageToTab(PageType pageType, string tabName)
{
SettingsWindow window = GetWindow();
window.TryOpenPage(pageType, tabName);
}
///
/// Navigate to the specified page if the window is initialized. Then optionally change the selected tab.
///
private void TryOpenPage(PageType pageType, string tabName = null)
{
if (_initLevel == InitializationLevel.Uninitialized)
{
return;
}
Page page = _model.AllPages.GetPage(pageType);
// Open the page
_navigationTreeWidget.ClickItem(page);
// Select the tab
if (!string.IsNullOrEmpty(tabName))
{
page.SelectTab(tabName);
}
}
private void OnNavigationTreeItemSelected(Page selectedPage)
{
_pageContainerWidget.ChangeTo(selectedPage);
}
private void OnEnable()
{
// Set title
string windowTitle = WINDOW_TITLE;
Texture windowIcon = EditorResources.Textures.WindowIcon.Get();
titleContent = new GUIContent(windowTitle, windowIcon);
Enabled?.Invoke(this);
}
///
/// Initialize this window so it can start drawing the GUI and acting on public methods (ex: OpenPage).
///
/// This is effectively the window's constructor.
///
public void Initialize(SettingsModel model)
{
_model = model;
bool isFirstTimeWindowHasEverBeenOpened = !_model.SettingsWindowHasEverBeenOpened;
if (isFirstTimeWindowHasEverBeenOpened)
{
// Create new navigation tree
_model.NavigationTreeState = new TreeViewState();
_navigationTreeWidget = new NavigationTreeWidget(_model.AllPages, _model.NavigationTreeState, false, OnNavigationTreeItemSelected);
// Delay the rest of initialization until the first OnGUI call so the window's position is refreshed.
// The position is (x=0, y=0) during the window's very first OnEnable() call ever (across all Unity sessions).
// The position refreshes to the "real" value (near the center of the screen) after the first OnGUI() call.
_initLevel = InitializationLevel.PartiallyInitialized;
}
else
{
// Restore navigation tree from the previous session
_navigationTreeWidget = new NavigationTreeWidget(_model.AllPages, _model.NavigationTreeState, true, OnNavigationTreeItemSelected);
// Set window bounds
minSize = SettingsGUIStyles.Window.MinSize;
_initLevel = InitializationLevel.FullyInitialized;
}
SettingsWindowUpdateController.AssignSettingsWindow(this);
}
///
/// Set the window's initial width & height.
///
/// This only gets called the very first time the window is opened in this game project across all Unity sessions.
/// After the first time, we let Unity automatically re-use the window's last size and position.
///
/// This call needs to be delayed until at least one frame after the first OnGUI() call,
/// otherwise the content may shift around when the window resizes.
///
private void DelayedInitialize()
{
EditorWindowHelper.SetWindowSizeAndBounds(this, SettingsGUIStyles.Window.InitialSize, SettingsGUIStyles.Window.MinSize, maxSize);
_model.SettingsWindowHasEverBeenOpened = true;
_initLevel = InitializationLevel.FullyInitialized;
}
private void OnDisable()
{
_pageContainerWidget.CloseWindow();
}
private void OnGUI()
{
if (!IsReadyToDrawGUI())
{
if (_initLevel == InitializationLevel.PartiallyInitialized)
{
DelayedInitialize();
}
GUILayout.Space(0f);
return;
}
_model.SerializedObject.Update();
using (new EditorGUILayout.HorizontalScope())
{
DrawNavigationTree();
EditorGUILayoutElements.VerticalDivider();
DrawPageContents();
}
_model.SerializedObject.ApplyModifiedProperties();
}
private bool IsReadyToDrawGUI()
{
return _initLevel == InitializationLevel.FullyInitialized;
}
private void DrawNavigationTree()
{
using (new EditorGUILayout.VerticalScope(SettingsGUIStyles.NavigationTree.VerticalLayout))
{
// The NavigationTreeWidget doesn't interact with EditorGUILayout.VerticalScope(), so we have to:
// (1) manually specify the Rect for where to draw the tree:
Vector2 topLeft = new Vector2(
x: 0f,
y: 0f
);
Vector2 topLeftWithMargin = new Vector2(
x: topLeft.x + SettingsGUIStyles.NavigationTree.VerticalLayout.margin.left,
y: topLeft.y + SettingsGUIStyles.NavigationTree.VerticalLayout.margin.top
);
Vector2 bottomRight = new Vector2(
x: SettingsGUIStyles.NavigationTree.FIXED_WIDTH,
y: position.height
);
Vector2 bottomRightWithMargin = new Vector2(
x: bottomRight.x - SettingsGUIStyles.NavigationTree.VerticalLayout.margin.right,
y: bottomRight.y - SettingsGUIStyles.NavigationTree.VerticalLayout.margin.bottom
);
Rect boxRect = new Rect(topLeftWithMargin, bottomRightWithMargin);
_navigationTreeWidget.OnGUI(boxRect);
// (2) add an empty GUI element to make the vertical group non-empty and therefore have a non-zero width:
GUILayout.Space(0f);
}
}
private void DrawPageContents()
{
using (new EditorGUILayout.VerticalScope(SettingsGUIStyles.PageContainer.VerticalLayout, GUILayout.ExpandWidth(true)))
{
_pageContainerWidget.OnGUI();
}
}
}
}