Files

396 lines
11 KiB
C#
Raw Permalink Normal View History

2026-01-08 16:50:20 +00:00
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using PlasticGui;
using Unity.PlasticSCM.Editor.UI.Progress;
namespace Unity.PlasticSCM.Editor.UI
{
internal abstract class PlasticDialog : EditorWindow, IPlasticDialogCloser
{
internal enum SizeToContent
{
Manual = 0,
Automatic = 1
}
protected virtual Rect DefaultRect
{
get
{
int pixelWidth = Screen.currentResolution.width;
float x = (pixelWidth - DEFAULT_WIDTH) / 2;
return new Rect(x, 200, DEFAULT_WIDTH, 1);
}
}
protected virtual bool IsResizable { get; set; }
internal void SetSizeToContent(SizeToContent sizeToContent)
{
mSizeToContent = sizeToContent;
}
internal virtual void OkButtonAction()
{
CompleteModal(ResponseType.Ok);
}
internal virtual void CancelButtonAction()
{
CompleteModal(ResponseType.Cancel);
}
internal void CloseButtonAction()
{
CompleteModal(ResponseType.None);
}
internal void ApplyButtonAction()
{
CompleteModal(ResponseType.Apply);
}
internal void AddControlConsumingEnterKey(string controlName)
{
mControlsConsumingEnterKey.Add(controlName);
}
internal void RunUtility(EditorWindow parentWindow)
{
InitializeVars(parentWindow);
if (!IsResizable)
MakeNonResizable();
ShowUtility();
}
internal ResponseType RunModal(EditorWindow parentWindow)
{
InitializeVars(parentWindow);
if (!IsResizable)
MakeNonResizable();
UI.RunModal.Dialog(this);
return mAnswer;
}
internal static void DoButtonsWithPlatformOrdering(
Action doPrimaryButton,
Action doCloseButton,
Action doCancelButton)
{
if (Application.platform == RuntimePlatform.WindowsEditor)
{
doPrimaryButton();
doCloseButton();
doCancelButton();
return;
}
doCancelButton();
doCloseButton();
doPrimaryButton();
}
protected virtual void OnGUI()
{
if (Event.current == null)
return;
// If the Dialog has been saved into the Unity editor layout and persisted between restarts, the methods
// to configure the dialogs will be skipped. Simple fix here is to close it when this state is detected.
// Fixes a NPE loop when the state mentioned above is occurring.
if (!mIsConfigured)
{
Close();
EditorGUIUtility.ExitGUI();
return;
}
// When a modal dialog is displayed, Unity's SynchronizationContext.Post() callbacks are not processed
// because the modal dialog runs its own event loop.
// We need to explicitly pump the EditorDispatcher queue to ensure that callbacks from background threads
// (like ThreadWaiter's afterOperationDelegate) are executed.
if (Event.current.type == EventType.Layout)
{
EditorDispatcher.Update();
}
if (!mFocusedOnce)
{
// Somehow the prevents the dialog from jumping when dragged
// NOTE(rafa): We cannot do every frame because the modal kidnaps focus for all processes (in mac at least)
Focus();
mFocusedOnce = true;
}
ProcessKeyActions();
GUI.Box(new Rect(0, 0, position.width, position.height), GUIContent.none, EditorStyles.label);
float margin = 25;
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.Space(margin);
using (new EditorGUILayout.VerticalScope())
{
GUILayout.Space(margin);
if (!string.IsNullOrEmpty(GetExplanation()))
Paragraph(GetExplanation());
DoComponentsArea();
GUILayout.Space(15);
DoButtonsArea();
DoCheckBoxArea_Legacy();
mProgressControls.UpdateProgress(this);
GUILayout.Space(margin);
}
GUILayout.Space(margin);
}
if (!IsResizable && mSizeToContent == SizeToContent.Automatic && Event.current.type == EventType.Repaint)
{
var lastRect = GUILayoutUtility.GetLastRect();
float desiredHeight = lastRect.yMax;
Rect newPos = position;
newPos.height = desiredHeight;
position = newPos;
maxSize = newPos.size;
minSize = maxSize;
}
if (Event.current.type != EventType.Layout)
mFocusedControlName = GUI.GetNameOfFocusedControl();
}
void OnDestroy()
{
if (!mIsConfigured)
return;
SaveSettings();
if (mParentWindow == null)
return;
mParentWindow.Focus();
}
protected virtual void SaveSettings() { }
protected virtual void DoComponentsArea() { }
protected abstract string GetTitle();
protected void Paragraph(string text)
{
GUILayout.Label(text, UnityStyles.Paragraph);
GUILayout.Space(DEFAULT_PARAGRAPH_SPACING);
}
protected virtual string GetExplanation() { return string.Empty; }
protected void TextBlockWithEndLink(
string url, string formattedExplanation, GUIStyle textblockStyle)
{
ExternalLink externalLink = new ExternalLink
{
Label = url,
Url = url
};
DrawTextBlockWithLink.ForExternalLink(
externalLink, formattedExplanation, textblockStyle);
}
protected static void Title(string text)
{
GUILayout.Label(text, UnityStyles.Dialog.Title);
}
protected static bool TitleToggle(string text, bool isOn)
{
return EditorGUILayout.ToggleLeft(text, isOn, UnityStyles.Dialog.Title);
}
protected static bool TitleToggle(string text, bool isOn, GUIStyle style)
{
return EditorGUILayout.ToggleLeft(text, isOn, style);
}
protected static bool NormalButton(string text)
{
int textWidth = (int)((GUIStyle)UnityStyles.Dialog.NormalButton)
.CalcSize(new GUIContent(text)).x;
return GUILayout.Button(
text,
UnityStyles.Dialog.NormalButton,
GUILayout.Width(Math.Max(80, textWidth)));
}
protected virtual void DoCheckBoxArea_Legacy() { }
protected virtual void DoButtonsArea()
{
using (new EditorGUILayout.HorizontalScope())
{
DrawProgressForDialogs.For(mProgressControls.ProgressData);
GUILayout.Space(10);
DoButtonsWithPlatformOrdering(DoOkButton, DoCloseButton, DoCancelButton);
}
}
protected virtual void DoOkButton()
{
if (string.IsNullOrEmpty(mOkButtonText))
return;
if (!NormalButton(mOkButtonText))
return;
OkButtonAction();
}
protected void DoCancelButton()
{
if (string.IsNullOrEmpty(mCancelButtonText))
return;
if (!NormalButton(mCancelButtonText))
return;
CancelButtonAction();
}
protected virtual void DoCloseButton()
{
if (string.IsNullOrEmpty(mCloseButtonText))
return;
if (!NormalButton(mCloseButtonText))
return;
CloseButtonAction();
}
void IPlasticDialogCloser.CloseDialog()
{
CompleteModal(ResponseType.Ok);
}
void ProcessKeyActions()
{
Event e = Event.current;
if (mEnterKeyAction != null &&
Keyboard.IsReturnOrEnterKeyPressed(e) &&
!ControlConsumesKey(mControlsConsumingEnterKey, mFocusedControlName))
{
mEnterKeyAction();
e.Use();
return;
}
if (mEscapeKeyAction != null &&
Keyboard.IsKeyPressed(e, KeyCode.Escape))
{
mEscapeKeyAction();
e.Use();
return;
}
}
void CompleteModal(ResponseType answer)
{
mAnswer = answer;
if (mParentWindow == null)
return;
Close();
Repaint();
}
void InitializeVars(EditorWindow parentWindow)
{
mIsConfigured = true;
mAnswer = ResponseType.None;
titleContent = new GUIContent(GetTitle());
mFocusedOnce = false;
position = DefaultRect;
mParentWindow = parentWindow;
}
void MakeNonResizable()
{
maxSize = DefaultRect.size;
minSize = maxSize;
}
static bool ControlConsumesKey(
List<string> controlsConsumingKey,
string focusedControlName)
{
if (string.IsNullOrEmpty(focusedControlName))
return false;
foreach (string controlName in controlsConsumingKey)
{
if (focusedControlName.Equals(controlName))
return true;
}
return false;
}
static GUISkin GetEditorSkin()
{
return EditorGUIUtility.isProSkin ?
EditorGUIUtility.GetBuiltinSkin(EditorSkin.Scene) :
EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector);
}
string mFocusedControlName;
bool mFocusedOnce;
bool mIsConfigured;
ResponseType mAnswer;
protected Action mEnterKeyAction = null;
protected Action mEscapeKeyAction = null;
protected string mOkButtonText = PlasticLocalization.Name.OkButton.GetString();
protected string mCancelButtonText = PlasticLocalization.Name.CancelButton.GetString();
protected string mCloseButtonText;
EditorWindow mParentWindow;
SizeToContent mSizeToContent = SizeToContent.Automatic;
protected ProgressControlsForDialogs mProgressControls = new ProgressControlsForDialogs();
List<string> mControlsConsumingEnterKey = new List<string>();
const float DEFAULT_WIDTH = 500f;
const float DEFAULT_PARAGRAPH_SPACING = 10f;
}
}