using System; using UnityEngine; namespace MeidoPhotoStudio.Plugin { using DropdownSelectArgs = DropdownHelper.DropdownSelectArgs; using DropdownCloseArgs = DropdownHelper.DropdownCloseArgs; public class Dropdown : BaseControl { public event EventHandler SelectionChange; public event EventHandler DropdownOpen; public event EventHandler DropdownClose; private bool clickedYou; private bool showDropdown; private readonly string label; private readonly bool isMenu; public string[] DropdownList { get; private set; } public int DropdownID { get; } private Vector2 scrollPos; public Vector2 ScrollPos => scrollPos; private Rect buttonRect; public Rect ButtonRect { get => buttonRect; private set => buttonRect = value; } private Vector2 elementSize; public Vector2 ElementSize => elementSize; private int selectedItemIndex; public int SelectedItemIndex { get => selectedItemIndex; set { selectedItemIndex = Mathf.Clamp(value, 0, DropdownList.Length - 1); OnDropdownEvent(SelectionChange); } } public string SelectedItem => DropdownList[SelectedItemIndex]; public Dropdown(string label, string[] itemList, int selectedItemIndex = 0) : this(itemList, selectedItemIndex) { isMenu = true; this.label = label; } public Dropdown(string[] itemList, int selectedItemIndex = 0) { DropdownID = DropdownHelper.DropdownID; SetDropdownItems(itemList, selectedItemIndex); DropdownHelper.SelectionChange += OnChangeSelection; DropdownHelper.DropdownClose += OnCloseDropdown; } // TODO: I don't think this works the way I think it does ~Dropdown() { DropdownHelper.SelectionChange -= OnChangeSelection; DropdownHelper.DropdownClose -= OnCloseDropdown; } public void SetDropdownItems(string[] itemList, int selectedItemIndex = -1) { if (selectedItemIndex < 0) selectedItemIndex = SelectedItemIndex; elementSize = Vector2.zero; // TODO: Calculate scrollpos position maybe if ((selectedItemIndex != this.selectedItemIndex) || (itemList.Length != DropdownList?.Length)) { scrollPos = Vector2.zero; } DropdownList = itemList; SelectedItemIndex = selectedItemIndex; } public void SetDropdownItem(int index, string newItem) { if (index < 0 || index >= DropdownList.Length) return; Vector2 itemSize = DropdownHelper.CalculateElementSize(newItem); if (itemSize.x > ElementSize.x) elementSize = itemSize; DropdownList[index] = newItem; } public void SetDropdownItem(string newItem) { SetDropdownItem(SelectedItemIndex, newItem); } public void Step(int dir) { dir = (int)Mathf.Sign(dir); SelectedItemIndex = Utility.Wrap(SelectedItemIndex + dir, 0, DropdownList.Length); } public void Draw(GUIStyle buttonStyle, params GUILayoutOption[] layoutOptions) { Draw(buttonStyle, null, layoutOptions); } public void Draw(GUIStyle buttonStyle, GUIStyle dropdownStyle = null, params GUILayoutOption[] layoutOptions) { bool clicked = GUILayout.Button( isMenu ? label : DropdownList[selectedItemIndex], buttonStyle, layoutOptions ); if (clicked) { showDropdown = !clickedYou; clickedYou = false; } if (showDropdown && Event.current.type == EventType.Repaint) InitializeDropdown(dropdownStyle); } public override void Draw(params GUILayoutOption[] layoutOptions) { GUIStyle buttonStyle = new GUIStyle(GUI.skin.button) { alignment = TextAnchor.MiddleLeft }; Draw(buttonStyle, layoutOptions); } private void OnChangeSelection(object sender, DropdownSelectArgs args) { if (args.DropdownID == DropdownID) { SelectedItemIndex = args.SelectedItemIndex; } } private void OnCloseDropdown(object sender, DropdownCloseArgs args) { if (args.DropdownID == DropdownID) { scrollPos = args.ScrollPos; clickedYou = args.ClickedYou; if (clickedYou) OnDropdownEvent(SelectionChange); OnDropdownEvent(DropdownClose); } } private void InitializeDropdown(GUIStyle dropdownStyle) { showDropdown = false; buttonRect = GUILayoutUtility.GetLastRect(); Vector2 rectPos = GUIUtility.GUIToScreenPoint(new Vector2(buttonRect.x, buttonRect.y)); buttonRect.x = rectPos.x; buttonRect.y = rectPos.y; if (elementSize == Vector2.zero) { elementSize = DropdownHelper.CalculateElementSize(DropdownList, dropdownStyle); } DropdownHelper.Set(this, dropdownStyle); OnDropdownEvent(DropdownOpen); } private void OnDropdownEvent(EventHandler handler) { handler?.Invoke(this, EventArgs.Empty); } } public static class DropdownHelper { public static event EventHandler SelectionChange; public static event EventHandler DropdownClose; private static int dropdownID = 100; public static int DropdownID => dropdownID++; private static GUIStyle defaultDropdownStyle; public static GUIStyle DefaultDropdownStyle { get { if (!initialized) InitializeStyle(); return defaultDropdownStyle; } } private static GUIStyle dropdownStyle; private static GUIStyle windowStyle; private static Rect buttonRect; private static string[] dropdownList; private static Vector2 scrollPos; private static int currentDropdownID; private static int selectedItemIndex; private static bool initialized; public static bool Visible { get; set; } public static bool DropdownOpen { get; private set; } private static bool onScrollBar; public static Rect dropdownWindow; private static Rect dropdownScrollRect; private static Rect dropdownRect; public static Vector2 CalculateElementSize(string item, GUIStyle style = null) { if (!initialized) InitializeStyle(); style ??= DefaultDropdownStyle; return style.CalcSize(new GUIContent(item)); } public static Vector2 CalculateElementSize(string[] list, GUIStyle style = null) { if (!initialized) InitializeStyle(); style ??= DefaultDropdownStyle; GUIContent content = new GUIContent(list[0]); Vector2 calculatedSize = style.CalcSize(content); for (int i = 1; i < list.Length; i++) { content.text = list[i]; Vector2 calcSize = style.CalcSize(content); if (calcSize.x > calculatedSize.x) calculatedSize = calcSize; } return calculatedSize; } public static void Set(Dropdown dropdown, GUIStyle style = null) { dropdownStyle = style ?? DefaultDropdownStyle; currentDropdownID = dropdown.DropdownID; dropdownList = dropdown.DropdownList; scrollPos = dropdown.ScrollPos; selectedItemIndex = dropdown.SelectedItemIndex; scrollPos = dropdown.ScrollPos; buttonRect = dropdown.ButtonRect; Vector2 calculatedSize = dropdown.ElementSize; float calculatedListHeight = calculatedSize.y * dropdownList.Length; float heightAbove = buttonRect.y; float heightBelow = Screen.height - heightAbove - buttonRect.height; float rectWidth = Mathf.Max(calculatedSize.x + 5, buttonRect.width); float rectHeight = Mathf.Min(calculatedListHeight, Mathf.Max(heightAbove, heightBelow)); if (calculatedListHeight > heightBelow && heightAbove > heightBelow) { dropdownWindow = new Rect(buttonRect.x, buttonRect.y - rectHeight, rectWidth + 18, rectHeight); } else { if (calculatedListHeight > heightBelow) rectHeight -= calculatedSize.y; dropdownWindow = new Rect(buttonRect.x, buttonRect.y + buttonRect.height, rectWidth + 18, rectHeight); } dropdownWindow.x = Mathf.Clamp(dropdownWindow.x, 0, Screen.width - rectWidth - 18); dropdownScrollRect = new Rect(0, 0, dropdownWindow.width, dropdownWindow.height); dropdownRect = new Rect(0, 0, dropdownWindow.width - 18, calculatedListHeight); DropdownOpen = true; Visible = true; } public static void HandleDropdown() { dropdownWindow = GUI.Window(Constants.dropdownWindowID, dropdownWindow, GUIFunc, "", windowStyle); if (Input.mouseScrollDelta.y != 0f && Visible && dropdownWindow.Contains(Event.current.mousePosition)) { Input.ResetInputAxes(); } } private static void GUIFunc(int id) { bool clicked = false; if (Event.current.type == EventType.MouseUp) clicked = true; scrollPos = GUI.BeginScrollView(dropdownScrollRect, scrollPos, dropdownRect); int selection = GUI.SelectionGrid(dropdownRect, selectedItemIndex, dropdownList, 1, dropdownStyle); GUI.EndScrollView(); bool clickedYou = false; if (Utility.AnyMouseDown()) { Vector2 mousePos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); bool clickedMe = dropdownWindow.Contains(mousePos); onScrollBar = mousePos.x > dropdownWindow.x + dropdownWindow.width - 12f; if (buttonRect.Contains(mousePos)) clickedYou = true; if (!clickedMe) DropdownOpen = false; } if (selection != selectedItemIndex || (clicked && !onScrollBar)) { SelectionChange?.Invoke(null, new DropdownSelectArgs(currentDropdownID, selection)); DropdownOpen = false; } if (!DropdownOpen) { Visible = false; DropdownClose?.Invoke(null, new DropdownCloseArgs(currentDropdownID, scrollPos, clickedYou)); } } private static void InitializeStyle() { defaultDropdownStyle = new GUIStyle(GUI.skin.button) { alignment = TextAnchor.MiddleLeft, margin = new RectOffset(0, 0, 0, 0) }; defaultDropdownStyle.padding.top = defaultDropdownStyle.padding.bottom = 2; defaultDropdownStyle.normal.background = Utility.MakeTex(2, 2, new Color(0f, 0f, 0f, 0.5f)); Texture2D whiteBackground = new Texture2D(2, 2); defaultDropdownStyle.onHover.background = defaultDropdownStyle.hover.background = defaultDropdownStyle.onNormal.background = whiteBackground; defaultDropdownStyle.onHover.textColor = defaultDropdownStyle.onNormal.textColor = defaultDropdownStyle.hover.textColor = Color.black; windowStyle = new GUIStyle(GUI.skin.box) { padding = new RectOffset(0, 0, 0, 0), alignment = TextAnchor.UpperRight }; initialized = true; } public class DropdownEventArgs : EventArgs { public int DropdownID { get; } public DropdownEventArgs(int dropdownID) => DropdownID = dropdownID; } public class DropdownSelectArgs : DropdownEventArgs { public int SelectedItemIndex { get; } public DropdownSelectArgs(int dropdownID, int selection) : base(dropdownID) { SelectedItemIndex = selection; } } public class DropdownCloseArgs : DropdownEventArgs { public Vector2 ScrollPos { get; } public bool ClickedYou { get; } public DropdownCloseArgs(int dropdownID, Vector2 scrollPos, bool clickedYou = false) : base(dropdownID) { ScrollPos = scrollPos; ClickedYou = clickedYou; } } } }