1 /++
2 	A small extension module to [arsd.minigui] that adds
3 	functions for creating widgets and windows from short
4 	XML descriptions.
5 
6 	If you choose to use this, it will require [arsd.dom]
7 	to be compiled into your project too.
8 
9 	---
10 	import arsd.minigui_xml;
11 	Window window = createWindowFromXml(`
12 		<MainWindow>
13 			<Button label="Hi!" />
14 		</MainWindow>
15 	`);
16 	---
17 
18 
19 	To add custom widgets to the minigui_xml factory, you need
20 	to register them with FIXME.
21 
22 	You can attach some events right in the XML using attributes.
23 	The attribute names are `onEVENTNAME` or `ondirectEVENTNAME`
24 	and the values are one of the following three value types:
25 
26 	$(LIST
27 		* If it starts with `&`, it is a delegate you need
28 		  to register using the FIXME function.
29 
30 		* If it starts with `(`, it is a string passed to
31 		  the [arsd.dom.querySelector] function to get an
32 		  element reference
33 
34 		* Otherwise, it tries to call a script function (if
35 		  scripting is available).
36 	)
37 
38 	Keep in mind
39 	For example, to make a page widget that changes based on a
40 	drop down selection, you may:
41 
42 	```xml
43 		<DropDownSelection onchange="$(+PageWidget).setCurrentTab">
44 			<option>Foo</option>
45 			<option>Bar</option>
46 		</DropDownSelection>
47 		<PageWidget name="mypage">
48 			<!-- contents elided -->
49 		</PageWidget>
50 	```
51 
52 	That will create a select widget that when it changes, it will
53 	look for the next PageWidget sibling (that's the meaning of `+PageWidget`,
54 	see css selector syntax for more) and call its `setCurrentTab`
55 	method.
56 
57 	Since the function knows `setCurrentTab` takes an integer, it will
58 	automatically pull the `intValue` member out of the event and pass
59 	it to the method.
60 
61 	The given XML is the same as the following D:
62 
63 	---
64 		auto select = new DropDownSelection(parent);
65 		select.addOption("Foo");
66 		select.addOption("Bar");
67 		auto page = new PageWidget(parent);
68 		page.name = "mypage";
69 
70 		select.addEventListener("change", (Event event) {
71 			page.setCurrentTab(event.intValue);
72 		});
73 	---
74 +/
75 module arsd.minigui_xml;
76 
77 public import arsd.minigui;
78 
79 import arsd.dom;
80 
81 private template ident(T...) {
82 	static if(is(T[0]))
83 		alias ident = T[0];
84 	else
85 		alias ident = void;
86 }
87 
88 private
89 Widget delegate(string[string] args, Widget parent)[string] widgetFactoryFunctions;
90 
91 private
92 void loadMiniguiPublicClasses() {
93 	if(widgetFactoryFunctions !is null)
94 		return;
95 
96 	import std.traits;
97 	import std.conv;
98 
99 	foreach(memberName; __traits(allMembers, mixin("arsd.minigui"))) static if(__traits(compiles, __traits(getMember, mixin("arsd.minigui"), memberName))) {
100 		alias Member = ident!(__traits(getMember, mixin("arsd.minigui"), memberName));
101 		static if(is(Member == class) && !isAbstractClass!Member && is(Member : Widget) && __traits(getProtection, Member) != "private") {
102 			widgetFactoryFunctions[memberName] = (string[string] args, Widget parent) {
103 				static if(is(Member : Dialog)) {
104 					return new Member();
105 				} else static if(is(Member : Window)) {
106 					return new Member("test");
107 				} else {
108 					auto paramNames = ParameterIdentifierTuple!(__traits(getMember, Member, "__ctor"));
109 					Parameters!(__traits(getMember, Member, "__ctor")) params;
110 
111 					foreach(idx, param; params[0 .. $-1]) {
112 						if(auto arg = paramNames[idx] in args) {
113 							static if(is(typeof(param) == MemoryImage)) {
114 
115 							} else static if(is(typeof(param) == Color)) {
116 								params[idx] = Color.fromString(*arg);
117 							} else
118 								params[idx] = to!(typeof(param))(*arg);
119 						}
120 					}
121 
122 					params[$-1] = parent;
123 
124 					auto widget = new Member(params);
125 
126 					if(auto st = "statusTip" in args)
127 						widget.statusTip = *st;
128 					if(auto st = "name" in args)
129 						widget.name = *st;
130 					return widget;
131 				}
132 			};
133 		}
134 	}
135 }
136 
137 ///
138 Widget makeWidgetFromString(string xml, Widget parent) {
139 	auto document = new Document(xml, true, true);
140 	auto r = document.root;
141 	return miniguiWidgetFromXml(r, parent);
142 }
143 
144 ///
145 Window createWindowFromXml(string xml) {
146 	return createWindowFromXml(new Document(xml, true, true));
147 }
148 ///
149 Window createWindowFromXml(Document document) {
150 	auto r = document.root;
151 	return cast(Window) miniguiWidgetFromXml(r, null);
152 }
153 ///
154 Widget miniguiWidgetFromXml(Element element, Widget parent) {
155 	if(widgetFactoryFunctions is null)
156 		loadMiniguiPublicClasses();
157 	if(auto factory = element.tagName in widgetFactoryFunctions) {
158 		auto p = (*factory)(element.attributes, parent);
159 		foreach(child; element.children)
160 			if(child.tagName != "#text")
161 				miniguiWidgetFromXml(child, p);
162 		return p;
163 	} else {
164 		import std.stdio;
165 		writeln("Unknown class: ", element.tagName);
166 		return null;
167 	}
168 }
169 
170 
Suggestion Box / Bug Report