您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
434 行
17 KiB
434 行
17 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using UnityEditor;
|
|
using UnityEditor.Connect;
|
|
using UnityEngine;
|
|
using UnityEngine.UIElements;
|
|
|
|
namespace Unity.Services.Core.Editor
|
|
{
|
|
/// <summary>
|
|
/// Base class to extend for an external service settings provider to become an editor game service settings provider
|
|
/// </summary>
|
|
public abstract class EditorGameServiceSettingsProvider : SettingsProvider
|
|
{
|
|
const string k_BasePath = "Project/Services/{0}";
|
|
const string k_InsufficientPermissionMsg = "You do not have the required permissions to activate or deactivate a service";
|
|
const string k_UserNameAnonymous = "anonymous";
|
|
const string k_AuthenticationErrorMessage = "An authentication error has occurred while trying to get access" +
|
|
" to the service, if the error persists please try restarting the editor.";
|
|
|
|
VisualElement m_ParentVisualElement;
|
|
IProjectStateRequest m_ProjectStateRequest;
|
|
ProjectState m_CurrentProjectState;
|
|
|
|
|
|
#if ENABLE_EDITOR_GAME_SERVICES
|
|
IProjectEditorDrawerFactory m_ProjectBindDrawerFactory;
|
|
IProjectEditorDrawerFactory m_CoppaDrawerFactory;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Editor game service for these project settings
|
|
/// </summary>
|
|
protected abstract IEditorGameService EditorGameService { get; }
|
|
|
|
/// <summary>
|
|
/// Title of the service that will be displayed in the Project Settings
|
|
/// </summary>
|
|
protected abstract string Title { get; }
|
|
|
|
/// <summary>
|
|
/// Description of the service that will be displayed in the Project Settings
|
|
/// </summary>
|
|
protected abstract string Description { get; }
|
|
|
|
/// <summary>
|
|
/// Builds and return the Services specific UI as a Visual Element
|
|
/// </summary>
|
|
/// <returns>
|
|
/// Return the parent node for this settings' detail UI.
|
|
/// </returns>
|
|
protected abstract VisualElement GenerateServiceDetailUI();
|
|
|
|
#if ENABLE_EDITOR_GAME_SERVICES
|
|
internal EditorGameServiceSettingsProvider(string path, SettingsScope scopes, IProjectEditorDrawerFactory projectBindDrawer,
|
|
IProjectEditorDrawerFactory projectCoppaDrawer, IProjectStateRequest projectStateRequest = null, IUserRoleRequest userRoleRequest = null, IEnumerable<string> keywords = null)
|
|
: base(path, scopes, keywords)
|
|
{
|
|
m_ProjectStateRequest = projectStateRequest ?? new ProjectStateRequest();
|
|
m_CurrentProjectState = m_ProjectStateRequest.GetProjectState();
|
|
m_ProjectBindDrawerFactory = projectBindDrawer;
|
|
m_CoppaDrawerFactory = projectCoppaDrawer;
|
|
activateHandler = ActivateSettingsProvider;
|
|
deactivateHandler = DeactivateSettingsProvider;
|
|
}
|
|
|
|
void ActivateSettingsProvider(string searchContext, VisualElement rootElement)
|
|
{
|
|
m_ParentVisualElement = GenerateParentElement();
|
|
rootElement.Add(m_ParentVisualElement);
|
|
RefreshUI();
|
|
|
|
#if ENABLE_EDITOR_GAME_SERVICES
|
|
CloudProjectSettingsEventManager.instance.projectStateChanged += OnProjectStateChanged;
|
|
EditorGameServiceRegistry.Instance.UserRoleHandler.UserRoleChanged += OnUserRoleChanged;
|
|
EditorGameServiceRegistry.Instance.UserRoleHandler.TrySendUserRoleRequest();
|
|
#endif
|
|
}
|
|
|
|
void DeactivateSettingsProvider()
|
|
{
|
|
#if ENABLE_EDITOR_GAME_SERVICES
|
|
CloudProjectSettingsEventManager.instance.projectStateChanged -= OnProjectStateChanged;
|
|
EditorGameServiceRegistry.Instance.UserRoleHandler.UserRoleChanged -= OnUserRoleChanged;
|
|
#endif
|
|
}
|
|
|
|
void OnProjectStateChanged()
|
|
{
|
|
if (EditorGameServiceRegistry.Instance.UserRoleHandler.IsBusy())
|
|
return;
|
|
|
|
var projectState = m_ProjectStateRequest.GetProjectState();
|
|
if (m_CurrentProjectState.HasDiff(projectState))
|
|
{
|
|
RefreshUI();
|
|
}
|
|
}
|
|
|
|
void OnUserRoleChanged(UserRole userRole)
|
|
{
|
|
RefreshUI();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="EditorGameServiceSettingsProvider"/> class.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path to the settings.
|
|
/// You SHOULD use <see cref="GenerateProjectSettingsPath"/> to provide it.
|
|
/// </param>
|
|
/// <param name="scopes">
|
|
/// The scope of the provided settings.
|
|
/// </param>
|
|
/// <param name="keywords">
|
|
/// Set of keywords for search purposes.
|
|
/// </param>
|
|
protected EditorGameServiceSettingsProvider(string path, SettingsScope scopes, IEnumerable<string> keywords = null)
|
|
: this(path, scopes, null, null, keywords : keywords) {}
|
|
#else
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="EditorGameServiceSettingsProvider"/> class.
|
|
/// </summary>
|
|
/// <param name="path">
|
|
/// The path to the settings.
|
|
/// You SHOULD use <see cref="GenerateProjectSettingsPath"/> to provide it.
|
|
/// </param>
|
|
/// <param name="scopes">
|
|
/// The scope of the provided settings.
|
|
/// </param>
|
|
/// <param name="keywords">
|
|
/// Set of keywords for search purposes.
|
|
/// </param>
|
|
protected EditorGameServiceSettingsProvider(string path, SettingsScope scopes, IEnumerable<string> keywords = null)
|
|
: base(path, scopes, keywords)
|
|
{
|
|
m_ProjectStateRequest = new ProjectStateRequest();
|
|
activateHandler = (searchContext, rootElement) => {
|
|
m_ParentVisualElement = GenerateParentElement();
|
|
rootElement.Add(m_ParentVisualElement);
|
|
RefreshUI();
|
|
};
|
|
deactivateHandler = () => {};
|
|
}
|
|
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Use this to standardize Service Project Settings path:
|
|
/// usage example:
|
|
/// var provider = new MyCloudServiceSettings(GenerateProjectSettingsPath("My Cloud Service"), SettingsScope.Project);
|
|
/// </summary>
|
|
/// <param name="serviceName">Name of the service to use in path</param>
|
|
/// <returns>The path to pass as argument to SettingsProvider</returns>
|
|
protected static string GenerateProjectSettingsPath(string serviceName)
|
|
{
|
|
return string.Format(k_BasePath, serviceName);
|
|
}
|
|
|
|
VisualElement GetSetupOrServiceUI(ProjectState projectState)
|
|
{
|
|
#if ENABLE_EDITOR_GAME_SERVICES
|
|
var uiBody = new VisualElement();
|
|
|
|
if (!IsUserOnline(projectState))
|
|
{
|
|
DrawOfflineUI(uiBody);
|
|
}
|
|
else if (!IsUserLoggedIn(projectState))
|
|
{
|
|
DrawLoggedOutUI(uiBody);
|
|
}
|
|
else if (!IsProjectBound(projectState))
|
|
{
|
|
DrawProjectBindingUI(uiBody);
|
|
}
|
|
else if (IsUserRoleRequestNotAuthorized())
|
|
{
|
|
DrawAccessTokenErrorUI(uiBody);
|
|
}
|
|
else if (!IsUserRoleSet())
|
|
{
|
|
DrawUserRoleUI(uiBody);
|
|
}
|
|
else if (!IsCoppaComplianceMet(EditorGameService, projectState.CoppaCompliance))
|
|
{
|
|
DrawCoppaComplianceUI(uiBody);
|
|
}
|
|
else
|
|
{
|
|
uiBody.Add(GenerateServiceDetailUI());
|
|
}
|
|
return uiBody;
|
|
#else
|
|
return GenerateUnsupportedDetailUI();
|
|
#endif
|
|
}
|
|
|
|
internal static bool IsUserOnline(ProjectState projectState)
|
|
{
|
|
return projectState.IsOnline;
|
|
}
|
|
|
|
void DrawOfflineUI(VisualElement parentVisualElement)
|
|
{
|
|
OfflineUiHelper.AddOfflineUI(parentVisualElement, RefreshUI);
|
|
}
|
|
|
|
void DrawAccessTokenErrorUI(VisualElement parentVisualElement)
|
|
{
|
|
AccessTokenErrorUiHelper.AddAccessTokenErrorUI(parentVisualElement);
|
|
Debug.LogWarning(k_AuthenticationErrorMessage);
|
|
}
|
|
|
|
internal static bool IsUserLoggedIn(ProjectState projectState)
|
|
{
|
|
return !string.IsNullOrEmpty(projectState.UserId) &&
|
|
!projectState.UserName.Equals(k_UserNameAnonymous, StringComparison.InvariantCultureIgnoreCase);
|
|
}
|
|
|
|
static void DrawLoggedOutUI(VisualElement parentVisualElement)
|
|
{
|
|
LoggedOutUiHelper.AddLoggedOutUI(parentVisualElement);
|
|
}
|
|
|
|
#if ENABLE_EDITOR_GAME_SERVICES
|
|
internal static bool IsProjectBound(ProjectState projectState)
|
|
{
|
|
return projectState.ProjectBound;
|
|
}
|
|
|
|
static bool IsUserRoleSet()
|
|
{
|
|
return EditorGameServiceRegistry.Instance.UserRoleHandler.CurrentUserRole != UserRole.Unknown;
|
|
}
|
|
|
|
static bool IsUserRoleRequestNotAuthorized()
|
|
{
|
|
return EditorGameServiceRegistry.Instance.UserRoleHandler.HasAuthorizationError;
|
|
}
|
|
|
|
void DrawUserRoleUI(VisualElement parentVisualElement)
|
|
{
|
|
UserRoleRequestUiHelper.AddUserRoleRequestUI(parentVisualElement);
|
|
}
|
|
|
|
#endif
|
|
|
|
void DrawProjectBindingUI(VisualElement parentVisualElement)
|
|
{
|
|
#if ENABLE_EDITOR_GAME_SERVICES
|
|
var projectBindDrawer = m_ProjectBindDrawerFactory == null ? new ProjectBindDrawer() : m_ProjectBindDrawerFactory.InstantiateDrawer();
|
|
projectBindDrawer.stateChangeButtonFired += RefreshUI;
|
|
|
|
if (projectBindDrawer is ProjectBindDrawer drawer)
|
|
{
|
|
drawer.exceptionCallback += (exception) => ShowExceptionVisual(parentVisualElement, ExceptionMessages.ProjectBinding, exception);
|
|
}
|
|
|
|
parentVisualElement.Add(projectBindDrawer.GetVisualElement());
|
|
#endif
|
|
}
|
|
|
|
#if ENABLE_EDITOR_GAME_SERVICES
|
|
static void ShowExceptionVisual(VisualElement exceptionContainer, string contextMessage, Exception exception)
|
|
{
|
|
exceptionContainer.Clear();
|
|
ExceptionHelper.AddExceptionVisual(exceptionContainer, contextMessage, exception.Message);
|
|
}
|
|
|
|
internal static bool IsCoppaComplianceMet(IEditorGameService editorGameService, CoppaCompliance currentCoppaStatus)
|
|
{
|
|
return editorGameService == null || !editorGameService.RequiresCoppaCompliance ||
|
|
currentCoppaStatus != CoppaCompliance.CoppaUndefined;
|
|
}
|
|
|
|
#endif
|
|
|
|
void DrawCoppaComplianceUI(VisualElement parentVisualElement)
|
|
{
|
|
#if ENABLE_EDITOR_GAME_SERVICES
|
|
var coppaDrawer = m_CoppaDrawerFactory == null ? new CoppaDrawer() : m_CoppaDrawerFactory.InstantiateDrawer();
|
|
coppaDrawer.stateChangeButtonFired += RefreshUI;
|
|
|
|
if (coppaDrawer is CoppaDrawer drawer)
|
|
{
|
|
drawer.exceptionCallback += (_, exception) => ShowExceptionVisual(parentVisualElement, ExceptionMessages.CoppaCompliance, exception);
|
|
}
|
|
|
|
parentVisualElement.Add(coppaDrawer.GetVisualElement());
|
|
if (!IsUserAllowedToEditCoppaCompliance(EditorGameServiceRegistry.Instance.UserRoleHandler.CurrentUserRole))
|
|
{
|
|
parentVisualElement.Q<VisualElement>(NodeName.CoppaContainer)?.Q<VisualElement>(className: ClassName.EditMode)?.SetEnabled(false);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
internal static bool IsUserAllowedToEditServiceToggle(IEditorGameService editorGameService, ProjectState projectState, UserRole userRole)
|
|
{
|
|
#if ENABLE_EDITOR_GAME_SERVICES
|
|
return IsCoppaComplianceMet(editorGameService, projectState.CoppaCompliance) &&
|
|
(userRole == UserRole.Manager || userRole == UserRole.Owner);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
internal static bool IsUserAllowedToEditCoppaCompliance(UserRole userRole)
|
|
{
|
|
return userRole == UserRole.Manager || userRole == UserRole.Owner;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The UI to show when the editor API does not support the Services SDK Core package
|
|
/// </summary>
|
|
/// <returns>Custom UI to show when unsupported</returns>
|
|
protected virtual VisualElement GenerateUnsupportedDetailUI()
|
|
{
|
|
return new VisualElement();
|
|
}
|
|
|
|
void RefreshUI()
|
|
{
|
|
m_ParentVisualElement.Clear();
|
|
var projectState = m_ProjectStateRequest.GetProjectState();
|
|
AddStyleSheetsToParentElement(m_ParentVisualElement);
|
|
m_ParentVisualElement.Add(GenerateCommonHeader(projectState));
|
|
m_ParentVisualElement.Add(GetSetupOrServiceUI(projectState));
|
|
TranslateStringsInTree(m_ParentVisualElement);
|
|
}
|
|
|
|
static VisualElement GenerateParentElement()
|
|
{
|
|
return new ScrollView();
|
|
}
|
|
|
|
static void AddStyleSheetsToParentElement(VisualElement parentElement)
|
|
{
|
|
parentElement.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>(UssPath.ServicesProjectSettingsCommon));
|
|
parentElement.styleSheets.Add(
|
|
EditorGUIUtility.isProSkin ?
|
|
AssetDatabase.LoadAssetAtPath<StyleSheet>(UssPath.ServicesProjectSettingsDark) :
|
|
AssetDatabase.LoadAssetAtPath<StyleSheet>(UssPath.ServicesProjectSettingsLight));
|
|
}
|
|
|
|
VisualElement GenerateCommonHeader(ProjectState projectState)
|
|
{
|
|
#if ENABLE_EDITOR_GAME_SERVICES
|
|
Action dashboardLinkClickAction = null;
|
|
SettingsCommonHeaderUiHelper.ToggleConfiguration toggleConfiguration = null;
|
|
|
|
if (projectState.ProjectBound)
|
|
{
|
|
if (EditorGameService != null && EditorGameService.HasDashboard)
|
|
{
|
|
dashboardLinkClickAction = EditorGameService.OpenDashboard;
|
|
}
|
|
|
|
if (EditorGameService?.Enabler != null)
|
|
{
|
|
var toggleValue = EditorGameService.Enabler.IsEnabled();
|
|
var toggleEnabled = IsUserAllowedToEditServiceToggle(EditorGameService, projectState, EditorGameServiceRegistry.Instance.UserRoleHandler.CurrentUserRole);
|
|
var tooltip = string.Empty;
|
|
if (!toggleEnabled)
|
|
{
|
|
tooltip = L10n.Tr(k_InsufficientPermissionMsg);
|
|
}
|
|
|
|
toggleConfiguration = new SettingsCommonHeaderUiHelper.ToggleConfiguration(toggleValue,
|
|
true, toggleEnabled, ToggleValueChangedActionAndRefreshUI, tooltip);
|
|
}
|
|
}
|
|
|
|
return SettingsCommonHeaderUiHelper.GenerateCommonHeader(Title, Description, toggleConfiguration, dashboardLinkClickAction);
|
|
#else
|
|
return SettingsCommonHeaderUiHelper.GenerateCommonHeader(Title, Description);
|
|
#endif
|
|
}
|
|
|
|
#if ENABLE_EDITOR_GAME_SERVICES
|
|
void ToggleValueChangedActionAndRefreshUI(ChangeEvent<bool> evt)
|
|
{
|
|
if (evt.newValue)
|
|
{
|
|
EditorGameService.Enabler.Enable();
|
|
}
|
|
else
|
|
{
|
|
EditorGameService.Enabler.Disable();
|
|
}
|
|
|
|
RefreshUI();
|
|
}
|
|
|
|
#endif
|
|
|
|
static bool CoppaComplianceMet(IEditorGameService editorGameService, ProjectState projectState)
|
|
{
|
|
#if ENABLE_EDITOR_GAME_SERVICES
|
|
return editorGameService == null || !editorGameService.RequiresCoppaCompliance || projectState.CoppaCompliance != CoppaCompliance.CoppaUndefined;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
internal static void TranslateStringsInTree(VisualElement rootElement)
|
|
{
|
|
rootElement.Query<TextElement>().ForEach((label) => label.text = L10n.Tr(label.text));
|
|
}
|
|
|
|
static class UssPath
|
|
{
|
|
internal const string ServicesProjectSettingsCommon = "Packages/com.unity.services.core/Editor/Core/EditorGameService/USS/ServicesProjectSettingsCommon.uss";
|
|
internal const string ServicesProjectSettingsDark = "Packages/com.unity.services.core/Editor/Core/EditorGameService/USS/ServicesProjectSettingsDark.uss";
|
|
internal const string ServicesProjectSettingsLight = "Packages/com.unity.services.core/Editor/Core/EditorGameService/USS/ServicesProjectSettingsLight.uss";
|
|
}
|
|
|
|
static class NodeName
|
|
{
|
|
internal const string CoppaContainer = "CoppaContainer";
|
|
}
|
|
|
|
static class ClassName
|
|
{
|
|
internal const string EditMode = "edit-mode";
|
|
}
|
|
|
|
static class ExceptionMessages
|
|
{
|
|
internal const string ProjectBinding = "There was an error during the Project Binding process. Please make sure you are online and logged in:";
|
|
internal const string CoppaCompliance = "There was an error during the COPPA Compliance process. Please make sure you are online and logged in:";
|
|
}
|
|
}
|
|
}
|