1 /** 2 * Copyright © Novelate 2020 3 * License: MIT (https://github.com/Novelate/NovelateEngine/blob/master/LICENSE) 4 * Author: Jacob Jensen (bausshf) 5 * Website: https://novelate.com/ 6 * ------ 7 * Novelate is a free and open-source visual novel engine and framework written in the D programming language. 8 * It can be used freely for both personal and commercial projects. 9 * ------ 10 * Module Description: 11 * A layer consists of multiple components that have equal importance in terms of rendering and event delegation. 12 */ 13 module novelate.screens.layer; 14 15 import novelate.ui.component; 16 import novelate.buildstate; 17 18 /// A layer of components. 19 final class Layer 20 { 21 private: 22 /// The screen name. 23 string _screenName; 24 /// The components. 25 Component[string] _components; 26 /// The current mouse position. 27 FloatVector _mousePosition; 28 29 public: 30 final: 31 package (novelate) 32 { 33 /** 34 * Creates a new layer. 35 * Params: 36 * screenName = The name of the screen. 37 */ 38 this(string screenName) 39 { 40 _screenName = screenName; 41 } 42 } 43 44 @property 45 { 46 /// Gets the amount of components within the layer. 47 size_t length() { return _components ? _components.length : 0; } 48 49 /// Gets the screen name. 50 string screenName() { return _screenName; } 51 } 52 53 static if (isManualMemory) 54 { 55 /// Cleans the layer and all its components. 56 void clean() 57 { 58 foreach (component; _components) 59 { 60 component.clean(); 61 } 62 } 63 } 64 65 66 /// Clears the layer for its components. 67 void clear() 68 { 69 if (!_components) 70 { 71 return; 72 } 73 74 static if (isManualMemory) 75 { 76 clean(); 77 } 78 79 _components.clear(); 80 } 81 82 /** 83 * Adds a component to the layer. 84 * Params: 85 * component = The component to add. 86 * name = The name of the component. This must be unique for the layer, otherwise an existing component will be replaced. 87 * cleanOldComponent = Boolean determining whether the old component's memory should be cleaned or not. Only used if "NOVELATE_MANUALMEMORY" is enabled. 88 */ 89 void addComponent(Component component, string name, bool cleanOldComponent = true) 90 { 91 static if (isManualMemory) 92 { 93 if (cleanOldComponent && _components && name in _components) 94 { 95 _components[name].clean(); 96 } 97 } 98 99 _components[name] = component; 100 } 101 102 /** 103 * Removes a component from the layer. 104 * Params: 105 * name = The name of the component to remove. 106 * cleanOldComponent = Boolean determining whether the component's memory should be cleaned or not. Only used if "NOVELATE_MANUALMEMORY" is enabled. 107 */ 108 void removeComponent(string name, bool cleanOldComponent = true) 109 { 110 if (!_components) 111 { 112 return; 113 } 114 115 static if (isManualMemory) 116 { 117 if (cleanOldComponent && _components && name in _components) 118 { 119 _components[name].clean(); 120 } 121 } 122 123 _components.remove(name); 124 } 125 126 /** 127 * Gets a component from the layer. You msut cast to the original component type for it to be useful. 128 * Params: 129 * name = The name of the component to get. 130 * Returns: 131 * The component if found, null otherwise. 132 */ 133 Component getComponent(string name) 134 { 135 if (!_components) 136 { 137 return null; 138 } 139 140 return _components.get(name, null); 141 } 142 143 /** 144 * Will render the components of the layer to the given window. 145 * Params: 146 * window = The window to render the components at. 147 */ 148 void render(ExternalWindow window) 149 { 150 if (!window) 151 { 152 return; 153 } 154 155 if (!_components) 156 { 157 return; 158 } 159 160 foreach (k,v; _components) 161 { 162 v.render(window); 163 } 164 } 165 166 /** 167 * Refreshes the layer and its components with a given width and height. Usually the resolution width and height. 168 * Params: 169 * width = The width to refresh with. 170 * height = The height to refresh with. 171 */ 172 void refresh(size_t width, size_t height) 173 { 174 if (!_components) 175 { 176 return; 177 } 178 179 foreach (k,v; _components) 180 { 181 v.refresh(width, height); 182 } 183 } 184 185 /** 186 * Handles key press events and delegates them to its components. 187 * Params: 188 * key = The key pressed. 189 * stopEvent = (ref) Boolean determining whether the event handling should no longer be delegated. 190 */ 191 void keyPress(KeyboardKey key, ref bool stopEvent) 192 { 193 if (!_components) 194 { 195 return; 196 } 197 198 foreach (k,v; _components) 199 { 200 import novelate.state : _activeScreenName; 201 202 if (_screenName != _activeScreenName) 203 { 204 break; 205 } 206 207 if (v.globalKeyPress) 208 { 209 v.globalKeyPress(key, stopEvent); 210 211 if (stopEvent) 212 { 213 break; 214 } 215 } 216 217 if (v.keyPress && v.intersect(_mousePosition)) 218 { 219 v.keyPress(key, stopEvent); 220 221 if (stopEvent) 222 { 223 break; 224 } 225 } 226 } 227 } 228 229 /** 230 * Handles key release events and delegates them to its components. 231 * Params: 232 * key = The key released. 233 * stopEvent = (ref) Boolean determining whether the event handling should no longer be delegated. 234 */ 235 void keyRelease(KeyboardKey key, ref bool stopEvent) 236 { 237 if (!_components) 238 { 239 return; 240 } 241 242 foreach (k,v; _components) 243 { 244 import novelate.state : _activeScreenName; 245 246 if (_screenName != _activeScreenName) 247 { 248 break; 249 } 250 251 if (v.globalKeyRelease) 252 { 253 v.globalKeyRelease(key, stopEvent); 254 255 if (stopEvent) 256 { 257 break; 258 } 259 } 260 261 if (v.keyRelease && v.intersect(_mousePosition)) 262 { 263 v.keyRelease(key, stopEvent); 264 265 if (stopEvent) 266 { 267 break; 268 } 269 } 270 } 271 } 272 273 /** 274 * Handles mouse button press events and delegates them to its components. 275 * Params: 276 * button = The mouse button pressed. 277 * stopEvent = (ref) Boolean determining whether the event handling should no longer be delegated. 278 */ 279 void mousePress(MouseButton button, ref bool stopEvent) 280 { 281 if (!_components) 282 { 283 return; 284 } 285 286 foreach (k,v; _components) 287 { 288 import novelate.state : _activeScreenName; 289 290 if (_screenName != _activeScreenName) 291 { 292 break; 293 } 294 295 if (v.globalMousePress) 296 { 297 v.globalMousePress(button, stopEvent); 298 299 if (stopEvent) 300 { 301 break; 302 } 303 } 304 305 auto mousePosX = ExternalEventState.mouseMoveEvent.x; 306 auto mousePosY = ExternalEventState.mouseMoveEvent.y; 307 308 if (v.mousePress && v.intersect(FloatVector(mousePosX, mousePosY))) 309 { 310 v.mousePress(button, stopEvent); 311 312 if (stopEvent) 313 { 314 break; 315 } 316 } 317 } 318 } 319 320 /** 321 * Handles mouse button release events and delegates them to its components. 322 * Params: 323 * button = The mouse button released. 324 * stopEvent = (ref) Boolean determining whether the event handling should no longer be delegated. 325 */ 326 void mouseRelease(MouseButton button, ref bool stopEvent) 327 { 328 import novelate.state; 329 330 if (!_components) 331 { 332 return; 333 } 334 335 foreach (k,v; _components) 336 { 337 import novelate.state : _activeScreenName; 338 339 if (_screenName != _activeScreenName) 340 { 341 break; 342 } 343 344 if (v.globalMouseRelease) 345 { 346 v.globalMouseRelease(button, stopEvent); 347 348 if (stopEvent) 349 { 350 break; 351 } 352 } 353 354 auto mousePosX = ExternalEventState.mouseMoveEvent.x; 355 auto mousePosY = ExternalEventState.mouseMoveEvent.y; 356 357 if (v.mouseRelease && v.intersect(FloatVector(mousePosX, mousePosY))) 358 { 359 v.mouseRelease(button, stopEvent); 360 361 if (stopEvent) 362 { 363 break; 364 } 365 } 366 } 367 } 368 369 /** 370 * Handles mouse move events and delegates them to its components. 371 * Params: 372 * mousePosition = The mouse position. 373 * stopEvent = (ref) Boolean determining whether the event handling should no longer be delegated. 374 */ 375 void mouseMove(FloatVector mousePosition, ref bool stopEvent) 376 { 377 _mousePosition = mousePosition; 378 379 if (!_components) 380 { 381 return; 382 } 383 384 foreach (k,v; _components) 385 { 386 import novelate.state : _activeScreenName; 387 388 if (_screenName != _activeScreenName) 389 { 390 break; 391 } 392 393 if (v.globalMouseMove) 394 { 395 v.globalMouseMove(_mousePosition, stopEvent); 396 397 if (stopEvent) 398 { 399 break; 400 } 401 } 402 403 if (v.mouseMove && v.intersect(_mousePosition)) 404 { 405 v.mouseMove(_mousePosition, stopEvent); 406 407 if (stopEvent) 408 { 409 break; 410 } 411 } 412 } 413 } 414 }