Editor/OptionalControlWidget.cs
using Editor;
using ExtendedBox.Primitives;
using Sandbox;
using System;
using System.Linq;
namespace ExtendedBox;
[CustomEditor(typeof(Optional<>))]
public class OptionalControlWidget : ControlWidget
{
public override bool SupportsMultiEdit => true;
private Type ValueType
{
get => SerializedProperty.PropertyType.GetGenericArguments()[0];
}
private object? NewValue
{
get
{
var type = ValueType;
if(type == typeof(string))
return string.Empty;
if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
return Activator.CreateInstance(type.GetGenericArguments()[0], true);
return Activator.CreateInstance(type, true);
}
}
private object? Value
{
get
{
return GetValue(SerializedProperty);
}
set
{
if(Equals(Value, value))
return;
var optionalType = SerializedProperty.GetValue<object>().GetType();
object newValue = Activator.CreateInstance(optionalType, value)!;
SerializedProperty.SetValue(newValue);
}
}
private bool HasValue
{
get => SerializedProperty.MultipleProperties.All(GetHasValue);
set
{
foreach(var serializedProperty in SerializedProperty.MultipleProperties)
{
if(GetHasValue(serializedProperty) == value)
continue;
var optional = serializedProperty.GetValue<object>();
object newValue;
if(!value)
newValue = optional.GetType().GetField(nameof(Optional<int>.Empty))!.GetValue(null)!;
else
newValue = Activator.CreateInstance(optional.GetType(), NewValue)!;
serializedProperty.SetValue(newValue);
}
}
}
private bool _hadValue = false;
public OptionalControlWidget(SerializedProperty property) : base(property)
{
Layout = Layout.Row();
RebuildLayout();
}
protected override void OnValueChanged()
{
if(HasValue != _hadValue)
RebuildLayout();
}
private void RebuildLayout()
{
Layout.Clear(true);
Layout.Add(new PropertyButton(SerializedProperty, () => HasValue, v => HasValue = v));
var hasValue = HasValue;
if(hasValue)
{
SerializedProperty.TryGetAsObject(out var optionalObject);
var valueProperty = TypeLibrary.CreateProperty(ValueType, nameof(Optional<int>.Value), () => Value!, v => Value = v, SerializedProperty.GetAttributes().ToArray(), optionalObject);
if(SerializedProperty.IsMultipleDifferentValues)
valueProperty = new MultipleValuesSerializedProperty(valueProperty);
var control = ControlWidget.Create(valueProperty);
Layout.Add(control);
}
else
{
var label = new Label($"{ValueType.Name} (Empty)");
if(SerializedProperty.IsMultipleDifferentValues)
label.Color = Theme.MultipleValues;
Layout.Add(label);
}
Layout.AddStretchCell();
_hadValue = hasValue;
}
private bool GetHasValue(SerializedProperty serializedProperty)
{
var optional = serializedProperty.GetValue<object>();
return (bool)optional.GetType().GetProperty(nameof(Optional<int>.HasValue))!.GetValue(optional)!;
}
private object? GetValue(SerializedProperty serializedProperty)
{
var optional = SerializedProperty.GetValue<object>();
if(!HasValue)
return null;
return optional.GetType().GetProperty(nameof(Optional<int>.Value))!.GetValue(optional);
}
}
file class MultipleValuesSerializedProperty : SerializedProperty.Proxy
{
protected override SerializedProperty ProxyTarget { get; }
public override bool IsMultipleDifferentValues => true;
public MultipleValuesSerializedProperty(SerializedProperty serializedProperty)
{
ProxyTarget = serializedProperty;
}
}
file class PropertyButton : Widget
{
private SerializedProperty _property;
private Func<bool> _hasValueGetter;
private Action<bool> _hasValueSetter;
public PropertyButton(SerializedProperty property, Func<bool> hasValueGetter, Action<bool> hasValueSetter)
{
_property = property;
_hasValueGetter = hasValueGetter;
_hasValueSetter = hasValueSetter;
FixedHeight = Theme.RowHeight;
FixedWidth = Theme.RowHeight;
HorizontalSizeMode = SizeMode.Flexible;
Cursor = CursorShape.Finger;
ToolTip = "Has Value";
}
protected override void OnPaint()
{
var icon = "eject";
var size = 15;
Color color = Theme.TextControl.WithAlpha(0.3f);
Paint.TextAntialiasing = true;
// Nullable - and null
var isMultiple = _property.IsMultipleDifferentValues;
if(_hasValueGetter() && !isMultiple)
{
icon = "radio_button_unchecked";
size = 10;
color = Theme.TextControl.WithAlpha(0.3f);
}
else
{
icon = "circle";
size = 10;
color = isMultiple ? Theme.MultipleValues : Theme.Blue;
color = color.WithAlpha(0.3f);
}
Paint.Pen = color;
Paint.DrawIcon(LocalRect, icon, size);
}
protected override void OnMouseClick(MouseEvent e)
{
_hasValueSetter(!_hasValueGetter());
Update();
}
}