// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; namespace FlaxEngine.GUI { /// /// Dropdown menu control allows to choose one item from the provided collection of options. /// /// public class Dropdown : Control { /// /// The root control used by the to show the items collections and track item selecting event. /// /// [HideInEditor] protected class DropdownRoot : Panel { private bool isMouseDown; /// /// Occurs when item gets clicked. Argument is item index. /// public Action ItemClicked; /// /// Occurs when popup lost focus. /// public Action LostFocus; /// /// The items container control. /// public ContainerControl ItemsContainer; /// public override bool OnMouseDown(Vector2 location, MouseButton button) { isMouseDown = true; var result = base.OnMouseDown(location, button); isMouseDown = false; if (!result) return false; var itemIndex = ItemsContainer?.GetChildIndexAt(location) ?? -1; if (itemIndex != -1) ItemClicked(itemIndex); return true; } /// public override void OnLostFocus() { base.OnLostFocus(); if (!isMouseDown) LostFocus(); } /// public override void OnDestroy() { ItemClicked = null; LostFocus = null; ItemsContainer = null; base.OnDestroy(); } } /// /// The items. /// protected List _items = new List(); /// /// The popup menu. May be null if has not been used yet. /// protected DropdownRoot _popup; private bool _mouseDown; /// /// The selected index of the item (-1 for no selection). /// protected int _selectedIndex = -1; /// /// Gets or sets the items collection. /// [EditorOrder(1), Tooltip("The items collection.")] public List Items { get => _items; set => _items = value; } /// /// Gets or sets the selected item (returns if no item is being selected). /// [HideInEditor, NoSerialize] public string SelectedItem { get => _selectedIndex != -1 ? _items[_selectedIndex] : string.Empty; set => SelectedIndex = _items.IndexOf(value); } /// /// Gets or sets the index of the selected. /// [EditorOrder(2), Limit(-1), Tooltip("The index of the selected item from the list.")] public int SelectedIndex { get => _selectedIndex; set { // Clamp index value = Mathf.Min(value, _items.Count - 1); // Check if index will change if (value != _selectedIndex) { // Select _selectedIndex = value; OnSelectedIndexChanged(); } } } /// /// Event fired when selected index gets changed. /// public event Action SelectedIndexChanged; /// /// Gets a value indicating whether this popup menu is opened. /// public bool IsPopupOpened => _popup != null; /// /// Gets or sets the font used to draw text. /// [EditorDisplay("Style"), EditorOrder(2000)] public FontReference Font { get; set; } /// /// Gets or sets the custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data. /// [EditorDisplay("Style"), EditorOrder(2000), Tooltip("Custom material used to render the text. It must has domain set to GUI and have a public texture parameter named Font used to sample font atlas texture with font characters data.")] public MaterialBase FontMaterial { get; set; } /// /// Gets or sets the color of the text. /// [EditorDisplay("Style"), EditorOrder(2000)] public Color TextColor { get; set; } /// /// Gets or sets the color of the border. /// [EditorDisplay("Style"), EditorOrder(2000)] public Color BorderColor { get; set; } /// /// Gets or sets the background color when dropdown popup is opened. /// [EditorDisplay("Style"), EditorOrder(2010)] public Color BackgroundColorSelected { get; set; } /// /// Gets or sets the border color when dropdown popup is opened. /// [EditorDisplay("Style"), EditorOrder(2020)] public Color BorderColorSelected { get; set; } /// /// Gets or sets the background color when dropdown is highlighted. /// [EditorDisplay("Style"), EditorOrder(2000)] public Color BackgroundColorHighlighted { get; set; } /// /// Gets or sets the border color when dropdown is highlighted. /// [EditorDisplay("Style"), EditorOrder(2000)] public Color BorderColorHighlighted { get; set; } /// /// Gets or sets the image used to render dropdown drop arrow icon. /// [EditorDisplay("Style"), EditorOrder(2000), Tooltip("The image used to render dropdown drop arrow icon.")] public IBrush ArrowImage { get; set; } /// /// Gets or sets the color used to render dropdown drop arrow icon. /// [EditorDisplay("Style"), EditorOrder(2000), Tooltip("The color used to render dropdown drop arrow icon.")] public Color ArrowColor { get; set; } /// /// Gets or sets the color used to render dropdown drop arrow icon (menu is opened). /// [EditorDisplay("Style"), EditorOrder(2000), Tooltip("The color used to render dropdown drop arrow icon (menu is opened).")] public Color ArrowColorSelected { get; set; } /// /// Gets or sets the color used to render dropdown drop arrow icon (menu is highlighted). /// [EditorDisplay("Style"), EditorOrder(2000), Tooltip("The color used to render dropdown drop arrow icon (menu is highlighted).")] public Color ArrowColorHighlighted { get; set; } /// /// Gets or sets the image used to render dropdown checked item icon. /// [EditorDisplay("Style"), EditorOrder(2000), Tooltip("The image used to render dropdown checked item icon.")] public IBrush CheckedImage { get; set; } /// /// Initializes a new instance of the class. /// public Dropdown() : base(0, 0, 120, 18.0f) { var style = Style.Current; Font = new FontReference(style.FontMedium); TextColor = style.Foreground; BackgroundColor = style.BackgroundNormal; BackgroundColorHighlighted = BackgroundColor; BackgroundColorSelected = BackgroundColor; BorderColor = style.BorderNormal; BorderColorHighlighted = style.BorderSelected; BorderColorSelected = BorderColorHighlighted; ArrowImage = new SpriteBrush(style.ArrowDown); ArrowColor = style.Foreground * 0.6f; ArrowColorSelected = style.BackgroundSelected; ArrowColorHighlighted = style.Foreground; CheckedImage = new SpriteBrush(style.CheckBoxTick); } /// /// Clears the items. /// public void ClearItems() { SelectedIndex = -1; _items.Clear(); } /// /// Adds the item. /// /// The item. public void AddItem(string item) { _items.Add(item); } /// /// Adds the items. /// /// The items. public void AddItems(IEnumerable items) { _items.AddRange(items); } /// /// Sets the items. /// /// The items. public void SetItems(IEnumerable items) { SelectedIndex = -1; _items.Clear(); _items.AddRange(items); } /// /// Called when selected item index gets changed. /// protected virtual void OnSelectedIndexChanged() { SelectedIndexChanged?.Invoke(this); } /// /// Called when item is clicked. /// /// The index. protected virtual void OnItemClicked(int index) { SelectedIndex = index; } /// /// Creates the popup menu (including items collection). /// protected virtual DropdownRoot CreatePopup() { // TODO: support using templates for the items collection container panel var popup = new DropdownRoot(); // TODO: support item templates var container = new VerticalPanel { AnchorPreset = AnchorPresets.StretchAll, BackgroundColor = BackgroundColor, AutoSize = false, Parent = popup, }; var border = new Border { BorderColor = BorderColorHighlighted, Width = 4.0f, AnchorPreset = AnchorPresets.StretchAll, Parent = popup, }; var itemsHeight = 20.0f; var itemsMargin = 20.0f; /* var itemsWidth = 40.0f; var font = Font.GetFont(); for (int i = 0; i < _items.Count; i++) { itemsWidth = Mathf.Max(itemsWidth, itemsMargin + 4 + font.MeasureText(_items[i]).X); } */ var itemsWidth = Width; for (int i = 0; i < _items.Count; i++) { var item = new Spacer { Height = itemsHeight, Width = itemsWidth, Parent = container, }; var label = new Label { X = itemsMargin, Width = itemsWidth - itemsMargin, Font = Font, TextColor = Color.White * 0.9f, TextColorHighlighted = Color.White, HorizontalAlignment = TextAlignment.Near, AnchorPreset = AnchorPresets.VerticalStretchRight, Text = _items[i], Parent = item, }; if (_selectedIndex == i) { var icon = new Image { Brush = CheckedImage, Width = itemsMargin, Margin = new Margin(4.0f, 6.0f, 4.0f, 4.0f), AnchorPreset = AnchorPresets.VerticalStretchLeft, Parent = item, }; } } popup.Size = new Vector2(itemsWidth, (itemsHeight + container.Spacing) * _items.Count + container.Margin.Height); popup.ItemsContainer = container; return popup; } /// /// Destroys the popup. /// protected virtual void DestroyPopup() { if (_popup != null) { _popup.Dispose(); _popup = null; } } /// public override void OnDestroy() { DestroyPopup(); base.OnDestroy(); } /// public override void Draw() { // Cache data var clientRect = new Rectangle(Vector2.Zero, Size); float margin = clientRect.Height * 0.2f; float boxSize = clientRect.Height - margin * 2; bool isOpened = IsPopupOpened; bool enabled = EnabledInHierarchy; Color backgroundColor = BackgroundColor; Color borderColor = BorderColor; Color arrowColor = ArrowColor; if (!enabled) { backgroundColor *= 0.5f; arrowColor *= 0.7f; } else if (isOpened || _mouseDown) { backgroundColor = BackgroundColorSelected; borderColor = BorderColorSelected; arrowColor = ArrowColorSelected; } else if (IsMouseOver) { backgroundColor = BackgroundColorHighlighted; borderColor = BorderColorHighlighted; arrowColor = ArrowColorHighlighted; } // Background Render2D.FillRectangle(clientRect, backgroundColor); Render2D.DrawRectangle(clientRect, borderColor); // Check if has selected item if (_selectedIndex > -1 && _selectedIndex < _items.Count) { // Draw text of the selected item var textRect = new Rectangle(margin, 0, clientRect.Width - boxSize - 2.0f * margin, clientRect.Height); Render2D.PushClip(textRect); var textColor = TextColor; Render2D.DrawText(Font.GetFont(), FontMaterial, _items[_selectedIndex], textRect, enabled ? textColor : textColor * 0.5f, TextAlignment.Near, TextAlignment.Center); Render2D.PopClip(); } // Arrow ArrowImage?.Draw(new Rectangle(clientRect.Width - margin - boxSize, margin, boxSize, boxSize), arrowColor); } /// public override void OnLostFocus() { base.OnLostFocus(); // Clear flags _mouseDown = false; } /// public override void OnMouseLeave() { // Clear flags _mouseDown = false; base.OnMouseLeave(); } /// public override bool OnMouseDown(Vector2 location, MouseButton button) { // Check mouse buttons if (button == MouseButton.Left) { // Set flag _mouseDown = true; } return base.OnMouseDown(location, button); } /// public override bool OnMouseUp(Vector2 location, MouseButton button) { // Check flags if (_mouseDown) { // Clear flag _mouseDown = false; var root = Root; if (_items.Count > 0 && root != null) { // Setup popup DestroyPopup(); _popup = CreatePopup(); // Update layout _popup.UnlockChildrenRecursive(); _popup.PerformLayout(); // Bind events _popup.ItemClicked += (index) => { OnItemClicked(index); DestroyPopup(); }; _popup.LostFocus += DestroyPopup; // Show dropdown popup Vector2 locationRootSpace = Location + new Vector2(0, Height); var parent = Parent; while (parent != null && parent != Root) { locationRootSpace = parent.PointToParent(ref location); parent = parent.Parent; } _popup.Location = locationRootSpace; _popup.Parent = root; _popup.Focus(); } } return true; } } }