using System; using System.Collections.Generic; using UnityEditor; using UnityEditor.UIElements; using UnityEngine; using UnityEngine.UIElements; using PlasticGui; namespace Unity.PlasticSCM.Editor.UI.UIElements { internal class MultiSelectDropdownItem { internal readonly string Name; internal readonly Texture Icon; internal MultiSelectDropdownItem(string name, Texture icon) { Name = name; Icon = icon; } } internal class MultiSelectDropdown : VisualElement { internal event Action SelectionChanged; internal MultiSelectDropdown(string labelPrefix) { mLabelPrefix = labelPrefix; mLabelElement = new TextElement(); mLabelElement.style.unityTextAlign = TextAnchor.MiddleLeft; mLabelElement.style.flexGrow = 1; VisualElement chevron = new VisualElement(); chevron.AddToClassList("unity-base-popup-field__arrow"); chevron.style.marginLeft = 4; mButton = new ToolbarButton(OnButtonClick); mButton.style.flexDirection = FlexDirection.Row; mButton.style.unityTextAlign = TextAnchor.MiddleLeft; mButton.style.height = 20; mButton.style.paddingBottom = 1; mButton.focusable = false; mButton.Add(mLabelElement); mButton.Add(chevron); Add(mButton); UpdateLabel(); UpdateEnabledState(); } internal void SetItems(IList items) { mItems = items ?? new List(); UpdateLabel(); UpdateEnabledState(); } internal HashSet Selected { get { return mSelected; } set { mSelected = value; UpdateLabel(); } } void OnButtonClick() { if (mItems.Count == 0) return; MultiSelectDropdownPopupContent content = new MultiSelectDropdownPopupContent( mItems, mSelected, OnPopupSelectionChanged); UnityEditor.PopupWindow.Show(mButton.worldBound, content); } void OnPopupSelectionChanged(HashSet newSelected) { mSelected = newSelected; UpdateLabel(); if (SelectionChanged != null) SelectionChanged(); } void UpdateLabel() { mLabelElement.text = BuildLabel(); } string BuildLabel() { if (mSelected == null) return mLabelPrefix; string firstSelected; int totalSelected; CountVisibleSelected(out firstSelected, out totalSelected); if (totalSelected == 0) return string.Format("{0}: {1}", mLabelPrefix, PlasticLocalization.Name.MultiSelectNoneSelected.GetString()); if (totalSelected == 1) return string.Format("{0}: {1}", mLabelPrefix, firstSelected); return string.Format("{0}: {1}", mLabelPrefix, PlasticLocalization.Name.MultiSelectOverflowFormat.GetString( firstSelected, totalSelected - 1)); } void CountVisibleSelected(out string firstSelected, out int totalSelected) { firstSelected = null; totalSelected = 0; foreach (MultiSelectDropdownItem item in mItems) { if (!mSelected.Contains(item.Name)) continue; if (firstSelected == null) firstSelected = item.Name; totalSelected++; } } void UpdateEnabledState() { mButton.SetEnabled(mItems.Count > 0); } readonly string mLabelPrefix; readonly TextElement mLabelElement; readonly ToolbarButton mButton; IList mItems = new List(); HashSet mSelected; } internal class MultiSelectDropdownPopupContent : PopupWindowContent { internal MultiSelectDropdownPopupContent( IList items, HashSet currentSelection, Action> onSelectionChanged) { mAllItems = items; mFilteredItems = new List(items); mSelected = currentSelection == null ? null : new HashSet(currentSelection); mOnSelectionChanged = onSelectionChanged; } public override Vector2 GetWindowSize() { int itemCount = mAllItems == null ? 0 : mAllItems.Count; float contentHeight = SEARCH_AREA_HEIGHT + (itemCount * ROW_HEIGHT) + FOOTER_HEIGHT; float height = Mathf.Clamp(contentHeight, POPUP_HEIGHT_MIN, POPUP_HEIGHT_MAX); return new Vector2(POPUP_WIDTH, height); } public override void OnOpen() { BuildGUI(editorWindow.rootVisualElement); } public override void OnGUI(Rect rect) { if (Event.current.type != EventType.KeyDown) return; if (Event.current.keyCode != KeyCode.Escape) return; editorWindow.Close(); Event.current.Use(); } void BuildGUI(VisualElement root) { root.style.flexDirection = FlexDirection.Column; ToolbarSearchField searchField = new ToolbarSearchField(); searchField.style.width = POPUP_WIDTH - 2 * SEARCH_HORIZONTAL_MARGIN; searchField.style.marginTop = SEARCH_HORIZONTAL_MARGIN; searchField.style.marginLeft = SEARCH_HORIZONTAL_MARGIN; searchField.style.marginRight = SEARCH_HORIZONTAL_MARGIN; searchField.style.marginBottom = SEARCH_HORIZONTAL_MARGIN; searchField.RegisterValueChangedCallback(OnSearchChanged); root.Add(searchField); mListView = new ListView(); mListView.itemsSource = mFilteredItems; mListView.fixedItemHeight = ROW_HEIGHT; mListView.makeItem = MakeRow; mListView.bindItem = BindRow; mListView.selectionType = SelectionType.None; mListView.style.flexGrow = 1; mListView.style.minHeight = 0; root.Add(mListView); VisualElement footer = new VisualElement(); footer.style.flexDirection = FlexDirection.Row; footer.style.justifyContent = Justify.FlexEnd; footer.style.flexShrink = 0; footer.style.paddingLeft = 4; footer.style.paddingRight = 4; footer.style.paddingBottom = 4; footer.style.paddingTop = 4; footer.style.borderTopWidth = 1; footer.style.borderTopColor = UnityStyles.Colors.BarBorder; Button resetButton = new Button(OnResetClick); resetButton.text = PlasticLocalization.Name.MultiSelectResetButton.GetString(); resetButton.style.fontSize = ROW_FONT_SIZE; resetButton.style.minWidth = 60; footer.Add(resetButton); root.Add(footer); } VisualElement MakeRow() { VisualElement row = new VisualElement(); row.style.flexDirection = FlexDirection.Row; row.style.alignItems = Align.Center; row.style.paddingLeft = ROW_HORIZONTAL_PADDING; row.style.paddingRight = ROW_HORIZONTAL_PADDING; row.style.height = ROW_HEIGHT; Image icon = new Image(); icon.name = ROW_ICON_NAME; icon.style.width = ICON_SIZE; icon.style.height = ICON_SIZE; icon.style.marginRight = 4; icon.scaleMode = ScaleMode.ScaleToFit; row.Add(icon); Label label = new Label(); label.name = ROW_LABEL_NAME; label.style.flexGrow = 1; label.style.fontSize = ROW_FONT_SIZE; label.style.unityTextAlign = TextAnchor.MiddleLeft; label.style.overflow = Overflow.Hidden; label.style.textOverflow = TextOverflow.Ellipsis; label.style.marginLeft = 0; label.style.marginRight = 0; label.style.paddingLeft = 0; label.style.paddingRight = 0; row.Add(label); Button hoverLink = new Button(); hoverLink.name = ROW_HOVER_LINK_NAME; hoverLink.style.color = UnityStyles.Colors.Link; hoverLink.style.fontSize = ROW_FONT_SIZE; hoverLink.style.marginLeft = 6; hoverLink.style.marginRight = 6; hoverLink.style.paddingLeft = 0; hoverLink.style.paddingRight = 0; hoverLink.style.borderTopWidth = 0; hoverLink.style.borderBottomWidth = 0; hoverLink.style.borderLeftWidth = 0; hoverLink.style.borderRightWidth = 0; hoverLink.style.backgroundColor = Color.clear; hoverLink.style.display = DisplayStyle.None; hoverLink.SetMouseCursor(MouseCursor.Link); hoverLink.clicked += () => OnHoverLinkClick(hoverLink); row.Add(hoverLink); Toggle checkbox = new Toggle(); checkbox.name = ROW_CHECKBOX_NAME; checkbox.style.marginLeft = 0; checkbox.style.marginRight = 0; checkbox.RegisterValueChangedCallback(OnCheckboxChanged); row.Add(checkbox); row.RegisterCallback(OnRowMouseEnter); row.RegisterCallback(OnRowMouseLeave); return row; } void BindRow(VisualElement element, int index) { if (index < 0 || index >= mFilteredItems.Count) return; MultiSelectDropdownItem item = mFilteredItems[index]; Image icon = element.Q(ROW_ICON_NAME); Label label = element.Q