1 module modernui.core; 2 3 import std.traits; 4 import std.variant; 5 import std.typecons; 6 7 import modernui.rx; 8 9 class PropertyChange 10 { 11 public immutable string name; 12 13 this(string name) 14 { 15 this.name = name; 16 } 17 } 18 19 interface INotifyPropertyChanged 20 { 21 @property Observable!PropertyChange propertyChanged(); 22 } 23 24 abstract class DependencyPropertyDescriptor 25 { 26 private string myName; 27 private TypeInfo myOwnerType; 28 private TypeInfo myValueType; 29 private bool myHasSetter; 30 31 this(string name, TypeInfo ownerType, TypeInfo valueType, bool hasSetter) 32 { 33 this.myName = name; 34 this.myOwnerType = ownerType; 35 this.myValueType = valueType; 36 this.myHasSetter = hasSetter; 37 } 38 39 @property TypeInfo valueType() { return this.myValueType; } 40 41 @property TypeInfo ownerType() { return this.myOwnerType; } 42 43 @property string name() { return this.myName; } 44 45 @property bool hasSetter() { return this.myHasSetter; } 46 47 abstract Variant getValue(DependencyObject owner) const; 48 49 abstract void setValue(DependencyObject owner, Variant value); 50 } 51 52 class DependencyPropertyDescriptorSpecialized(TOwner, T) : DependencyPropertyDescriptor 53 { 54 alias Getter = T delegate(TOwner); 55 alias Setter = void delegate(TOwner,T); 56 private Getter myGetValue; 57 private Setter mySetValue; 58 59 T getValueTyped(TOwner owner) const 60 { 61 return this.myGetValue(owner); 62 } 63 64 void setValueTyped(TOwner owner, T value) 65 { 66 this.mySetValue(owner, value); 67 } 68 69 override Variant getValue(DependencyObject owner) const 70 { 71 Variant value = this.getValueTyped(cast(TOwner)owner); 72 return value; 73 } 74 75 override void setValue(DependencyObject owner, Variant value) 76 { 77 this.setValueTyped(cast(TOwner)owner, value.get!T()); 78 } 79 80 this(string name, Getter getter, Setter setter) 81 { 82 super(name, typeid(TOwner), typeid(T), setter !is null); 83 this.myGetValue = getter; 84 this.mySetValue = setter; 85 } 86 } 87 88 enum DependencyProperty; 89 90 template nameof(alias identifier) 91 { 92 string nameof = __traits(identifier, identifier); 93 } 94 95 private alias helper(alias A) = A; 96 97 private template isDependencyProperty(alias T) 98 { 99 enum bool isDependencyProperty = isCallable!T && hasUDA!(T, DependencyProperty); 100 } 101 102 class ClassDescriptor 103 { 104 const string name; 105 const TypeInfo type; 106 const ClassDescriptor base; 107 const DependencyPropertyDescriptor[string] dependencyPropertiesByName; 108 109 const(DependencyPropertyDescriptor) getFlattenProperty(string name) immutable 110 { 111 auto bag = rebindable!(const ClassDescriptor)(this); 112 while(bag !is null) 113 { 114 if(name in bag.dependencyPropertiesByName) 115 { 116 return bag.dependencyPropertiesByName[name]; 117 } 118 119 bag = bag.base; 120 } 121 122 return null; 123 } 124 125 public this(string name, TypeInfo type, ClassDescriptor base, const DependencyPropertyDescriptor[string] dependencyPropertiesByName) 126 { 127 this.name = name; 128 this.type = type; 129 this.base = base; 130 this.dependencyPropertiesByName = dependencyPropertiesByName; 131 } 132 } 133 134 abstract class DependencyObject : INotifyPropertyChanged 135 { 136 private auto subjectPropertyChanged = new Subject!PropertyChange(); 137 138 private static ClassDescriptor[TypeInfo] classDescriptors; 139 140 protected bool setProperty(alias Identifier, TValue)(ref TValue val, TValue newValue) 141 { 142 if(val == newValue) 143 { 144 return false; 145 } 146 147 val = newValue; 148 subjectPropertyChanged.next(new PropertyChange(nameof!Identifier)); 149 return true; 150 } 151 152 @property ClassDescriptor classDescriptor() 153 { 154 return classDescriptors[typeid(this)]; 155 } 156 157 protected static void registerClass(TheClass)() 158 { 159 // Populate the descriptors for each dependency property 160 DependencyPropertyDescriptor[string] properties; 161 foreach(memberName; __traits(derivedMembers, TheClass)) 162 { 163 // Skip constructors and process just public members 164 static if(memberName != "this" && __traits(getProtection, __traits(getMember, TheClass, memberName)) == "public") 165 { 166 foreach(member; __traits(getOverloads, TheClass, memberName)) 167 { 168 // We are looking for getters 169 static if(isDependencyProperty!member && !is(ReturnType!member == void)) 170 { 171 alias PropertyType = ReturnType!member; 172 173 // We get an invoker for the getter 174 PropertyType delegate(TheClass) getter = (instance) { return __traits(getMember, instance, memberName)(); }; 175 176 // Now we will go for the setter, we try to compile to test if it has an available setter method 177 void delegate(TheClass, PropertyType) setter = null; 178 static if(__traits(compiles, setter = (instance, value) { __traits(getMember, instance, memberName)(value); })) 179 { 180 setter = (instance, value) { __traits(getMember, instance, memberName)(value); }; 181 } 182 183 // Build and add the new property descriptor 184 auto propDesc = new DependencyPropertyDescriptorSpecialized!(TheClass, PropertyType)(memberName, getter, setter); 185 properties[memberName] = propDesc; 186 break; 187 } 188 } 189 } 190 } 191 192 // Get the descriptor for the base class 193 auto baseClassTypeInfo = typeid(__traits(parent, TheClass)); 194 ClassDescriptor baseClassDescriptor = null; 195 if(baseClassTypeInfo in classDescriptors) 196 { 197 baseClassDescriptor = classDescriptors[baseClassTypeInfo]; 198 } 199 200 // Build and registers the new class descriptor 201 auto classDesc = new ClassDescriptor(__traits(identifier, TheClass), typeid(TheClass), baseClassDescriptor, properties); 202 classDescriptors[typeid(TheClass)] = classDesc; 203 } 204 205 override @property Observable!PropertyChange propertyChanged() { return subjectPropertyChanged; } 206 } 207 208 mixin template DefineDependencyProperty(Type, alias name) 209 { 210 mixin("private " ~ Type.stringof ~ " " ~ "_dp_" ~ name ~ ";"); 211 mixin("@DependencyProperty @property " ~ Type.stringof ~ " " ~ name ~ "() { return _dp_" ~ name ~ "; }" ); 212 mixin("@DependencyProperty @property void " ~ name ~ "("~ Type.stringof ~" value) { this.setProperty!" ~ name ~ "(this._dp_" ~ name ~ ", value); }" ); 213 } 214 215 mixin template DefineDependencyPropertyReadOnly(Type, alias name) 216 { 217 mixin("private " ~ Type.stringof ~ " " ~ "_dp_" ~ name ~ ";"); 218 mixin("@DependencyProperty @property " ~ Type.stringof ~ " " ~ name ~ "() { return _dp_" ~ name ~ "; }" ); 219 mixin("private @DependencyProperty @property void " ~ name ~ "("~ Type.stringof ~" value) { this.setProperty!" ~ name ~ "(this._dp_" ~ name ~ ", value); }" ); 220 } 221 222 version(unittest) 223 { 224 // Sample of basic use case 225 final class TestElement : DependencyObject 226 { 227 // Define your dependency property 228 mixin DefineDependencyProperty!(double, "width"); 229 230 // Initialize the internal reflection data structures 231 static this() { registerClass!(typeof(this))(); } 232 } 233 } 234 235 unittest 236 { 237 auto element = new TestElement; 238 element.width = 34; 239 string propertyWhatChanged = null; 240 241 // Subscribe a simple change listener 242 element.propertyChanged.then!PropertyChange((v) { 243 propertyWhatChanged = v.name; 244 }); 245 246 import std.algorithm; 247 assert(propertyWhatChanged != nameof!(TestElement.width)); 248 assert(element.width == 34); 249 element.width = 15; 250 251 assert(propertyWhatChanged == nameof!(TestElement.width)); 252 assert(element.width == 15); 253 }