1 (* Copyright (C) DooM 2D:Forever Developers
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // ////////////////////////////////////////////////////////////////////////// //
22 mWidth, mHeight: Integer;
25 mChildren: array of THControl;
26 mFocused: THControl; // valid only for top-level controls
27 mGrab: THControl; // valid only for top-level controls
28 mEscClose: Boolean; // valid only for top-level controls
32 function getEnabled (): Boolean;
33 procedure setEnabled (v: Boolean); inline;
35 function getFocused (): Boolean; inline;
36 procedure setFocused (v: Boolean); inline;
38 function isMyChild (ctl: THControl): Boolean;
40 function findFirstFocus (): THControl;
41 function findLastFocus (): THControl;
43 function findNextFocus (cur: THControl): THControl;
44 function findPrevFocus (cur: THControl): THControl;
46 procedure activated (); virtual;
47 procedure blurred (); virtual;
50 constructor Create (ax, ay, aw, ah: Integer; aparent: THControl=nil);
51 destructor Destroy (); override;
53 // `sx` and `sy` are screen coordinates
54 procedure drawControl (sx, sy: Integer); virtual;
56 // called after all children drawn
57 procedure drawControlPost (sx, sy: Integer); virtual;
59 procedure draw (); virtual;
61 function topLevel (): THControl; inline;
63 // returns `true` if global coords are inside this control
64 function toLocal (var x, y: Integer): Boolean;
65 procedure toGlobal (var x, y: Integer);
67 // x and y are global coords
68 function controlAtXY (x, y: Integer): THControl;
70 function mouseEvent (var ev: THMouseEvent): Boolean; virtual; // returns `true` if event was eaten
71 function keyEvent (var ev: THKeyEvent): Boolean; virtual; // returns `true` if event was eaten
73 function prevSibling (): THControl;
74 function nextSibling (): THControl;
75 function firstChild (): THControl; inline;
76 function lastChild (): THControl; inline;
79 property x0: Integer read mX;
80 property y0: Integer read mY;
81 property height: Integer read mHeight;
82 property width: Integer read mWidth;
83 property enabled: Boolean read getEnabled write setEnabled;
84 property parent: THControl read mParent;
85 property focused: Boolean read getFocused write setFocused;
86 property escClose: Boolean read mEscClose write mEscClose;
87 property eatKeys: Boolean read mEatKeys write mEatKeys;
91 THTopWindow = class(THControl)
95 mDragStartX, mDragStartY: Integer;
98 procedure blurred (); override;
101 constructor Create (const atitle: AnsiString; ax, ay: Integer; aw: Integer=-1; ah: Integer=-1);
103 procedure appendChild (ctl: THControl);
105 // `sx` and `sy` are screen coordinates
106 procedure drawControl (sx, sy: Integer); override;
107 procedure drawControlPost (sx, sy: Integer); override;
109 function mouseEvent (var ev: THMouseEvent): Boolean; override; // returns `true` if event was eaten
113 THCtlCBListBox = class(THControl)
115 mItems: array of AnsiString;
116 mChecks: array of PBoolean;
120 constructor Create (ax, ay: Integer; aparent: THControl=nil);
121 destructor Destroy (); override;
123 procedure appendItem (const atext: AnsiString; bv: PBoolean);
125 procedure drawControl (sx, sy: Integer); override;
127 function mouseEvent (var ev: THMouseEvent): Boolean; override;
128 function keyEvent (var ev: THKeyEvent): Boolean; override;
131 // ////////////////////////////////////////////////////////////////////////// //
133 uiTopList: array of THControl = nil;
136 function uiMouseEvent (var ev: THMouseEvent): Boolean;
142 if (Length(uiTopList) = 0) then result := false else result := uiTopList[High(uiTopList)].mouseEvent(ev);
143 if not result and (ev.kind = ev.Press) then
145 for f := High(uiTopList) downto 0 do
149 if uiTopList[f].toLocal(lx, ly) then
152 if uiTopList[f].mEnabled and (f <> High(uiTopList)) then
154 uiTopList[High(uiTopList)].blurred();
155 ctmp := uiTopList[f];
157 for c := f+1 to High(uiTopList) do uiTopList[c-1] := uiTopList[c];
158 uiTopList[High(uiTopList)] := ctmp;
160 result := ctmp.mouseEvent(ev);
169 function uiKeyEvent (var ev: THKeyEvent): Boolean;
171 if (Length(uiTopList) = 0) then result := false else result := uiTopList[High(uiTopList)].keyEvent(ev);
172 if (ev.kind = ev.Release) then begin result := true; exit; end;
181 for f := 0 to High(uiTopList) do
185 if (f <> High(uiTopList)) then darkenRect(ctl.x0, ctl.y0, ctl.width, ctl.height, 128);
190 procedure uiAddWindow (ctl: THControl);
194 if (ctl = nil) then exit;
196 for f := 0 to High(uiTopList) do
198 if (uiTopList[f] = ctl) then
200 if (f <> High(uiTopList)) then
202 uiTopList[High(uiTopList)].blurred();
203 for c := f+1 to High(uiTopList) do uiTopList[c-1] := uiTopList[c];
204 uiTopList[High(uiTopList)] := ctl;
210 if (Length(uiTopList) > 0) then uiTopList[High(uiTopList)].blurred();
211 SetLength(uiTopList, Length(uiTopList)+1);
212 uiTopList[High(uiTopList)] := ctl;
218 procedure uiRemoveWindow (ctl: THControl);
222 if (ctl = nil) then exit;
224 for f := 0 to High(uiTopList) do
226 if (uiTopList[f] = ctl) then
229 for c := f+1 to High(uiTopList) do uiTopList[c-1] := uiTopList[c];
230 SetLength(uiTopList, Length(uiTopList)-1);
237 function uiVisibleWindow (ctl: THControl): Boolean;
242 if (ctl = nil) then exit;
244 for f := 0 to High(uiTopList) do
246 if (uiTopList[f] = ctl) then begin result := true; exit; end;
251 // ////////////////////////////////////////////////////////////////////////// //
252 constructor THControl.Create (ax, ay, aw, ah: Integer; aparent: THControl=nil);
269 destructor THControl.Destroy ();
273 if (mParent <> nil) then
276 for f := 0 to High(mParent.mChildren) do
278 if (mParent.mChildren[f] = self) then
280 for c := f+1 to High(mParent.mChildren) do mParent.mChildren[c-1] := mParent.mChildren[c];
281 SetLength(mParent.mChildren, Length(mParent.mChildren)-1);
285 for f := 0 to High(mChildren) do
287 mChildren[f].mParent := nil;
294 procedure THControl.activated ();
299 procedure THControl.blurred ();
305 function THControl.topLevel (): THControl; inline;
308 while (result.mParent <> nil) do result := result.mParent;
312 function THControl.getEnabled (): Boolean;
317 if (not mEnabled) or (mWidth < 1) or (mHeight < 1) then exit;
319 while (ctl <> nil) do
321 if (not ctl.mEnabled) or (ctl.mWidth < 1) or (ctl.mHeight < 1) then exit;
328 procedure THControl.setEnabled (v: Boolean); inline;
330 if (mEnabled = v) then exit;
332 if not v and focused then setFocused(false);
336 function THControl.getFocused (): Boolean; inline;
338 if (mParent = nil) then result := (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)] = self) else result := (topLevel.mFocused = self);
342 procedure THControl.setFocused (v: Boolean); inline;
349 if (tl.mFocused = self) then
352 tl.mFocused := tl.findNextFocus(self);
353 if (tl.mFocused = self) then tl.mFocused := nil;
357 if (not mEnabled) or (not mCanFocus) then exit;
358 if (tl.mFocused <> self) then
360 tl.mFocused.blurred();
362 if (tl.mGrab <> self) then tl.mGrab := nil;
368 function THControl.isMyChild (ctl: THControl): Boolean;
371 while (ctl <> nil) do
373 if (ctl.mParent = self) then exit;
380 // returns `true` if global coords are inside this control
381 function THControl.toLocal (var x, y: Integer): Boolean;
386 while (ctl <> nil) do
392 result := (x >= 0) and (y >= 0) and (x < mWidth) and (y < mHeight);
396 procedure THControl.toGlobal (var x, y: Integer);
401 while (ctl <> nil) do
410 // x and y are global coords
411 function THControl.controlAtXY (x, y: Integer): THControl;
417 if (not mEnabled) or (mWidth < 1) or (mHeight < 1) then exit;
420 if not toLocal(lx, ly) then exit;
421 for f := High(mChildren) downto 0 do
423 result := mChildren[f].controlAtXY(x, y);
424 if (result <> nil) then exit;
430 function THControl.prevSibling (): THControl;
434 if (mParent <> nil) then
436 for f := 1 to High(mParent.mChildren) do
438 if (mParent.mChildren[f] = self) then begin result := mParent.mChildren[f-1]; exit; end;
444 function THControl.nextSibling (): THControl;
448 if (mParent <> nil) then
450 for f := 0 to High(mParent.mChildren)-1 do
452 if (mParent.mChildren[f] = self) then begin result := mParent.mChildren[f+1]; exit; end;
458 function THControl.firstChild (): THControl; inline;
460 if (Length(mChildren) <> 0) then result := mChildren[0] else result := nil;
463 function THControl.lastChild (): THControl; inline;
465 if (Length(mChildren) <> 0) then result := mChildren[High(mChildren)] else result := nil;
469 function THControl.findFirstFocus (): THControl;
476 for f := 0 to High(mChildren) do
478 result := mChildren[f].findFirstFocus();
479 if (result <> nil) then exit;
481 if mCanFocus then result := self;
486 function THControl.findLastFocus (): THControl;
493 for f := High(mChildren) downto 0 do
495 result := mChildren[f].findLastFocus();
496 if (result <> nil) then exit;
498 if mCanFocus then result := self;
503 function THControl.findNextFocus (cur: THControl): THControl;
508 if not isMyChild(cur) then cur := nil;
509 if (cur = nil) then begin result := findFirstFocus(); exit; end;
510 result := cur.findFirstFocus();
511 if (result <> nil) and (result <> cur) then exit;
514 cur := cur.nextSibling;
515 if (cur = nil) then break;
516 result := cur.findFirstFocus();
517 if (result <> nil) then exit;
519 result := findFirstFocus();
524 function THControl.findPrevFocus (cur: THControl): THControl;
529 if not isMyChild(cur) then cur := nil;
530 if (cur = nil) then begin result := findLastFocus(); exit; end;
532 result := cur.findLastFocus();
533 if (result <> nil) and (result <> cur) then exit;
536 cur := cur.prevSibling;
537 if (cur = nil) then break;
538 result := cur.findLastFocus();
539 if (result <> nil) then exit;
541 result := findLastFocus();
546 procedure THControl.draw ();
550 scxywh: array[0..3] of GLint;
553 procedure setScissor (x, y, w, h: Integer);
559 if (w < 1) or (h < 1) or (scxywh[2] < 1) or (scxywh[3] < 1) then begin glScissor(0, 0, 0, 0); exit; end;
564 sx1 := sx0+scxywh[2]-1;
565 sy1 := sy0+scxywh[3]-1;
566 //conwritefln('0: (%d,%d)-(%d,%d) (%d,%d)-(%d,%d)', [sx0, sy0, sx1, sy1, x, y, x1, y1]);
567 if (x1 < sx0) or (y1 < sy0) or (x > sx1) or (y > sy1) then begin glScissor(0, 0, 0, 0); exit; end;
568 if (x < sx0) then x := sx0;
569 if (y < sy0) then y := sy0;
570 if (x1 > sx1) then x1 := sx1;
571 if (y1 > sy1) then y1 := sy1;
572 //conwritefln('1: (%d,%d)-(%d,%d) (%d,%d)-(%d,%d)', [sx0, sy0, sx1, sy1, x, y, x1, y1]);
573 glScissor(x, y, x1-x+1, y1-y+1);
577 if (mWidth < 1) or (mHeight < 1) then exit;
581 //conwritefln('[%s]: (%d,%d)-(%d,%d) (%d,%d)', [ClassName, mX, mY, mWidth, mHeight, x, y]);
588 wassc := (glIsEnabled(GL_SCISSOR_TEST) <> 0);
591 glGetIntegerv(GL_SCISSOR_BOX, @scxywh[0]);
595 glGetIntegerv(GL_VIEWPORT, @scxywh[0]);
597 //conwritefln('(%d,%d)-(%d,%d)', [scxywh[0], scxywh[1], scxywh[2], scxywh[3]]);
599 glEnable(GL_SCISSOR_TEST);
600 setScissor(x, gWinSizeY-(y+mHeight-1)-1, mWidth, mHeight);
603 if (mParent = nil) then setScissor(x+2, gWinSizeY-(y+mHeight-1-2)-1, mWidth-4, mHeight-14);
604 for f := 0 to High(mChildren) do mChildren[f].draw();
605 if (mParent = nil) then setScissor(x, gWinSizeY-(y+mHeight-1)-1, mWidth, mHeight);
606 drawControlPost(x, y);
607 glScissor(scxywh[0], scxywh[1], scxywh[2], scxywh[3]);
608 if wassc then glEnable(GL_SCISSOR_TEST) else glDisable(GL_SCISSOR_TEST);
612 procedure THControl.drawControl (sx, sy: Integer);
614 if (mParent = nil) then darkenRect(sx, sy, mWidth, mHeight, 64);
615 //fillRect(sx, sy, mWidth, mHeight, 0, 0, 255, 120);
619 procedure THControl.drawControlPost (sx, sy: Integer);
624 function THControl.mouseEvent (var ev: THMouseEvent): Boolean;
629 if not mEnabled then exit;
630 if (mParent = nil) then
632 if (mGrab <> nil) then
634 result := mGrab.mouseEvent(ev);
635 if (ev.kind = ev.Release) then mGrab := nil;
639 if (mWidth < 1) or (mHeight < 1) then exit;
640 ctl := controlAtXY(ev.x, ev.y);
641 if (ctl <> nil) and (ctl <> self) then
643 if (ctl <> topLevel.mFocused) then ctl.setFocused(true);
644 result := ctl.mouseEvent(ev);
649 function THControl.keyEvent (var ev: THKeyEvent): Boolean;
654 if not mEnabled then exit;
655 if (topLevel.mFocused <> self) and isMyChild(topLevel.mFocused) and topLevel.mFocused.mEnabled then result := topLevel.mFocused.keyEvent(ev);
656 if (mParent = nil) then
658 if (ev.kstate = THKeyEvent.ModShift) and (ev.scan = SDL_SCANCODE_TAB) then
661 if (ev.kind = ev.Press) then
663 ctl := findPrevFocus(mFocused);
664 if (ctl <> mFocused) then
672 if (ev.kstate = 0) and (ev.scan = SDL_SCANCODE_TAB) then
675 if (ev.kind = ev.Press) then
677 ctl := findNextFocus(mFocused);
678 if (ctl <> mFocused) then
686 if mEscClose and (ev.kind = ev.Press) and (ev.kstate = 0) and (ev.scan = SDL_SCANCODE_ESCAPE) then
689 uiRemoveWindow(self);
693 if mEatKeys then result := true;
697 // ////////////////////////////////////////////////////////////////////////// //
698 constructor THTopWindow.Create (const atitle: AnsiString; ax, ay: Integer; aw: Integer=-1; ah: Integer=-1);
701 if (Length(mTitle) > 0) then
703 if (ah < 14) then ah := 14;
704 if (aw < Length(mTitle)*8+4) then aw := Length(mTitle)*8+4;
708 if (ah < 4) then ah := 4;
709 if (aw < 4) then aw := 4;
712 inherited Create(ax, ay, aw, ah, nil);
716 procedure THTopWindow.appendChild (ctl: THControl);
720 if (ctl = nil) then exit;
721 if (ctl.mParent <> nil) then exit;
722 if (Length(mTitle) > 0) then myofs := 12 else myofs := 2;
723 SetLength(mChildren, Length(mChildren)+1);
724 mChildren[High(mChildren)] := ctl;
728 if (ctl.mWidth > 0) and (ctl.mHeight > 0) and
729 (ctl.mX+ctl.mWidth > 2) and (ctl.mY+ctl.mHeight > myofs-2) then
731 if (mWidth+2 < ctl.mX+ctl.mWidth) then mWidth := ctl.mX+ctl.mWidth+2;
732 if (mHeight+2 < ctl.mY+ctl.mHeight) then mHeight := ctl.mY+ctl.mHeight+2;
734 if (mFocused = nil) and ctl.mEnabled and ctl.mCanFocus and (ctl.mWidth > 0) and (ctl.mHeight > 0) then mFocused := ctl;
738 procedure THTopWindow.drawControl (sx, sy: Integer);
740 //if (mParent = nil) then darkenRect(sx, sy, mWidth, mHeight, 200);
741 fillRect(sx, sy, mWidth, mHeight, 0, 0, 128);
745 procedure THTopWindow.drawControlPost (sx, sy: Integer);
766 drawRect(mX, mY, mWidth, mHeight, r, g, b);
767 if (Length(mTitle) > 0) then
769 fillRect(mX+1, mY+1, mWidth-2, 9, r, g, b);
770 drawText8(mX+2, mY+1, mTitle, 0, 0, 0);
775 procedure THTopWindow.blurred ();
782 function THTopWindow.mouseEvent (var ev: THMouseEvent): Boolean;
787 if not mEnabled then exit;
788 if (mWidth < 1) or (mHeight < 1) then exit;
792 mX += ev.x-mDragStartX;
793 mY += ev.y-mDragStartY;
796 if (ev.kind = ev.Release) then mDragging := false;
801 if (ev.kind = ev.Press) and (ev.but = ev.Left) then
805 if toLocal(lx, ly) then
807 if ((Length(mTitle) > 0) and (ly < 12)) or ((Length(mTitle) = 0) and (ly < 2)) then
818 result := inherited mouseEvent(ev);
822 // ////////////////////////////////////////////////////////////////////////// //
823 constructor THCtlCBListBox.Create (ax, ay: Integer; aparent: THControl=nil);
828 inherited Create(ax, ay, 4, 4);
832 destructor THCtlCBListBox.Destroy ();
840 procedure THCtlCBListBox.appendItem (const atext: AnsiString; bv: PBoolean);
842 if (Length(atext)*8+4+3*8+2 > mWidth) then mWidth := Length(atext)*8+4+3*8+2;
843 SetLength(mItems, Length(mItems)+1);
844 mItems[High(mItems)] := atext;
845 SetLength(mChecks, Length(mChecks)+1);
846 mChecks[High(mChecks)] := bv;
847 if (Length(mItems)*8+4 > mHeight) then mHeight := Length(mItems)*8+4;
848 if (mCurIndex < 0) then mCurIndex := 0;
852 procedure THCtlCBListBox.drawControl (sx, sy: Integer);
856 //fillRect(sx, sy, mWidth, mHeight, 0, 128, 0);
859 for f := 0 to High(mItems) do
861 if (mCurIndex = f) then fillRect(sx-2, sy, mWidth, 8, 0, 128, 0);
862 if (mChecks[f] <> nil) then
864 drawText8(sx, sy, '[ ]', 255, 255, 255);
865 if mChecks[f]^ then drawText8(sx+6, sy, 'x', 255, 255, 255);
866 drawText8(sx+3*8+2, sy, mItems[f], 255, 255, 0);
873 function THCtlCBListBox.mouseEvent (var ev: THMouseEvent): Boolean;
877 result := inherited mouseEvent(ev);
878 if not result and (Length(mItems) > 0) and (ev.kind = ev.Press) then
882 if toLocal(lx, ly) then
884 if (ly < 2) then ly := 2;
886 if (ly < 0) then ly := 0 else if (ly > High(mItems)) then ly := High(mItems);
888 if (mChecks[ly] <> nil) then mChecks[ly]^ := not mChecks[ly]^;
894 function THCtlCBListBox.keyEvent (var ev: THKeyEvent): Boolean;
896 result := inherited keyEvent(ev);
897 if not getFocused then exit;
899 if (ev.kstate = 0) and (ev.kind = ev.Press) then
909 SDL_SCANCODE_PAGEDOWN:
912 mCurIndex := High(mItems);
917 if (Length(mItems) > 0) then
919 if (mCurIndex < 0) then mCurIndex := Length(mItems);
920 while (mCurIndex > 0) do
923 if (mChecks[mCurIndex] <> nil) then break;
934 if (Length(mItems) > 0) then
936 if (mCurIndex < 0) then mCurIndex := -1;
937 while (mCurIndex < High(mItems)) do
940 if (mChecks[mCurIndex] <> nil) then break;
952 if (mCurIndex >= 0) and (mCurIndex < Length(mChecks)) and (mChecks[mCurIndex] <> nil) then mChecks[mCurIndex]^ := not mChecks[mCurIndex]^;