1 module evael.graphics.gui.controls.Container; 2 3 import std.math; 4 5 public import evael.graphics.gui.controls.Control; 6 import evael.graphics.GUI; 7 8 import evael.utils.Math; 9 import evael.utils.Size; 10 import evael.utils.Rectangle; 11 import evael.utils.Color; 12 13 abstract class Container : Control 14 { 15 /// Children list 16 protected Control[] m_controls; 17 18 /// Current focused child 19 protected Control m_focusedControl; 20 protected Control m_controlUnderMouse; 21 22 /// Current selected control tooltip 23 protected Tooltip m_tooltip; 24 25 /// Container should be resized if a child control is bigger ? 26 protected bool m_autoResize; 27 28 protected ScrollBar m_verticalScrollBar, m_horizontalScrollBar; 29 30 mixin(ControlGetter!("Button")); 31 mixin(ControlGetter!("ListBox")); 32 mixin(ControlGetter!("TextBox")); 33 mixin(ControlGetter!("Window")); 34 mixin(ControlGetter!("PictureBox")); 35 mixin(ControlGetter!("TextArea")); 36 mixin(ControlGetter!("TextBlock")); 37 mixin(ControlGetter!("ProgressBar")); 38 39 public this()(in auto ref vec2 position, in auto ref Size!int size) 40 { 41 super(position, size); 42 43 this.m_autoResize = true; 44 45 this.m_tooltip = new Tooltip(); 46 this.m_tooltip.id = int.max; 47 this.m_tooltip.hide(); 48 49 this.addChild(this.m_tooltip); 50 } 51 52 public override void update(in float deltaTime) 53 { 54 super.update(deltaTime); 55 56 foreach(control; this.m_controls) 57 { 58 control.update(deltaTime); 59 } 60 } 61 62 /** 63 * Draws the container with children 64 */ 65 public override void draw(in float deltaTime) 66 { 67 if(!this.m_isVisible) 68 { 69 return; 70 } 71 72 super.draw(deltaTime); 73 74 foreach(i; 1..this.m_controls.length) 75 { 76 this.m_controls[i].draw(deltaTime); 77 } 78 79 this.m_tooltip.draw(deltaTime); 80 } 81 82 /** 83 * Event called on mouse button click 84 * Params: 85 * mouseButton : mouse button 86 * mousePosition : mouse position 87 */ 88 public override void onMouseClick(in MouseButton mouseButton, in ref vec2 mousePosition) 89 { 90 super.onMouseClick(mouseButton, mousePosition); 91 92 if(this.m_controlUnderMouse !is null && this.m_controlUnderMouse.isEnabled) 93 { 94 this.m_controlUnderMouse.onMouseClick(mouseButton, mousePosition); 95 } 96 } 97 98 /** 99 * Event called on mouse button release 100 * Params: 101 * mouseButton : mouse button 102 */ 103 public override void onMouseUp(in MouseButton mouseButton) 104 { 105 super.onMouseUp(mouseButton); 106 107 if(this.m_controlUnderMouse !is null && this.m_controlUnderMouse.isEnabled) 108 { 109 this.m_controlUnderMouse.onMouseUp(mouseButton); 110 111 if(this.m_controlUnderMouse.isFocusable) 112 { 113 this.focusControl(this.m_controlUnderMouse); 114 } 115 } 116 } 117 118 /** 119 * Event called when mouse enters in control's rect 120 * Params: 121 * mousePosition : mouse position 122 */ 123 public override void onMouseMove(in ref vec2 position) 124 { 125 super.onMouseMove(position); 126 127 if(!this.m_isVisible) 128 return; 129 130 auto lastControlUnderMouse = this.m_controlUnderMouse; 131 132 this.m_controlUnderMouse = this.getControlUnderMouse(position); 133 134 if(this.m_controlUnderMouse !is null) 135 { 136 this.m_controlUnderMouse.onMouseMove(position); 137 } 138 139 // We unfocus last focused item 140 if(lastControlUnderMouse !is null && lastControlUnderMouse != this.m_controlUnderMouse) 141 { 142 lastControlUnderMouse.onMouseLeave(); 143 } 144 } 145 146 /** 147 * Event called when mouse leaves control's rect 148 */ 149 public override void onMouseLeave() 150 { 151 super.onMouseLeave(); 152 153 // Mouse is leaving container, we hide the tooltip 154 this.m_tooltip.hide(); 155 156 if(this.m_controlUnderMouse !is null && this.m_controlUnderMouse.isEnabled) 157 { 158 this.m_controlUnderMouse.onMouseLeave(); 159 } 160 } 161 162 /** 163 * Event called on character input 164 * Params: 165 * text : 166 */ 167 public override void onText(in int key) 168 { 169 super.onText(key); 170 171 if(this.m_focusedControl !is null && this.m_focusedControl.isEnabled) 172 { 173 this.m_focusedControl.onText(key); 174 } 175 } 176 177 178 /** 179 * Event called on key input 180 * Params: 181 * key : pressed key 182 */ 183 public override void onKey(in int key) 184 { 185 super.onKey(key); 186 187 if(this.m_focusedControl !is null && this.m_focusedControl.isEnabled) 188 { 189 this.m_focusedControl.onKey(key); 190 } 191 } 192 193 /** 194 * Adds child control 195 * Params: 196 * control : control to add 197 */ 198 public void addChild(Control control) 199 { 200 control.parent = this; 201 202 // We check if item is added at runtime 203 if(this.m_initialized) 204 { 205 control.nvg = this.m_nvg; 206 207 if(control.name in this.m_theme.subThemes) 208 { 209 // This control have a custom theme 210 control.theme = this.m_theme.subThemes[control.name].copy(); 211 } 212 else 213 { 214 control.theme = this.m_theme.copy(); 215 } 216 217 control.initialize(); 218 this.updateChildPosition(control); 219 220 } 221 222 this.m_controls ~= control; 223 224 import std.algorithm : sort; 225 226 this.m_controls.sort!((a, b) => a.zIndex < b.zIndex); 227 } 228 229 /** 230 * Returns child control by id 231 * Params: 232 * id : id 233 */ 234 public T getControl(T : Control)(in uint id) nothrow @nogc 235 { 236 foreach(control; this.m_controls) 237 { 238 if(control.id == id) 239 { 240 return cast(T)control; 241 } 242 } 243 244 return null; 245 } 246 247 /** 248 * Returns the control under the mouse 249 */ 250 private Control getControlUnderMouse(in ref vec2 position) nothrow 251 { 252 foreach_reverse(control; this.m_controls) 253 { 254 if(!control.isVisible) 255 continue; 256 257 immutable rect = Rectangle!float(control.realPosition.x, control.realPosition.y, control.size); 258 259 if(rect.isIn(position)) 260 { 261 return control; 262 } 263 } 264 265 return null; 266 } 267 268 /** 269 * Initializes control 270 */ 271 public override void initialize() 272 { 273 super.initialize(); 274 275 // If we add this container directly in GuiManager, we need to do this 276 if(this.m_name in this.m_theme.subThemes) 277 { 278 this.m_theme = this.m_theme.subThemes[this.m_name].copy(); 279 } 280 281 foreach(childControl; this.m_controls) 282 { 283 childControl.nvg = this.m_nvg; 284 285 // write("Searching theme for ", childControl.name); 286 287 if(childControl.name in this.m_theme.subThemes) 288 { 289 // writeln("... found! ", this.m_theme.subThemes[childControl.name].name); 290 291 // This control have a custom theme 292 childControl.theme = this.m_theme.subThemes[childControl.name].copy(); 293 } 294 else 295 { 296 // writeln("... not found!"); 297 298 auto themePtr = this.m_theme.parent; 299 300 while(themePtr) 301 { 302 if(childControl.name in themePtr.subThemes) 303 { 304 childControl.theme = themePtr.subThemes[childControl.name].copy(); 305 break; 306 } 307 308 themePtr = themePtr.parent; 309 } 310 311 if(!themePtr) 312 { 313 childControl.theme = this.m_theme.copy(); 314 } 315 316 // writeln("Child theme is ", childControl.theme.name); 317 } 318 319 childControl.initialize(); 320 321 if(this.m_autoResize) 322 { 323 if(childControl.size.width > this.m_size.width) 324 { 325 this.m_size.width = childControl.size.width; 326 } 327 328 if(childControl.size.height > this.m_size.height) 329 { 330 this.m_size.height = childControl.size.height; 331 } 332 } 333 334 this.updateChildPosition(childControl); 335 } 336 } 337 338 /** 339 * Resize the container if a child control is bigger 340 */ 341 public void reSize() 342 { 343 foreach(childControl; this.m_controls) 344 { 345 if(childControl.size.width > this.m_size.width) 346 { 347 this.m_size.width = childControl.size.width; 348 } 349 350 if(childControl.size.height > this.m_size.height) 351 { 352 this.m_size.height = childControl.size.height; 353 } 354 } 355 } 356 357 358 /** 359 * Updates child control position using dock property 360 * Params: 361 * childControl : child control 362 */ 363 protected void updateChildPosition(Control childControl) 364 { 365 switch(childControl.dock) 366 { 367 case Dock.Fill: 368 { 369 childControl.position = vec2(0, 0); 370 childControl.size = this.m_size; 371 break; 372 } 373 case Dock.Right: 374 { 375 childControl.position = vec2(this.m_size.width - childControl.size.width, childControl.position.y); 376 childControl.realPosition = this.m_realPosition + childControl.position; 377 378 childControl.size = Size!int(childControl.size.width, childControl.size.height); 379 break; 380 } 381 case Dock.Top: 382 { 383 childControl.position = vec2(0, 0); 384 childControl.realPosition = this.m_realPosition + childControl.position; 385 386 childControl.size = Size!int(childControl.size.width, childControl.size.height); 387 break; 388 } 389 case Dock.Bottom: 390 { 391 childControl.position = vec2(0, this.m_size.height - childControl.size.height); 392 childControl.realPosition = this.m_realPosition + childControl.position; 393 394 childControl.size = Size!int(this.m_size.width, childControl.size.height); 395 break; 396 } 397 default: 398 break; 399 } 400 } 401 402 403 /** 404 * Removes a control from control list 405 * Params: 406 * control : control to remove 407 */ 408 public void remove(Control toRemove) 409 { 410 import std.algorithm : remove; 411 412 foreach(i, control; this.m_controls) 413 { 414 if(control == toRemove) 415 { 416 this.m_controls = this.m_controls.remove(i); 417 return; 418 } 419 } 420 } 421 422 /** 423 * Gives focus to a control 424 */ 425 public void focusControl(Control control) 426 { 427 control.focus(); 428 429 this.m_focusedControl = control; 430 } 431 432 /** 433 * Removes focus from a control 434 */ 435 public void unfocusControl(Control control) 436 { 437 control.unfocus(); 438 439 this.m_focusedControl = null; 440 } 441 442 @property 443 public override void opacity(in ubyte value) @nogc 444 { 445 super.opacity = value; 446 447 foreach(control; this.m_controls) 448 { 449 control.opacity = value; 450 } 451 } 452 453 @property 454 { 455 public override ref const(vec2) realPosition() const nothrow @nogc 456 { 457 return this.m_realPosition; 458 } 459 460 public override void realPosition(in vec2 value) nothrow 461 { 462 // We do this because we need to calculate viewport's rect in Control class 463 super.realPosition = value; 464 465 foreach(childControl; this.m_controls) 466 { 467 childControl.realPosition = this.m_realPosition + childControl.position; 468 } 469 } 470 471 public Control[] controls() nothrow @nogc 472 { 473 return this.m_controls; 474 } 475 476 public Control focusedControl() nothrow @nogc 477 { 478 return this.m_focusedControl; 479 } 480 481 public Control controlUnderMouse() nothrow @nogc 482 { 483 return this.m_controlUnderMouse; 484 } 485 486 public Tooltip tooltip() nothrow @nogc 487 { 488 return this.m_tooltip; 489 } 490 } 491 492 template ControlGetter(string controlClass) 493 { 494 enum ControlGetter = 495 "public " ~ controlClass ~ " get" ~ controlClass ~ "(in uint id) nothrow @nogc 496 { 497 return this.getControl!(" ~ controlClass ~ ")(id); 498 }"; 499 } 500 }