1 module modernui.ui.control;
2 
3 import modernui.core;
4 import modernui.rx;
5 import modernui.collections;
6 import modernui.ui.core;
7 import modernui.ui.render;
8 
9 import std.algorithm.iteration;
10 import std.container.array;
11 
12 abstract class Visual : DependencyObject
13 {
14 	static this() { registerClass!(typeof(this)); }
15 	this()
16 	{
17 		myVisualChildren = new ReactiveList!Visual;
18 		myInvalidMeasure = new Subject!None;
19 		myInvalidArrangement = new Subject!None;
20 		visualChildren.itemsAdded.then!(ItemsAdded!Visual)(&onVisualChildrenAdded);
21 		visualChildren.itemsRemoved.then!(ItemsRemoved!Visual)(&onVisualChildrenRemoved);
22 	}
23 
24 	mixin DefineDependencyPropertyReadOnly!(Visual, "visualParent");
25 	mixin DefineDependencyPropertyReadOnly!(Size, "desiredSize");
26 	mixin DefineDependencyPropertyReadOnly!(Rect, "actualRect");
27 
28 	private ReactiveList!Visual myVisualChildren;
29 	protected @property ReactiveList!Visual visualChildren() { return myVisualChildren; }
30 
31 	private Subject!None myInvalidMeasure;
32 	@property Observable!None invalidMeasure() { return myInvalidMeasure; }
33 
34 	private Subject!None myInvalidArrangement;
35 	@property Observable!None invalidArrangement() { return myInvalidArrangement; }
36 
37 	protected void invalidateMeasurement() { myInvalidMeasure.next(None.val); }
38 	protected void invalidateArrangement() { myInvalidArrangement.next(None.val); }
39 
40 	protected abstract Size measure(const Size measure);
41 
42 	protected abstract void arrange(const Rect site);
43 
44 	protected abstract void render(RenderContext context);
45 
46 	private void onVisualChildrenAdded(ItemsAdded!Visual info)
47 	{
48 		foreach(newItem; info.newItems)
49 		{
50 			newItem.visualParent = this;
51 		}
52 
53 		invalidateMeasurement();
54 	}
55 
56 	private void onVisualChildrenRemoved(ItemsRemoved!Visual info)
57 	{
58 		foreach(oldItem; info.oldItems)
59 		{
60 			oldItem.visualParent = null;
61 		}
62 
63 		invalidateMeasurement();
64 	}
65 }
66 
67 abstract class UIElement : Visual
68 {
69 	static this() { registerClass!(typeof(this)); }
70 
71 	mixin DefineDependencyProperty!(Thickness, "border");
72 	mixin DefineDependencyProperty!(Thickness, "padding");
73 	mixin DefineDependencyProperty!(Thickness, "margin");
74 	mixin DefineDependencyProperty!(double, "width");
75 	mixin DefineDependencyProperty!(double, "height");
76 	mixin DefineDependencyProperty!(HorizontalAlignment, "horizontalAlignment");
77 	mixin DefineDependencyProperty!(VerticalAlignment, "verticalAlignment");
78 	mixin DefineDependencyProperty!(bool, "isVisible");
79 	mixin DefineDependencyProperty!(bool, "isCollapsible");
80 
81 	// assumes children already measured, no recursive impl.
82 	protected override Size measure(const Size sizeAvailable)
83 	{
84 		import std.math : isNaN;
85 		auto size = Size.init;
86 		if(isCollapsible && !isVisible)
87 		{
88 			desiredSize = size;
89 			return size;
90 		}
91 
92 		if(isNaN(width) || isNaN(height))
93 		{
94 			foreach(v; this.visualChildren[])
95 			{
96 				size = size.unionWith(v.desiredSize);
97 			}
98 		}
99 
100 		immutable auto r = [margin, border, padding];
101 		if(isNaN(width))
102 		{
103 			size.width = width;
104 		} else {
105 			size.width += r.map!(x => x.widthContribution).sum;
106 		}
107 
108 		if(isNaN(height))
109 		{
110 			size.height = height;
111 		} else {
112 			size.height += r.map!(x => x.heightContribution).sum;
113 		}
114 
115 		desiredSize = size;
116 		return size;
117 	}
118 
119 	// actualRect is relative to parent.
120 	protected override void arrange(const Rect site)
121 	{
122 		// skips unuseful recursion
123 		if(site == actualRect) return;
124 
125 		actualRect = site;
126 		immutable auto r = [margin, border, padding];
127 		auto left = r.map!(x => x.left).sum;
128 		auto right = r.map!(x => x.right).sum;
129 		auto top = r.map!(x => x.top).sum;
130 		auto bottom = r.map!(x => x.bottom).sum;
131 
132 		auto location = Point(left, top);
133 		auto size = site.size.shrink(Size(left + right, top + bottom));
134 		auto childrenSite = Rect(location, size);
135 		foreach(v; visualChildren[])
136 		{
137 			v.arrange(childrenSite);
138 		}
139 	}
140 }
141 
142 class TextElement : UIElement
143 {
144 	static this() { registerClass!(typeof(this)); }
145 
146 	mixin DefineDependencyProperty!(string, "text");
147 
148 	protected override void render(RenderContext context)
149 	{
150 		// TODO
151 	}
152 
153 	this()
154 	{
155 		propertyChanged.then!PropertyChange((x) {
156 			if(x.name != nameof!text) return;
157 			invalidateMeasurement();
158 		});
159 	}
160 }
161 
162 abstract class ContentControl : UIElement
163 {
164 	static this() { registerClass!(typeof(this)); }
165 
166 	mixin DefineDependencyProperty!(Visual, "content");
167 
168 	protected void changeChild(Visual child)
169 	{
170 		if(visualChildren.length == 0)
171 		{
172 			visualChildren.add(child);
173 		}
174 		else
175 		{
176 			visualChildren[0] = child;
177 		}
178 
179 		invalidateMeasurement();
180 	}
181 
182 	this()
183 	{
184 		propertyChanged.then!PropertyChange((x) {
185 			if(x.name != nameof!content) return;
186 			changeChild(content);
187 		});
188 	}
189 }
190 
191 version(Windows)
192 {
193 	import core.sys.windows.windows;
194 
195 	version(Unicode)
196 	{
197 		import std.utf : toUTF16z;
198 		private alias toTStringz = toUTF16z;
199 	} 
200 	else
201 	{
202 		import std.string : toStringz;
203 		private alias toTStringz = toStringz;
204 	}
205 
206 	class Window : ContentControl
207 	{
208 		static this() { registerClass!(typeof(this)); }
209 
210 		static bool isClassRegistered = false;
211 
212 		static immutable string className = "ModernUIWindow";
213 
214 		private struct DescendantInfo {
215 			int level;
216 			Array!Subscription subscriptions;
217 		}
218 
219 		private DescendantInfo[Visual] descendants;
220 		private Window selfReference;
221 		private RenderContext myRenderContext;
222 
223 		private HINSTANCE hInstance = null;
224 		private HWND hWnd = null;
225 
226 		private void registerWindowClass() {
227 			HWND hWnd;
228 			MSG  msg;
229 			WNDCLASS wndclass;
230 
231 			wndclass.style         = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
232 			wndclass.lpfnWndProc   = &WindowProc;
233 			wndclass.cbClsExtra    = 0;
234 			wndclass.cbWndExtra    = 0;
235 			wndclass.hInstance     = hInstance;
236 			wndclass.hIcon         = LoadIcon(null, IDI_APPLICATION);
237 			wndclass.hCursor       = LoadCursor(null, IDC_ARROW);
238 			wndclass.hbrBackground = cast(HBRUSH)GetStockObject(WHITE_BRUSH);
239 			wndclass.lpszMenuName  = null;
240 			wndclass.lpszClassName = className.toTStringz;
241 
242 			if (!RegisterClass(&wndclass))
243 			{
244 				throw new Error("Error registering the window class");
245 			}
246 		}
247 
248 		private void createWindow()
249 		{
250 			Window* wnd = &selfReference;
251 			hWnd = CreateWindowEx(
252 						0, 
253 						className.toTStringz,  // window class name
254 						"test".toTStringz,     // window caption
255 						WS_THICKFRAME   |
256 						WS_MAXIMIZEBOX  |
257 						WS_MINIMIZEBOX  |
258 						WS_SYSMENU      |
259 						WS_VISIBLE,           // window style
260 						CW_USEDEFAULT,        // initial x position
261 						CW_USEDEFAULT,        // initial y position
262 						600,                  // initial x size
263 						400,                  // initial y size
264 						HWND_DESKTOP,         // parent window handle
265 						null,                 // window menu handle
266 						hInstance,            // program instance handle
267 						wnd);                 // creation parameters;
268 
269 			if(hWnd is null)
270 			{
271 				throw new Error("Could not create window");
272 			}
273 		}
274 
275 		this()
276 		{
277 			selfReference = this;
278 			hInstance = GetModuleHandle(null);
279 			if(!isClassRegistered) 
280 			{
281 				registerWindowClass();
282 				isClassRegistered = true;
283 			}
284 
285 			createWindow();
286 
287 			auto desc = DescendantInfo.init;
288 			desc.level = 0;
289 			descendants[this] = desc;
290 
291 			visualChildren.itemsAdded.then!(ItemsAdded!Visual)(&onDescendantsAdded);
292 			visualChildren.itemsRemoved.then!(ItemsRemoved!Visual)(&onDescendantsRemoved);
293 		}
294 
295 		// For new descendants added, ensure its children are tracked too
296 		void onDescendantsAdded(ItemsAdded!Visual x)
297 		{
298 			foreach(newItem; x.newItems)
299 			{
300 				auto desc = DescendantInfo.init;
301 				auto subscription = newItem.visualChildren.itemsAdded.then!(ItemsAdded!Visual)(&onDescendantsAdded);
302 				desc.subscriptions.insertBack(subscription);
303 				subscription = newItem.visualChildren.itemsRemoved.then!(ItemsRemoved!Visual)(&onDescendantsRemoved);
304 				desc.subscriptions.insertBack(subscription);
305 				desc.level = descendants[newItem.visualParent].level + 1;
306 				descendants[newItem] = desc;
307 			}
308 		}
309 
310 		void onDescendantsRemoved(ItemsRemoved!Visual x)
311 		{
312 			foreach(oldItem; x.oldItems)
313 			{
314 				auto desc = descendants[oldItem];
315 				foreach(subscription; desc.subscriptions)
316 				{
317 					subscription.release;
318 				}
319 				descendants.remove(oldItem);
320 			}
321 		}
322 
323 		void show()
324 		{
325 			ShowWindow(hWnd, SW_SHOWDEFAULT);
326 			UpdateWindow(hWnd);
327 		}
328 
329 		void messageLoop()
330 		{
331 			MSG  msg;
332 			while (GetMessageA(&msg, null, 0, 0))
333 			{
334 				TranslateMessage(&msg);
335 				DispatchMessageA(&msg);
336 			}
337 		}
338 
339 		protected override void render(RenderContext rc)
340 		{
341 			rc.clear(Color(1.0,1.0,1.0));
342 			foreach(child; visualChildren) child.render(rc);
343 		}
344 
345 		extern(Windows)
346 		static LRESULT WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) nothrow
347 		{
348 			try
349 			{
350 				if(message == WM_NCCREATE)
351 				{
352 					auto pCreate = cast(CREATESTRUCT*)lParam;
353 					auto wnd = cast(Window*)pCreate.lpCreateParams;
354 					SetWindowLongPtr(hWnd, GWLP_USERDATA, cast(size_t)wnd);
355 					wnd.myRenderContext = new modernui.ui.direct2d.Direct2DRenderContext(hWnd);
356 					return true;
357 				}
358 
359 				auto self = cast(Window*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
360 				switch (message)
361 				{
362 					case WM_PAINT:
363 						self.myRenderContext.begin;
364 						scope(exit) self.myRenderContext.end;
365 						self.render(self.myRenderContext);
366 						break;
367 
368 					case WM_DESTROY:
369 						PostQuitMessage(0);
370 						break;
371 
372 					case WM_SIZE:
373 						self.myRenderContext.resize;
374 						break;
375 
376 					default:
377 						return DefWindowProcA(hWnd, message, wParam, lParam);
378 				}
379 			}
380 			catch(Throwable t)
381 			{
382 				PostQuitMessage(240);
383 			}
384 
385 			return 0;
386 		}
387 	}
388 }