1/* 2 Copyright (c) 2013 yvt 3 4 This file is part of OpenSpades. 5 6 OpenSpades is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 OpenSpades is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with OpenSpades. If not, see <http://www.gnu.org/licenses/>. 18 19 */ 20 21namespace spades { 22 namespace ui { 23 24 funcdef void EventHandler(UIElement@ sender); 25 funcdef void PasteClipboardEventHandler(string text); 26 27 /** Manages all input/output and rendering of the UI framework. */ 28 class UIManager { 29 private Renderer@ renderer; 30 private AudioDevice@ audioDevice; 31 32 Vector2 MouseCursorPosition; 33 UIElement@ RootElement; 34 UIElement@ ActiveElement; 35 Cursor@ DefaultCursor; 36 float Time = 0.f; 37 bool IsControlPressed = false; 38 bool IsShiftPressed = false; 39 bool IsAltPressed = false; 40 bool IsMetaPressed = false; 41 42 // IME (Input Method Editor) support 43 string EditingText; 44 int EditingStart = 0; 45 int EditingLength = 0; 46 47 private UIElement@ mouseCapturedElement; 48 private UIElement@ mouseHoverElement; 49 50 private PasteClipboardEventHandler@ clipboardEventHandler; 51 52 private Timer@[] timers = {}; 53 private KeyRepeatManager keyRepeater; 54 private KeyRepeatManager charRepeater; 55 56 Renderer@ Renderer { 57 get final { return renderer; } 58 } 59 60 AudioDevice@ AudioDevice { 61 get final { return audioDevice; } 62 } 63 64 UIManager(Renderer@ renderer, AudioDevice@ audioDevice) { 65 @this.renderer = renderer; 66 @this.audioDevice = audioDevice; 67 68 @RootElement = UIElement(this); 69 RootElement.Size = Vector2(renderer.ScreenWidth, renderer.ScreenHeight); 70 71 @DefaultCursor = Cursor(this, renderer.RegisterImage("Gfx/UI/Cursor.png"), Vector2(8.f, 8.f)); 72 MouseCursorPosition = Vector2(renderer.ScreenWidth * 0.5f, renderer.ScreenHeight * 0.5f); 73 74 @keyRepeater.handler = KeyRepeatEventHandler(this.HandleKeyInner); 75 @charRepeater.handler = KeyRepeatEventHandler(this.HandleCharInner); 76 } 77 78 private MouseButton TranslateMouseButton(string key){ 79 if(key == "LeftMouseButton") { 80 return spades::ui::MouseButton::LeftMouseButton; 81 }else if(key == "RightMouseButton") { 82 return spades::ui::MouseButton::RightMouseButton; 83 }else if(key == "MiddleMouseButton") { 84 return spades::ui::MouseButton::MiddleMouseButton; 85 }else if(key == "MouseButton4") { 86 return spades::ui::MouseButton::MouseButton4; 87 }else if(key == "MouseButton5") { 88 return spades::ui::MouseButton::MouseButton5; 89 }else { 90 return spades::ui::MouseButton::None; 91 } 92 } 93 94 private UIElement@ GetMouseActiveElement() { 95 if(mouseCapturedElement !is null){ 96 return mouseCapturedElement; 97 } 98 if(not RootElement.Enable) { 99 return null; 100 } 101 UIElement@ elm = RootElement.MouseHitTest(MouseCursorPosition); 102 return elm; 103 } 104 105 private Cursor@ GetCurrentCursor() { 106 UIElement@ e = GetMouseActiveElement(); 107 if(e is null) { 108 return DefaultCursor; 109 } 110 return e.Cursor; 111 } 112 113 void WheelEvent(float x, float y) { 114 UIElement@ e = GetMouseActiveElement(); 115 if(e !is null) { 116 e.MouseWheel(y); 117 } 118 } 119 120 private void MouseEventDone() { 121 UIElement@ e = GetMouseActiveElement(); 122 if(e !is null) { 123 e.MouseMove(e.ScreenToClient(MouseCursorPosition)); 124 } 125 126 // check for mouse enter/leave 127 if(e is null) { 128 @e = RootElement.MouseHitTest(MouseCursorPosition); 129 } 130 if(e !is mouseHoverElement) { 131 if(mouseHoverElement !is null) { 132 mouseHoverElement.MouseLeave(); 133 } 134 @mouseHoverElement = e; 135 if(e !is null) { 136 e.MouseEnter(); 137 } 138 } 139 } 140 141 void MouseEvent(float x, float y) { 142 /* 143 MouseCursorPosition = Vector2( 144 Clamp(MouseCursorPosition.x + x, 0.f, renderer.ScreenWidth), 145 Clamp(MouseCursorPosition.y + y, 0.f, renderer.ScreenHeight) 146 ); 147 */ 148 // in current version, absolute mouse mode is supported. 149 MouseCursorPosition = Vector2( 150 Clamp(x, 0.f, renderer.ScreenWidth), 151 Clamp(y, 0.f, renderer.ScreenHeight) 152 ); 153 154 MouseEventDone(); 155 } 156 157 // forces key repeat to stop. 158 void KeyPanic() { 159 keyRepeater.KeyUp(); 160 charRepeater.KeyUp(); 161 EditingText = ""; 162 EditingStart = 0; 163 EditingLength = 0; 164 IsShiftPressed = false; 165 IsControlPressed = false; 166 IsAltPressed = false; 167 IsMetaPressed = false; 168 } 169 170 void KeyEvent(string key, bool down) { 171 if(key.length == 0){ 172 if(!down) { 173 keyRepeater.KeyUp(); 174 charRepeater.KeyUp(); 175 } 176 return; 177 } 178 if(key == "Shift") { 179 IsShiftPressed = down; 180 } 181 if(key == "Control") { 182 IsControlPressed = down; 183 } 184 if(key == "Alt") { 185 IsAltPressed = down; 186 } 187 if(key == "Meta") { 188 IsMetaPressed = down; 189 } 190 if(key == "WheelUp") { 191 UIElement@ e = GetMouseActiveElement(); 192 if(e !is null) { 193 e.MouseWheel(-1.f); 194 } 195 return; 196 } 197 if(key == "WheelDown") { 198 UIElement@ e = GetMouseActiveElement(); 199 if(e !is null) { 200 e.MouseWheel(1.f); 201 } 202 return; 203 } 204 205 MouseButton mb = TranslateMouseButton(key); 206 if(mb != spades::ui::MouseButton::None) { 207 UIElement@ e = GetMouseActiveElement(); 208 if(e !is null) { 209 if(down) { 210 @mouseCapturedElement = e; 211 if(e.AcceptsFocus) { 212 // give keyboard focus, too 213 @ActiveElement = e; 214 } 215 e.MouseDown(mb, e.ScreenToClient(MouseCursorPosition)); 216 } else { 217 // FIXME: release the mouse capture when all button are released? 218 @mouseCapturedElement = null; 219 e.MouseUp(mb, e.ScreenToClient(MouseCursorPosition)); 220 MouseEventDone(); 221 } 222 } 223 return; 224 } 225 226 if(down) { 227 HandleKeyInner(key); 228 keyRepeater.KeyDown(key); 229 }else{ 230 keyRepeater.KeyUp(); 231 charRepeater.KeyUp(); 232 UIElement@ e = ActiveElement; 233 if(e !is null) { 234 e.KeyUp(key); 235 } 236 } 237 } 238 239 void TextInputEvent(string chr) { 240 charRepeater.KeyDown(chr); 241 HandleCharInner(chr); 242 } 243 244 void TextEditingEvent(string chr, int start, int len) { 245 EditingText = chr; 246 EditingStart = GetByteIndexForString(chr, start); 247 EditingLength = GetByteIndexForString(chr, len, EditingStart) - EditingStart; 248 } 249 250 bool AcceptsTextInput { 251 get { 252 if(ActiveElement is null) { 253 EditingText = ""; 254 EditingStart = 0; 255 EditingLength = 0; 256 } 257 return ActiveElement !is null; 258 } 259 } 260 261 AABB2 TextInputRect { 262 get { 263 UIElement@ e = ActiveElement; 264 if(e !is null) { 265 AABB2 rt = e.TextInputRect; 266 Vector2 off = e.ScreenPosition; 267 rt.min += off; 268 rt.max += off; 269 return rt; 270 }else{ 271 return AABB2(); 272 } 273 } 274 } 275 276 private void HandleKeyInner(string key) { 277 { 278 UIElement@ e = ActiveElement; 279 if(EditingText.length > 0) { 280 // now text is being composed by IME. 281 // ignore some keys to resolve confliction. 282 if(key == "Escape" || key == "BackSpace" || key == "Left" || key == "Right" || 283 key == "Space" || key == "Enter" || key == "Up" || key == "Down" || 284 key == "Tab") { 285 return; 286 } 287 } 288 if(e !is null) { 289 e.KeyDown(key); 290 } else { 291 ProcessHotKey(key); 292 } 293 } 294 } 295 296 private void HandleCharInner(string chr) { 297 UIElement@ e = ActiveElement; 298 if(e !is null) { 299 e.KeyPress(chr); 300 } 301 } 302 303 304 void ProcessHotKey(string key) { 305 if(RootElement.Visible) { 306 RootElement.HotKey(key); 307 } 308 } 309 310 void AddTimer(Timer@ timer) { 311 timers.insertLast(timer); 312 } 313 314 void RemoveTimer(Timer@ timer) { 315 int idx = -1; 316 Timer@[]@ t = timers; 317 for(int i = t.length - 1; i >= 0; i--){ 318 if(t[i] is timer) { 319 idx = i; 320 break; 321 } 322 } 323 if(idx >= 0){ 324 timers.removeAt(idx); 325 } 326 } 327 328 void PlaySound(string filename) { 329 if(audioDevice !is null) { 330 audioDevice.PlayLocal(audioDevice.RegisterSound(filename), AudioParam()); 331 } 332 } 333 334 void RunFrame(float dt) { 335 if(ActiveElement !is null) { 336 if(not (ActiveElement.IsVisible and ActiveElement.IsEnabled)) { 337 @ActiveElement = null; 338 } 339 } 340 341 if(clipboardEventHandler !is null) { 342 if(GotClipboardData()) { 343 clipboardEventHandler(GetClipboardData()); 344 @clipboardEventHandler = null; 345 } 346 } 347 348 Timer@[]@ timers = this.timers; 349 for(int i = timers.length - 1; i >= 0; i--) { 350 timers[i].RunFrame(dt); 351 } 352 keyRepeater.RunFrame(dt); 353 charRepeater.RunFrame(dt); 354 Time += dt; 355 } 356 357 void Render() { 358 if(RootElement.Visible) { 359 RootElement.Render(); 360 } 361 362 // render cursor 363 Cursor@ c = GetCurrentCursor(); 364 if(c !is null) { 365 c.Render(MouseCursorPosition); 366 } 367 } 368 369 void Copy(string text) { 370 SetClipboardData(text); 371 } 372 373 void Paste(PasteClipboardEventHandler@ handler) { 374 RequestClipboardData(); 375 @clipboardEventHandler = handler; 376 } 377 } 378 379 funcdef void KeyRepeatEventHandler(string key); 380 class KeyRepeatManager { 381 string lastKey; 382 float nextDelay; 383 KeyRepeatEventHandler@ handler; 384 385 void KeyDown(string key) { 386 lastKey = key; 387 nextDelay = 0.2f; 388 } 389 void KeyUp() { 390 lastKey = ""; 391 } 392 393 void RunFrame(float dt) { 394 if(lastKey.length == 0) 395 return; 396 nextDelay -= dt; 397 if(nextDelay < 0.f) { 398 handler(lastKey); 399 nextDelay = Max(nextDelay + 0.06f, 0.f); 400 } 401 } 402 } 403 404 funcdef void TimerTickEventHandler(Timer@); 405 class Timer { 406 private UIManager@ manager; 407 TimerTickEventHandler@ Tick; 408 409 /** Minimum interval with which the timer fires. */ 410 float Interval = 1.f; 411 412 bool AutoReset = true; 413 414 private float nextDelay; 415 416 Timer(UIManager@ manager) { 417 @this.manager = manager; 418 } 419 420 UIManager@ Manager { 421 get final { 422 return manager; 423 } 424 } 425 426 void OnTick() { 427 if(Tick !is null) { 428 Tick(this); 429 } 430 } 431 432 /** Called by UIManager. */ 433 void RunFrame(float dt) { 434 nextDelay -= dt; 435 if(nextDelay < 0.f) { 436 OnTick(); 437 if(AutoReset) { 438 nextDelay = Max(nextDelay + Interval, 0.f); 439 } else { 440 Stop(); 441 } 442 } 443 } 444 445 void Start() { 446 nextDelay = Interval; 447 manager.AddTimer(this); 448 } 449 450 void Stop() { 451 manager.RemoveTimer(this); 452 } 453 454 } 455 456 enum MouseButton { 457 None, 458 LeftMouseButton, 459 RightMouseButton, 460 MiddleMouseButton, 461 MouseButton4, 462 MouseButton5 463 } 464 465 class UIElementIterator { 466 private bool initial = true; 467 private UIElement@ e; 468 UIElementIterator(UIElement@ parent) { 469 @e = parent; 470 } 471 UIElement@ Current { 472 get { return initial ? null : e; } 473 } 474 bool MoveNext() { 475 if(initial) { 476 @e = e.FirstChild; 477 initial = false; 478 } else { 479 @e = e.NextSibling; 480 } 481 return @e !is null; 482 } 483 } 484 485 class UIElementReverseIterator { 486 private bool initial = true; 487 private UIElement@ e; 488 UIElementReverseIterator(UIElement@ parent) { 489 @e = parent; 490 } 491 UIElement@ Current { 492 get { return initial ? null : e; } 493 } 494 bool MoveNext() { 495 if(initial) { 496 @e = e.LastChild; 497 initial = false; 498 } else { 499 @e = e.PrevSibling; 500 } 501 return @e !is null; 502 } 503 } 504 505 class UIElement { 506 private UIManager@ manager; 507 private UIElement@ parent; 508 //private UIElement@[] children = {}; 509 private UIElement@ firstChild, lastChild; 510 private UIElement@ prevSibling, nextSibling; 511 private Font@ fontOverride; 512 private Cursor@ cursorOverride; 513 514 EventHandler@ MouseEntered; 515 EventHandler@ MouseLeft; 516 517 bool Visible = true; 518 bool Enable = true; 519 520 /** When AcceptsFocus is set to true, this element can be activated when 521 * it receives a mouse event. */ 522 bool AcceptsFocus = false; 523 524 /** When IsMouseInteractive is set to true, this element receives mouse events. */ 525 bool IsMouseInteractive = false; 526 527 /** When this is set to true, all mouse interraction outside the client area is 528 * ignored for all sub-elements, reducing the CPU load. The visual is not clipped. */ 529 bool ClipMouse = true; 530 531 Vector2 Position; 532 Vector2 Size; 533 534 535 UIElement(UIManager@ manager) { 536 @this.manager = manager; 537 } 538 539 UIElement@ Parent { 540 get final { return parent; } 541 set final { 542 if(value is this.Parent){ 543 return; 544 } 545 546 // remove from old container 547 if(parent !is null){ 548 parent.RemoveChild(this); 549 } 550 551 // add to new container 552 if(value !is null){ 553 value.AddChild(this); 554 } 555 } 556 } 557 558 UIManager@ Manager { 559 get final { return manager; } 560 } 561 562 // used by UIElementIterator. Do not use. 563 UIElement@ FirstChild { 564 get final { return firstChild; } 565 } 566 // used by UIElementIterator. Do not use. 567 UIElement@ LastChild { 568 get final { return lastChild; } 569 } 570 571 // used by UIElementIterator. Do not use. 572 UIElement@ NextSibling { 573 get final { return nextSibling; } 574 } 575 // used by UIElementIterator. Do not use. 576 UIElement@ PrevSibling { 577 get final { return prevSibling; } 578 } 579 580 UIElement@[]@ GetChildren() final { 581 UIElement@[] elems = {}; 582 UIElement@ e = firstChild; 583 while(e !is null) { 584 elems.insertLast(e); 585 @e = e.nextSibling; 586 } 587 return elems; 588 //return array<spades::ui::UIElement@>(children); 589 } 590 591 void AppendChild(UIElement@ element) { 592 593 UIElement@ oldParent = element.Parent; 594 if(oldParent is this){ 595 return; 596 }else if(oldParent !is null){ 597 @element.Parent = null; 598 } 599 600 if(firstChild is null) { 601 @firstChild = element; 602 @lastChild = element; 603 @element.parent = this; 604 } else { 605 @element.prevSibling = @lastChild; 606 @lastChild.nextSibling = @element; 607 @lastChild = @element; 608 @element.parent = this; 609 } 610 } 611 void PrependChild(UIElement@ element) { 612 613 UIElement@ oldParent = element.Parent; 614 if(oldParent is this){ 615 return; 616 }else if(oldParent !is null){ 617 @element.Parent = null; 618 } 619 620 if(firstChild is null) { 621 @firstChild = element; 622 @lastChild = element; 623 @element.parent = this; 624 } else { 625 @element.nextSibling = @firstChild; 626 @firstChild.prevSibling = @element; 627 @firstChild = @element; 628 @element.parent = this; 629 } 630 } 631 632 void AddChild(UIElement@ element) { 633 AppendChild(element); 634 } 635 636 void RemoveChild(UIElement@ element) { 637 if(element.parent !is this) { 638 return; 639 } 640 641 if(firstChild is element) { 642 if(lastChild is element) { 643 @firstChild = null; 644 @lastChild = null; 645 } else { 646 @firstChild = element.nextSibling; 647 @firstChild.prevSibling = null; 648 } 649 } else if(lastChild is element) { 650 @lastChild = element.prevSibling; 651 @lastChild.nextSibling = null; 652 } else { 653 @element.prevSibling.nextSibling = @element.nextSibling; 654 @element.nextSibling.prevSibling = @element.prevSibling; 655 } 656 @element.prevSibling = null; 657 @element.nextSibling = null; 658 @element.parent = null; 659 660 /* 661 int index = -1; 662 UIElement@[]@ c = children; 663 for(int i = c.length - 1; i >= 0; i--) { 664 if(c[i] is element) { 665 index = i; 666 break; 667 } 668 } 669 670 if(index >= 0){ 671 children.removeAt(index); 672 @element.parent = null; 673 }*/ 674 } 675 676 Font@ Font { 677 get final { 678 if(fontOverride !is null) { 679 return fontOverride; 680 } 681 if(parent is null) { 682 return null; 683 } 684 return parent.Font; 685 } 686 set { 687 @fontOverride = value; 688 } 689 } 690 691 Cursor@ Cursor { 692 get final { 693 if(cursorOverride !is null) { 694 return cursorOverride; 695 } 696 if(parent is null) { 697 return Manager.DefaultCursor; 698 } 699 return parent.Cursor; 700 } 701 set { 702 @cursorOverride = value; 703 } 704 } 705 706 bool IsVisible { 707 get final { 708 if(not Visible) return false; 709 if(parent is null) return this is Manager.RootElement; 710 return parent.IsVisible; 711 } 712 } 713 714 bool IsEnabled { 715 get final { 716 if(not Enable) return false; 717 if(parent is null) return true; 718 return parent.IsEnabled; 719 } 720 } 721 722 bool IsFocused { 723 get final { 724 return manager.ActiveElement is this; 725 } 726 } 727 728 Vector2 ScreenPosition { 729 get final { 730 if(parent is null) { 731 return Position; 732 } 733 return Position + parent.ScreenPosition; 734 } 735 } 736 737 AABB2 Bounds { 738 get final { 739 return AABB2(Position, Position + Size); 740 } 741 set { 742 Position = value.min; 743 Size = value.max - value.min; 744 OnResized(); 745 } 746 } 747 748 AABB2 ScreenBounds { 749 get final { 750 Vector2 screenPos = ScreenPosition; 751 return AABB2(screenPos, screenPos + Size); 752 } 753 } 754 755 // IME supports 756 757 AABB2 TextInputRect { 758 get { 759 return AABB2(0.f, 0.f, Size.x, Size.y); 760 } 761 } 762 763 int TextEditingRangeStart { 764 get final { 765 if(this.IsFocused) { 766 return Manager.EditingStart; 767 }else{ 768 return 0; 769 } 770 } 771 } 772 773 int TextEditingRangeLength { 774 get final { 775 if(this.IsFocused) { 776 return Manager.EditingLength; 777 }else{ 778 return 0; 779 } 780 } 781 } 782 783 string EditingText { 784 get final { 785 if(this.IsFocused) { 786 return Manager.EditingText; 787 }else{ 788 return ""; 789 } 790 } 791 } 792 793 void OnResized() { 794 } 795 796 // relativePos is parent relative 797 UIElement@ MouseHitTest(Vector2 relativePos) final { 798 if(ClipMouse) { 799 if(not Bounds.Contains(relativePos)) { 800 return null; 801 } 802 } 803 804 805 Vector2 p = relativePos - Position; 806 807 UIElementReverseIterator iterator(this); 808 while(iterator.MoveNext()) { 809 UIElement@ e = iterator.Current; 810 if(not (e.Visible and e.Enable)) { 811 continue; 812 } 813 UIElement@ elem = e.MouseHitTest(p); 814 if(elem !is null){ 815 return elem; 816 } 817 } 818 819 if(IsMouseInteractive) { 820 if(Bounds.Contains(relativePos)) { 821 return this; 822 } 823 } 824 825 return null; 826 } 827 828 Vector2 ScreenToClient(Vector2 scr) final { 829 return scr - ScreenPosition; 830 } 831 832 void MouseWheel(float delta) { 833 if(Parent !is null) { 834 Parent.MouseWheel(delta); 835 } 836 } 837 void MouseDown(MouseButton button, Vector2 clientPosition) { 838 if(Parent !is null) { 839 Parent.MouseDown(button, clientPosition); 840 } 841 } 842 void MouseMove(Vector2 clientPosition) { 843 if(Parent !is null) { 844 Parent.MouseMove(clientPosition); 845 } 846 } 847 void MouseUp(MouseButton button, Vector2 clientPosition) { 848 if(Parent !is null) { 849 Parent.MouseUp(button, clientPosition); 850 } 851 } 852 void MouseEnter() { 853 if(MouseEntered !is null) { 854 MouseEntered(this); 855 } 856 } 857 void MouseLeave() { 858 if(MouseLeft !is null) { 859 MouseLeft(this); 860 } 861 } 862 863 void KeyDown(string key) { 864 manager.ProcessHotKey(key); 865 } 866 void KeyUp(string key) { 867 } 868 869 void KeyPress(string text) { 870 } 871 872 void HotKey(string key) { 873 UIElementReverseIterator iterator(this); 874 while(iterator.MoveNext()) { 875 if(iterator.Current.Visible and iterator.Current.Enable) { 876 iterator.Current.HotKey(key); 877 } 878 } 879 } 880 881 void Render() { 882 UIElementIterator iterator(this); 883 while(iterator.MoveNext()) { 884 if(iterator.Current.Visible) { 885 iterator.Current.Render(); 886 } 887 } 888 } 889 } 890 891 class Cursor { 892 private UIManager@ manager; 893 private Image@ image; 894 private Vector2 hotSpot; 895 896 Cursor(UIManager@ manager, Image@ image, Vector2 hotSpot) { 897 @this.manager = manager; 898 @this.image = image; 899 this.hotSpot = hotSpot; 900 } 901 902 void Render(Vector2 pos) { 903 Renderer@ renderer = manager.Renderer; 904 renderer.ColorNP = Vector4(1.f, 1.f, 1.f, 1.f); 905 renderer.DrawImage(image, Vector2(pos.x - hotSpot.x, pos.y - hotSpot.y)); 906 } 907 } 908 909 910 class UIElementDeque { 911 private UIElement@[]@ arr; 912 private int startIndex = 0; 913 private int count = 0; 914 915 UIElementDeque() { 916 @arr = array<spades::ui::UIElement@>(4); 917 } 918 919 private int MapIndex(int idx) const { 920 idx += startIndex; 921 if(idx >= int(arr.length)) 922 idx -= int(arr.length); 923 return idx; 924 } 925 926 UIElement@ get_opIndex(int idx) const { 927 return arr[MapIndex(idx)]; 928 } 929 void set_opIndex(int idx, UIElement@ e) { 930 @arr[MapIndex(idx)] = e; 931 } 932 933 void Reserve(int c) { 934 if(int(arr.length) >= c){ 935 return; 936 } 937 int newCount = int(arr.length); 938 while(newCount < c) { 939 newCount *= 2; 940 } 941 942 UIElement@[] newarr = array<spades::ui::UIElement@>(newCount); 943 UIElement@[] oldarr = arr; 944 int len = int(oldarr.length); 945 int idx = startIndex; 946 for(int i = 0; i < count; i++) { 947 @newarr[i] = oldarr[idx]; 948 idx += 1; 949 if(idx >= len) { 950 idx = 0; 951 } 952 } 953 @arr = newarr; 954 startIndex = 0; 955 } 956 957 void PushFront(UIElement@ elem) { 958 Reserve(count + 1); 959 startIndex = (startIndex == 0) ? (arr.length - 1) : (startIndex - 1); 960 count++; 961 @this.Front = elem; 962 } 963 964 void PushBack(UIElement@ elem) { 965 Reserve(count + 1); 966 count++; 967 @this.Back = elem; 968 } 969 970 void PopFront() { 971 @this.Front = null; 972 startIndex = MapIndex(1); 973 count--; 974 } 975 976 void PopBack() { 977 @this.Back = null; 978 count--; 979 } 980 981 int Count { 982 get { return count; } 983 } 984 985 void Clear() { 986 @arr = array<spades::ui::UIElement@>(4); 987 count = 0; 988 startIndex = 0; 989 } 990 991 UIElement@ Front { 992 get const { return arr[startIndex]; } 993 set { @arr[startIndex] = value; } 994 } 995 UIElement@ Back { 996 get const { return arr[MapIndex(count - 1)]; } 997 set { @arr[MapIndex(count - 1)] = value; } 998 } 999 } 1000 } 1001}