Last active
December 1, 2024 05:31
-
-
Save SolarianZ/37636f213723c1a2079abe1d7769827e to your computer and use it in GitHub Desktop.
{"category": "Unity Engine/Editor/Extensions", "keywords": "Unity, Editor, DisplayDialog, Modal, Checkbox, Button"} Display a modal dialog window with multiple checkboxes and buttons in the Unity Editor.
This file contains hidden or 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
| using System; | |
| using System.Collections.Generic; | |
| using UnityEditor; | |
| using UnityEngine; | |
| using UnityEngine.UIElements; | |
| /// <summary> | |
| /// Display a modal dialog window with multiple checkboxes and buttons in the Unity Editor. | |
| /// </summary> | |
| public static class ModalDialog | |
| { | |
| [Flags] | |
| public enum CheckboxModes | |
| { | |
| None = 0, | |
| AtLeastOne = 1, | |
| AtMostOne = 2, | |
| ToggleGroup = AtLeastOne | AtMostOne, | |
| } | |
| public static void ShowMessage(string title, string message, string positiveButton) | |
| { | |
| EditorUtility.DisplayDialog(title, message, positiveButton); | |
| } | |
| public static bool ShowMessage(string title, string message, string positiveButton, string negativeButton) | |
| { | |
| return EditorUtility.DisplayDialog(title, message, positiveButton, negativeButton); | |
| } | |
| public static int ShowMessage(string title, string message, params string[] buttons) | |
| { | |
| if (buttons == null || buttons.Length == 0) | |
| throw new ArgumentNullException(nameof(buttons)); | |
| return ShowMessage(DialogWindow.CalcPosition(), MessageType.None, title, message, buttons); | |
| } | |
| public static int ShowMessage(MessageType messageType, string title, string message, params string[] buttons) | |
| { | |
| if (buttons == null || buttons.Length == 0) | |
| throw new ArgumentNullException(nameof(buttons)); | |
| return ShowMessage(DialogWindow.CalcPosition(), messageType, title, message, buttons); | |
| } | |
| public static int ShowMessage(Rect position, MessageType messageType, string title, string message, params string[] buttons) | |
| { | |
| if (buttons == null || buttons.Length == 0) | |
| throw new ArgumentNullException(nameof(buttons)); | |
| return DialogWindow.ShowModal(position, messageType, title, message, null, CheckboxModes.None, buttons); | |
| } | |
| public static int ShowMessageWithCheckboxes(MessageType messageType, string title, string message, | |
| KeyValuePair<string, bool>[] checkboxes, CheckboxModes checkboxModes, params string[] buttons) | |
| { | |
| if (checkboxes == null || checkboxes.Length == 0) | |
| throw new ArgumentNullException(nameof(checkboxes)); | |
| if (buttons == null || buttons.Length == 0) | |
| throw new ArgumentNullException(nameof(buttons)); | |
| return DialogWindow.ShowModal(DialogWindow.CalcPosition(), messageType, title, message, checkboxes, checkboxModes, buttons); | |
| } | |
| public static int ShowMessageWithCheckboxes(Rect position, MessageType messageType, string title, string message, | |
| KeyValuePair<string, bool>[] checkboxes, CheckboxModes checkboxModes, params string[] buttons) | |
| { | |
| if (checkboxes == null || checkboxes.Length == 0) | |
| throw new ArgumentNullException(nameof(checkboxes)); | |
| if (buttons == null || buttons.Length == 0) | |
| throw new ArgumentNullException(nameof(buttons)); | |
| return DialogWindow.ShowModal(position, messageType, title, message, checkboxes, checkboxModes, buttons); | |
| } | |
| private class DialogWindow : EditorWindow | |
| { | |
| public static int ShowModal(Rect position, MessageType messageType, string title, string message, | |
| KeyValuePair<string, bool>[] checkboxes, CheckboxModes checkboxModes, string[] buttons) | |
| { | |
| DialogWindow dialog = CreateInstance<DialogWindow>(); | |
| dialog.position = position; | |
| dialog.titleContent = new GUIContent(title); | |
| dialog.MessageType = messageType; | |
| dialog.Message = message; | |
| dialog.Checkboxes = checkboxes; | |
| dialog.CheckboxModes = checkboxModes; | |
| dialog.ButtonTexts = buttons; | |
| dialog.ShowModalUtility(); | |
| return dialog.SelectedOptionIndex; | |
| } | |
| public static Rect CalcPosition() => CalcPosition(DefaultSize); | |
| public static Rect CalcPosition(Vector2 size) | |
| { | |
| return new Rect(Screen.currentResolution.width / 2f - size.x / 2f, | |
| Screen.currentResolution.height / 2f - size.y / 2f, size.x, size.y); | |
| } | |
| public static Vector2 DefaultSize => new Vector2(500, 300); | |
| public MessageType MessageType { get; set; } | |
| public string Message { get; set; } | |
| public KeyValuePair<string, bool>[] Checkboxes { get; set; } | |
| public CheckboxModes CheckboxModes { get; set; } | |
| public string[] ButtonTexts { get; set; } | |
| public int SelectedOptionIndex { get; private set; } = -1; | |
| private void OnEnable() | |
| { | |
| minSize = DefaultSize; | |
| } | |
| private void CreateGUI() | |
| { | |
| CreateMessageGUI(); | |
| CreateCheckboxGUI(); | |
| CreateOptionButtonGUI(); | |
| } | |
| private void CreateMessageGUI() | |
| { | |
| // Message Container | |
| VisualElement messageContainer = new VisualElement | |
| { | |
| style = | |
| { | |
| flexDirection = FlexDirection.Row, | |
| flexGrow = 1, | |
| flexShrink = 1, | |
| minHeight = 100, | |
| }, | |
| }; | |
| rootVisualElement.Add(messageContainer); | |
| // Message Icon | |
| if (MessageType != MessageType.None) | |
| { | |
| Image messageIcon = new Image | |
| { | |
| style = | |
| { | |
| width = 32, | |
| height = 32, | |
| marginLeft = 4, | |
| marginTop = 4, | |
| flexShrink = 0, | |
| }, | |
| }; | |
| switch (MessageType) | |
| { | |
| case MessageType.None: messageIcon.image = null; break; | |
| case MessageType.Info: messageIcon.image = EditorGUIUtility.IconContent("info@2x").image; break; | |
| case MessageType.Warning: messageIcon.image = EditorGUIUtility.IconContent("warning@2x").image; break; | |
| case MessageType.Error: messageIcon.image = EditorGUIUtility.IconContent("error@2x").image; break; | |
| default: throw new ArgumentOutOfRangeException(nameof(MessageType), MessageType, null); | |
| } | |
| messageContainer.Add(messageIcon); | |
| } | |
| // Message ScrollView | |
| ScrollView messageScrollView = new ScrollView(ScrollViewMode.Vertical) | |
| { | |
| style = | |
| { | |
| flexGrow = 1, | |
| } | |
| }; | |
| messageContainer.Add(messageScrollView); | |
| // Message Text | |
| Label messageLabel = new Label(Message) | |
| { | |
| #if UNITY_2021_3_OR_NEWER | |
| enableRichText = true, | |
| #endif | |
| #if UNITY_2022_3_OR_NEWER | |
| selection = | |
| { | |
| isSelectable = true, | |
| }, | |
| #endif | |
| style = | |
| { | |
| flexGrow = 1, | |
| flexShrink = 1, | |
| marginRight = 4, | |
| marginLeft = 4, | |
| marginTop = 4, | |
| marginBottom = 4, | |
| overflow = Overflow.Hidden, | |
| #if UNITY_2021_3_OR_NEWER | |
| textOverflow = TextOverflow.Ellipsis, | |
| #endif | |
| whiteSpace = WhiteSpace.Normal, | |
| } | |
| }; | |
| messageScrollView.Add(messageLabel); | |
| } | |
| private void CreateCheckboxGUI() | |
| { | |
| if (Checkboxes == null || Checkboxes.Length == 0) | |
| return; | |
| // Checkbox Container | |
| VisualElement checkboxContainer = new VisualElement | |
| { | |
| style = | |
| { | |
| justifyContent = Justify.FlexEnd, | |
| flexGrow = 0, | |
| flexShrink = 0, | |
| marginTop = 8, | |
| maxHeight = 120, | |
| } | |
| }; | |
| rootVisualElement.Add(checkboxContainer); | |
| // Checkboxes ScrollView | |
| ScrollView checkboxScrollView = new ScrollView(ScrollViewMode.Vertical) | |
| { | |
| style = | |
| { | |
| flexGrow = 1, | |
| } | |
| }; | |
| checkboxContainer.Add(checkboxScrollView); | |
| // Checkboxes | |
| string toggleNamePrefix = "modal-dialog__checkbox"; | |
| for (int i = 0; i < Checkboxes.Length; i++) | |
| { | |
| int index = i; | |
| Toggle toggle = new Toggle(Checkboxes[i].Key) | |
| { | |
| name = $"{toggleNamePrefix}-{index}", | |
| value = Checkboxes[i].Value, | |
| style = | |
| { | |
| marginLeft = 8, | |
| marginRight = 8, | |
| } | |
| }; | |
| DecorateToggle(toggle); | |
| checkboxScrollView.Add(toggle); | |
| // Checkbox mode | |
| toggle.RegisterValueChangedCallback(evt => | |
| { | |
| Checkboxes[index] = new KeyValuePair<string, bool>(Checkboxes[index].Key, evt.newValue); | |
| if (CheckboxModes == CheckboxModes.None) | |
| return; | |
| if (CheckboxModes.HasFlag(CheckboxModes.AtLeastOne) && !evt.newValue) | |
| { | |
| bool hasOtherChecked = false; | |
| for (int j = 0; j < Checkboxes.Length; j++) | |
| { | |
| if (Checkboxes[j].Value && j != index) | |
| { | |
| hasOtherChecked = true; | |
| break; | |
| } | |
| } | |
| if (!hasOtherChecked) | |
| { | |
| Checkboxes[index] = new KeyValuePair<string, bool>(Checkboxes[index].Key, true); | |
| checkboxScrollView.Q<Toggle>($"{toggleNamePrefix}-{index}").SetValueWithoutNotify(true); | |
| } | |
| } | |
| if (CheckboxModes.HasFlag(CheckboxModes.AtMostOne) && evt.newValue) | |
| { | |
| for (int j = 0; j < Checkboxes.Length; j++) | |
| { | |
| if (j == index) | |
| continue; | |
| Checkboxes[j] = new KeyValuePair<string, bool>(Checkboxes[j].Key, false); | |
| checkboxScrollView.Q<Toggle>($"{toggleNamePrefix}-{j}").SetValueWithoutNotify(false); | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| private void CreateOptionButtonGUI() | |
| { | |
| // Option Container | |
| VisualElement optionContainer = new VisualElement | |
| { | |
| style = | |
| { | |
| flexDirection = FlexDirection.Row, | |
| flexGrow = 0, | |
| flexShrink = 0, | |
| justifyContent = Justify.FlexEnd, | |
| marginRight = 8, | |
| marginLeft = 8, | |
| marginTop = 8, | |
| marginBottom = 8, | |
| flexWrap = Wrap.Wrap, | |
| maxHeight = 44, | |
| } | |
| }; | |
| rootVisualElement.Add(optionContainer); | |
| // Option Buttons | |
| for (int i = 0; i < ButtonTexts.Length; i++) | |
| { | |
| int index = i; | |
| Button optionButton = new Button(() => OnClickOptionButton(index)) | |
| { | |
| text = ButtonTexts[i], | |
| style = | |
| { | |
| minWidth = 50, | |
| } | |
| }; | |
| optionContainer.Add(optionButton); | |
| if (i == 0) | |
| optionButton.Focus(); | |
| } | |
| } | |
| private void OnClickOptionButton(int index) | |
| { | |
| SelectedOptionIndex = index; | |
| Close(); | |
| } | |
| private static void DecorateToggle(Toggle toggle) | |
| { | |
| VisualElement toggleInput = toggle.Q<VisualElement>(className: Toggle.inputUssClassName); | |
| toggleInput.style.flexGrow = 0; | |
| Label toggleLabel = toggle.Q<Label>(className: Toggle.labelUssClassName); | |
| #if UNITY_2021_3_OR_NEWER | |
| toggleLabel.enableRichText = true; | |
| toggleLabel.style.textOverflow = TextOverflow.Ellipsis; | |
| #endif | |
| toggleLabel.style.flexGrow = 1; | |
| toggleLabel.style.marginLeft = 2; | |
| toggleLabel.style.marginRight = 0; | |
| toggleLabel.style.paddingLeft = 2; | |
| toggleLabel.style.paddingRight = 1; | |
| toggleLabel.style.overflow = Overflow.Hidden; | |
| toggleLabel.style.whiteSpace = WhiteSpace.Normal; | |
| toggle.Remove(toggleLabel); | |
| toggleInput.Add(toggleLabel); | |
| } | |
| } | |
| } |
Author
SolarianZ
commented
Nov 17, 2024

Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment