2020-03-16 19:13:33 +01:00
using System.Linq ;
2019-09-12 12:39:18 +02:00
using Microsoft.Xna.Framework ;
2020-03-16 19:13:33 +01:00
using MLEM.Misc ;
2019-09-12 12:39:18 +02:00
namespace MLEM.Ui.Elements {
2020-05-22 17:02:24 +02:00
/// <summary>
/// A dropdown component to use inside of a <see cref="UiSystem"/>.
/// A dropdown is a component that contains a hidden panel which is displayed upon pressing the dropdown button.
/// </summary>
2019-09-12 12:39:18 +02:00
public class Dropdown : Button {
2020-05-22 17:02:24 +02:00
/// <summary>
/// The panel that this dropdown contains. It will be displayed upon pressing the dropdown button.
/// </summary>
2023-12-28 17:16:31 +01:00
public Panel Panel { get ; private set ; }
2020-05-22 17:02:24 +02:00
/// <summary>
/// This property stores whether the dropdown is currently opened or not
/// </summary>
2019-09-12 12:39:18 +02:00
public bool IsOpen {
get = > ! this . Panel . IsHidden ;
2019-11-18 22:36:55 +01:00
set {
this . Panel . IsHidden = ! value ;
this . OnOpenedOrClosed ? . Invoke ( this ) ;
}
2019-09-12 12:39:18 +02:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// An event that is invoked when <see cref="IsOpen"/> changes
/// </summary>
2019-11-18 22:36:55 +01:00
public GenericCallback OnOpenedOrClosed ;
2019-09-12 12:39:18 +02:00
2023-12-28 17:16:31 +01:00
/// <summary>
/// Creates a new dropdown with the given settings and no text or tooltip.
/// </summary>
/// <param name="anchor">The dropdown's anchor</param>
/// <param name="size">The dropdown button's size</param>
/// <param name="panelHeight">The height of the <see cref="Panel"/>. If this is 0, the panel will be set to <see cref="Element.SetHeightBasedOnChildren"/>.</param>
/// <param name="scrollPanel">Whether this dropdown's <see cref="Panel"/> should automatically add a scroll bar to scroll towards elements that are beyond the area it covers.</param>
/// <param name="autoHidePanelScrollbar">Whether this dropdown's <see cref="Panel"/>'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 <paramref name="scrollPanel"/> is <see langword="true"/>.</param>
public Dropdown ( Anchor anchor , Vector2 size , float panelHeight = 0 , bool scrollPanel = false , bool autoHidePanelScrollbar = true ) : base ( anchor , size ) {
this . Initialize ( panelHeight , scrollPanel , autoHidePanelScrollbar ) ;
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Creates a new dropdown with the given settings
/// </summary>
/// <param name="anchor">The dropdown's anchor</param>
/// <param name="size">The dropdown button's size</param>
/// <param name="text">The text displayed on the dropdown button</param>
/// <param name="tooltipText">The text displayed as a tooltip when hovering over the dropdown button</param>
2023-08-14 17:50:07 +02:00
/// <param name="panelHeight">The height of the <see cref="Panel"/>. If this is 0, the panel will be set to <see cref="Element.SetHeightBasedOnChildren"/>.</param>
/// <param name="scrollPanel">Whether this dropdown's <see cref="Panel"/> should automatically add a scroll bar to scroll towards elements that are beyond the area it covers.</param>
/// <param name="autoHidePanelScrollbar">Whether this dropdown's <see cref="Panel"/>'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 <paramref name="scrollPanel"/> is <see langword="true"/>.</param>
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 ) {
2023-12-28 17:16:31 +01:00
this . Initialize ( panelHeight , scrollPanel , autoHidePanelScrollbar ) ;
}
/// <summary>
/// Creates a new dropdown with the given settings
/// </summary>
/// <param name="anchor">The dropdown's anchor</param>
/// <param name="size">The dropdown button's size</param>
/// <param name="textCallback">The text displayed on the dropdown button</param>
/// <param name="tooltipTextCallback">The text displayed as a tooltip when hovering over the dropdown button</param>
/// <param name="panelHeight">The height of the <see cref="Panel"/>. If this is 0, the panel will be set to <see cref="Element.SetHeightBasedOnChildren"/>.</param>
/// <param name="scrollPanel">Whether this dropdown's <see cref="Panel"/> should automatically add a scroll bar to scroll towards elements that are beyond the area it covers.</param>
/// <param name="autoHidePanelScrollbar">Whether this dropdown's <see cref="Panel"/>'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 <paramref name="scrollPanel"/> is <see langword="true"/>.</param>
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 ) ;
2019-09-12 12:39:18 +02:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Adds an element to this dropdown's <see cref="Panel"/>
/// </summary>
/// <param name="element">The element to add</param>
2022-05-03 19:07:53 +02:00
/// <param name="index">The index to add the child at, or -1 to add it to the end of the <see cref="Element.Children"/> list</param>
public void AddElement ( Element element , int index = - 1 ) {
this . Panel . AddChild ( element , index ) ;
2020-03-16 19:13:33 +01:00
// Since the dropdown causes elements to be over each other,
// usual gamepad code doesn't apply
element . GetGamepadNextElement = ( dir , usualNext ) = > {
2022-03-10 15:04:52 +01:00
if ( dir = = Direction2 . Left | | dir = = Direction2 . Right )
return null ;
2020-03-16 19:13:33 +01:00
if ( dir = = Direction2 . Up ) {
2022-03-17 20:46:49 +01:00
var prev = element . GetOlderSibling ( e = > e . CanBeSelected ) ;
2020-03-16 19:22:08 +01:00
return prev ? ? this ;
2020-03-16 19:13:33 +01:00
} else if ( dir = = Direction2 . Down ) {
2022-03-17 20:46:49 +01:00
return element . GetSiblings ( e = > e . CanBeSelected & & e . GetOlderSibling ( s = > s . CanBeSelected ) = = element ) . FirstOrDefault ( ) ;
2020-03-16 19:13:33 +01:00
}
return usualNext ;
} ;
2019-09-12 12:39:18 +02:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Adds a pressable <see cref="Paragraph"/> element to this dropdown's <see cref="Panel"/>
/// </summary>
/// <param name="text">The text to display</param>
/// <param name="pressed">The resulting paragraph's <see cref="Element.OnPressed"/> event</param>
2022-05-03 19:07:53 +02:00
/// <param name="index">The index to add the child at, or -1 to add it to the end of the <see cref="Element.Children"/> list</param>
public Element AddElement ( string text , GenericCallback pressed = null , int index = - 1 ) {
return this . AddElement ( p = > text , pressed , index ) ;
2019-09-12 12:39:18 +02:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Adds a pressable <see cref="Paragraph"/> element to this dropdown's <see cref="Panel"/>.
/// By default, the paragraph's text color will change from <see cref="Color.White"/> to <see cref="Color.LightGray"/> when hovering over it.
/// </summary>
/// <param name="text">The text to display</param>
/// <param name="pressed">The resulting paragraph's <see cref="Element.OnPressed"/> event</param>
2022-05-03 19:07:53 +02:00
/// <param name="index">The index to add the child at, or -1 to add it to the end of the <see cref="Element.Children"/> list</param>
public Element AddElement ( Paragraph . TextCallback text , GenericCallback pressed = null , int index = - 1 ) {
2019-09-12 12:39:18 +02:00
var paragraph = new Paragraph ( Anchor . AutoLeft , 1 , text ) {
CanBeMoused = true ,
CanBeSelected = true ,
PositionOffset = new Vector2 ( 0 , 1 )
} ;
if ( pressed ! = null )
paragraph . OnPressed + = pressed ;
2019-11-05 13:28:41 +01:00
paragraph . OnMouseEnter + = e = > paragraph . TextColor = Color . LightGray ;
paragraph . OnMouseExit + = e = > paragraph . TextColor = Color . White ;
2022-05-03 19:07:53 +02:00
this . AddElement ( paragraph , index ) ;
2020-11-08 18:16:12 +01:00
return paragraph ;
2019-09-12 12:39:18 +02:00
}
2023-12-28 17:16:31 +01:00
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 . 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 )
2024-06-11 18:47:22 +02:00
return this . Panel . Children . FirstOrDefault ( c = > c . CanBeSelected ) ? ? usualNext ;
2023-12-28 17:16:31 +01:00
return usualNext ;
} ;
}
2019-09-12 12:39:18 +02:00
}
2022-06-17 18:23:47 +02:00
}