1 /** 2 Gui Element 3 4 Copyright: (c) Enalye 2017 5 License: Zlib 6 Authors: Enalye 7 */ 8 9 module atelier.ui.gui_element; 10 11 import atelier.render, atelier.core, atelier.common, atelier.render; 12 import atelier.ui.gui_manager, atelier.ui.gui_overlay; 13 14 /// Alignment on the horizontal axis relative to its parent. 15 enum GuiAlignX { 16 left, 17 center, 18 right 19 } 20 21 /// Alignment on the vertical axis relative to its parent. 22 enum GuiAlignY { 23 top, 24 center, 25 bottom 26 } 27 28 /// Single state of a GUI. \ 29 /// Used with **addState**, **setState**, **doTransitionState** etc. 30 struct GuiState { 31 /// Position offset relative to its alignment. (0 = No modification) 32 Vec2f offset = Vec2f.zero; 33 /// Size scale of the GUI. (1 = Default) 34 Vec2f scale = Vec2f.one; 35 /// Color of the GUI. (White = Default) 36 Color color = Color.white; 37 /// Opacity of the GUI (1 = Default) 38 float alpha = 1f; 39 /// Blend of the canvas if present (Alpha blending = Default) 40 Blend blend = Blend.alpha; 41 /// Angle of the GUI. (0 = Default) 42 float angle = 0f; 43 /// Time (in seconds) to get to this state with **doTransitionState()**. 44 float time = .5f; 45 /// When fully in this state, **onCallback()** will be called with **callback**. 46 string callback; 47 /// The easing algorithm used to get to this state. 48 EasingFunction easing = &easeLinear; 49 } 50 51 /// Base class of all GUI elements. 52 class GuiElement { 53 private { 54 Canvas _canvas; 55 bool _hasCanvas; 56 } 57 58 package { 59 GuiElement[] _children; 60 Hint _hint; 61 bool _isRegistered = true; 62 bool _isLocked, _isMovable, _isHovered, _isClicked, _isSelected, 63 _hasFocus, _isInteractable = true, _hasEventHook; 64 Vec2f _position = Vec2f.zero, _size = Vec2f.zero, _anchor = Vec2f.half, 65 _padding = Vec2f.zero, _center = Vec2f.zero, _origin = Vec2f.zero; 66 GuiElement _callbackGuiElement; 67 string _callbackId; 68 69 //Iteration 70 bool _isIterating, _isWarping = true; 71 uint _idChildIterator; 72 Timer _iteratorTimer, _iteratorTimeOutTimer; 73 74 Vec2f _screenCoords; 75 GuiAlignX _alignX = GuiAlignX.left; 76 GuiAlignY _alignY = GuiAlignY.top; 77 78 //States 79 string _currentStateName = "default"; 80 GuiState _currentState, _targetState, _initState; 81 GuiState[string] _states; 82 Timer _timer; 83 } 84 85 package void setScreenCoords(Vec2f screenCoords) { 86 _screenCoords = screenCoords; 87 if (_hasCanvas && _canvas !is null) { 88 _center = _size / 2f; 89 _origin = Vec2f.zero; 90 } 91 else { 92 _center = screenCoords; 93 _origin = _center - _size / 2f; 94 } 95 } 96 97 void setCanvas(bool hasCanvas_, bool isSmooth = false) { 98 _hasCanvas = hasCanvas_; 99 if (_hasCanvas) { 100 if (_size.x > 2f && _size.y > 2f) { 101 _canvas = new Canvas(_size, isSmooth); 102 _canvas.position = _canvas.size / 2f; 103 } 104 else 105 _canvas = null; 106 } 107 else { 108 _canvas = null; 109 } 110 } 111 112 @property { 113 /// If set, the canvas used to be rendered on. 114 final Canvas canvas() { 115 return _canvas; 116 } 117 /// Is this GUI using a canvas ? 118 final bool hasCanvas() const { 119 return _hasCanvas; 120 } 121 /// Ditto 122 final bool hasCanvas(bool hasCanvas_) { 123 setCanvas(hasCanvas_); 124 return _hasCanvas; 125 } 126 127 /// The hint window to be shown when hovering this GUI. 128 final Hint hint() { 129 return _hint; 130 } 131 132 /// The list of all its children. 133 const(GuiElement[]) children() const { 134 return _children; 135 } 136 /// Ditto 137 GuiElement[] children() { 138 return _children; 139 } 140 /// The list of all its own children. 141 final GuiElement[] nodes() { 142 return _children; 143 } 144 145 /// Return the first child gui. 146 GuiElement firstChild() { 147 if (!_children.length) 148 return null; 149 return _children[0]; 150 } 151 152 /// Return the last child gui. 153 GuiElement lastChild() { 154 if (!_children.length) 155 return null; 156 return _children[$ - 1]; 157 } 158 159 /// The number of children it currently has. 160 size_t childCount() const { 161 return _children.length; 162 } 163 164 /// Is this gui locked ? \ 165 /// Call **onLock()** on change. 166 final bool isLocked() const { 167 return _isLocked; 168 } 169 /// Ditto 170 final bool isLocked(bool isLocked_) { 171 if (isLocked_ != _isLocked) { 172 _isLocked = isLocked_; 173 onLock(); 174 return _isLocked; 175 } 176 return _isLocked = isLocked_; 177 } 178 179 /// Is this gui movable ? \ 180 /// Call **onMovable()** on change. 181 final bool isMovable() const { 182 return _isMovable; 183 } 184 /// Ditto 185 final bool isMovable(bool isMovable_) { 186 if (isMovable_ != _isMovable) { 187 _isMovable = isMovable_; 188 onMovable(); 189 return _isMovable; 190 } 191 return _isMovable = isMovable_; 192 } 193 194 /// Is this gui hovered ? \ 195 /// Call **onHover()** on change. (<- Not for now) 196 final bool isHovered() const { 197 return _isHovered; 198 } 199 /// Ditto 200 final bool isHovered(bool isHovered_) { 201 if (isHovered_ != _isHovered) { 202 _isHovered = isHovered_; 203 onHover(); 204 return _isHovered; 205 } 206 return _isHovered = isHovered_; 207 } 208 209 /// Is this gui clicked ? 210 final bool isClicked() const { 211 return _isClicked; 212 } 213 /// Ditto 214 final bool isClicked(bool isClicked_) { 215 return _isClicked = isClicked_; 216 } 217 218 /// Is this gui selected ? \ 219 /// Call **onSelect()** on change. 220 final bool isSelected() const { 221 return _isSelected; 222 } 223 /// Ditto 224 final bool isSelected(bool isSelected_) { 225 if (isSelected_ != _isSelected) { 226 _isSelected = isSelected_; 227 onSelect(); 228 return _isSelected; 229 } 230 return _isSelected = isSelected_; 231 } 232 233 /// Does this gui has focus ? \ 234 /// Call **onFocus()** on change. 235 final bool hasFocus() const { 236 return _hasFocus; 237 } 238 /// Ditto 239 final bool hasFocus(bool hasFocus_) { 240 if (hasFocus_ != _hasFocus) { 241 _hasFocus = hasFocus_; 242 if (_hasFocus) 243 setFocusedElement(this); 244 onFocus(); 245 return _hasFocus; 246 } 247 return _hasFocus = hasFocus_; 248 } 249 250 /// Is this gui interactable ? \ 251 /// Call **onInteractable()** on change. 252 final bool isInteractable() const { 253 return _isInteractable; 254 } 255 /// Ditto 256 final bool isInteractable(bool isInteractable_) { 257 if (isInteractable_ != _isInteractable) { 258 _isInteractable = isInteractable_; 259 onInteractable(); 260 return _isInteractable; 261 } 262 return _isInteractable = isInteractable_; 263 } 264 265 /// The gui position relative to its parent and alignment. \ 266 /// Call **onPosition()** and **onDeltaPosition()** and **onCenter()** on change. 267 final Vec2f position() { 268 return _position; 269 } 270 /// Ditto 271 final Vec2f position(Vec2f position_) { 272 auto oldPosition = _position; 273 _position = position_; 274 onDeltaPosition(position_ - oldPosition); 275 onPosition(); 276 onCenter(); 277 return _position; 278 } 279 280 /// The scale of the gui (changed by GuiState). 281 final Vec2f scale() const { 282 return _currentState.scale; 283 } 284 /// Ditto 285 final Vec2f scale(Vec2f scale_) { 286 return _currentState.scale = scale_; 287 } 288 /// The total size (size + scale) of the gui. 289 final Vec2f scaledSize() const { 290 return _size * _currentState.scale; 291 } 292 293 /// The unscaled size of the gui. \ 294 /// Call **onSize()** and **onDeltaSize()** and **onCenter()** on change. \ 295 /// Resize the canvas if it was set (It reallocate the canvas, so be careful). 296 final Vec2f size() const { 297 return _size; 298 } 299 /// Ditto 300 final Vec2f size(Vec2f size_) { 301 auto oldSize = _size; 302 _size = size_ - _padding; 303 304 if (_hasCanvas && oldSize != size_) { 305 if (_size.x > 2f && _size.y > 2f) { 306 _canvas = new Canvas(_size); 307 _canvas.position = _canvas.size / 2f; 308 } 309 else 310 _canvas = null; 311 } 312 313 onDeltaSize(_size - oldSize); 314 onSize(); 315 onCenter(); 316 return _size; 317 } 318 319 /// The anchor of the gui. (I don't think it's used right now). \ 320 /// Call **onAnchor()** and **onDeltaAnchor()** and **onCenter()** on change. 321 final Vec2f anchor() const { 322 return _anchor; 323 } 324 /// Ditto 325 final Vec2f anchor(Vec2f anchor_) { 326 auto oldAnchor = _anchor; 327 _anchor = anchor_; 328 onDeltaAnchor(anchor_ - oldAnchor); 329 onAnchor(); 330 onCenter(); 331 return _anchor; 332 } 333 334 /* 335 Old algorithm: 336 _position + _size * (Vec2f.half - _anchor); 337 */ 338 339 /// Center of the gui. \ 340 /// If the canvas is set, only drawOverlay have the coordinate of its parent, the rest are in a relative coordinate. 341 final Vec2f center() const { 342 return _center; 343 } 344 /// The top left corner of the gui. \ 345 /// If the canvas is set, only drawOverlay have the coordinate of its parent, the rest are in a relative coordinate. 346 final Vec2f origin() const { 347 return _origin; 348 } 349 350 /// Extra space on top of its size. \ 351 /// Call **onPadding()** and update the gui size. 352 final Vec2f padding() const { 353 return _padding; 354 } 355 /// Ditto 356 final Vec2f padding(Vec2f padding_) { 357 _padding = padding_; 358 size(_size); 359 onPadding(); 360 return _padding; 361 } 362 363 /// Color of the actual state (GuiState) of the gui. \ 364 /// Call **onColor()** on change. 365 final Color color() const { 366 return _currentState.color; 367 } 368 /// Ditto 369 final Color color(Color color_) { 370 if (color_ != _currentState.color) { 371 _currentState.color = color_; 372 onColor(); 373 } 374 return _currentState.color; 375 } 376 377 /// Alpha of the actual state (GuiState) of the gui. \ 378 /// Call **onAlpha()** on change. 379 final float alpha() const { 380 return _currentState.alpha; 381 } 382 /// Ditto 383 final float alpha(float alpha_) { 384 if (alpha_ != _currentState.alpha) { 385 _currentState.alpha = alpha_; 386 onAlpha(); 387 } 388 return _currentState.alpha; 389 } 390 391 /// Angle of the actual state (GuiState) of the gui. 392 final float angle() const { 393 return _currentState.angle; 394 } 395 /// Ditto 396 final float angle(float angle_) { 397 _currentState.angle = angle_; 398 onAngle(); 399 return _currentState.angle; 400 } 401 } 402 403 /// Gui initialization options 404 enum Init { 405 /// Default 406 none = 0x0, 407 /// Is focused ? 408 focus = 0x1, 409 /// Initialize the gui locked 410 locked = 0x2, 411 /// The gui can be moved around with the mouse 412 movable = 0x4, 413 /// The gui will ignore mouse events 414 notInteractable = 0x8, 415 /// The gui will receive mouse events that are destined to its children 416 eventHook = 0x10 417 } 418 419 /// Default ctor. 420 this() { 421 } 422 423 /// Default ctor. 424 protected final void setInitFlags(int options = Init.none) { 425 _hasFocus = cast(bool)(options & Init.focus); 426 _isLocked = cast(bool)(options & Init.locked); 427 _isMovable = cast(bool)(options & Init.movable); 428 _isInteractable = cast(bool) !(options & Init.notInteractable); 429 _hasEventHook = cast(bool)(options & Init.eventHook); 430 } 431 432 /// Is it inside the gui ? 433 bool isInside(const Vec2f pos) const { 434 return (_screenCoords - pos).isBetween(-_size / 2f, _size / 2f); 435 } 436 437 /// Is it inside the gui and the gui is interactable ? \ 438 /// Used to capture events. 439 final bool isOnInteractableGuiElement(Vec2f pos) const { 440 if (isInside(pos)) 441 return _isInteractable; 442 return false; 443 } 444 445 /// Update the hint (Text that appear when hovering the gui) of the gui. 446 final void setHint(string text) { 447 _hint = makeHint(text); 448 } 449 450 /// Set an id that will be sent to the specified gui when **triggerCallback()** is fired. 451 final void setCallback(GuiElement callbackGuiElement, string callback) { 452 _callbackGuiElement = callbackGuiElement; 453 _callbackId = callback; 454 } 455 456 /// Send a previously set (with **setCallback()**) id to the previously specified gui. 457 final protected void triggerCallback() { 458 if (_callbackGuiElement !is null) { 459 _callbackGuiElement.onCallback(_callbackId); 460 } 461 } 462 463 /// Start a transition from the current state to the specified state. 464 final void doTransitionState(string stateName) { 465 const auto ptr = stateName in _states; 466 if (!(ptr)) 467 throw new Exception("No state " ~ stateName ~ " in GuiElement"); 468 _currentStateName = stateName; 469 _initState = _currentState; 470 _targetState = *ptr; 471 _timer.start(_targetState.time); 472 } 473 474 /// The gui is set *immediately* to the specified state without transition. 475 final void setState(string stateName) { 476 const auto ptr = stateName in _states; 477 if (!(ptr)) 478 throw new Exception("No state " ~ stateName ~ " in GuiElement"); 479 _currentStateName = stateName; 480 _initState = *ptr; 481 _targetState = *ptr; 482 _currentState = *ptr; 483 } 484 485 /// Current state name or currently transitioning to. 486 final string getState() const { 487 return _currentStateName; 488 } 489 490 /// Add a new state to the list. 491 final void addState(string stateName, GuiState state) { 492 _states[stateName] = state; 493 } 494 495 /// Does this gui receive events (with **onEvent()**) ? 496 final void setEventHook(bool hasHook) { 497 _hasEventHook = hasHook; 498 } 499 500 /// Sets the alignment relative to its parent. \ 501 /// Position will be calculated from the specified alignement. 502 final void setAlign(GuiAlignX x, GuiAlignY y) { 503 _alignX = x; 504 _alignY = y; 505 } 506 /// Ditto 507 final void setAlignX(GuiAlignX x) { 508 _alignX = x; 509 } 510 /// Ditto 511 final void setAlignY(GuiAlignY y) { 512 _alignY = y; 513 } 514 515 /// Returns the alignment relative to its parent. 516 final GuiAlignX getAlignX() { 517 return _alignX; 518 } 519 /// Ditto 520 final GuiAlignY getAlignY() { 521 return _alignY; 522 } 523 524 /// Override this to set the gui logic. 525 /// The deltaTime is the time ratio for the last frame to the next. 526 /// It is equivallent to Actual framerate / Nominal framerate. 527 /// Ideally, it's equal to 1. 528 /// ___ 529 /// If the canvas is set, the coordinate are those *inside* the canvas. 530 void update(float deltaTime) { 531 cast(void) deltaTime; 532 } 533 534 /// Override this to render the gui itself. \ 535 /// If the canvas is set, the coordinate are those *inside* the canvas. 536 void draw() { 537 } 538 539 /// Override this to render things above the gui. \ 540 /// If the canvas is set, the coordinate are those *outside* the canvas. 541 void drawOverlay() { 542 } 543 544 /// With the eventHook, receive all events. \ 545 /// Useful if you want to control like for text input. 546 void onEvent(Event event) { 547 cast(void) event; 548 } 549 550 /// Fired when clicked on. 551 void onSubmit() { 552 } 553 554 /// Fired upon cancelling. 555 void onCancel() { 556 } 557 558 /// Fired on the next tab event. 559 void onNextTab() { 560 } 561 562 /// Fired on the previous tab event. 563 void onPreviousTab() { 564 } 565 566 /// Fired when going up. 567 void onUp() { 568 } 569 570 /// Fired when going down. 571 void onDown() { 572 } 573 574 /// Fired when going left. 575 void onLeft() { 576 } 577 578 /// Fired when going right. 579 void onRight() { 580 } 581 582 /// Fired when the application is exiting. \ 583 /// Every gui in the tree receive this. 584 void onQuit() { 585 } 586 587 public { 588 /// Called when the lock state is changed. 589 void onLock() { 590 } 591 592 /// Called when the movable state is changed. 593 void onMovable() { 594 } 595 596 /// Called when the hover state is changed. 597 void onHover() { 598 } 599 600 /// Called when the select state is changed. 601 void onSelect() { 602 } 603 604 /// Called when the focus state is changed. 605 void onFocus() { 606 } 607 608 /// Called when the interactable state is changed. 609 void onInteractable() { 610 } 611 612 /// Called when the position is changed. \ 613 /// The delta value is the difference with the last position. 614 void onDeltaPosition(Vec2f delta) { 615 cast(void) delta; 616 } 617 618 /// Called when the position is changed. 619 void onPosition() { 620 } 621 622 /// Called when the size is changed. \ 623 /// The delta value is the difference with the last size. 624 void onDeltaSize(Vec2f delta) { 625 cast(void) delta; 626 } 627 628 /// Called when the size is changed. 629 void onSize() { 630 } 631 632 /// Called when the anchor is changed. \ 633 /// The delta value is the difference with the last anchor. 634 void onDeltaAnchor(Vec2f delta) { 635 cast(void) delta; 636 } 637 638 /// Called when the anchor is changed. 639 void onAnchor() { 640 } 641 642 /// Called when the center of the gui moved. 643 void onCenter() { 644 } 645 646 /// Called when the padding change. 647 void onPadding() { 648 } 649 650 /// Called when the color change. 651 void onColor() { 652 } 653 654 /// Called when the opacity change. 655 void onAlpha() { 656 } 657 658 /// Called when the angle change. 659 void onAngle() { 660 } 661 662 /// Any callback set to this gui will call this. 663 void onCallback(string id) { 664 cast(void) id; 665 } 666 } 667 668 /// Add a gui as a child of this one. 669 void prependChild(GuiElement child) { 670 _children = child ~ _children; 671 } 672 673 /// Add a gui as a child of this one. 674 void appendChild(GuiElement child) { 675 _children ~= child; 676 } 677 678 /// Remove all the children. 679 void removeChildren() { 680 _children.length = 0uL; 681 } 682 683 /// Remove the child at the specified index. 684 void removeChild(size_t index) { 685 if (!_children.length) 686 return; 687 if (index + 1u == _children.length) 688 _children.length--; 689 else if (index == 0u) 690 _children = _children[1 .. $]; 691 else 692 _children = _children[0 .. index] ~ _children[index + 1 .. $]; 693 } 694 695 /// Remove the specified child. 696 void removeChild(GuiElement gui) { 697 foreach (size_t i, GuiElement child; _children) { 698 if (child is gui) { 699 removeChild(i); 700 return; 701 } 702 } 703 } 704 705 /// Unregister itself from its parent or root. 706 void removeSelf() { 707 _isRegistered = false; 708 } 709 }