1 module rpui.rpdl_widget_factory;
2 
3 import std.path;
4 import std.meta;
5 import std.traits : hasUDA, getUDAs, isFunction, ParameterDefaults, Unqual;
6 import std.container.array;
7 
8 import rpdl;
9 import rpdl.node;
10 import rpui.basic_rpdl_exts;
11 import rpui.gapi_rpdl_exts;
12 
13 import gapi.texture;
14 
15 import rpui.math;
16 import rpui.primitives;
17 import rpui.traits;
18 import rpui.widget;
19 import rpui.view;
20 
21 import rpui.widgets.button.widget;
22 import rpui.widgets.panel.widget;
23 import rpui.widgets.stack_layout.widget;
24 import rpui.widgets.label.widget;
25 import rpui.widgets.multiline_label.widget;
26 import rpui.widgets.checkbox.widget;
27 import rpui.widgets.text_input.widget;
28 import rpui.widgets.list_menu.widget;
29 import rpui.widgets.list_menu_item.widget;
30 import rpui.widgets.list_menu_items_divider.widget;
31 import rpui.widgets.drop_list_menu.widget;
32 import rpui.widgets.tab_layout.widget;
33 import rpui.widgets.tab_button.widget;
34 import rpui.widgets.chain_layout.widget;
35 import rpui.widgets.check_button.widget;
36 import rpui.widgets.switch_button.widget;
37 import rpui.widgets.dialog.widget;
38 import rpui.widgets.tree_list.widget;
39 import rpui.widgets.tree_list_node.widget;
40 import rpui.widgets.canvas.widget;
41 import rpui.widgets.main_menu.widget;
42 import rpui.widgets.main_menu_item.widget;
43 import rpui.widgets.toolbar.widget;
44 import rpui.widgets.toolbar_tab_layout.widget;
45 import rpui.widgets.toolbar_tab_button.widget;
46 import rpui.widgets.toolbar_items_layout.widget;
47 import rpui.widgets.toolbar_item.widget;
48 import rpui.widgets.toolbar_items_divider.widget;
49 
50 /// Factory for construction view from rpdl layout data.
51 final class RpdlWidgetFactory {
52     /// Root view widget - container for other widgets.
53     @property Widget rootWidget() { return rootWidget_; }
54     private Widget rootWidget_;
55 
56     private RpdlTree layoutData;
57     private View view;
58 
59     this(View view, in string fileName) {
60         layoutData = new RpdlTree(dirName(fileName));
61         layoutData.load(baseName(fileName), RpdlTree.FileType.text);
62         debug layoutData.save(baseName(fileName ~ ".bin"), RpdlTree.FileType.bin);
63         this.view = view;
64     }
65 
66     /**
67      * This is a main method of factory - it will create and insert widgets
68      * by reading the children of `widgetNode`, if `widgetNode` is null
69      * then reading will be from layout root node.
70      */
71     void createWidgets(Node widgetNode = null) {
72         if (widgetNode is null) {
73             widgetNode = layoutData.root;
74         }
75 
76         foreach (Node childNode; widgetNode.children) {
77             if (auto objectNode = cast(ObjectNode) childNode) {
78                 auto widget = createWidgetFromNode(objectNode);
79                 readVisibleRules(widget, objectNode);
80                 widget.onPostCreate();
81             } else {
82                 throw new Error("Failed to create widget");
83             }
84         }
85     }
86 
87     /// Create widget depends of `widgetNode.name` and insert to `parentWidget`.
88     Widget createWidgetFromNode(ObjectNode widgetNode, Widget parentWidget = null) {
89         switch (widgetNode.name) {
90             case "Button":
91                 return createWidget!Button(widgetNode, parentWidget);
92 
93             case "Panel":
94                 return createWidget!Panel(widgetNode, parentWidget);
95 
96             case "StackLayout":
97                 return createWidget!StackLayout(widgetNode, parentWidget);
98 
99             case "Label":
100                 return createWidget!Label(widgetNode, parentWidget);
101 
102             case "MultilineLabel":
103                 return createWidget!MultilineLabel(widgetNode, parentWidget);
104 
105             case "Checkbox":
106                 return createWidget!Checkbox(widgetNode, parentWidget);
107 
108             case "TextInput":
109                 return createWidget!TextInput(widgetNode, parentWidget);
110 
111             case "ListMenu":
112                 return createWidget!ListMenu(widgetNode, parentWidget);
113 
114             case "ListMenuItem":
115                 return createWidget!ListMenuItem(widgetNode, parentWidget);
116 
117             case "DropListMenu":
118                 return createWidget!DropListMenu(widgetNode, parentWidget);
119 
120             case "TabLayout":
121                 return createWidget!TabLayout(widgetNode, parentWidget);
122 
123             case "TabButton":
124                 return createWidget!TabButton(widgetNode, parentWidget);
125 
126             case "ChainLayout":
127                 return createWidget!ChainLayout(widgetNode, parentWidget);
128 
129             case "CheckButton":
130                 return createWidget!CheckButton(widgetNode, parentWidget);
131 
132             case "SwitchButton":
133                 return createWidget!SwitchButton(widgetNode, parentWidget);
134 
135             case "TreeListNode":
136                 return createWidget!TreeListNode(widgetNode, parentWidget);
137 
138             case "TreeList":
139                 return createWidget!TreeList(widgetNode, parentWidget);
140 
141             case "Dialog":
142                 return createWidget!Dialog(widgetNode, parentWidget);
143 
144             case "Canvas":
145                 return createWidget!Canvas(widgetNode, parentWidget);
146 
147             case "MainMenu":
148                 return createWidget!MainMenu(widgetNode, parentWidget);
149 
150             case "MainMenuItem":
151                 return createWidget!MainMenuItem(widgetNode, parentWidget);
152 
153             case "Toolbar":
154                 return createWidget!Toolbar(widgetNode, parentWidget);
155 
156             case "ToolbarTabLayout":
157                 return createWidget!ToolbarTabLayout(widgetNode, parentWidget);
158 
159             case "ToolbarTabButton":
160                 return createWidget!ToolbarTabButton(widgetNode, parentWidget);
161 
162             case "ToolbarItemsLayout":
163                 return createWidget!ToolbarItemsLayout(widgetNode, parentWidget);
164 
165             case "ToolbarItem":
166                 return createWidget!ToolbarItem(widgetNode, parentWidget);
167 
168             case "ToolbarItemsDivider":
169                 return createWidget!ToolbarItemsDivider(widgetNode, parentWidget);
170 
171             case "ListMenuItemsDivider":
172                 return createWidget!ListMenuItemsDivider(widgetNode, parentWidget);
173 
174             default:
175                 throw new Error("Unspecified widget type " ~ widgetNode.name);
176         }
177     }
178 
179     /**
180      * Create widget from `widgetNode` data and insert it to `parentWidget`.
181      * If `parentWidget` is null then insert to `uiManager` root view widget.
182      */
183     Widget createWidget(T : Widget)(ObjectNode widgetNode, Widget parentWidget = null) {
184         T widget = new T();
185         readFields!T(widget, widgetNode);
186 
187         if (parentWidget !is null) {
188             parentWidget.children.addWidget(widget);
189         } else {
190             rootWidget_ = view.rootWidget;
191             view.addWidget(widget);
192         }
193 
194         // Create children widgets
195         foreach (Node childNode; widgetNode.children) {
196             if (auto objectNode = cast(ObjectNode) childNode) {
197                 Widget newWidget = createWidgetFromNode(objectNode, widget);
198                 readVisibleRules(newWidget, objectNode);
199                 newWidget.onPostCreate();
200             }
201         }
202 
203         return widget;
204     }
205 
206     // Tell the system how to interprete types of fields in widgets
207     // and how to extract them
208     // first argumen is name of type
209     // second is accessor in RpdlTree
210     // third is selector - additional path to find value
211     private enum name = 0;
212     private enum accessor = 1;
213     private enum selector = 2;
214 
215     private enum typesMap = AliasSeq!(
216         ["bool", "optBoolean", ".0"],
217         ["int", "optInteger", ".0"],
218         ["float", "optNumber", ".0"],
219         ["string", "optString", ".0"],
220         ["dstring", "optUTF32String", ".0"],
221 
222         ["vec2", "optVec2f", ""],
223         ["vec3", "optVec3f", ""],
224         ["vec4", "optVec4f", ""],
225         ["vec2i", "optVec2i", ""],
226         ["vec3i", "optVec3i", ""],
227         ["vec4i", "optVec4i", ""],
228         ["vec2ui", "optVec2ui", ""],
229         ["vec3ui", "optVec3ui", ""],
230         ["vec4ui", "optVec4ui", ""],
231 
232         ["Texture2DCoords", "optTexCoord", ""],
233         ["Rect", "optRect", ""],
234         ["FrameRect", "optFrameRect", ""],
235         ["IntRect", "optIntRect", ""],
236     );
237 
238     /**
239      * Finds all fields in widget with @`rpui.widget.Widget.field` attribute
240      * and fill widget members with values from rpdl file.
241      */
242     void readFields(T : Widget)(T widget, ObjectNode widgetNode) {
243         foreach (symbolName; getSymbolsNamesByUDA!(T, Widget.field)) {
244             auto defaultValue = mixin("widget." ~ symbolName);
245             alias SymbolType = typeof(defaultValue);
246 
247             static if (is(SymbolType == Array!CT, CT)) {
248                 auto array = widgetNode.getNode(symbolName);
249 
250                 if (array is null)
251                     continue;
252 
253                 foreach (node; array.children) {
254                     readArrayField!(T, CT, symbolName)(widget, widgetNode, node.name);
255                 }
256             }
257             else {
258                 readField!(T, SymbolType, symbolName)(widget, widgetNode, defaultValue);
259             }
260         }
261     }
262 
263     private void readField(T : Widget, SymbolType, string symbolName)
264         (T widget, Node widgetNode, SymbolType defaultValue = SymbolType.init)
265     {
266         bool foundType = false;
267 
268         foreach (type; typesMap) {
269             mixin("alias RawType = " ~ type[name] ~ ";");
270 
271             static if (is(SymbolType == RawType)) {
272                 foundType = true;
273                 const fullSymbolPath = symbolName ~ type[selector];
274                 enum call = "widgetNode." ~ type[accessor] ~ "(fullSymbolPath, defaultValue)";
275                 const value = mixin(call);
276 
277                 static if (type[name] == "dstring") {
278                     assert(view.resources.strings !is null);
279                     mixin("widget." ~ symbolName ~ " = view.resources.strings.parseString(value);");
280                 } else {
281                     mixin("widget." ~ symbolName ~ " = value;");
282                 }
283 
284                 break;
285             }
286         }
287 
288         if (!foundType) {
289             static if (is(SymbolType == enum)) {
290                 const value = widgetNode.optEnum!(SymbolType)(symbolName ~ ".0", defaultValue);
291                 mixin("widget." ~ symbolName ~ " = value;");
292             } else
293 
294             // Check if unsupported type is array
295             static if (is(SymbolType == CT[], CT)) {
296                 auto array = widgetNode.getNode(symbolName);
297 
298                 if (array is null)
299                     return;
300 
301                 foreach (node; array.children) {
302                     readArrayField!(T, Unqual!CT, symbolName)(widget, widgetNode, node.name);
303                 }
304             } else {
305                 assert(false, "type " ~ SymbolType.stringof ~ " doesn't allow");
306             }
307         }
308     }
309 
310     private void readArrayField(T : Widget, SymbolType, string symbolName)
311         (T widget, Node widgetNode, string nodeName)
312     {
313         const defaultValue = SymbolType.init;
314         bool foundType = false;
315 
316         foreach (type; typesMap) {
317             mixin("alias RawType = " ~ type[name] ~ ";");
318 
319             static if (is(SymbolType == RawType)) {
320                 foundType = true;
321                 const fullSymbolPath = symbolName ~ "." ~ nodeName;// ~ type[selector];
322                 enum call = "widgetNode." ~ type[accessor] ~ "(fullSymbolPath, defaultValue)";
323                 const value = mixin(call);
324 
325                 // assign value to widget field
326                 static if (type[name] == "dstring") {
327                     mixin("widget." ~ symbolName ~ " ~= uiManager.stringsRes.parseString(value);");
328                 } else {
329                     mixin("widget." ~ symbolName ~ " ~= value;");
330                 }
331 
332                 break;
333             }
334         }
335 
336         assert(foundType, "type " ~ SymbolType.stringof ~ " doesn't allow");
337     }
338 
339     private void readVisibleRules(Widget widget, ObjectNode widgetNode) {
340         const rule = widgetNode.optString("tabVisibleRule.0", null);
341 
342         if (rule !is null ) {
343             auto dependWidget = cast(TabButton) rootWidget.resolver.findWidgetByName(rule);
344 
345             if (dependWidget !is null) {
346                 widget.visibleRules.insert(() => dependWidget.checked);
347             }
348         }
349     }
350 }