XGUI/Elements/Selector.cs
using Sandbox;
using Sandbox.UI;
using Sandbox.UI.Construct;
using System;
using System.Collections.Generic;
using System.Linq;
namespace XGUI;
/// <summary>
/// A UI control which provides multiple options via a dropdown box.
/// </summary>
[Library( "selector" )]
public class Selector : Button
{
Pane Pane;
/// <summary>
/// The icon of an arrow pointing down on the right of the element.
/// </summary>
protected IconPanel DropdownIndicator;
/// <summary>
/// Called when the value has been changed,
/// </summary>
public System.Action<string> ValueChanged { get; set; }
/// <summary>
/// Called just before opening, allows options to be dynamic
/// </summary>
public System.Func<List<Option>> BuildOptions { get; set; }
/// <summary>
/// The options to show on click. You can edit these directly via this property.
/// </summary>
public List<Option> Options { get; set; } = new();
Option selected;
object _value;
int _valueHash;
/// <summary>
/// The current string value. This is useful to have if Selected is null.
/// </summary>
public object Value
{
get => _value;
set
{
if ( _valueHash == HashCode.Combine( value ) )
return;
if ( $"{_value}" == $"{value}" )
return;
_valueHash = HashCode.Combine( value );
_value = value;
if ( BuildOptions != null )
{
Options = BuildOptions.Invoke();
}
if ( _value != null && Options.Count == 0 )
{
PopulateOptionsFromType( _value.GetType() );
}
Select( _value?.ToString(), false );
}
}
/// <summary>
/// The currently selected option.
/// </summary>
public Option Selected
{
get => selected;
set
{
if ( selected == value ) return;
selected = value;
if ( selected != null )
{
Value = $"{selected.Value}";
Icon = selected.Icon;
Text = selected.Title;
ValueChanged?.Invoke( Value.ToString() );
CreateEvent( "onchange" );
CreateValueEvent( "value", selected?.Value );
}
}
}
public Selector()
{
AddClass( "selector" );
DropdownIndicator = Add.Icon( "u", "selector_indicator" );
}
public Selector( Panel parent ) : this()
{
Parent = parent;
}
public override void SetPropertyObject( string name, object value )
{
base.SetPropertyObject( name, value );
}
/// <summary>
/// Given the type, populate options. This is useful if you're an enum type.
/// </summary>
private void PopulateOptionsFromType( Type type )
{
if ( type == typeof( bool ) )
{
Options.Add( new Option( "True", true ) );
Options.Add( new Option( "False", false ) );
return;
}
if ( type.IsEnum )
{
var names = type.GetEnumNames();
var values = type.GetEnumValues();
for ( int i = 0; i < names.Length; i++ )
{
Options.Add( new Option( names[i], values.GetValue( i ) ) );
}
return;
}
//Log.Info( $"Dropdown Type: {type}" );
}
public bool IsOpen = false;
protected override void OnMouseDown( MousePanelEvent e )
{
base.OnMouseDown( e );
if ( !IsOpen )
{
Open();
}
else
{
Close();
}
}
/// <summary>
/// Open the dropdown.
/// </summary>
public void Open()
{
IsOpen = true;
Pane = new Pane( this, Pane.PositionMode.BelowStretch, 0.0f );
Pane.Focus();
Pane.AddClass( "flat-top" );
if ( BuildOptions != null )
{
Options = BuildOptions.Invoke();
}
foreach ( var option in Options )
{
var o = Pane.AddOption( option.Title, option.Icon, () => Select( option ) );
if ( Selected != null && option.Value == Selected.Value )
{
o.AddClass( "active" );
}
}
}
public void Close()
{
if ( Pane != null )
{
Pane.Delete();
Pane = null;
}
Focus();
IsOpen = false;
}
/// <summary>
/// Select an option.
/// </summary>
protected virtual void Select( Option option, bool triggerChange = true )
{
if ( !triggerChange )
{
selected = option;
if ( option != null )
{
Value = option.Value;
Icon = option.Icon;
Text = option.Title;
}
}
else
{
Selected = option;
}
Close();
}
/// <summary>
/// Select an option by value string.
/// </summary>
protected virtual void Select( string value, bool triggerChange = true )
{
Select( Options.FirstOrDefault( x => string.Equals( x.Value.ToString(), value, StringComparison.OrdinalIgnoreCase ) ), triggerChange );
}
string Override = null;
public override void Tick()
{
base.Tick();
SetClass( "open", Pane != null && !Pane.IsDeleting );
SetClass( "active", Pane != null && !Pane.IsDeleting );
if ( Pane != null )
{
Pane.Style.Width = Box.Rect.Width * ScaleFromScreen;
}
}
public override void SetProperty( string name, string value )
{
if ( name == "default" )
{
Override = value;
return;
}
base.SetProperty( name, value );
}
protected override void OnParametersSet()
{
// Only clear if we have some options to populate
if ( Children.Any( x => x.ElementName.Equals( "option", StringComparison.OrdinalIgnoreCase ) ) ) Options.Clear();
foreach ( var child in Children.ToList() )
{
if ( child.ElementName.Equals( "option", StringComparison.OrdinalIgnoreCase ) )
{
var o = new Option();
o.Title = string.Join( "", child.Descendants.OfType<Label>().Select( x => x.Text ) );
o.Value = child.GetAttribute( "value", o.Title );
o.Icon = child.GetAttribute( "icon", null );
Options.Add( o );
if ( Override == child.GetAttribute( "value", o.Title ) )
{
Select( o, true );
}
}
}
}
}