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 }