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