1 module rpui.view_component; 2 3 public import rpui.view_component.attributes; 4 5 import core.memory; 6 7 import std.meta; 8 import std.traits; 9 import std.path; 10 11 import rpui.events; 12 import rpui.widget_events; 13 import rpui.shortcuts : Shortcuts; 14 import rpui.view; 15 import rpui.widget; 16 import rpui.traits; 17 import rpui.paths; 18 import rpui.rpdl_widget_factory; 19 import rpui.events_observer; 20 21 /** 22 * ViewComponent is a container for widgets with additional attributes processing 23 * such as `rpui.view.attributes.accessors` for more convinient way to 24 * access widgets, attach shortcuts to view methods and so on. 25 */ 26 abstract class ViewComponent { 27 private View view; 28 private Widget rootWidget; 29 RpdlWidgetFactory widgetFactory; 30 private Subscriber shortcutsSubscriber; 31 32 final View getView() { 33 return view; 34 } 35 36 @shortcut("General.focusNext") 37 final void focusNext() { 38 view.focusNext(); 39 } 40 41 @shortcut("General.focusPrev") 42 final void focusPrev() { 43 view.focusPrev(); 44 } 45 46 @shortcut("General.copy") 47 final void copy() { 48 view.events.notify(CopyCommand()); 49 } 50 51 @shortcut("General.paste") 52 final void paste() { 53 view.events.notify(PasteCommand()); 54 } 55 56 @shortcut("General.cut") 57 final void cut() { 58 view.events.notify(CutCommand()); 59 } 60 61 @shortcut("General.unselect") 62 final void unselect() { 63 view.events.notify(UnselectCommand()); 64 } 65 66 @shortcut("General.selectAll") 67 final void selectAll() { 68 view.events.notify(SelectAllCommand()); 69 } 70 71 void onCreate() { 72 } 73 74 void onDestroy() { 75 } 76 77 void onProgress(in ProgressEvent event) { 78 } 79 80 /** 81 * Create viewComponent with `view` from `layoutFileName` and load shortcuts 82 * from `shortcutsFileName`. 83 */ 84 this(this T)(View view, in string layoutFileName, in string shortcutsFileName) { 85 assert(view !is null); 86 this.view = view; 87 88 auto shortcuts = Shortcuts.createFromFile(shortcutsFileName); 89 view.shortcuts.merge(shortcuts); 90 91 widgetFactory = new RpdlWidgetFactory(view, layoutFileName); 92 widgetFactory.createWidgets(); 93 rootWidget = widgetFactory.rootWidget; 94 assert(rootWidget !is null); 95 readAttributes!T(); 96 97 shortcutsSubscriber = view.events.subscribe!KeyReleasedEvent( 98 event => shortcuts.onKeyReleased(event.key) 99 ); 100 } 101 102 this(this T)(View view, Widget rootWidget) { 103 assert(view !is null); 104 assert(rootWidget !is null); 105 106 this.view = view; 107 this.rootWidget = rootWidget; 108 readAttributes!T(); 109 } 110 111 ~this() { 112 view.events.unsubscribe(shortcutsSubscriber); 113 onDestroy(); 114 } 115 116 /** 117 * Create new view instance from file placed in $(I res/ui/layouts). 118 * Instance will be created of `T` type. 119 */ 120 static createFromFile(T : ViewComponent)(View view, in string fileName) { 121 const paths = createPathes(); 122 const layoutPath = buildPath(paths.resources, "ui", "layouts", fileName); 123 const shortcutsPath = buildPath(paths.resources, "ui", "shortcuts", "general.rdl"); 124 auto component = new T(view, layoutPath, shortcutsPath); 125 component.onCreate(); 126 return component; 127 } 128 129 /** 130 * Create new view instance from file placed in $(I res/ui/layouts). 131 * Instance will be created of `T` type. 132 */ 133 static createFromFileWithShortcuts(T : ViewComponent)(View view, in string fileName, in string shortcuts = "") { 134 const paths = createPathes(); 135 const layoutPath = buildPath(paths.resources, "ui", "layouts", fileName); 136 const shortcutsPath = buildPath(paths.resources, "ui", "shortcuts", shortcuts == "" ? fileName : shortcuts); 137 auto component = new T(view, layoutPath, shortcutsPath); 138 component.onCreate(); 139 return component; 140 } 141 142 /** 143 * Create new view instance from file placed in $(I res/ui/layouts) with custom shorcuts 144 * `shortcutsFilename`. Instance will be created of `T` type. 145 */ 146 static createFromFile(T : ViewComponent)(View view, in string layoutFileName, 147 in string shortcutsFilename) 148 { 149 const paths = createPathes(); 150 const layoutPath = buildPath(paths.resources, "ui", "layouts", layoutFileName); 151 const shortcutsPath = buildPath(paths.resources, "ui", "shortcuts", shortcutsFilename); 152 auto component = new T(view, layoutPath, shortcutsPath); 153 component.onCreate(); 154 return component; 155 } 156 157 private void readAttributes(T : ViewComponent)() { 158 T viewComponent = cast(T) this; 159 readEventsAttributes(viewComponent); 160 readBindWidgetAttributes(viewComponent); 161 readBindGroupWidgets(viewComponent); 162 readShortcutsAttributes(viewComponent); 163 } 164 165 private void readEventsAttributes(T : ViewComponent)(T viewComponent) { 166 enum events = AliasSeq!( 167 "onClick", 168 "onDblClick", 169 "onFocus", 170 "onBlur", 171 "onMouseWheel", 172 "onKeyReleased", 173 "onTextEntered", 174 "onMouseMove", 175 "onMouseWheel", 176 "onMouseEnter", 177 "onMouseLeave", 178 "onMouseDown", 179 "onMouseUp" 180 ); 181 182 static foreach (eventName; events) { 183 readEventAttribute!(T, eventName)(viewComponent); 184 } 185 } 186 187 /** 188 * Read event listener attributes and assign this listener to 189 * widget with name uda.widgetName, where uda is attribute 190 */ 191 private void readEventAttribute(T : ViewComponent, string eventName)(T viewComponent) { 192 mixin("alias event = " ~ eventName ~ "Listener;"); 193 194 foreach (symbolName; getSymbolsNamesByUDA!(T, event)) { 195 mixin("alias symbol = T." ~ symbolName ~ ";"); 196 assert(isFunction!symbol); 197 198 foreach (uda; getUDAs!(symbol, event)) { 199 Widget widget = findWidgetByName!(uda.widgetName); 200 assert(widget !is null, "Widget hasn't found: " ~ uda.widgetName); 201 202 enum widgetEventName = eventName[2..$]; 203 204 // widget.events.subscribe!(clickEvent)(&viewComponent.onOkButtonClick); 205 mixin("widget.events.subscribe!(" ~ widgetEventName ~ "Event)(&viewComponent." ~ symbolName ~ ");"); 206 } 207 } 208 } 209 210 /// Reading ViewWidget attributes to extract widget by name to variable 211 private void readBindWidgetAttributes(T : ViewComponent)(T viewComponent) { 212 foreach (symbolName; getSymbolsNamesByUDA!(T, bindWidget)) { 213 mixin("alias symbol = T." ~ symbolName ~ ";"); 214 215 foreach (uda; getUDAs!(symbol, bindWidget)) { 216 enum widgetName = getNameFromAttribute!uda(symbolName); 217 218 Widget widget = findWidgetByName!(widgetName); 219 assert(widget !is null, widgetName ~ " not found"); 220 221 // alias WidgetType = typeof(view.cancelButton); 222 // view.cancelButton = cast(WidgetType) widget; 223 mixin("alias WidgetType = typeof(viewComponent." ~ symbolName ~ ");"); 224 mixin("viewComponent." ~ symbolName ~ " = cast(WidgetType) widget;"); 225 } 226 } 227 } 228 229 /// Find widget in relative view root widget. 230 Widget findWidgetByName(alias name)() { 231 assert(rootWidget !is null); 232 233 if (rootWidget.name == name) { 234 return rootWidget; 235 } else { 236 return rootWidget.resolver.findWidgetByName(name); 237 } 238 } 239 240 /** 241 * Get widget name from attribute or set as symbolName 242 * if empty or if it is struct 243 */ 244 private static string getNameFromAttribute(alias uda)(in string symbolName) { 245 static if (isType!uda) { 246 return symbolName; 247 } else { 248 static if (uda.widgetName == "") { 249 return symbolName; 250 } else { 251 return uda.widgetName; 252 } 253 } 254 } 255 256 /** 257 * Reading GroupViewWidgets attributes to extract widget children by parent 258 * widget name to variable 259 */ 260 private void readBindGroupWidgets(T : ViewComponent)(T viewComponent) { 261 foreach (symbolName; getSymbolsNamesByUDA!(T, bindGroupWidgets)) { 262 mixin("alias symbol = T." ~ symbolName ~ ";"); 263 264 foreach (uda; getUDAs!(symbol, bindGroupWidgets)) { 265 enum parentWidgetName = getNameFromAttribute!uda(symbolName); 266 267 Widget parentWidget = findWidgetByName!(parentWidgetName); 268 assert(parentWidget !is null); 269 270 // alias WidgetType = ForeachType!(typeof(viewComponent.buttons)); 271 // alias symbolType = typeof(viewComponent.buttons); 272 mixin("alias WidgetType = ForeachType!(typeof(viewComponent." ~ symbolName ~ "));"); 273 mixin("alias symbolType = typeof(viewComponent." ~ symbolName ~ ");"); 274 275 uint staticArrayIndex = 0; 276 277 foreach (Widget childWidget; parentWidget.children) { 278 // Select correct widget - if associatedWidget is null then get 279 // child widget. For example row in StackLayout has one single widget 280 // this widget will be associated because of this widget is our 281 // content and row is just wrapper 282 Widget targetWidget = childWidget.associatedWidget; 283 284 if (targetWidget is null) 285 targetWidget = childWidget; 286 287 auto typedTargetWidget = cast(WidgetType) targetWidget; 288 immutable t = "viewComponent." ~ symbolName ~ " ~= typedTargetWidget;"; 289 290 static if (is(StaticArrayTypeOf!symbolType)) { 291 // viewComponent.buttons[staticArrayIndex] = typedTargetWidget; 292 mixin("viewComponent." ~ symbolName ~ "[staticArrayIndex] = typedTargetWidget;"); 293 } else { 294 // viewComponent.buttons ~= typedTargetWidget; 295 mixin("viewComponent." ~ symbolName ~ " ~= typedTargetWidget;"); 296 } 297 298 ++staticArrayIndex; 299 } 300 } 301 } 302 } 303 304 private void readShortcutsAttributes(T : ViewComponent)(T viewComponent) { 305 foreach (symbolName; getSymbolsNamesByUDA!(T, shortcut)) { 306 // alias symbol = T.shortcutAction; 307 mixin("alias symbol = T." ~ symbolName ~ ";"); 308 309 foreach (uda; getUDAs!(symbol, shortcut)) { 310 if (uda.retrieve) { 311 // shortcuts.attachByPath(shortcutPath, &viewComponent.shortcutAction); 312 view.shortcuts.attachByPath(uda.value, &mixin("viewComponent." ~ symbolName)); 313 } else { 314 // shortcuts.attach(shortcutPath, &viewComponent.shortcutAction); 315 view.shortcuts.attach(uda.value, &mixin("viewComponent." ~ symbolName)); 316 } 317 } 318 } 319 } 320 }