// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Standard Library
using System;
using System.Collections.Generic;
// Unity
using UnityEditor;
using UnityEngine;
// GameKit
using AWS.GameKit.Editor.FileStructure;
using AWS.GameKit.Editor.Windows.Settings;
using AWS.GameKit.Runtime.Models;
namespace AWS.GameKit.Editor.GUILayoutExtensions
{
///
/// Provides new types of GUI elements for Unity's EditorGUILayout class.
///
/// See: https://docs.unity3d.com/ScriptReference/EditorGUILayout.html
///
public static class EditorGUILayoutElements
{
private const string INFO_ICON_ID = "console.infoicon"; // Icon ids provided by Unity that can be retrieved with EditorGUIUtility.IconContent
private const string EMPTY_PREFIX_SPACE = " "; // If we use string.Empty, the prefix label won't take up any space in the layout - use a single space instead
private const int HELP_ICON_HEIGHT = 35;
private const int OVERRIDE_SLIDER_TEXT_WIDTH = 50;
private static string SELECT_FILE = L10n.Tr("Select file");
///
/// Make a vertical divider.
///
/// A vertical divider is a thin line used to separate two sections inside an EditorGUILayout.BeginHorizontal() and EditorGUILayout.EndHorizontal() block.
///
/// The divider color changes automatically when the user changes their Editor Theme between dark and light mode.
///
/// The width of the divider line.
/// The amount of padding around the top and bottom of the divider line.
public static void VerticalDivider(float width = 1f, int verticalPadding = 0)
{
GUIStyle lineStyle = new GUIStyle
{
fixedWidth = width,
stretchHeight = true,
stretchWidth = false,
margin = new RectOffset(0, 0, verticalPadding, verticalPadding),
normal =
{
background = EditorResources.Textures.Colors.GUILayoutDivider.Get()
}
};
GUILayout.Box(GUIContent.none, lineStyle);
}
///
/// Make a horizontal divider.
///
/// A horizontal divider is a thin line used to separate two sections inside an EditorGUILayout.BeginVertical() and EditorGUILayout.EndVertical() block.
///
/// The divider color changes automatically when the user changes their Editor Theme between dark and light mode.
///
/// The height of the divider line.
/// The amount of padding around the top and bottom of the divider line.
public static void HorizontalDivider(float height = 1f, int verticalPadding = 0)
{
GUIStyle lineStyle = new GUIStyle
{
fixedHeight = height,
stretchHeight = false,
stretchWidth = true,
margin = new RectOffset(0, 0, verticalPadding, verticalPadding),
normal =
{
background = EditorResources.Textures.Colors.GUILayoutDivider.Get()
}
};
GUILayout.Box(GUIContent.none, lineStyle);
}
///
/// Creates a toolbar that persists which tab of the toolbar is selected across sessions.
///
/// A reference to the selectedTabId that is used to determine which tab should be draw in DrawTabContent.
/// The names of the tabs that should be added to the toolbar.
/// Determines how the toolbar buttons will be sized.
/// The style of the toolbar.
public static int CreateFeatureToolbar(int selectedTabId, string[] tabNames, GUI.ToolbarButtonSize tabSelectorButtonSize, GUIStyle tabSelectorStyle)
{
return GUILayout.Toolbar(selectedTabId, tabNames, tabSelectorStyle, tabSelectorButtonSize);
}
///
/// Make a help box with a similar style to Unity's default help box, except this help box has a read more link.
///
/// The message that should be displayed in the help box.
/// The url that the read more link should redirect to.
public static void HelpBoxWithReadMore(string message, string url)
{
LinkWidget.Options options = new LinkWidget.Options()
{
OverallStyle = SettingsGUIStyles.Page.CustomHelpBoxText,
ContentOffset = new Vector2(
x: -4,
y: -4
),
Alignment = LinkWidget.Alignment.Right
};
LinkWidget _readMoreLinkWidget = new LinkWidget(L10n.Tr("Read more"), url, options);
using (EditorGUILayout.HorizontalScope horizontalScope = new EditorGUILayout.HorizontalScope(EditorStyles.helpBox))
{
Rect helpBoxRect = horizontalScope.rect;
using (new EditorGUILayout.VerticalScope())
{
GUIStyle style = new GUIStyle();
float centeredContentOffset = (helpBoxRect.height - HELP_ICON_HEIGHT)/2 - 1;
style.contentOffset = new Vector2(0, centeredContentOffset);
GUILayout.Box(EditorGUIUtility.IconContent(INFO_ICON_ID).image, style, GUILayout.Height(HELP_ICON_HEIGHT));
}
using (new EditorGUILayout.VerticalScope())
{
EditorGUILayout.LabelField(message, SettingsGUIStyles.Page.CustomHelpBoxText);
using (new EditorGUILayout.HorizontalScope())
{
_readMoreLinkWidget.OnGUI();
}
}
}
}
///
/// Make a GUI element that is tinted with a color.
///
/// Use this when you need to change the color of a GUI element without changing it's GUIStyle.
///
/// The color to tint the GUI element.
/// A callback function which creates the GUI element.
public static void TintedElement(Color color, Action createElement)
{
Color originalBackgroundColor = GUI.backgroundColor;
GUI.backgroundColor = color;
createElement();
GUI.backgroundColor = originalBackgroundColor;
}
///
/// Make a GUI element that is tinted with a color.
///
/// Use this when you need to change the color of a GUI element without changing it's GUIStyle.
///
/// The return type of the createElement() callback function.
/// The color to tint the GUI element.
/// A callback function which creates the GUI element.
/// The return value of the createElement() callback function. This should be the new value entered by the user in the GUI element.
public static T TintedElement(Color color, Func createElement)
{
Color originalBackgroundColor = GUI.backgroundColor;
GUI.backgroundColor = color;
T result = createElement();
GUI.backgroundColor = originalBackgroundColor;
return result;
}
///
/// Make a stylized single press button that can be colored and disabled.
///
/// The text to display on the button.
/// Whether the button is enabled or not. By default the button is enabled.
/// The tooltip to display on the button. By default no tooltip is displayed.
/// The color to make the button when it is enabled. By default the button is Unity's default button color (grey). The button is grey when disabled.
/// An image to display before the text. By default no image is displayed.
/// The size to make the image. By default the image is be drawn at it's full size (i.e. not scaled).
/// Custom value for the minimum width of the button, if not provided then a common default is used.
/// True when the user clicks the button.
public static bool Button(
string text,
bool isEnabled = true,
string tooltip = "",
Nullable colorWhenEnabled = null,
Texture image = null,
Nullable imageSize = null,
float minWidth = SettingsGUIStyles.Buttons.MIN_WIDTH_NORMAL)
{
Vector2 finalImageSize = imageSize ?? EditorGUIUtility.GetIconSize();
GUIContent buttonContent = new GUIContent(text, image, tooltip);
using (new EditorGUIUtility.IconSizeScope(finalImageSize))
{
using (new EditorGUI.DisabledScope(!isEnabled))
{
if (isEnabled && colorWhenEnabled.HasValue)
{
return TintedElement(colorWhenEnabled.Value, () =>
GUILayout.Button(buttonContent, SettingsGUIStyles.Buttons.ColoredButtonNormal, GUILayout.MinWidth(minWidth)));
}
else
{
return GUILayout.Button(buttonContent, SettingsGUIStyles.Buttons.GreyButtonNormal, GUILayout.MinWidth(minWidth));
}
}
}
}
///
/// Make a stylized single press button, that is half the size of normal buttons, that can be colored and disabled.
///
/// The text to display on the button.
/// Whether the button is enabled or not. By default the button is enabled.
/// The tooltip to display on the button. By default no tooltip is displayed.
/// The color to make the button when it is enabled. By default the button is Unity's default button color (grey). The button is grey when disabled.
/// An image to display before the text. By default no image is displayed.
/// The size to make the image. By default the image is be drawn at it's full size (i.e. not scaled).
/// Custom value for the minimum width of the button, if not provided then a common default is used.
/// True when the user clicks the button.
public static bool SmallButton(
string text,
bool isEnabled = true,
string tooltip = "",
Nullable colorWhenEnabled = null,
Texture image = null,
Nullable imageSize = null,
float minWidth = SettingsGUIStyles.Buttons.MIN_WIDTH_SMALL)
{
Vector2 finalImageSize = imageSize ?? EditorGUIUtility.GetIconSize();
GUIContent buttonContent = new GUIContent(text, image, tooltip);
using (new EditorGUIUtility.IconSizeScope(finalImageSize))
{
using (new EditorGUI.DisabledScope(!isEnabled))
{
if (isEnabled && colorWhenEnabled.HasValue)
{
return TintedElement(colorWhenEnabled.Value, () =>
GUILayout.Button(buttonContent, SettingsGUIStyles.Buttons.ColoredButtonSmall, GUILayout.MinWidth(minWidth)));
}
else
{
return GUILayout.Button(buttonContent, SettingsGUIStyles.Buttons.GreyButtonSmall, GUILayout.MinWidth(minWidth));
}
}
}
}
///
/// Make a section header.
///
/// The string to display.
/// The level of indentation to place in front of the title.
/// Where to anchor the text.
public static void SectionHeader(string title, int indentationLevel = 0, TextAnchor textAnchor = TextAnchor.MiddleLeft)
{
GUIStyle headerStyle = CommonGUIStyles.SetIndentationLevel(CommonGUIStyles.SectionHeader, indentationLevel);
headerStyle.alignment = textAnchor;
EditorGUILayout.LabelField(title, headerStyle);
}
///
/// Make a section header with a description on the same line. The text is formatted as Title: Description with Title in bold.
///
/// The title string to display in bold.
/// The description string to display after the title and colon.
/// The level of indentation to place in front of the title.
/// Where to anchor the text.
public static void SectionHeaderWithDescription(string title, string description, int indentationLevel = 0, TextAnchor textAnchor = TextAnchor.MiddleLeft)
{
GUIStyle headerStyle = CommonGUIStyles.SetIndentationLevel(CommonGUIStyles.SectionHeaderWithDescription, indentationLevel);
headerStyle.alignment = textAnchor;
string text = $"{title}: {description}";
EditorGUILayout.LabelField(text, headerStyle);
}
///
/// Make a vertical gap between the previous and next GUI sections.
///
/// Number of additional pixels to add/subtract from the gap.
public static void SectionSpacer(float extraPixels = 0)
{
EditorGUILayout.Space(CommonGUIStyles.SpaceBetweenSections + extraPixels);
}
///
/// Make a horizontal line to divide two sections.
///
public static void SectionDivider()
{
SectionSpacer(extraPixels: -10);
HorizontalDivider();
SectionSpacer(extraPixels: -10);
}
///
/// Make a prefix label for a user input.
///
/// The string to display.
/// The level of indentation to place in front of the prefix label.
public static void PrefixLabel(string inputLabel, int indentationLevel = 1)
{
GUIStyle labelStyle = CommonGUIStyles.SetIndentationLevel(CommonGUIStyles.InputLabel, indentationLevel);
EditorGUILayout.PrefixLabel(inputLabel, labelStyle, labelStyle);
KeepPreviousPrefixLabelEnabled();
}
///
/// Make a custom field.
///
/// A string to display before the custom field.
/// A callback function which creates the field.
/// The level of indentation to place in front of the label.
/// When set to false, the input field will be greyed-out and and non-interactable.
/// Used to add a custom GUIStyle, else defaults to EditorStyles.textArea
public static void CustomField(string inputLabel, Action createField, int indentationLevel = 1, bool isEnabled = true, GUIStyle guiStyle = null)
{
GUIStyle labelStyle = CommonGUIStyles.SetIndentationLevel(guiStyle ?? CommonGUIStyles.InputLabel, indentationLevel);
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PrefixLabel(inputLabel, labelStyle);
KeepPreviousPrefixLabelEnabled();
using (new EditorGUI.DisabledScope(!isEnabled))
{
createField();
}
}
}
///
/// Make a custom field and temporarily override the labelWidth variable in the editor.
///
/// A string to display before the custom field.
/// A float that will override EditorGUIUtility.labelWidth for the life of this method.
/// A callback function which creates the field.
/// The level of indentation to place in front of the label.
/// When set to false, the input field will be greyed-out and and non-interactable.
/// Used to add a custom GUIStyle, else defaults to EditorStyles.textArea
public static void CustomField(string inputLabel, float labelWidth, Action createField, int indentationLevel = 1, bool isEnabled = true, GUIStyle guiStyle = null)
{
float originalLabelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = labelWidth;
CustomField(inputLabel, createField, indentationLevel, isEnabled, guiStyle);
EditorGUIUtility.labelWidth = originalLabelWidth;
}
///
/// Make a custom field.
///
/// The type of data the field contains.
/// A string to display before the custom field.
/// A callback function which creates the field and returns the field's new value.
/// The level of indentation to place in front of the label.
/// When set to false, the input field will be greyed-out and and non-interactable.
/// Used to add a custom GUIStyle, else defaults to EditorStyles.textArea
/// The field's new value entered by the user.
public static T CustomField(string inputLabel, Func createField, int indentationLevel = 1, bool isEnabled = true, GUIStyle guiStyle = null)
{
GUIStyle labelStyle = CommonGUIStyles.SetIndentationLevel(guiStyle ?? CommonGUIStyles.InputLabel, indentationLevel);
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PrefixLabel(inputLabel, labelStyle, labelStyle);
KeepPreviousPrefixLabelEnabled();
using (new EditorGUI.DisabledScope(!isEnabled))
{
return createField();
}
}
}
///
/// Make a text input field.
///
/// A string to display before the text input area.
/// The current text in the text input area.
/// The level of indentation to place in front of the label.
/// Optional placeholder text to display when the input field is empty.
/// When set to false, the input field will be greyed-out and and non-interactable.
/// The text entered by the user.
public static string TextField(string inputLabel, string currentText, int indentationLevel = 1, string placeholderText = "", bool isEnabled = true)
{
return CustomField(inputLabel, () => {
string newText = EditorGUILayout.TextField(currentText);
if (!String.IsNullOrEmpty(placeholderText) && String.IsNullOrEmpty(newText))
{
DisplayPlaceholderTextOverLastField(placeholderText);
}
return newText;
}, indentationLevel, isEnabled);
}
///
/// Make a text input area that handles multiple lines.
///
/// A string to display before the text input area.
/// The current text in the text input area.
/// The level of indentation to place in front of the label.
/// Optional placeholder text to display when the input field is empty.
/// When set to false, the input field will be greyed-out and and non-interactable.
/// Used to add a custom GUIStyle to the fields label, else defaults to CommonGUIStyles.InputLabel
/// Used to add a custom GUIStyle to the text area, else defaults to EditorStyles.textArea
/// (Optional) Additional GUILayout params to apply to the text area.
/// The text entered by the user.
public static string DescriptionField(
string inputLabel,
string currentText,
int indentationLevel = 1,
string placeholderText = "",
bool isEnabled = true,
GUIStyle labelGuiStyle = null,
GUIStyle textAreaGuiStyle = null,
params GUILayoutOption[] options)
{
return CustomField(inputLabel, () => {
string newText = EditorGUILayout.TextArea(currentText, textAreaGuiStyle ?? EditorStyles.textArea, options);
if (!String.IsNullOrEmpty(placeholderText) && String.IsNullOrEmpty(newText))
{
using (new EditorGUI.DisabledScope(true))
{
EditorGUI.TextArea(GUILayoutUtility.GetLastRect(), placeholderText, CommonGUIStyles.PlaceholderTextArea);
}
}
return newText;
}, indentationLevel, isEnabled, labelGuiStyle);
}
///
/// Make a key-value dictionary field that will display a list of keys and a list of values.
///
/// The type of the key List. This type must be serializable.
/// The type of the value List. This type must be serializable.
/// The serialized property corresponding to the keys list.
/// The serialized property corresponding to the values list.
/// The list of keys to be drawn.
/// The list of values to be drawn.
/// The label that should be shown above the list of keys.
/// The label that should be shown above the list of values.
/// Optional field is set to true if the GUI should be disabled for typing into and the options to add. Subtract fields will also be removed if isReadonly is true.
/// When true, allows editing of the dictionary.
/// Optional field for adding spacing between each pair in a vertical section.
public static void SerializableExamplesDictionary(
SerializedProperty serializedKeysListProperty,
SerializedProperty serializedValuesListProperty,
List keysList,
List valuesList,
string keysLabel,
string valuesLabel,
bool isReadonly = false,
int paddingBetweenKeyAndValue = 2,
int paddingBetweenPairs = 4)
{
using (new EditorGUILayout.HorizontalScope(SettingsGUIStyles.FeatureExamplesTab.DictionaryKeyValues))
{
EditorGUILayout.LabelField(keysLabel, GUILayout.MinWidth(0));
EditorGUILayout.LabelField(valuesLabel, GUILayout.MinWidth(0));
if (!isReadonly)
{
GUILayout.Space(CommonGUIStyles.INLINE_ICON_SIZE);
}
}
using (new EditorGUI.DisabledScope(isReadonly))
{
using (new EditorGUILayout.VerticalScope())
{
for (int i = 0; i < serializedKeysListProperty.arraySize; i++)
{
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PropertyField(serializedKeysListProperty.GetArrayElementAtIndex(i),
GUIContent.none);
GUILayout.Space(paddingBetweenKeyAndValue);
EditorGUILayout.PropertyField(serializedValuesListProperty.GetArrayElementAtIndex(i),
GUIContent.none);
if (!isReadonly)
{
using (new EditorGUI.DisabledScope(serializedKeysListProperty.arraySize <= 1))
{
GUILayout.Box(EditorResources.Textures.SettingsWindow.MinusIcon.Get(), SettingsGUIStyles.Icons.InlineIcons);
Rect minusButtonRect = GUILayoutUtility.GetLastRect();
// Create a button with no text but spans the length of both the icon created above
if (GUI.Button(minusButtonRect, "", GUIStyle.none))
{
keysList.RemoveAt(i);
valuesList.RemoveAt(i);
}
if (serializedKeysListProperty.arraySize > 1)
{
EditorGUIUtility.AddCursorRect(minusButtonRect, MouseCursor.Link);
}
}
}
}
GUILayout.Space(paddingBetweenPairs);
}
}
if (!isReadonly)
{
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.Box(EditorResources.Textures.SettingsWindow.PlusIcon.Get(), SettingsGUIStyles.Icons.NormalSize);
Rect plusButtonRect = GUILayoutUtility.GetLastRect();
// Create a button with no text but spans the length of both the icon created above
if (GUI.Button(plusButtonRect, "", GUIStyle.none))
{
keysList.Add(default);
valuesList.Add(default);
}
EditorGUIUtility.AddCursorRect(plusButtonRect, MouseCursor.Link);
GUILayout.FlexibleSpace();
}
EditorGUILayout.Space(5);
}
}
}
///
/// Creates a UI element that will display a list in the same style as the SerializableExamplesDictionary
///
/// The serialized property corresponding to the keys list.
/// The list of values to be drawn.
/// The label that should be shown above the list of keys.
/// When true, allows editing of the list.
/// Optional field for adding spacing between each element in a vertical section.
public static void SerializableExamplesList(
SerializedProperty serializedListProperty,
List list,
string label,
bool isReadonly = false,
int paddingBetweenElements = 4)
{
using (new EditorGUILayout.HorizontalScope(SettingsGUIStyles.FeatureExamplesTab.DictionaryKeyValues))
{
EditorGUILayout.LabelField(label, GUILayout.MinWidth(0));
if (!isReadonly)
{
GUILayout.Space(CommonGUIStyles.INLINE_ICON_SIZE);
}
}
using (new EditorGUI.DisabledScope(isReadonly))
{
using (new EditorGUILayout.VerticalScope())
{
for (int i = 0; i < serializedListProperty.arraySize; i++)
{
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.PropertyField(serializedListProperty.GetArrayElementAtIndex(i), GUIContent.none);
if (!isReadonly)
{
using (new EditorGUI.DisabledScope(serializedListProperty.arraySize <= 1))
{
GUILayout.Box(EditorResources.Textures.SettingsWindow.MinusIcon.Get(), SettingsGUIStyles.Icons.InlineIcons);
Rect minusButtonRect = GUILayoutUtility.GetLastRect();
// Create a button with no text but spans the length of both the icon created above
if (GUI.Button(minusButtonRect, "", GUIStyle.none))
{
list.RemoveAt(i);
}
if (serializedListProperty.arraySize > 1)
{
EditorGUIUtility.AddCursorRect(minusButtonRect, MouseCursor.Link);
}
}
}
}
GUILayout.Space(paddingBetweenElements);
}
}
if (!isReadonly)
{
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.Box(EditorResources.Textures.SettingsWindow.PlusIcon.Get(), SettingsGUIStyles.Icons.NormalSize);
Rect plusButtonRect = GUILayoutUtility.GetLastRect();
// Create a button with no text but spans the length of both the text and the icon
if (GUI.Button(plusButtonRect, "", GUIStyle.none))
{
list.Add(default);
}
EditorGUIUtility.AddCursorRect(plusButtonRect, MouseCursor.Link);
GUILayout.FlexibleSpace();
}
EditorGUILayout.Space(5);
}
}
}
///
/// Make a label field. Useful for displaying read only information.
///
/// A string to display in the label key (left) field.
/// A string to display in the label value (right) field.
/// The level of indentation to place in front of the label key.
public static void LabelField(string labelKey, string labelValue, int indentationLevel = 1)
{
CustomField(labelKey, () => EditorGUILayout.LabelField(labelValue), indentationLevel);
}
///
/// Make a password input field. All characters entered are displayed as '*'.
///
/// A string to display before the password input area.
/// The current password in the password input area.
/// The level of indentation to place in front of the label.
/// Optional placeholder text to display when the input field is empty.
/// When set to false, the input field will be greyed-out and and non-interactable.
/// The password entered by the user.
public static string PasswordField(string inputLabel, string currentPassword, int indentationLevel = 1, string placeholderText = "", bool isEnabled = true)
{
return CustomField(inputLabel, () =>
{
string newPassword = EditorGUILayout.PasswordField(currentPassword);
if (!String.IsNullOrEmpty(placeholderText) && String.IsNullOrEmpty(newPassword))
{
DisplayPlaceholderTextOverLastField(placeholderText);
}
return newPassword;
}, indentationLevel, isEnabled);
}
///
/// Make a property field.
///
/// A string to display before the property input area.
/// The serialized property to edit.
/// The level of indentation to place in front of the label.
/// Optional placeholder text to display when the input field is empty.
/// When set to false, the input field will be greyed-out and and non-interactable.
/// (Optional) Additional GUILayout params to apply to the field.
public static void PropertyField(string inputLabel, SerializedProperty property, int indentationLevel = 1, string placeholderText = "", bool isEnabled = true, params GUILayoutOption[] options)
{
CustomField(inputLabel, () =>
{
EditorGUILayout.PropertyField(property, GUIContent.none, options);
if (!String.IsNullOrEmpty(placeholderText) && String.IsNullOrEmpty(property.stringValue))
{
DisplayPlaceholderTextOverLastField(placeholderText);
}
}, indentationLevel, isEnabled);
}
///
/// Make a description field.
///
/// The description to display.
/// The level of indentation to place in front of the description.
/// Where to anchor the text.
public static void Description(string description, int indentationLevel = 1, TextAnchor textAnchor = TextAnchor.MiddleLeft)
{
GUIStyle descriptionStyle = CommonGUIStyles.SetIndentationLevel(CommonGUIStyles.Description, indentationLevel);
descriptionStyle.alignment = textAnchor;
descriptionStyle.wordWrap = true;
EditorGUILayout.LabelField(description, descriptionStyle);
}
public static void ErrorText(string error, int indentationLevel = 1)
{
GUIStyle errorStyle = CommonGUIStyles.SetIndentationLevel(CommonGUIStyles.Description, indentationLevel);
errorStyle.normal.textColor = CommonGUIStyles.ErrorRed;
EditorGUILayout.LabelField(error, errorStyle);
}
///
/// Make an on/off toggle field.
///
/// A string to display before the toggle field.
/// The current value of the toggle field.
/// The level of indentation to place in front of the label.
/// When set to false, the input field will be greyed-out and and non-interactable.
/// The new value of the toggle field.
public static bool ToggleField(string inputLabel, bool currentValue, int indentationLevel = 1, bool isEnabled = true)
{
return CustomField(inputLabel, () =>
{
bool newValue = GUILayout.Toggle(currentValue, string.Empty);
// Make the toggle field only be selectable when the mouse is directly on it, rather than anywhere on it's row.
// Otherwise, the user can accidentally toggle this field without knowing it.
GUILayout.FlexibleSpace();
return newValue;
},
indentationLevel, isEnabled);
}
///
/// Make an on/off toggle with a label to the left of the checkbox.
///
/// A string to display before after the checkbox.
/// The current value of the toggle.
/// When set to false, the input field will be greyed-out and and non-interactable.
/// The new value of the toggle.
public static bool ToggleLeft(string inputLabel, bool currentValue, bool isEnabled = true)
{
using (new EditorGUI.DisabledScope(!isEnabled))
{
float originalLabelWidth = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = GUI.skin.label.CalcSize(new GUIContent(inputLabel)).x;
bool result = EditorGUILayout.ToggleLeft(inputLabel, currentValue);
EditorGUIUtility.labelWidth = originalLabelWidth;
return result;
}
}
///
/// Make an int slider field.
///
/// A string to display before the int slider field.
/// The current value of the slider.
/// The minimum value of the slider.
/// The maximum value of the slider.
/// The level of indentation to place in front of the label.
/// When set to false, the input field will be greyed-out and and non-interactable.
/// The new value of the int slider.
public static int IntSlider(string inputLabel, int currentValue, int minValue, int maxValue, int indentationLevel = 1, bool isEnabled = true)
{
return CustomField(inputLabel, () =>
{
return EditorGUILayout.IntSlider(currentValue, minValue, maxValue);
}, indentationLevel, isEnabled);
}
///
/// Make a slider that allows a user to override the min or max by using the text field value.
///
/// A string to display before the int slider field.
/// The current value of the slider.
/// The minimum value of the slider.
/// The maximum value of the slider.
/// The level of indentation to place in front of the label.
/// When set to false, the input field will be greyed-out and and non-interactable.
/// The new value of the int slider.
public static int OverrideSlider(string inputLabel, int currentValue, int minValue, int maxValue, int indentationLevel = 1, bool isEnabled = true)
{
return CustomField(inputLabel, () =>
{
float sliderValue = GUILayout.HorizontalSlider(currentValue, minValue, maxValue);
float finalValue = sliderValue;
string textValue = GUILayout.TextField(finalValue.ToString(), GUILayout.Width(OVERRIDE_SLIDER_TEXT_WIDTH));
if (!float.TryParse(textValue, out finalValue))
{
finalValue = string.IsNullOrEmpty(textValue) ? minValue : sliderValue;
}
return (int)finalValue;
}, indentationLevel, isEnabled);
}
///
/// Make a text field that can also be populated by a file selector, accessible via an inline button.
///
/// A string to display before the text field and file selector button.
/// The current value of the text field.
/// The title of the file selection panel.
/// The file extension which can be selected.
/// The level of indentation to place in front of the label.
/// When set to false, the input field will be greyed-out and non-interactable.
/// Whether to open a file selection screen that opens existing files, or lets you enter the name of a non-existing file.
/// The new value of the text field.
public static string FileSelection(string inputLabel, string currentValue, string filePanelTitle, string fileExtension = "*", int indentationLevel = 1, bool isEnabled = true, bool openingFile = true)
{
CustomField(inputLabel, () =>
{
using (new EditorGUILayout.HorizontalScope())
{
currentValue = EditorGUILayout.TextField(currentValue);
if (GUILayout.Button(SELECT_FILE, GUILayout.Width(GUI.skin.button.CalcSize(new GUIContent(SELECT_FILE)).x + 16)))
{
if (openingFile)
{
currentValue = EditorUtility.OpenFilePanel(filePanelTitle, string.Empty, fileExtension);
}
else
{
currentValue = EditorUtility.SaveFilePanel(filePanelTitle, string.Empty, string.Empty, fileExtension);
}
// If the above TextField has focus before clicking the Select File button, it will not update after a file is selected. Forcing it to loose focus afterwards will force it to update.
GUI.FocusControl(null);
}
};
}, indentationLevel, isEnabled);
return currentValue;
}
///
/// Make an empty prefix label.
///
///
/// Useful for aligning unlabeled fields with inputs.
///
public static void EmptyPrefixLabel()
{
PrefixLabel(EMPTY_PREFIX_SPACE);
}
///
/// Keep the previous EditorGUILayout.PrefixLabel() from getting disabled when the GUI element it labels is disabled.
///
/// This method must be called between EditorGUILayout.PrefixLabel() and the next EditorGUILayout or GUILayout function.
/// See below for an example.
///
/// Explanation: PrefixLabels automatically get disabled when the GUI element they label is disabled.
/// This happens even if the PrefixLabel itself is wrapped in a using EditorGUI.DisabledScope(true){} block.
/// This method creates an invisible, zero-width label field between the PrefixLabel and the GUI element which it labels.
/// The invisible label field is never disabled, so the PrefixLabel remains enabled.
///
///
/// This shows how to use this method to keep a prefix label from getting disabled.
///
/// EditorGUILayout.PrefixLabel("Foo:");
/// EditorGUILayoutElements.KeepPreviousPrefixLabelEnabled();
/// using (new EditorGUI.DisabledScope(!_isFooInputFieldDisabled))
/// {
/// _currentFoo = EditorGUILayout.TextField(currentFoo);
/// }
///
///
public static void KeepPreviousPrefixLabelEnabled()
{
// Surprisingly, Width(0f) is not actually zero width.
EditorGUILayout.LabelField(string.Empty, new GUIStyle(), GUILayout.Width(-3f));
}
///
/// Draw an icon representing a feature's deployment status.
///
/// The current deployment status of a feature.
public static void DeploymentStatusIcon(FeatureStatus deploymentStatus)
{
Texture icon = GetDeploymentStatusIcon(deploymentStatus);
if (icon != null)
{
GUILayout.Box(icon, CommonGUIStyles.DeploymentStatusIcon);
}
}
///
/// Draw an icon button for hard refreshing feature deployment statuses.
///
/// Indicates if feature refresh is in progress.
/// A box will be rendered instead of a button if the refresh is in progress. During refresh the method will always return false.
/// True when the user clicks the button.
public static bool DeploymentRefreshIconButton(bool isRefreshing)
{
Texture icon;
if (isRefreshing)
{
icon = EditorResources.Textures.FeatureStatusWaiting.Get();
GUILayout.Box(new GUIContent(icon, L10n.Tr("Refreshing deployment status of all features.")), CommonGUIStyles.RefreshIcon);
return false;
}
icon = EditorResources.Textures.FeatureStatusRefresh.Get();
return GUILayout.Button(new GUIContent(icon, L10n.Tr("Refresh deployment status of all features.")), CommonGUIStyles.RefreshIcon);
}
///
/// Displays a tooltip under the users mouse pointer when the mouse pointer is inside of the last element rendered before this call.
///
/// Tooltip to display
public static void DrawToolTip(string message)
{
if (Event.current.type == EventType.Repaint && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
{
Vector3 mousePos = Event.current.mousePosition;
Vector2 size = GUI.skin.box.CalcSize(new GUIContent(message));
GUI.Label(new Rect(mousePos.x - size.x, mousePos.y - size.y, size.x, size.y), message, SettingsGUIStyles.Tooltip.Text);
}
}
internal static Texture GetDeploymentStatusIcon(FeatureStatus deploymentStatus)
{
switch (deploymentStatus)
{
case FeatureStatus.Undeployed:
// fall through
case FeatureStatus.Unknown:
return null;
case FeatureStatus.Deployed:
return EditorResources.Textures.FeatureStatusSuccess.Get();
case FeatureStatus.Error:
// fall through
case FeatureStatus.RollbackComplete:
return EditorResources.Textures.FeatureStatusError.Get();
default:
return EditorResources.Textures.FeatureStatusWorking.Get();
}
}
private static void DisplayPlaceholderTextOverLastField(string placeholderText)
{
using (new EditorGUI.DisabledScope(true))
{
EditorGUI.LabelField(GUILayoutUtility.GetLastRect(), placeholderText, CommonGUIStyles.PlaceholderLabel);
}
}
}
}