using System; using Robust.Client.UserInterface.Controls; namespace Robust.Client.UserInterface { /// Control on which the property was changed. /// public delegate void AttachedPropertyChangedCallback(Control owner, AttachedPropertyChangedEventArgs eventArgs); public delegate void AttachedPropertyChangedCallback(Control owner, AttachedPropertyChangedEventArgs eventArgs); /// /// An attached property is a property that can be assigned to any control, /// without having to modify the base class to add it. /// This is useful for storing data for specific controls like /// [Virtual] public class AttachedProperty { /// /// The name of the property. /// public string Name { get; } /// /// The type that defines the attached property. /// public Type OwningType { get; } /// /// The type of the value stored in the property. /// public Type PropertyType { get; } /// /// The default value of the property. /// This is returned if no value is set and is called. /// public object? DefaultValue { get; } /// /// An optional validation function. /// If the value to fails this check, an exception will be thrown. /// public Func? Validate { get; } /// /// A callback to run whenever this property changes on a control. /// public AttachedPropertyChangedCallback? Changed { get; } internal AttachedProperty(string name, Type owningType, Type propertyType, object? defaultValue = null, Func? validate = null, AttachedPropertyChangedCallback? changed = null) { Name = name; OwningType = owningType; PropertyType = propertyType; DefaultValue = defaultValue; Validate = validate; Changed = changed; } /// /// Parameters correspond to properties on this class. /// public static AttachedProperty Create( string name, Type owningType, Type propertyType, object? defaultValue = null, Func? validate = null, AttachedPropertyChangedCallback? changed = null) { if (propertyType.IsValueType && defaultValue == null) { // Use activator to create uninitialized version of value type. defaultValue = Activator.CreateInstance(propertyType)!; } return new AttachedProperty(name, owningType, propertyType, defaultValue, validate, changed); } } public sealed class AttachedProperty : AttachedProperty { public new Func? Validate { get; } public new AttachedPropertyChangedCallback? Changed { get; } public new T DefaultValue { get; } internal AttachedProperty(string name, Type owningType, T defaultValue, Func? validate = null, AttachedPropertyChangedCallback? changed = null) : base(name, owningType, typeof(T), defaultValue, validate != null ? o => validate!((T) o!) : null, changed != null ? (o, ev) => changed!(o, new AttachedPropertyChangedEventArgs((T) ev.NewValue!, (T) ev.OldValue!)) : null) { Validate = validate; Changed = changed; DefaultValue = defaultValue; } public static AttachedProperty Create(string name, Type owningType, T defaultValue = default!, Func? validate = null, AttachedPropertyChangedCallback? changed = null) { if (!typeof(T).IsValueType && defaultValue == null) { throw new ArgumentNullException(nameof(defaultValue), "Got defaultValue that is null for reference type." + "If this is a non-nullable reference type," + "make sure to fill in a default value with the parameter." + "If this is intended to be nullable," + "use the CreateNull() overload (and make sure to set the type nullability correctly!)."); } return new AttachedProperty(name, owningType, defaultValue, validate, changed); } // TODO: C# 9: use nullable T on the returned attached property here. public static AttachedProperty CreateNull(string name, Type owningType, T defaultValue = default!, Func? validate = null, AttachedPropertyChangedCallback? changed = null) { if (typeof(T).IsValueType) { throw new ArgumentException("Type must not be a value type. Use regular create for that" + " (yes, even for nullable value types)."); } return new AttachedProperty(name, owningType, defaultValue, validate, changed); } } /// /// Event args for when an attached property on a control changes. /// public readonly struct AttachedPropertyChangedEventArgs { public AttachedPropertyChangedEventArgs(object? newValue, object? oldValue) { NewValue = newValue; OldValue = oldValue; } public object? NewValue { get; } public object? OldValue { get; } } public readonly struct AttachedPropertyChangedEventArgs { public T NewValue { get; } public T OldValue { get; } public AttachedPropertyChangedEventArgs(T newValue, T oldValue) { NewValue = newValue; OldValue = oldValue; } } }