using System.Linq;
using Microsoft.Xna.Framework;
using MLEM.Maths;
using MLEM.Textures;
using MLEM.Ui.Style;
namespace MLEM.Ui.Elements {
///
/// A dropdown component to use inside of a .
/// A dropdown is a component that contains a hidden panel which is displayed upon pressing the dropdown button.
/// Elements can be added to the dropdown's through or one of its overloads, which additionally causes them to have correct gamepad navigation behavior.
///
public class Dropdown : Button {
///
/// The panel that this dropdown contains. It will be displayed upon pressing the dropdown button.
/// To add elements to this panel, you can use or one of its overloads, which additionally causes them to have correct gamepad navigation behavior.
///
public Panel Panel { get; private set; }
///
/// The that contains the currently active arrow texture for this dropdown, which is either or .
///
public Image Arrow { get; private set; }
///
/// This property stores whether the dropdown is currently opened or not
///
public bool IsOpen {
get => !this.Panel.IsHidden;
set {
this.Panel.IsHidden = !value;
this.UpdateArrowStyle();
this.OnOpenedOrClosed?.Invoke(this);
}
}
///
/// An event that is invoked when changes
///
public GenericCallback OnOpenedOrClosed;
///
/// A style property containing the that should be passed to the child element.
///
public StyleProp ArrowPadding {
get => this.arrowPadding;
set {
this.arrowPadding = value;
this.UpdateArrowStyle();
}
}
///
/// A style property containing the that should be displayed on this dropdown's when the dropdown is closed ( is ).
///
public StyleProp ClosedArrowTexture {
get => this.closedArrowTexture;
set {
this.closedArrowTexture = value;
this.UpdateArrowStyle();
}
}
///
/// A style property containing the that should be displayed on this dropdown's when the dropdown is open ( is ).
///
public StyleProp OpenedArrowTexture {
get => this.openedArrowTexture;
set {
this.openedArrowTexture = value;
this.UpdateArrowStyle();
}
}
///
/// A style property containing the that a paragraph added to this dropdown through should have by default.
/// For elements added through other overloads of , this property has no effect.
///
public StyleProp TextColor;
///
/// A style property containing the that a paragraph added to this dropdown through should have when hovered.
/// For elements added through other overloads of , this property has no effect.
///
public StyleProp HoveredTextColor;
private StyleProp openedArrowTexture;
private StyleProp closedArrowTexture;
private StyleProp arrowPadding;
///
/// Creates a new dropdown with the given settings and no text or tooltip.
///
/// The dropdown's anchor
/// The dropdown button's size
/// The height of the . If this is 0, the panel will be set to .
/// Whether this dropdown's should automatically add a scroll bar to scroll towards elements that are beyond the area it covers.
/// Whether this dropdown's 's scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if is .
public Dropdown(Anchor anchor, Vector2 size, float panelHeight = 0, bool scrollPanel = false, bool autoHidePanelScrollbar = true) : base(anchor, size) {
this.Initialize(panelHeight, scrollPanel, autoHidePanelScrollbar);
}
///
/// Creates a new dropdown with the given settings
///
/// The dropdown's anchor
/// The dropdown button's size
/// The text displayed on the dropdown button
/// The text displayed as a tooltip when hovering over the dropdown button
/// The height of the . If this is 0, the panel will be set to .
/// Whether this dropdown's should automatically add a scroll bar to scroll towards elements that are beyond the area it covers.
/// Whether this dropdown's 's scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if is .
public Dropdown(Anchor anchor, Vector2 size, string text = null, string tooltipText = null, float panelHeight = 0, bool scrollPanel = false, bool autoHidePanelScrollbar = true) : base(anchor, size, text, tooltipText) {
this.Initialize(panelHeight, scrollPanel, autoHidePanelScrollbar);
}
///
/// Creates a new dropdown with the given settings
///
/// The dropdown's anchor
/// The dropdown button's size
/// The text displayed on the dropdown button
/// The text displayed as a tooltip when hovering over the dropdown button
/// The height of the . If this is 0, the panel will be set to .
/// Whether this dropdown's should automatically add a scroll bar to scroll towards elements that are beyond the area it covers.
/// Whether this dropdown's 's scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if is .
public Dropdown(Anchor anchor, Vector2 size, Paragraph.TextCallback textCallback = null, Paragraph.TextCallback tooltipTextCallback = null, float panelHeight = 0, bool scrollPanel = false, bool autoHidePanelScrollbar = true) : base(anchor, size, textCallback, tooltipTextCallback) {
this.Initialize(panelHeight, scrollPanel, autoHidePanelScrollbar);
}
///
/// Adds an element to this dropdown's
///
/// The element to add
/// The index to add the child at, or -1 to add it to the end of the list
public Element AddElement(Element element, int index = -1) {
this.Panel.AddChild(element, index);
// Since the dropdown causes elements to be over each other,
// usual gamepad code doesn't apply
element.GetGamepadNextElement = (dir, usualNext) => {
if (dir == Direction2.Left || dir == Direction2.Right)
return null;
if (dir == Direction2.Up) {
var prev = element.GetOlderSibling(e => e.CanBeSelected);
return prev ?? this;
} else if (dir == Direction2.Down) {
return element.GetSiblings(e => e.CanBeSelected && e.GetOlderSibling(s => s.CanBeSelected) == element).FirstOrDefault();
}
return usualNext;
};
return element;
}
///
/// Adds a pressable element to this dropdown's
///
/// The text to display
/// The resulting paragraph's event
/// The index to add the child at, or -1 to add it to the end of the list
public Paragraph AddElement(string text, GenericCallback pressed = null, int index = -1) {
return this.AddElement(p => text, pressed, index);
}
///
/// Adds a pressable element to this dropdown's .
/// By default, the paragraph's text color will change from to when hovering over it.
///
/// The text to display
/// The resulting paragraph's event
/// The index to add the child at, or -1 to add it to the end of the list
public Paragraph AddElement(Paragraph.TextCallback text, GenericCallback pressed = null, int index = -1) {
var paragraph = new Paragraph(Anchor.AutoLeft, 1, text) {
CanBeMoused = true,
CanBeSelected = true,
PositionOffset = new Vector2(0, 1)
};
if (pressed != null)
paragraph.OnPressed += pressed;
paragraph.OnMouseEnter += e => paragraph.TextColor = this.HoveredTextColor;
paragraph.OnMouseExit += e => paragraph.TextColor = this.TextColor;
this.AddElement(paragraph, index);
return paragraph;
}
private void Initialize(float panelHeight, bool scrollPanel, bool autoHidePanelScrollbar) {
this.Panel = this.AddChild(new Panel(Anchor.TopCenter, Vector2.Zero, panelHeight == 0, scrollPanel, autoHidePanelScrollbar) {
IsHidden = true
});
this.Arrow = this.AddChild(new Image(Anchor.CenterRight, new Vector2(-1, 1), this.ClosedArrowTexture));
this.UpdateArrowStyle();
this.OnAreaUpdated += e => {
this.Panel.Size = new Vector2(e.Area.Width / e.Scale, panelHeight);
this.Panel.PositionOffset = new Vector2(0, e.Area.Height / e.Scale);
};
this.OnOpenedOrClosed += e => this.Priority = this.IsOpen ? 10000 : 0;
this.OnPressed += e => {
this.IsOpen = !this.IsOpen;
// close other dropdowns in the same root when we open
if (this.IsOpen) {
this.Root.Element.AndChildren(o => {
if (o != this && o is Dropdown d && d.IsOpen)
d.IsOpen = false;
});
}
};
this.GetGamepadNextElement = (dir, usualNext) => {
// Force navigate down to our first child if we're open
if (this.IsOpen && dir == Direction2.Down)
return this.Panel.Children.FirstOrDefault(c => c.CanBeSelected) ?? usualNext;
return usualNext;
};
}
///
protected override void InitStyle(UiStyle style) {
base.InitStyle(style);
this.ArrowPadding = this.ArrowPadding.OrStyle(style.DropdownArrowPadding);
this.ClosedArrowTexture = this.ClosedArrowTexture.OrStyle(style.DropdownClosedArrowTexture);
this.OpenedArrowTexture = this.OpenedArrowTexture.OrStyle(style.DropdownOpenedArrowTexture);
this.TextColor = this.TextColor.OrStyle(style.DropdownTextColor);
this.HoveredTextColor = this.HoveredTextColor.OrStyle(style.DropdownHoveredTextColor);
this.UpdateArrowStyle();
}
private void UpdateArrowStyle() {
this.Arrow.Padding = this.Arrow.Padding.OrStyle(this.ArrowPadding, 1);
this.Arrow.Texture = this.IsOpen ? this.OpenedArrowTexture : this.ClosedArrowTexture;
}
}
}