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