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 }