1 module evael.graphics.gui.controls.Control; 2 3 import std.conv; 4 import std.typecons; 5 6 public 7 { 8 import derelict.nanovg.nanovg; 9 10 import evael.graphics.gui.controls.ContextMenuStrip; 11 import evael.graphics.gui.Theme; 12 import evael.graphics.gui.State; 13 import evael.graphics.gui.Icons; 14 15 import evael.system.Input; 16 } 17 18 import evael.graphics.gui.animations.Animation; 19 import evael.graphics.gui.animations.AnimationSet; 20 21 import evael.graphics.Font; 22 import evael.graphics.Texture; 23 24 import evael.utils.Math; 25 import evael.utils.Size; 26 import evael.utils.Rectangle; 27 28 abstract class Control 29 { 30 enum Dock : ubyte 31 { 32 None, 33 Fill, 34 Left, 35 Bottom, 36 Right, 37 Top, 38 } 39 40 protected alias OnClickEvent = void delegate(Control sender); 41 protected alias OnDragEvent = void delegate(Control sender, vec2 mousePosition); 42 protected alias OnDropEvent = void delegate(Control sender, vec2 mousePosition); 43 44 protected OnClickEvent m_onClickEvent; 45 protected OnClickEvent m_onDoubleClickEvent; 46 protected OnDragEvent m_onDragEvent; 47 protected OnDropEvent m_onDropEvent; 48 49 /// NanoVG context 50 public NVGcontext* m_nvg; 51 52 /// Control id 53 private uint m_id; 54 55 /// Control theme 56 protected Theme m_theme; 57 58 /// Parent control 59 protected Control m_parent; 60 61 /// Control name, used in GuiManager when loading themes 62 protected string m_name; 63 64 /// Control position 65 protected vec2 m_position; 66 67 /// Control real position for borders in shader 68 protected vec2 m_realPosition; 69 70 /// Control position before drag and drop 71 protected vec2 m_positionBeforeDragAndDrop; 72 73 /// Control size 74 protected Size!int m_size; 75 76 /// Control texture 77 protected Texture m_texture; 78 79 /// Is control under the mouse ? 80 protected bool m_hasFocus; 81 82 /// Is control displayed ? 83 protected bool m_isVisible; 84 85 /// Is control enabled ? 86 protected bool m_isEnabled; 87 88 /// Is control focusable ? 89 protected bool m_isFocusable; 90 91 /// Control has been initialized ? 92 protected bool m_initialized; 93 94 /// Control can be moved with mouse ? 95 protected bool m_movable; 96 97 /// Control opacity 98 protected ubyte m_opacity; 99 100 /// Dock 101 protected Dock m_dock; 102 103 /// Control tooltip 104 protected wstring m_tooltipText; 105 106 /// Texture coords 107 protected Rectangle!float m_textureCoords; 108 109 /// Last click tick for button double click detection 110 private long m_lastClickTick; 111 112 /// ContextMenu for this control 113 protected ContextMenuStrip m_contextMenu; 114 115 /// Last mouse position 116 protected vec2 m_mousePosition; 117 118 /// Current fill color 119 protected Color m_fillColor; 120 121 /// Clicked buttons 122 protected bool[MouseButton] m_mouseButtonsStates; 123 124 /// Last mouse button clicked 125 protected MouseButton m_lastClickedMouseButton; 126 127 /// Mouse button used to drag current control 128 protected Nullable!MouseButton m_draggingMouseButton; 129 130 /// z-index 131 protected uint m_zIndex; 132 133 /// Control animations 134 protected AnimationSet m_animationSet; 135 136 137 public this(in vec2 position, in Size!int size) nothrow 138 { 139 this.m_position = position; 140 this.m_size = size; 141 142 this.m_hasFocus = false; 143 this.m_isVisible = true; 144 this.m_isEnabled = true; 145 this.m_isFocusable = false; 146 this.m_movable = false; 147 148 this.m_opacity = 255; 149 150 this.m_textureCoords = Rectangle!float(0, 0, 0, 0); 151 152 this.m_mouseButtonsStates = [MouseButton.Left : false, MouseButton.Right : false]; 153 this.m_draggingMouseButton = Nullable!MouseButton(); 154 } 155 156 public void update(in float deltaTime) 157 { 158 if (this.m_animationSet !is null) 159 { 160 this.m_animationSet.update(deltaTime); 161 } 162 } 163 164 /** 165 * Draws the control 166 */ 167 public void draw(in float deltaTime) 168 { 169 if (this.m_theme.background.type == Background.Type.Transparent) 170 { 171 return; 172 } 173 174 immutable x = this.m_realPosition.x; 175 immutable y = this.m_realPosition.y; 176 immutable w = this.m_size.width; 177 immutable h = this.m_size.height; 178 179 immutable cornerRadius = this.m_theme.cornerRadius; 180 181 auto vg = this.m_nvg; 182 183 NVGpaint shadowPaint; 184 NVGpaint headerPaint; 185 186 // Control 187 nvgSave(vg); 188 189 nvgTranslate(vg, x, y); 190 // nvgScale(vg, 2, 2); 191 192 if(this.m_theme.background.type == Background.Type.Solid) 193 { 194 nvgBeginPath(vg); 195 nvgRoundedRect(vg, 0, 0, w, h, cornerRadius); 196 nvgFillColor(vg, this.m_fillColor.asNvg); 197 nvgFill(vg); 198 199 if (this.m_theme.drawDropShadow) 200 { 201 // Drop shadow 202 shadowPaint = nvgBoxGradient(vg, 0, 0, w, h, cornerRadius, 10, 203 nvgRGBA(0, 0, 0, this.m_opacity), nvgRGBA(0, 0, 0, 0)); 204 205 nvgBeginPath(vg); 206 nvgRect(vg, -10, -10, w + 20, h + 20); 207 nvgRoundedRect(vg, 0, 0, w, h, cornerRadius); 208 nvgPathWinding(vg, NVGsolidity.NVG_HOLE); 209 nvgFillPaint(vg, shadowPaint); 210 nvgFill(vg); 211 } 212 } 213 214 // Border 215 if (this.m_theme.borderType == Theme.BorderType.Solid) 216 { 217 nvgBeginPath(vg); 218 nvgRoundedRect(vg, 0, 0, w, h, cornerRadius); 219 nvgStrokeColor(vg, this.m_theme.borderColor.asNvg); 220 nvgStroke(vg); 221 } 222 223 nvgRestore(vg); 224 } 225 226 /** 227 * Event called on mouse button click 228 * Params: 229 * mouseButton : mouse button 230 * mousePosition : mouse position 231 */ 232 public void onMouseClick(in MouseButton mouseButton, in ref vec2 mousePosition) 233 { 234 import evael.utils.Functions; 235 236 this.m_mouseButtonsStates[mouseButton] = true; 237 this.m_lastClickedMouseButton = mouseButton; 238 239 immutable long currentAppTick = timeSinceProgramStarted().total!"msecs"; 240 241 // We check for fast double click 242 if (currentAppTick - this.m_lastClickTick < 400) 243 { 244 // Avoid triple click 245 this.m_lastClickTick = 0; 246 247 if (this.m_onDoubleClickEvent !is null) 248 { 249 this.m_onDoubleClickEvent(this); 250 } 251 252 return; 253 } 254 255 this.m_lastClickTick = currentAppTick; 256 } 257 258 /** 259 * Event called on mouse button release 260 * Params: 261 * mouseButton : mouse button 262 */ 263 public void onMouseUp(in MouseButton mouseButton) 264 { 265 this.m_mouseButtonsStates[mouseButton] = false; 266 267 if (this.m_onClickEvent !is null && this.m_isEnabled) 268 { 269 this.m_onClickEvent(this); 270 } 271 } 272 273 /** 274 * Event called when mouse enters in control's rect 275 * Params: 276 * mousePosition : mouse position 277 */ 278 public void onMouseMove(in ref vec2 mousePosition) 279 { 280 this.m_mousePosition = mousePosition; 281 } 282 283 /** 284 * Event called when mouse leaves control's rect 285 */ 286 public void onMouseLeave() 287 { 288 289 } 290 291 /** 292 * Event called on character input 293 * Params: 294 * text : 295 */ 296 public void onText(in int key) 297 { 298 299 } 300 301 /** 302 * Event called on key input 303 * Params: 304 * key : pressed key 305 */ 306 public void onKey(in int key) 307 { 308 309 } 310 311 /** 312 * Event called when control has been dragged 313 * Params: 314 * mousePosition : mouse position 315 */ 316 public void onDrag(in ref vec2 mousePosition) 317 { 318 // We set drag and drop mousebutton 319 if (this.m_draggingMouseButton.isNull) 320 { 321 this.m_draggingMouseButton = Nullable!MouseButton(this.m_lastClickedMouseButton); 322 } 323 324 if (this.m_onDragEvent !is null) 325 { 326 this.m_onDragEvent(this, mousePosition); 327 } 328 } 329 330 /** 331 * Event called when control has been dropped 332 * Params: 333 * mousePosition : mouse position 334 */ 335 public void onDrop(in ref vec2 mousePosition) 336 { 337 // We unset drag and drop mousebutton 338 if (this.m_draggingMouseButton.isNull) 339 { 340 this.m_draggingMouseButton = Nullable!MouseButton(); 341 } 342 343 if (this.m_onDropEvent !is null) 344 { 345 this.m_onDropEvent(this, mousePosition); 346 } 347 } 348 349 /** 350 * Control gains focus 351 */ 352 public void onFocus() 353 { 354 355 } 356 357 /** 358 * Control lose focus 359 */ 360 public void onUnfocus() 361 { 362 363 } 364 365 /** 366 * Condition for drag and drop of this control 367 */ 368 public bool canBeDragged() nothrow 369 { 370 return this.m_movable; 371 } 372 373 /** 374 * Initializes control 375 */ 376 public void initialize() 377 { 378 this.m_initialized = true; 379 380 this.m_fillColor = this.m_theme.background.colorStateList.normal; 381 382 if (this.m_parent !is null) 383 { 384 this.m_realPosition = this.m_position + this.m_parent.m_realPosition; 385 } 386 else 387 { 388 this.m_realPosition = this.m_position; 389 } 390 391 if (this.m_contextMenu !is null) 392 { 393 this.m_contextMenu.initialize(); 394 } 395 396 // We create a nvg id for the texture 397 if (this.m_texture !is null) 398 { 399 if (this.m_nvg !is null && this.m_texture.nvgId == 0) 400 { 401 this.m_texture.nvgId = nvglCreateImageFromHandleGL3(this.m_nvg, 402 this.m_texture.id, this.m_texture.size.width, 403 this.m_texture.size.height, 0); 404 } 405 } 406 } 407 408 /** 409 * Displays the control 410 */ 411 public void show() nothrow @nogc 412 { 413 this.m_isVisible = true; 414 } 415 416 /** 417 * Hides the control 418 */ 419 public void hide() nothrow @nogc 420 { 421 this.m_isVisible = false; 422 } 423 424 /** 425 * Gives focus to the control 426 */ 427 public void focus() 428 { 429 this.m_hasFocus = true; 430 431 this.onFocus(); 432 } 433 434 /** 435 * Removes focus from the control 436 */ 437 public void unfocus() 438 { 439 this.m_hasFocus = false; 440 441 this.onUnfocus(); 442 } 443 444 /** 445 * Enables the control 446 */ 447 public void enable() 448 { 449 this.m_isEnabled = true; 450 } 451 452 /** 453 * Disables the control 454 */ 455 public void disable() 456 { 457 this.m_isEnabled = false; 458 this.switchState!(State.Disabled); 459 } 460 461 /** 462 * Switchs control state 463 */ 464 public void switchState(State state)() nothrow 465 { 466 this.m_fillColor = this.m_theme.background.colorStateList.fromEnum!(state)(); 467 } 468 469 /** 470 * Starts a single animation 471 * Params: 472 * animation : 473 */ 474 public void startAnimation(Animation animation) 475 { 476 auto animationSet = new AnimationSet(); 477 animationSet.add(animation); 478 479 this.startAnimation(animationSet); 480 } 481 482 /** 483 * Starts a set of animations 484 * Params: 485 * animationSet : animations 486 */ 487 public void startAnimation(AnimationSet animationSet) 488 { 489 this.m_animationSet = animationSet; 490 this.m_animationSet.control = this; 491 492 this.m_animationSet.onSequenceEndEvent = () { 493 this.m_animationSet.dispose(); 494 this.m_animationSet = null; 495 }; 496 } 497 498 @property public void opacity(in ubyte value) @nogc 499 { 500 this.m_fillColor.a = value; 501 this.m_opacity = value; 502 503 if (this.theme !is null) 504 { 505 this.m_theme.borderColor.a = value; 506 this.m_theme.fontColor.a = value; 507 } 508 } 509 510 /** 511 * Properties 512 */ 513 @property 514 { 515 public void nvg(NVGcontext* nvg) nothrow @nogc 516 { 517 this.m_nvg = nvg; 518 } 519 520 public Theme theme() nothrow @nogc 521 { 522 return this.m_theme; 523 } 524 525 public void theme(Theme value) nothrow @nogc 526 { 527 this.m_theme = value; 528 } 529 530 public uint id() const nothrow @nogc 531 { 532 return this.m_id; 533 } 534 535 public void id(in uint value) nothrow @nogc 536 { 537 this.m_id = value; 538 } 539 540 public ubyte opacity() nothrow 541 { 542 return this.m_opacity; 543 } 544 545 public ref const(vec2) position() const nothrow @nogc 546 { 547 return this.m_position; 548 } 549 550 public void position(in vec2 value) nothrow @nogc 551 { 552 this.m_position = value; 553 } 554 555 public ref const(vec2) realPosition() const nothrow @nogc 556 { 557 return this.m_realPosition; 558 } 559 560 public void realPosition(in vec2 value) nothrow @nogc 561 { 562 this.m_realPosition = value; 563 } 564 565 public ref const(vec2) positionBeforeDragAndDrop() const nothrow @nogc 566 { 567 return this.m_positionBeforeDragAndDrop; 568 } 569 570 public void positionBeforeDragAndDrop(in vec2 value) nothrow @nogc 571 { 572 this.m_positionBeforeDragAndDrop = value; 573 } 574 575 public ref const(Size!int) size() const nothrow @nogc 576 { 577 return this.m_size; 578 } 579 580 public void size(in Size!int value) nothrow @nogc 581 { 582 this.m_size = value; 583 } 584 585 public Texture texture() nothrow @nogc 586 { 587 return this.m_texture; 588 } 589 590 public void texture(Texture value) nothrow @nogc 591 { 592 this.m_texture = value; 593 594 // Default texcoords : full texture 595 if (this.m_texture !is null) 596 { 597 if (this.m_nvg !is null && this.m_texture.nvgId == 0) 598 { 599 this.m_texture.nvgId = nvglCreateImageFromHandleGL3(this.m_nvg, 600 this.m_texture.id, this.m_texture.size.width, 601 this.m_texture.size.height, 0); 602 } 603 604 this.m_textureCoords = Rectanglef(0, 0, 605 this.m_texture.size.width, this.m_texture.size.height); 606 } 607 } 608 609 public bool hasFocus() const nothrow @nogc 610 { 611 return this.m_hasFocus; 612 } 613 614 public bool isVisible() const nothrow @nogc 615 { 616 return this.m_isVisible; 617 } 618 619 public void isVisible(in bool value) nothrow @nogc 620 { 621 this.m_isVisible = value; 622 } 623 624 public Control parent() nothrow @nogc 625 { 626 return this.m_parent; 627 } 628 629 public void parent(Control value) nothrow @nogc 630 { 631 this.m_parent = value; 632 } 633 634 public bool isClicked() const nothrow 635 { 636 return this.m_mouseButtonsStates[MouseButton.Left] 637 || this.m_mouseButtonsStates[MouseButton.Right]; 638 } 639 640 public bool isEnabled() const nothrow @nogc 641 { 642 return this.m_isEnabled; 643 } 644 645 public bool movable() const nothrow @nogc 646 { 647 return this.m_movable; 648 } 649 650 public void movable(in bool value) nothrow @nogc 651 { 652 this.m_movable = value; 653 } 654 655 public bool isFocusable() const nothrow @nogc 656 { 657 return this.m_isFocusable; 658 } 659 660 public void isFocusable(in bool value) nothrow @nogc 661 { 662 this.m_isFocusable = value; 663 } 664 665 public Dock dock() const nothrow @nogc 666 { 667 return this.m_dock; 668 } 669 670 public void dock(Dock value) nothrow @nogc 671 { 672 this.m_dock = value; 673 } 674 675 public void onClickEvent(OnClickEvent callback) nothrow @nogc 676 { 677 this.m_onClickEvent = callback; 678 } 679 680 public void onDoubleClickEvent(OnClickEvent callback) nothrow @nogc 681 { 682 this.m_onDoubleClickEvent = callback; 683 } 684 685 public wstring tooltipText() const nothrow @nogc 686 { 687 return this.m_tooltipText; 688 } 689 690 public void tooltipText(in wstring tooltip) nothrow @nogc 691 { 692 this.m_tooltipText = tooltip; 693 } 694 695 public void tooltipText(in string tooltip) 696 { 697 this.m_tooltipText = to!wstring(tooltip); 698 } 699 700 public ContextMenuStrip contextMenu() nothrow @nogc 701 { 702 return this.m_contextMenu; 703 } 704 705 public void onDragEvent(OnDragEvent callback) nothrow @nogc 706 { 707 this.m_onDragEvent = callback; 708 } 709 710 public void onDropEvent(OnDropEvent callback) nothrow @nogc 711 { 712 this.m_onDropEvent = callback; 713 } 714 715 public void textureCoords(in Rectangle!float value) nothrow @nogc 716 { 717 this.m_textureCoords = value; 718 } 719 720 public string name() const nothrow @nogc 721 { 722 return this.m_name; 723 } 724 725 public void name(in string value) nothrow @nogc 726 { 727 this.m_name = value; 728 } 729 730 public const(bool[MouseButton]) mouseButtonsStates() const nothrow @nogc 731 { 732 return this.m_mouseButtonsStates; 733 } 734 735 public MouseButton lastClickedMouseButton() const nothrow @nogc 736 { 737 return this.m_lastClickedMouseButton; 738 } 739 740 public Nullable!MouseButton draggingMouseButton() const nothrow @nogc 741 { 742 return this.m_draggingMouseButton; 743 } 744 745 public ref const(Color) fillColor() const nothrow @nogc 746 { 747 return this.m_fillColor; 748 } 749 750 public void fillColor(in Color value) nothrow @nogc 751 { 752 this.m_fillColor = value; 753 } 754 755 public uint zIndex() const nothrow @nogc 756 { 757 return this.m_zIndex; 758 } 759 760 public void zIndex(in uint value) nothrow @nogc 761 { 762 this.m_zIndex = value; 763 } 764 } 765 }