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 }