// #define GENERATE_DESATURATED_ICONS #if UNITY_2019_1_OR_NEWER #define SHORTCUT_MANAGER #endif using UnityEngine; using UnityEngine.ProBuilder; namespace UnityEditor.ProBuilder { /// /// Base class from which any action that is represented in the ProBuilder toolbar inherits. /// public abstract class MenuAction { /// /// A flag indicating the state of a menu action. This determines whether the menu item is visible, and if visible, enabled. /// [System.Flags] public enum MenuActionState { /// /// The button is not visible in the toolbar. /// Hidden = 0x0, /// /// The button is visible in the toolbar. /// Visible = 0x1, /// /// The button (and by proxy, the action it performs) are valid given the current selection. /// Enabled = 0x2, /// /// Button and action are both visible in the toolbar and valid given the current selection. /// VisibleAndEnabled = 0x3 }; /// /// Path to the ProBuilder menu category. /// /// /// Use this where you wish to add a top level menu item. /// internal const string probuilderMenuPath = "Tools/ProBuilder/"; /// /// The unicode character for the control key symbol on Windows, or command key on macOS. /// internal const char keyCommandSuper = PreferenceKeys.CMD_SUPER; /// /// The unicode character for the shift key symbol. /// internal const char keyCommandShift = PreferenceKeys.CMD_SHIFT; /// /// The unicode character for the option key symbol on macOS. /// /// internal const char keyCommandOption = PreferenceKeys.CMD_OPTION; /// /// The unicode character for the alt key symbol on Windows. /// internal const char keyCommandAlt = PreferenceKeys.CMD_ALT; /// /// The unicode character for the delete key symbol. /// internal const char keyCommandDelete = PreferenceKeys.CMD_DELETE; static readonly GUIContent AltButtonContent = new GUIContent("+", ""); static readonly Vector2 AltButtonSize = new Vector2(21, 0); Vector2 m_LastCalculatedSize = Vector2.zero; protected MenuAction() { iconMode = ProBuilderEditor.s_IsIconGui; } /// /// Compare two menu items precedence by their category and priority modifier. /// /// /// /// internal static int CompareActionsByGroupAndPriority(MenuAction left, MenuAction right) { if (left == null) { if (right == null) return 0; else return -1; } else { if (right == null) { return 1; } else { int l = (int)left.group, r = (int)right.group; if (l < r) return -1; else if (l > r) return 1; else { int lp = left.toolbarPriority < 0 ? int.MaxValue : left.toolbarPriority, rp = right.toolbarPriority < 0 ? int.MaxValue : right.toolbarPriority; return lp.CompareTo(rp); } } } } Texture2D m_DesaturatedIcon = null; /// /// By default this function will look for an image named `${icon}_disabled`. If your disabled icon is somewhere else override this function. /// protected virtual Texture2D disabledIcon { get { if (m_DesaturatedIcon == null) { if (icon == null) return null; m_DesaturatedIcon = IconUtility.GetIcon(string.Format("Toolbar/{0}_disabled", icon.name)); #if GENERATE_DESATURATED_ICONS if (!m_DesaturatedIcon) m_DesaturatedIcon = ProBuilder2.EditorCommon.DebugUtilities.pb_GenerateDesaturatedImage.CreateDesaturedImage(icon); #endif } return m_DesaturatedIcon; } } /// /// What category this action belongs in. /// public abstract ToolbarGroup group { get; } /// /// Optional value influences where in the toolbar this menu item will be placed. /// /// 0 is first, 1 is second, -1 is no preference. /// /// public virtual int toolbarPriority { get { return -1; } } /// /// The icon to be displayed for this action. /// /// /// Not used when toolbar is in text mode. /// public abstract Texture2D icon { get; } /// /// The contents to display for this menu action's tooltip. /// public abstract TooltipContent tooltip { get; } /// /// Optional override for the action title displayed in the toolbar button. /// /// /// If unimplemented the tooltip title is used. /// public virtual string menuTitle { get { return tooltip.title; } } /// /// True if this class should have an entry built into the hardware menu. This is not implemented for custom actions. /// protected virtual bool hasFileMenuEntry { get { return true; } } /// /// Is the current mode and selection valid for this action? /// /// A flag indicating both the visibility and enabled state for an action. public MenuActionState menuActionState { get { if (hidden) return MenuActionState.Hidden; if (enabled) return MenuActionState.VisibleAndEnabled; return MenuActionState.Visible; } } /// /// In which SelectMode states is this action applicable. Drives the `virtual bool hidden { get; }` property unless overridden. /// public virtual SelectMode validSelectModes { get { return SelectMode.Any; } } /// /// A check for whether or not the action is valid given the current selection. /// /// /// True if this action is valid with current selection and mode. public virtual bool enabled { get { return ProBuilderEditor.instance != null && ProBuilderEditor.selectMode.ContainsFlag(validSelectModes) && !ProBuilderEditor.selectMode.ContainsFlag(SelectMode.InputTool); } } /// /// Is this action visible in the ProBuilder toolbar? /// /// This returns false by default. /// /// True if this action should be shown in the toolbar with the current mode and settings, false otherwise. public virtual bool hidden { get { return !ProBuilderEditor.selectMode.ContainsFlag(validSelectModes); } } /// /// Get a flag indicating the visibility and enabled state of an extra options menu modifier for this action. /// /// A flag specifying whether an options icon should be displayed for this action button. If your action implements some etra options, you must also implement OnSettingsGUI. protected virtual MenuActionState optionsMenuState { get { return MenuActionState.Hidden; } } /// /// Perform whatever action this menu item is supposed to do. You are resposible for implementing Undo. /// /// A new ActionResult with a summary of the state of the action's success. public abstract ActionResult DoAction(); protected virtual void DoAlternateAction() { MenuOption.Show(OnSettingsGUI, OnSettingsEnable, OnSettingsDisable); } /// /// Implement the extra settings GUI for your action in this method. /// protected virtual void OnSettingsGUI() {} /// /// Called when the settings window is opened. /// protected virtual void OnSettingsEnable() {} /// /// Called when the settings window is closed. /// protected virtual void OnSettingsDisable() {} protected bool iconMode { get; set; } /// /// Draw a menu button. Returns true if the button is active and settings are enabled, false if settings are not enabled. /// /// /// /// /// /// internal bool DoButton(bool isHorizontal, bool showOptions, ref Rect optionsRect, params GUILayoutOption[] layoutOptions) { bool wasEnabled = GUI.enabled; bool buttonEnabled = (menuActionState & MenuActionState.Enabled) == MenuActionState.Enabled; GUI.enabled = buttonEnabled; GUI.backgroundColor = Color.white; if (iconMode) { if (GUILayout.Button(buttonEnabled || !disabledIcon ? icon : disabledIcon, ToolbarGroupUtility.GetStyle(group, isHorizontal), layoutOptions)) { if (showOptions && (optionsMenuState & MenuActionState.VisibleAndEnabled) == MenuActionState.VisibleAndEnabled) { DoAlternateAction(); } else { ActionResult result = DoAction(); EditorUtility.ShowNotification(result.notification); } } if ((optionsMenuState & MenuActionState.VisibleAndEnabled) == MenuActionState.VisibleAndEnabled) { Rect r = GUILayoutUtility.GetLastRect(); r.x = r.x + r.width - 16; r.y += 0; r.width = 14; r.height = 14; GUI.Label(r, IconUtility.GetIcon("Toolbar/Options", IconSkin.Pro), GUIStyle.none); optionsRect = r; GUI.enabled = wasEnabled; return buttonEnabled; } else { GUI.enabled = wasEnabled; return false; } } else { GUI.backgroundColor = ToolbarGroupUtility.GetColor(group); // in text mode always use the vertical layout. isHorizontal = false; GUILayout.BeginHorizontal(MenuActionStyles.rowStyleVertical, layoutOptions); if (GUILayout.Button(menuTitle, MenuActionStyles.buttonStyleVertical)) { ActionResult res = DoAction(); EditorUtility.ShowNotification(res.notification); } MenuActionState altState = optionsMenuState; if ((altState & MenuActionState.Visible) == MenuActionState.Visible) { GUI.enabled = GUI.enabled && (altState & MenuActionState.Enabled) == MenuActionState.Enabled; if (DoAltButton(GUILayout.MaxWidth(21), GUILayout.MaxHeight(16))) DoAlternateAction(); } GUILayout.EndHorizontal(); GUI.backgroundColor = Color.white; GUI.enabled = wasEnabled; return false; } } bool DoAltButton(params GUILayoutOption[] options) { return GUILayout.Button(AltButtonContent, MenuActionStyles.altButtonStyle, options); } /// /// Get the rendered width of this GUI item. /// /// /// internal Vector2 GetSize(bool isHorizontal) { if (iconMode) { m_LastCalculatedSize = ToolbarGroupUtility.GetStyle(ToolbarGroup.Object, isHorizontal).CalcSize(UI.EditorGUIUtility.TempContent(null, null, icon)); } else { // in text mode always use the vertical layout. isHorizontal = false; m_LastCalculatedSize = MenuActionStyles.buttonStyleVertical.CalcSize(UI.EditorGUIUtility.TempContent(menuTitle)) + AltButtonSize; } return m_LastCalculatedSize; } } }