-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move and slightly improve ConfirmDialog #683
This moves the ConfirmDialog out of the Drawable directories, since it is used in more situations than Drawable-specific menus. Additionally, some minor refactorings and improvements were made, notably: * The dialog is now more configurable. Specifically, the title, description, button texts, button icons, and button colors can all be set to custom values. There is also a pre-made configuration for delete confirmations. * The visuals of the dialog have been slightly changed. For example, the confirm button is now green by default, the dialog is slightly larger, there are icons on the buttons, and so on. * The dialog should now be able to handle differing text lengths better. * A static convenience method has been added to make it easier to ask the user confirmation questions. It suffices to call this static method on the class with the desired configuration (i.e., message etc.) and then await the result (see below), without the need for the caller to have access to a fitting GameObject for the dialog (or needing to handle destroying it). * The dialog has been turned into a `PlatformDependentComponent`. * A fade-in and fade-out animation has been implemented for the dialog. * Instead of utilizing callbacks, the method to show the dialog now leverages its asynchronous nature by simply waiting until the user made their choice, then returning a boolean indicating that choice. This should also more easily enable patterns such as early return, as is commonly done with the analogous `window.confirm()` method in Web development.
- Loading branch information
Showing
9 changed files
with
748 additions
and
208 deletions.
There are no files selected for viewing
610 changes: 505 additions & 105 deletions
610
.../Prefabs/UI/Drawable/ConfirmDialog.prefab → ...Resources/Prefabs/UI/ConfirmDialog.prefab
Large diffs are not rendered by default.
Oops, something went wrong.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
using System; | ||
using System.Threading; | ||
using Cysharp.Threading.Tasks; | ||
using DG.Tweening; | ||
using Michsky.UI.ModernUIPack; | ||
using SEE.GO; | ||
using SEE.Utils; | ||
using TMPro; | ||
using UnityEngine; | ||
using UnityEngine.Events; | ||
using UnityEngine.UI; | ||
|
||
namespace SEE.UI.Menu | ||
{ | ||
/// <summary> | ||
/// Configuration for the confirm dialog. | ||
/// </summary> | ||
/// <param name="description">The description of the dialog, can be up to three lines long.</param> | ||
/// <param name="title">The title of the dialog displayed in the top bar. Should be kept short.</param> | ||
/// <param name="yesText">The text of the confirm button.</param> | ||
/// <param name="noText">The text of the cancel button.</param> | ||
/// <param name="yesIcon">The icon of the confirm button, given as a FontAwesome character (see <see cref="Icons"/>).</param> | ||
/// <param name="noIcon">The icon of the cancel button, given as a FontAwesome character (see <see cref="Icons"/>).</param> | ||
/// <param name="yesColor">The color of the confirm button. Note that the text will be white, so choose a dark color.</param> | ||
public record ConfirmConfiguration( | ||
string description, | ||
string title = "Are you sure?", | ||
string yesText = "Confirm", | ||
string noText = "Cancel", | ||
char yesIcon = Icons.Checkmark, | ||
char noIcon = 'X', | ||
Color? yesColor = null | ||
) | ||
{ | ||
/// <summary> | ||
/// A pre-made configuration for a delete dialog. | ||
/// </summary> | ||
/// <param name="description">The description of the dialog, can be up to three lines long.</param> | ||
/// <returns>A configuration for a delete dialog.</returns> | ||
public static ConfirmConfiguration Delete(string description) | ||
{ | ||
return new ConfirmConfiguration(description, title: "Really delete?", | ||
yesText: "Delete", yesIcon: Icons.Trash, yesColor: Color.red.Darker()); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// A simple configurable confirmation dialog with a "confirm" and a "cancel" button. | ||
/// </summary> | ||
public class ConfirmDialog : PlatformDependentComponent | ||
{ | ||
/// <summary> | ||
/// The duration of the fade animation in seconds. | ||
/// </summary> | ||
private const float animationDuration = 1.0f; | ||
|
||
/// <summary> | ||
/// Prefab for the dialog. | ||
/// </summary> | ||
private const string dialogMenuPrefab = UIPrefabFolder + "ConfirmDialog"; | ||
|
||
/// <summary> | ||
/// The default color of the confirm button. | ||
/// </summary> | ||
private static readonly Color defaultColor = Color.green.Darker(); // Slightly darker green. | ||
|
||
/// <summary> | ||
/// Whether the dialog should be destroyed after it is closed (and faded out). | ||
/// </summary> | ||
private bool oneTime; | ||
|
||
/// <summary> | ||
/// The dialog menu game object. | ||
/// </summary> | ||
private GameObject Dialog { get; set; } | ||
|
||
/// <summary> | ||
/// The canvas group of the dialog, used for fading in and out. | ||
/// </summary> | ||
private CanvasGroup canvasGroup { get; set; } | ||
|
||
/// <summary> | ||
/// The title of the dialog. | ||
/// </summary> | ||
private TextMeshProUGUI title { get; set; } | ||
|
||
/// <summary> | ||
/// The description of the dialog. | ||
/// Can be up to three lines long. | ||
/// </summary> | ||
private TextMeshProUGUI description { get; set; } | ||
|
||
/// <summary> | ||
/// The negative/"cancel" button of the dialog. | ||
/// </summary> | ||
private ButtonManagerBasic noButton { get; set; } | ||
|
||
/// <summary> | ||
/// The positive/"confirm" button of the dialog. | ||
/// </summary> | ||
private ButtonManagerBasic yesButton { get; set; } | ||
|
||
/// <summary> | ||
/// The image component of the <see cref="yesButton"/> controlling its color. | ||
/// </summary> | ||
private Image yesButtonImage { get; set; } | ||
|
||
/// <summary> | ||
/// The TextMeshPro component for the icon of the <see cref="noButton"/>. | ||
/// </summary> | ||
private TextMeshProUGUI noIcon { get; set; } | ||
|
||
/// <summary> | ||
/// The TextMeshPro component for the icon of the <see cref="yesButton"/>. | ||
/// </summary> | ||
private TextMeshProUGUI yesIcon { get; set; } | ||
|
||
/// <summary> | ||
/// An event that is invoked when the user makes a choice in the dialog (including closing it). | ||
/// </summary> | ||
private UnityEvent<bool> onChoiceMade { get; } = new(); | ||
|
||
/// <summary> | ||
/// The game object under which the dialogs are instantiated. | ||
/// </summary> | ||
private static readonly Lazy<GameObject> dialogGameObject = new(() => new("ConfirmDialogs")); | ||
|
||
/// <summary> | ||
/// The ongoing fade animation of the dialog, if any. | ||
/// </summary> | ||
private Tweener existingFade; | ||
|
||
/// <summary> | ||
/// Initializes the dialog. | ||
/// </summary> | ||
protected override void StartDesktop() | ||
{ | ||
dialog = PrefabInstantiator.InstantiatePrefab(dialogMenuPrefab, Canvas.transform, false); | ||
canvasGroup = dialog.MustGetComponent<CanvasGroup>(); | ||
noButton = dialog.transform.Find("Content/Buttons/Cancel").gameObject.MustGetComponent<ButtonManagerBasic>(); | ||
noIcon = noButton.transform.Find("Texts/Icon").gameObject.MustGetComponent<TextMeshProUGUI>(); | ||
yesButton = dialog.transform.Find("Content/Buttons/Confirm").gameObject.MustGetComponent<ButtonManagerBasic>(); | ||
yesIcon = yesButton.transform.Find("Texts/Icon").gameObject.MustGetComponent<TextMeshProUGUI>(); | ||
yesButtonImage = yesButton.gameObject.MustGetComponent<Image>(); | ||
title = dialog.transform.Find("Dragger/Text").gameObject.MustGetComponent<TextMeshProUGUI>(); | ||
description = dialog.transform.Find("Content/Description").gameObject.MustGetComponent<TextMeshProUGUI>(); | ||
ButtonManagerBasic CloseButton = dialog.transform.Find("Dragger/CancelDragger").gameObject.MustGetComponent<ButtonManagerBasic>(); | ||
|
||
yesButton.clickEvent.AddListener(() => onChoiceMade.Invoke(true)); | ||
noButton.clickEvent.AddListener(() => onChoiceMade.Invoke(false)); | ||
CloseButton.clickEvent.AddListener(() => onChoiceMade.Invoke(false)); | ||
onChoiceMade.AddListener(_ => CloseMenu()); | ||
} | ||
|
||
private void OnDestroy() | ||
{ | ||
if (Dialog != null) | ||
{ | ||
Destroyer.Destroy(Dialog); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Sets up the dialog with the given <paramref name="configuration"/> and fades it in. | ||
/// </summary> | ||
/// <param name="configuration">The configuration for the dialog.</param> | ||
private void ShowMenu(ConfirmConfiguration configuration) | ||
{ | ||
title.text = configuration.title; | ||
description.text = configuration.description; | ||
yesButton.buttonText = configuration.yesText; | ||
yesButton.UpdateUI(); | ||
noButton.buttonText = configuration.noText; | ||
noButton.UpdateUI(); | ||
yesIcon.text = configuration.yesIcon.ToString(); | ||
yesButtonImage.color = configuration.yesColor ?? defaultColor; | ||
noIcon.text = configuration.noIcon.ToString(); | ||
|
||
dialog.SetActive(true); | ||
dialog.transform.SetAsLastSibling(); | ||
existingFade?.Kill(); | ||
existingFade = canvasGroup.DOFade(1f, animationDuration); | ||
} | ||
|
||
/// <summary> | ||
/// Closes the dialog by fading it out. | ||
/// </summary> | ||
private void CloseMenu() | ||
{ | ||
existingFade?.Kill(); | ||
existingFade = canvasGroup.DOFade(0f, animationDuration).OnComplete(DisableMenu); | ||
} | ||
|
||
/// <summary> | ||
/// Disables/destroys the dialog after it has been faded out. | ||
/// </summary> | ||
private void DisableMenu() | ||
{ | ||
if (oneTime) | ||
{ | ||
Destroyer.Destroy(this); | ||
} | ||
else | ||
{ | ||
dialog.SetActive(false); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Shows a confirmation dialog with the given <paramref name="configuration"/> | ||
/// and waits for the user to make a choice. Their choice is returned as a <see cref="bool"/>. | ||
/// </summary> | ||
/// <param name="configuration">The configuration for the dialog.</param> | ||
/// <returns>Whether the user confirmed the dialog.</returns> | ||
public static async UniTask<bool> ConfirmAsync(ConfirmConfiguration configuration) | ||
{ | ||
ConfirmDialog dialog = dialogGameObject.Value.AddComponent<ConfirmDialog>(); | ||
dialog.oneTime = true; | ||
await UniTask.WaitUntil(() => dialog.dialog != null); // May need to wait for initialization. | ||
dialog.ShowMenu(configuration); | ||
return await dialog.onChoiceMade.OnInvokeAsync(CancellationToken.None); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
fileFormatVersion: 2 | ||
guid: 5c29c87d6b544ee2b20d225feaf7b824 | ||
timeCreated: 1731674969 |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.