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 {$INCLUDE ../shared/a_modes.inc}
30 // ////////////////////////////////////////////////////////////////////////// //
32 THControlClass
= class of THControl
;
36 type TActionCB
= procedure (me
: THControl
; uinfo
: Integer);
42 mWidth
, mHeight
: Integer;
43 mFrameWidth
, mFrameHeight
: Integer;
46 mChildren
: array of THControl
;
47 mFocused
: THControl
; // valid only for top-level controls
48 mGrab
: THControl
; // valid only for top-level controls
49 mEscClose
: Boolean; // valid only for top-level controls
58 function getEnabled (): Boolean;
59 procedure setEnabled (v
: Boolean); inline;
61 function getFocused (): Boolean; inline;
62 procedure setFocused (v
: Boolean); inline;
64 function isMyChild (ctl
: THControl
): Boolean;
66 function findFirstFocus (): THControl
;
67 function findLastFocus (): THControl
;
69 function findNextFocus (cur
: THControl
): THControl
;
70 function findPrevFocus (cur
: THControl
): THControl
;
72 procedure activated (); virtual;
73 procedure blurred (); virtual;
75 //WARNING! do not call scissor functions outside `.draw*()` API!
76 // reset scissor to whole control
77 procedure resetScissor ();
78 // set scissor to this internal rect (in local coords)
79 procedure setScissor (lx
, ly
, lw
, lh
: Integer);
82 procedure setScissorGLInternal (x
, y
, w
, h
: Integer);
88 mDefSize
: TLaySize
; // default size
89 mMaxSize
: TLaySize
; // maximum size
98 mLayDefSize
: TLaySize
;
99 mLayMaxSize
: TLaySize
;
102 // layouter interface
103 function getDefSize (): TLaySize
; inline; // default size; <0: use max size
104 //procedure setDefSize (const sz: TLaySize); inline; // default size; <0: use max size
105 function getMargins (): TLayMargins
; inline;
106 function getMaxSize (): TLaySize
; inline; // max size; <0: set to some huge value
107 //procedure setMaxSize (const sz: TLaySize); inline; // max size; <0: set to some huge value
108 function getFlex (): Integer; inline; // <=0: not flexible
109 function isHorizBox (): Boolean; inline; // horizontal layout for children?
110 procedure setHorizBox (v
: Boolean); inline; // horizontal layout for children?
111 function canWrap (): Boolean; inline; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
112 procedure setCanWrap (v
: Boolean); inline; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
113 function isLineStart (): Boolean; inline; // `true` if this ctl should start a new line; ignored for vertical boxes
114 procedure setLineStart (v
: Boolean); inline; // `true` if this ctl should start a new line; ignored for vertical boxes
115 function getAlign (): Integer; inline; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
116 procedure setAlign (v
: Integer); inline; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
117 function getExpand (): Boolean; inline; // expanding in non-main direction: `true` will ignore align and eat all available space
118 procedure setExpand (v
: Boolean); inline; // expanding in non-main direction: `true` will ignore align and eat all available space
119 function getHGroup (): AnsiString; inline; // empty: not grouped
120 procedure setHGroup (const v
: AnsiString); inline; // empty: not grouped
121 function getVGroup (): AnsiString; inline; // empty: not grouped
122 procedure setVGroup (const v
: AnsiString); inline; // empty: not grouped
124 procedure setActualSizePos (constref apos
: TLayPos
; constref asize
: TLaySize
); inline;
126 procedure layPrepare (); virtual; // called before registering control in layouter
129 property flex
: Integer read mFlex write mFlex
;
130 property flDefaultSize
: TLaySize read mDefSize write mDefSize
;
131 property flMaxSize
: TLaySize read mMaxSize write mMaxSize
;
132 property flHoriz
: Boolean read isHorizBox write setHorizBox
;
133 property flCanWrap
: Boolean read canWrap write setCanWrap
;
134 property flLineStart
: Boolean read isLineStart write setLineStart
;
135 property flAlign
: Integer read getAlign write setAlign
;
136 property flExpand
: Boolean read getExpand write setExpand
;
137 property flHGroup
: AnsiString read getHGroup write setHGroup
;
138 property flVGroup
: AnsiString read getVGroup write setVGroup
;
141 function parsePos (par
: TTextParser
): TLayPos
;
142 function parseSize (par
: TTextParser
): TLaySize
;
143 function parseBool (par
: TTextParser
): Boolean;
144 function parseAnyAlign (par
: TTextParser
): Integer;
145 function parseHAlign (par
: TTextParser
): Integer;
146 function parseVAlign (par
: TTextParser
): Integer;
147 procedure parseTextAlign (par
: TTextParser
; var h
, v
: Integer);
148 procedure parseChildren (par
: TTextParser
); // par should be on '{'; final '}' is eaten
151 // par is on property data
152 // there may be more data in text stream, don't eat it!
153 // return `true` if property name is valid and value was parsed
154 // return `false` if property name is invalid; don't advance parser in this case
155 // throw on property data errors
156 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; virtual;
158 // par should be on '{'; final '}' is eaten
159 procedure parseProperties (par
: TTextParser
);
162 constructor Create ();
163 constructor Create (ax
, ay
, aw
, ah
: Integer);
164 destructor Destroy (); override;
166 // `sx` and `sy` are screen coordinates
167 procedure drawControl (sx
, sy
: Integer); virtual;
169 // called after all children drawn
170 procedure drawControlPost (sx
, sy
: Integer); virtual;
172 procedure draw (); virtual;
174 function topLevel (): THControl
; inline;
176 // returns `true` if global coords are inside this control
177 function toLocal (var x
, y
: Integer): Boolean;
178 procedure toGlobal (var x
, y
: Integer);
180 // x and y are global coords
181 function controlAtXY (x
, y
: Integer): THControl
;
183 function mouseEvent (var ev
: THMouseEvent
): Boolean; virtual; // returns `true` if event was eaten
184 function keyEvent (var ev
: THKeyEvent
): Boolean; virtual; // returns `true` if event was eaten
186 function prevSibling (): THControl
;
187 function nextSibling (): THControl
;
188 function firstChild (): THControl
; inline;
189 function lastChild (): THControl
; inline;
191 procedure appendChild (ctl
: THControl
); virtual;
194 property id
: AnsiString read mId
;
195 property x0
: Integer read mX
;
196 property y0
: Integer read mY
;
197 property height
: Integer read mHeight
;
198 property width
: Integer read mWidth
;
199 property enabled
: Boolean read getEnabled write setEnabled
;
200 property parent
: THControl read mParent
;
201 property focused
: Boolean read getFocused write setFocused
;
202 property escClose
: Boolean read mEscClose write mEscClose
;
203 property eatKeys
: Boolean read mEatKeys write mEatKeys
;
207 THTopWindow
= class(THControl
)
211 mDragStartX
, mDragStartY
: Integer;
212 mWaitingClose
: Boolean;
214 mFreeOnClose
: Boolean; // default: false
217 procedure blurred (); override;
220 closeCB
: TActionCB
; // called after window was removed from ui window list
223 constructor Create (const atitle
: AnsiString; ax
, ay
: Integer; aw
: Integer=-1; ah
: Integer=-1);
225 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
227 procedure centerInScreen ();
229 // `sx` and `sy` are screen coordinates
230 procedure drawControl (sx
, sy
: Integer); override;
231 procedure drawControlPost (sx
, sy
: Integer); override;
233 function keyEvent (var ev
: THKeyEvent
): Boolean; override; // returns `true` if event was eaten
234 function mouseEvent (var ev
: THMouseEvent
): Boolean; override; // returns `true` if event was eaten
237 property freeOnClose
: Boolean read mFreeOnClose write mFreeOnClose
;
241 THCtlSimpleText
= class(THControl
)
251 mItems
: array of TItem
;
254 constructor Create (ax
, ay
: Integer);
255 destructor Destroy (); override;
257 procedure appendItem (const atext
: AnsiString; acentered
: Boolean=false; ahline
: Boolean=false);
259 procedure drawControl (sx
, sy
: Integer); override;
261 function mouseEvent (var ev
: THMouseEvent
): Boolean; override;
262 function keyEvent (var ev
: THKeyEvent
): Boolean; override;
266 THCtlCBListBox
= class(THControl
)
276 mItems
: array of TItem
;
280 constructor Create (ax
, ay
: Integer);
281 destructor Destroy (); override;
283 procedure appendItem (const atext
: AnsiString; bv
: PBoolean; aaction
: TActionCB
=nil);
285 procedure drawControl (sx
, sy
: Integer); override;
287 function mouseEvent (var ev
: THMouseEvent
): Boolean; override;
288 function keyEvent (var ev
: THKeyEvent
): Boolean; override;
291 // ////////////////////////////////////////////////////////////////////// //
292 THCtlBox
= class(THControl
)
295 mCaption
: AnsiString;
298 constructor Create (ahoriz
: Boolean);
300 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
302 procedure drawControl (sx
, sy
: Integer); override;
304 function mouseEvent (var ev
: THMouseEvent
): Boolean; override;
305 function keyEvent (var ev
: THKeyEvent
): Boolean; override;
308 THCtlHBox
= class(THCtlBox
)
310 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
313 THCtlVBox
= class(THCtlBox
)
315 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
319 THCtlTextLabel
= class(THControl
)
322 mHAlign
: Integer; // -1: left; 0: center; 1: right; default: left
323 mVAlign
: Integer; // -1: top; 0: center; 1: bottom; default: center
326 constructor Create (const atext
: AnsiString);
327 //destructor Destroy (); override;
329 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
331 procedure drawControl (sx
, sy
: Integer); override;
333 function mouseEvent (var ev
: THMouseEvent
): Boolean; override;
334 function keyEvent (var ev
: THKeyEvent
): Boolean; override;
338 // ////////////////////////////////////////////////////////////////////////// //
339 function uiMouseEvent (ev
: THMouseEvent
): Boolean;
340 function uiKeyEvent (ev
: THKeyEvent
): Boolean;
344 // ////////////////////////////////////////////////////////////////////////// //
345 procedure uiAddWindow (ctl
: THControl
);
346 procedure uiRemoveWindow (ctl
: THControl
); // will free window if `mFreeOnClose` is `true`
347 function uiVisibleWindow (ctl
: THControl
): Boolean;
350 // ////////////////////////////////////////////////////////////////////////// //
352 procedure uiLayoutCtl (ctl
: THControl
);
355 // ////////////////////////////////////////////////////////////////////////// //
357 gh_ui_scale
: Single = 1.0;
367 // ////////////////////////////////////////////////////////////////////////// //
369 knownCtlClasses
: array of record
370 klass
: THControlClass
;
375 procedure registerCtlClass (aklass
: THControlClass
; const aname
: AnsiString);
377 assert(aklass
<> nil);
378 assert(Length(aname
) > 0);
379 SetLength(knownCtlClasses
, Length(knownCtlClasses
)+1);
380 knownCtlClasses
[High(knownCtlClasses
)].klass
:= aklass
;
381 knownCtlClasses
[High(knownCtlClasses
)].name
:= aname
;
385 function findCtlClass (const aname
: AnsiString): THControlClass
;
389 for f
:= 0 to High(knownCtlClasses
) do
391 if (strEquCI1251(aname
, knownCtlClasses
[f
].name
)) then
393 result
:= knownCtlClasses
[f
].klass
;
401 // ////////////////////////////////////////////////////////////////////////// //
403 TFlexLayouter
= specialize TFlexLayouterBase
<THControl
>;
405 procedure uiLayoutCtl (ctl
: THControl
);
409 if (ctl
= nil) then exit
;
410 lay
:= TFlexLayouter
.Create();
415 writeln('============================');
418 writeln('=== initial ===');
421 //lay.calcMaxSizeInternal(0);
424 writeln('=== after first pass ===');
428 writeln('=== after second pass ===');
433 writeln('=== final ===');
442 // ////////////////////////////////////////////////////////////////////////// //
444 uiTopList
: array of THControl
= nil;
447 function uiMouseEvent (ev
: THMouseEvent
): Boolean;
453 ev
.x
:= trunc(ev
.x
/gh_ui_scale
);
454 ev
.y
:= trunc(ev
.y
/gh_ui_scale
);
455 ev
.dx
:= trunc(ev
.dx
/gh_ui_scale
); //FIXME
456 ev
.dy
:= trunc(ev
.dy
/gh_ui_scale
); //FIXME
457 if (Length(uiTopList
) = 0) then result
:= false else result
:= uiTopList
[High(uiTopList
)].mouseEvent(ev
);
458 if not result
and (ev
.press
) then
460 for f
:= High(uiTopList
) downto 0 do
464 if uiTopList
[f
].toLocal(lx
, ly
) then
467 if uiTopList
[f
].mEnabled
and (f
<> High(uiTopList
)) then
469 uiTopList
[High(uiTopList
)].blurred();
470 ctmp
:= uiTopList
[f
];
472 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
473 uiTopList
[High(uiTopList
)] := ctmp
;
475 result
:= ctmp
.mouseEvent(ev
);
484 function uiKeyEvent (ev
: THKeyEvent
): Boolean;
486 ev
.x
:= trunc(ev
.x
/gh_ui_scale
);
487 ev
.y
:= trunc(ev
.y
/gh_ui_scale
);
488 if (Length(uiTopList
) = 0) then result
:= false else result
:= uiTopList
[High(uiTopList
)].keyEvent(ev
);
489 if (ev
.release
) then begin result
:= true; exit
; end;
498 glMatrixMode(GL_MODELVIEW
);
502 glScalef(gh_ui_scale
, gh_ui_scale
, 1);
503 for f
:= 0 to High(uiTopList
) do
507 if (f
<> High(uiTopList
)) then darkenRect(ctl
.x0
, ctl
.y0
, ctl
.width
, ctl
.height
, 128);
510 glMatrixMode(GL_MODELVIEW
);
516 procedure uiAddWindow (ctl
: THControl
);
520 if (ctl
= nil) then exit
;
522 if not (ctl
is THTopWindow
) then exit
; // alas
523 for f
:= 0 to High(uiTopList
) do
525 if (uiTopList
[f
] = ctl
) then
527 if (f
<> High(uiTopList
)) then
529 uiTopList
[High(uiTopList
)].blurred();
530 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
531 uiTopList
[High(uiTopList
)] := ctl
;
537 if (Length(uiTopList
) > 0) then uiTopList
[High(uiTopList
)].blurred();
538 SetLength(uiTopList
, Length(uiTopList
)+1);
539 uiTopList
[High(uiTopList
)] := ctl
;
544 procedure uiRemoveWindow (ctl
: THControl
);
548 if (ctl
= nil) then exit
;
550 if not (ctl
is THTopWindow
) then exit
; // alas
551 for f
:= 0 to High(uiTopList
) do
553 if (uiTopList
[f
] = ctl
) then
556 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
557 SetLength(uiTopList
, Length(uiTopList
)-1);
558 if (ctl
is THTopWindow
) then
561 if assigned(THTopWindow(ctl
).closeCB
) then THTopWindow(ctl
).closeCB(ctl
, 0);
563 if (THTopWindow(ctl
).mFreeOnClose
) then FreeAndNil(ctl
);
572 function uiVisibleWindow (ctl
: THControl
): Boolean;
577 if (ctl
= nil) then exit
;
579 if not (ctl
is THTopWindow
) then exit
; // alas
580 for f
:= 0 to High(uiTopList
) do
582 if (uiTopList
[f
] = ctl
) then begin result
:= true; exit
; end;
587 // ////////////////////////////////////////////////////////////////////////// //
588 constructor THControl
.Create ();
605 mDrawShadow
:= false;
607 // layouter interface
608 mDefSize
:= TLaySize
.Create(64, 8); // default size
609 mMaxSize
:= TLaySize
.Create(-1, -1); // maximum size
616 mAlign
:= -1; // left/top
621 constructor THControl
.Create (ax
, ay
, aw
, ah
: Integer);
631 destructor THControl
.Destroy ();
635 if (mParent
<> nil) then
638 for f
:= 0 to High(mParent
.mChildren
) do
640 if (mParent
.mChildren
[f
] = self
) then
642 for c
:= f
+1 to High(mParent
.mChildren
) do mParent
.mChildren
[c
-1] := mParent
.mChildren
[c
];
643 SetLength(mParent
.mChildren
, Length(mParent
.mChildren
)-1);
647 for f
:= 0 to High(mChildren
) do
649 mChildren
[f
].mParent
:= nil;
656 // ////////////////////////////////////////////////////////////////////////// //
657 function THControl
.getDefSize (): TLaySize
; inline; begin result
:= mLayDefSize
; end;
658 function THControl
.getMaxSize (): TLaySize
; inline; begin result
:= mLayMaxSize
; end;
659 function THControl
.getFlex (): Integer; inline; begin result
:= mFlex
; end;
660 function THControl
.isHorizBox (): Boolean; inline; begin result
:= mHoriz
; end;
661 procedure THControl
.setHorizBox (v
: Boolean); inline; begin mHoriz
:= v
; end;
662 function THControl
.canWrap (): Boolean; inline; begin result
:= mCanWrap
; end;
663 procedure THControl
.setCanWrap (v
: Boolean); inline; begin mCanWrap
:= v
; end;
664 function THControl
.isLineStart (): Boolean; inline; begin result
:= mLineStart
; end;
665 procedure THControl
.setLineStart (v
: Boolean); inline; begin mLineStart
:= v
; end;
666 function THControl
.getAlign (): Integer; inline; begin result
:= mAlign
; end;
667 procedure THControl
.setAlign (v
: Integer); inline; begin mAlign
:= v
; end;
668 function THControl
.getExpand (): Boolean; inline; begin result
:= mExpand
; end;
669 procedure THControl
.setExpand (v
: Boolean); inline; begin mExpand
:= v
; end;
670 function THControl
.getHGroup (): AnsiString; inline; begin result
:= mHGroup
; end;
671 procedure THControl
.setHGroup (const v
: AnsiString); inline; begin mHGroup
:= v
; end;
672 function THControl
.getVGroup (): AnsiString; inline; begin result
:= mVGroup
; end;
673 procedure THControl
.setVGroup (const v
: AnsiString); inline; begin mVGroup
:= v
; end;
675 function THControl
.getMargins (): TLayMargins
; inline;
677 result
:= TLayMargins
.Create(mFrameHeight
, mFrameWidth
, mFrameHeight
, mFrameWidth
);
680 procedure THControl
.setActualSizePos (constref apos
: TLayPos
; constref asize
: TLaySize
); inline; begin
681 //writeln(self.className, '; pos=', apos.toString, '; size=', asize.toString);
682 if (mParent
<> nil) then
691 procedure THControl
.layPrepare ();
693 mLayDefSize
:= mDefSize
;
694 mLayMaxSize
:= mMaxSize
;
695 if (mLayMaxSize
.w
>= 0) then mLayMaxSize
.w
+= mFrameWidth
*2;
696 if (mLayMaxSize
.h
>= 0) then mLayMaxSize
.h
+= mFrameHeight
*2;
700 // ////////////////////////////////////////////////////////////////////////// //
701 function THControl
.parsePos (par
: TTextParser
): TLayPos
;
705 if (par
.eatDelim('[')) then ech
:= ']' else par
.expectDelim('(');
706 result
.x
:= par
.expectInt();
707 par
.eatDelim(','); // optional comma
708 result
.y
:= par
.expectInt();
709 par
.eatDelim(','); // optional comma
710 par
.expectDelim(ech
);
713 function THControl
.parseSize (par
: TTextParser
): TLaySize
;
717 if (par
.eatDelim('[')) then ech
:= ']' else par
.expectDelim('(');
718 result
.h
:= par
.expectInt();
719 par
.eatDelim(','); // optional comma
720 result
.w
:= par
.expectInt();
721 par
.eatDelim(','); // optional comma
722 par
.expectDelim(ech
);
725 function THControl
.parseBool (par
: TTextParser
): Boolean;
728 par
.eatIdOrStr('true', false) or
729 par
.eatIdOrStr('yes', false) or
730 par
.eatIdOrStr('tan', false);
733 if (not par
.eatIdOrStr('false', false)) and (not par
.eatIdOrStr('no', false)) and (not par
.eatIdOrStr('ona', false)) then
735 par
.error('boolean value expected');
740 function THControl
.parseAnyAlign (par
: TTextParser
): Integer;
742 if (par
.eatIdOrStr('left', false)) or (par
.eatIdOrStr('top', false)) then result
:= -1
743 else if (par
.eatIdOrStr('right', false)) or (par
.eatIdOrStr('bottom', false)) then result
:= 1
744 else if (par
.eatIdOrStr('center', false)) then result
:= 0
745 else par
.error('invalid align value');
748 function THControl
.parseHAlign (par
: TTextParser
): Integer;
750 if (par
.eatIdOrStr('left', false)) then result
:= -1
751 else if (par
.eatIdOrStr('right', false)) then result
:= 1
752 else if (par
.eatIdOrStr('center', false)) then result
:= 0
753 else par
.error('invalid horizontal align value');
756 function THControl
.parseVAlign (par
: TTextParser
): Integer;
758 if (par
.eatIdOrStr('top', false)) then result
:= -1
759 else if (par
.eatIdOrStr('bottom', false)) then result
:= 1
760 else if (par
.eatIdOrStr('center', false)) then result
:= 0
761 else par
.error('invalid vertical align value');
764 procedure THControl
.parseTextAlign (par
: TTextParser
; var h
, v
: Integer);
766 wasH
: Boolean = false;
767 wasV
: Boolean = false;
771 if (par
.eatIdOrStr('left', false)) then
773 if wasH
then par
.error('too many align directives');
778 if (par
.eatIdOrStr('right', false)) then
780 if wasH
then par
.error('too many align directives');
785 if (par
.eatIdOrStr('hcenter', false)) then
787 if wasH
then par
.error('too many align directives');
792 if (par
.eatIdOrStr('top', false)) then
794 if wasV
then par
.error('too many align directives');
799 if (par
.eatIdOrStr('bottom', false)) then
801 if wasV
then par
.error('too many align directives');
806 if (par
.eatIdOrStr('vcenter', false)) then
808 if wasV
then par
.error('too many align directives');
813 if (par
.eatIdOrStr('center', false)) then
815 if wasV
or wasH
then par
.error('too many align directives');
824 if not wasV
and not wasH
then par
.error('invalid align value');
827 // par should be on '{'; final '}' is eaten
828 procedure THControl
.parseProperties (par
: TTextParser
);
832 if (not par
.eatDelim('{')) then exit
;
833 while (not par
.eatDelim('}')) do
835 if (not par
.isIdOrStr
) then par
.error('property name expected');
838 par
.eatDelim(':'); // optional
839 if not parseProperty(pn
, par
) then par
.errorfmt('invalid property name ''%s''', [pn
]);
840 par
.eatDelim(','); // optional
844 // par should be on '{'
845 procedure THControl
.parseChildren (par
: TTextParser
);
850 par
.expectDelim('{');
851 while (not par
.eatDelim('}')) do
853 if (not par
.isIdOrStr
) then par
.error('control name expected');
854 cc
:= findCtlClass(par
.tokStr
);
855 if (cc
= nil) then par
.errorfmt('unknown control name: ''%s''', [par
.tokStr
]);
856 //writeln('children for <', par.tokStr, '>: <', cc.className, '>');
858 par
.eatDelim(':'); // optional
860 //writeln(' mHoriz=', ctl.mHoriz);
862 ctl
.parseProperties(par
);
867 //writeln(': ', ctl.mDefSize.toString);
869 par
.eatDelim(','); // optional
874 function THControl
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
877 if (strEquCI1251(prname
, 'id')) then begin mId
:= par
.expectStrOrId(true); exit
; end; // allow empty strings
878 if (strEquCI1251(prname
, 'flex')) then begin flex
:= par
.expectInt(); exit
; end;
880 if (strEquCI1251(prname
, 'defsize')) then begin mDefSize
:= parseSize(par
); exit
; end;
881 if (strEquCI1251(prname
, 'maxsize')) then begin mMaxSize
:= parseSize(par
); exit
; end;
882 if (strEquCI1251(prname
, 'wrap')) then begin mCanWrap
:= parseBool(par
); exit
; end;
883 if (strEquCI1251(prname
, 'linestart')) then begin mLineStart
:= parseBool(par
); exit
; end;
884 if (strEquCI1251(prname
, 'expand')) then begin mExpand
:= parseBool(par
); exit
; end;
886 if (strEquCI1251(prname
, 'align')) then begin mAlign
:= parseAnyAlign(par
); exit
; end;
887 if (strEquCI1251(prname
, 'hgroup')) then begin mHGroup
:= par
.expectStrOrId(true); exit
; end; // allow empty strings
888 if (strEquCI1251(prname
, 'vgroup')) then begin mVGroup
:= par
.expectStrOrId(true); exit
; end; // allow empty strings
890 if (strEquCI1251(prname
, 'canfocus')) then begin mCanFocus
:= parseBool(par
); exit
; end;
891 if (strEquCI1251(prname
, 'enabled')) then begin mEnabled
:= parseBool(par
); exit
; end;
892 if (strEquCI1251(prname
, 'disabled')) then begin mEnabled
:= not parseBool(par
); exit
; end;
893 if (strEquCI1251(prname
, 'escclose')) then begin mEscClose
:= not parseBool(par
); exit
; end;
894 if (strEquCI1251(prname
, 'eatkeys')) then begin mEatKeys
:= not parseBool(par
); exit
; end;
899 // ////////////////////////////////////////////////////////////////////////// //
900 procedure THControl
.activated ();
905 procedure THControl
.blurred ();
911 function THControl
.topLevel (): THControl
; inline;
914 while (result
.mParent
<> nil) do result
:= result
.mParent
;
918 function THControl
.getEnabled (): Boolean;
923 if (not mEnabled
) or (mWidth
< 1) or (mHeight
< 1) then exit
;
925 while (ctl
<> nil) do
927 if (not ctl
.mEnabled
) or (ctl
.mWidth
< 1) or (ctl
.mHeight
< 1) then exit
;
934 procedure THControl
.setEnabled (v
: Boolean); inline;
936 if (mEnabled
= v
) then exit
;
938 if not v
and focused
then setFocused(false);
942 function THControl
.getFocused (): Boolean; inline;
944 if (mParent
= nil) then result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = self
) else result
:= (topLevel
.mFocused
= self
);
948 procedure THControl
.setFocused (v
: Boolean); inline;
955 if (tl
.mFocused
= self
) then
958 tl
.mFocused
:= tl
.findNextFocus(self
);
959 if (tl
.mFocused
= self
) then tl
.mFocused
:= nil;
963 if (not mEnabled
) or (not mCanFocus
) then exit
;
964 if (tl
.mFocused
<> self
) then
966 tl
.mFocused
.blurred();
968 if (tl
.mGrab
<> self
) then tl
.mGrab
:= nil;
974 function THControl
.isMyChild (ctl
: THControl
): Boolean;
977 while (ctl
<> nil) do
979 if (ctl
.mParent
= self
) then exit
;
986 // returns `true` if global coords are inside this control
987 function THControl
.toLocal (var x
, y
: Integer): Boolean;
992 while (ctl
<> nil) do
998 result
:= (x
>= 0) and (y
>= 0) and (x
< mWidth
) and (y
< mHeight
);
1002 procedure THControl
.toGlobal (var x
, y
: Integer);
1007 while (ctl
<> nil) do
1016 // x and y are global coords
1017 function THControl
.controlAtXY (x
, y
: Integer): THControl
;
1023 if (not mEnabled
) or (mWidth
< 1) or (mHeight
< 1) then exit
;
1026 if not toLocal(lx
, ly
) then exit
;
1027 for f
:= High(mChildren
) downto 0 do
1029 result
:= mChildren
[f
].controlAtXY(x
, y
);
1030 if (result
<> nil) then exit
;
1036 function THControl
.prevSibling (): THControl
;
1040 if (mParent
<> nil) then
1042 for f
:= 1 to High(mParent
.mChildren
) do
1044 if (mParent
.mChildren
[f
] = self
) then begin result
:= mParent
.mChildren
[f
-1]; exit
; end;
1050 function THControl
.nextSibling (): THControl
;
1054 if (mParent
<> nil) then
1056 for f
:= 0 to High(mParent
.mChildren
)-1 do
1058 if (mParent
.mChildren
[f
] = self
) then begin result
:= mParent
.mChildren
[f
+1]; exit
; end;
1064 function THControl
.firstChild (): THControl
; inline;
1066 if (Length(mChildren
) <> 0) then result
:= mChildren
[0] else result
:= nil;
1069 function THControl
.lastChild (): THControl
; inline;
1071 if (Length(mChildren
) <> 0) then result
:= mChildren
[High(mChildren
)] else result
:= nil;
1075 function THControl
.findFirstFocus (): THControl
;
1082 for f
:= 0 to High(mChildren
) do
1084 result
:= mChildren
[f
].findFirstFocus();
1085 if (result
<> nil) then exit
;
1087 if mCanFocus
then result
:= self
;
1092 function THControl
.findLastFocus (): THControl
;
1099 for f
:= High(mChildren
) downto 0 do
1101 result
:= mChildren
[f
].findLastFocus();
1102 if (result
<> nil) then exit
;
1104 if mCanFocus
then result
:= self
;
1109 function THControl
.findNextFocus (cur
: THControl
): THControl
;
1114 if not isMyChild(cur
) then cur
:= nil;
1115 if (cur
= nil) then begin result
:= findFirstFocus(); exit
; end;
1116 result
:= cur
.findFirstFocus();
1117 if (result
<> nil) and (result
<> cur
) then exit
;
1120 cur
:= cur
.nextSibling
;
1121 if (cur
= nil) then break
;
1122 result
:= cur
.findFirstFocus();
1123 if (result
<> nil) then exit
;
1125 result
:= findFirstFocus();
1130 function THControl
.findPrevFocus (cur
: THControl
): THControl
;
1135 if not isMyChild(cur
) then cur
:= nil;
1136 if (cur
= nil) then begin result
:= findLastFocus(); exit
; end;
1138 result
:= cur
.findLastFocus();
1139 if (result
<> nil) and (result
<> cur
) then exit
;
1142 cur
:= cur
.prevSibling
;
1143 if (cur
= nil) then break
;
1144 result
:= cur
.findLastFocus();
1145 if (result
<> nil) then exit
;
1147 result
:= findLastFocus();
1152 procedure THControl
.appendChild (ctl
: THControl
);
1154 if (ctl
= nil) then exit
;
1155 if (ctl
.mParent
<> nil) then exit
;
1156 SetLength(mChildren
, Length(mChildren
)+1);
1157 mChildren
[High(mChildren
)] := ctl
;
1158 ctl
.mParent
:= self
;
1159 Inc(ctl
.mX
, mFrameWidth
);
1160 Inc(ctl
.mY
, mFrameHeight
);
1161 if (ctl
.mWidth
> 0) and (ctl
.mHeight
> 0) and
1162 (ctl
.mX
+ctl
.mWidth
> mFrameWidth
) and (ctl
.mY
+ctl
.mHeight
> mFrameHeight
) then
1164 if (mWidth
+mFrameWidth
< ctl
.mX
+ctl
.mWidth
) then mWidth
:= ctl
.mX
+ctl
.mWidth
+mFrameWidth
;
1165 if (mHeight
+mFrameHeight
< ctl
.mY
+ctl
.mHeight
) then mHeight
:= ctl
.mY
+ctl
.mHeight
+mFrameHeight
;
1167 if (mFocused
= nil) and ctl
.mEnabled
and ctl
.mCanFocus
and (ctl
.mWidth
> 0) and (ctl
.mHeight
> 0) then mFocused
:= ctl
;
1171 procedure THControl
.setScissorGLInternal (x
, y
, w
, h
: Integer);
1173 if not scallowed
then exit
;
1174 x
:= trunc(x
*gh_ui_scale
);
1175 y
:= trunc(y
*gh_ui_scale
);
1176 w
:= trunc(w
*gh_ui_scale
);
1177 h
:= trunc(h
*gh_ui_scale
);
1178 scis
.combineRect(x
, y
, w
, h
);
1182 procedure THControl
.resetScissor ();
1186 if not scallowed
then exit
;
1190 setScissorGLInternal(x
, y
, mWidth
, mHeight
);
1194 procedure THControl
.setScissor (lx
, ly
, lw
, lh
: Integer);
1197 //ox, oy, ow, oh: Integer;
1199 if not scallowed
then exit
;
1200 //ox := lx; oy := ly; ow := lw; oh := lh;
1201 if not intersectRect(lx
, ly
, lw
, lh
, 0, 0, mWidth
, mHeight
) then
1203 //writeln('oops: <', self.className, '>: old=(', ox, ',', oy, ')-[', ow, ',', oh, ']');
1204 glScissor(0, 0, 0, 0);
1210 setScissorGLInternal(x
, y
, lw
, lh
);
1214 procedure THControl
.draw ();
1219 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1223 //conwritefln('[%s]: (%d,%d)-(%d,%d) (%d,%d)', [ClassName, mX, mY, mWidth, mHeight, x, y]);
1225 scis
.save(true); // scissoring enabled
1227 //glEnable(GL_SCISSOR_TEST);
1231 if (mFrameWidth
<> 0) or (mFrameHeight
<> 0) then setScissor(mFrameWidth
, mFrameHeight
, mWidth
-mFrameWidth
*2, mHeight
-mFrameHeight
*2);
1232 for f
:= 0 to High(mChildren
) do mChildren
[f
].draw();
1233 if (mFrameWidth
<> 0) or (mFrameHeight
<> 0) then resetScissor();
1234 drawControlPost(x
, y
);
1242 procedure THControl
.drawControl (sx
, sy
: Integer);
1244 if (mParent
= nil) then darkenRect(sx
, sy
, mWidth
, mHeight
, 64);
1248 procedure THControl
.drawControlPost (sx
, sy
: Integer);
1251 if mDrawShadow
and (mWidth
> 0) and (mHeight
> 0) then
1253 setScissorGLInternal(sx
+8, sy
+8, mWidth
, mHeight
);
1254 darkenRect(sx
+mWidth
, sy
+8, 8, mHeight
, 128);
1255 darkenRect(sx
+8, sy
+mHeight
, mWidth
-8, 8, 128);
1260 function THControl
.mouseEvent (var ev
: THMouseEvent
): Boolean;
1265 if not mEnabled
then exit
;
1266 if (mParent
= nil) then
1268 if (mGrab
<> nil) then
1270 result
:= mGrab
.mouseEvent(ev
);
1271 if (ev
.release
) then mGrab
:= nil;
1275 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1276 ctl
:= controlAtXY(ev
.x
, ev
.y
);
1277 if (ctl
<> nil) and (ctl
<> self
) then
1279 if (ctl
<> topLevel
.mFocused
) then ctl
.setFocused(true);
1280 result
:= ctl
.mouseEvent(ev
);
1282 else if (ctl
= self
) and assigned(actionCB
) then
1289 function THControl
.keyEvent (var ev
: THKeyEvent
): Boolean;
1294 if not mEnabled
then exit
;
1295 if (topLevel
.mFocused
<> self
) and isMyChild(topLevel
.mFocused
) and topLevel
.mFocused
.mEnabled
then result
:= topLevel
.mFocused
.keyEvent(ev
);
1296 if (mParent
= nil) then
1298 if (ev
= 'S-Tab') then
1301 ctl
:= findPrevFocus(mFocused
);
1302 if (ctl
<> mFocused
) then
1309 if (ev
= 'Tab') then
1312 ctl
:= findNextFocus(mFocused
);
1313 if (ctl
<> mFocused
) then
1320 if mEscClose
and (ev
= 'Escape') then
1323 uiRemoveWindow(self
);
1327 if mEatKeys
then result
:= true;
1331 // ////////////////////////////////////////////////////////////////////////// //
1332 constructor THTopWindow
.Create (const atitle
: AnsiString; ax
, ay
: Integer; aw
: Integer=-1; ah
: Integer=-1);
1334 inherited Create(ax
, ay
, aw
, ah
);
1338 if (mWidth
< mFrameWidth
*2+3*8) then mWidth
:= mFrameWidth
*2+3*8;
1339 if (mHeight
< mFrameHeight
*2) then mHeight
:= mFrameHeight
*2;
1340 if (Length(mTitle
) > 0) then
1342 if (mWidth
< Length(mTitle
)*8+mFrameWidth
*2+3*8) then mWidth
:= Length(mTitle
)*8+mFrameWidth
*2+3*8;
1345 mDrawShadow
:= true;
1346 mWaitingClose
:= false;
1352 function THTopWindow
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
1354 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) then
1356 mTitle
:= par
.expectStrOrId(true);
1360 if (strEquCI1251(prname
, 'children')) then
1366 if (strEquCI1251(prname
, 'orientation')) or (strEquCI1251(prname
, 'orient')) then
1368 if (par
.eatIdOrStr('horizontal', false)) or (par
.eatIdOrStr('horiz', false)) then mHoriz
:= true
1369 else if (par
.eatIdOrStr('vertical', false)) or (par
.eatIdOrStr('vert', false)) then mHoriz
:= false
1370 else par
.error('`horizontal` or `vertical` expected');
1374 result
:= inherited parseProperty(prname
, par
);
1378 procedure THTopWindow
.centerInScreen ();
1380 if (mWidth
> 0) and (mHeight
> 0) then
1382 mX
:= trunc((gScrWidth
/gh_ui_scale
-mWidth
)/2);
1383 mY
:= trunc((gScrHeight
/gh_ui_scale
-mHeight
)/2);
1388 procedure THTopWindow
.drawControl (sx
, sy
: Integer);
1390 fillRect(sx
, sy
, mWidth
, mHeight
, 0, 0, 128);
1394 procedure THTopWindow
.drawControlPost (sx
, sy
: Integer);
1403 drawRectUI(mX
+4, mY
+4, mWidth
-8, mHeight
-8, r
, g
, b
);
1407 drawRectUI(mX
+3, mY
+3, mWidth
-6, mHeight
-6, r
, g
, b
);
1408 drawRectUI(mX
+5, mY
+5, mWidth
-10, mHeight
-10, r
, g
, b
);
1409 setScissor(mFrameWidth
, 0, 3*8, 8);
1410 fillRect(mX
+mFrameWidth
, mY
, 3*8, 8, 0, 0, 128);
1411 drawText8(mX
+mFrameWidth
, mY
, '[ ]', r
, g
, b
);
1412 if mInClose
then drawText8(mX
+mFrameWidth
+7, mY
, '#', 0, 255, 0)
1413 else drawText8(mX
+mFrameWidth
+7, mY
, '*', 0, 255, 0);
1415 if (Length(mTitle
) > 0) then
1417 setScissor(mFrameWidth
+3*8, 0, mWidth
-mFrameWidth
*2-3*8, 8);
1418 tx
:= (mX
+3*8)+((mWidth
-3*8)-Length(mTitle
)*8) div 2;
1419 fillRect(tx
-3, mY
, Length(mTitle
)*8+3+2, 8, 0, 0, 128);
1420 drawText8(tx
, mY
, mTitle
, r
, g
, b
);
1422 inherited drawControlPost(sx
, sy
);
1426 procedure THTopWindow
.blurred ();
1429 mWaitingClose
:= false;
1435 function THTopWindow
.keyEvent (var ev
: THKeyEvent
): Boolean;
1437 result
:= inherited keyEvent(ev
);
1438 if not getFocused
then exit
;
1439 if (ev
= 'M-F3') then
1441 uiRemoveWindow(self
);
1448 function THTopWindow
.mouseEvent (var ev
: THMouseEvent
): Boolean;
1453 if not mEnabled
then exit
;
1454 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1458 mX
+= ev
.x
-mDragStartX
;
1459 mY
+= ev
.y
-mDragStartY
;
1460 mDragStartX
:= ev
.x
;
1461 mDragStartY
:= ev
.y
;
1462 if (ev
.release
) then mDragging
:= false;
1469 if toLocal(lx
, ly
) then
1475 if (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+3*8) then
1477 //uiRemoveWindow(self);
1478 mWaitingClose
:= true;
1484 mDragStartX
:= ev
.x
;
1485 mDragStartY
:= ev
.y
;
1490 if (lx
< mFrameWidth
) or (lx
>= mWidth
-mFrameWidth
) or (ly
>= mHeight
-mFrameHeight
) then
1493 mDragStartX
:= ev
.x
;
1494 mDragStartY
:= ev
.y
;
1500 if (ev
.release
) then
1502 if mWaitingClose
and (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+3*8) then
1504 uiRemoveWindow(self
);
1508 mWaitingClose
:= false;
1514 if mWaitingClose
then
1516 mInClose
:= (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+3*8);
1525 if (not ev
.motion
) then mWaitingClose
:= false;
1528 result
:= inherited mouseEvent(ev
);
1532 // ////////////////////////////////////////////////////////////////////////// //
1533 constructor THCtlSimpleText
.Create (ax
, ay
: Integer);
1536 inherited Create(ax
, ay
, 4, 4);
1540 destructor THCtlSimpleText
.Destroy ();
1547 procedure THCtlSimpleText
.appendItem (const atext
: AnsiString; acentered
: Boolean=false; ahline
: Boolean=false);
1551 if (Length(atext
)*8+3*8+2 > mWidth
) then mWidth
:= Length(atext
)*8+3*8+2;
1552 SetLength(mItems
, Length(mItems
)+1);
1553 it
:= @mItems
[High(mItems
)];
1555 it
.centered
:= acentered
;
1557 if (Length(mItems
)*8 > mHeight
) then mHeight
:= Length(mItems
)*8;
1561 procedure THCtlSimpleText
.drawControl (sx
, sy
: Integer);
1567 for f
:= 0 to High(mItems
) do
1574 if it
.centered
then begin b
:= 255; tx
:= sx
+(mWidth
-Length(it
.title
)*8) div 2; end;
1578 if (Length(it
.title
) = 0) then
1580 drawHLine(sx
+4, sy
+3, mWidth
-8, r
, g
, b
);
1582 else if (tx
-3 > sx
+4) then
1584 drawHLine(sx
+4, sy
+3, tx
-3-(sx
+3), r
, g
, b
);
1585 drawHLine(tx
+Length(it
.title
)*8, sy
+3, mWidth
-4, r
, g
, b
);
1588 drawText8(tx
, sy
, it
.title
, r
, g
, b
);
1594 function THCtlSimpleText
.mouseEvent (var ev
: THMouseEvent
): Boolean;
1598 result
:= inherited mouseEvent(ev
);
1601 if not result
and toLocal(lx
, ly
) then
1608 function THCtlSimpleText
.keyEvent (var ev
: THKeyEvent
): Boolean;
1610 result
:= inherited keyEvent(ev
);
1614 // ////////////////////////////////////////////////////////////////////////// //
1615 constructor THCtlCBListBox
.Create (ax
, ay
: Integer);
1619 inherited Create(ax
, ay
, 4, 4);
1623 destructor THCtlCBListBox
.Destroy ();
1630 procedure THCtlCBListBox
.appendItem (const atext
: AnsiString; bv
: PBoolean; aaction
: TActionCB
=nil);
1634 if (Length(atext
)*8+3*8+2 > mWidth
) then mWidth
:= Length(atext
)*8+3*8+2;
1635 SetLength(mItems
, Length(mItems
)+1);
1636 it
:= @mItems
[High(mItems
)];
1639 it
.actionCB
:= aaction
;
1640 if (Length(mItems
)*8 > mHeight
) then mHeight
:= Length(mItems
)*8;
1641 if (mCurIndex
< 0) then mCurIndex
:= 0;
1645 procedure THCtlCBListBox
.drawControl (sx
, sy
: Integer);
1650 for f
:= 0 to High(mItems
) do
1653 if (mCurIndex
= f
) then fillRect(sx
, sy
, mWidth
, 8, 0, 128, 0);
1654 if (it
.varp
<> nil) then
1656 if it
.varp
^ then drawText8(sx
, sy
, '[x]', 255, 255, 255) else drawText8(sx
, sy
, '[ ]', 255, 255, 255);
1657 drawText8(sx
+3*8+2, sy
, it
.title
, 255, 255, 0);
1659 else if (Length(it
.title
) > 0) then
1661 tx
:= sx
+(mWidth
-Length(it
.title
)*8) div 2;
1662 if (tx
-3 > sx
+4) then
1664 drawHLine(sx
+4, sy
+3, tx
-3-(sx
+3), 255, 255, 255);
1665 drawHLine(tx
+Length(it
.title
)*8, sy
+3, mWidth
-4, 255, 255, 255);
1667 drawText8(tx
, sy
, it
.title
, 255, 255, 255);
1671 drawHLine(sx
+4, sy
+3, mWidth
-8, 255, 255, 255);
1678 function THCtlCBListBox
.mouseEvent (var ev
: THMouseEvent
): Boolean;
1683 result
:= inherited mouseEvent(ev
);
1686 if not result
and toLocal(lx
, ly
) then
1689 if (ev
= 'lmb') then
1692 if (ly
>= 0) and (ly
< Length(mItems
)) then
1695 if (it
.varp
<> nil) then
1698 it
.varp
^ := not it
.varp
^;
1699 if assigned(it
.actionCB
) then it
.actionCB(self
, Integer(it
.varp
^));
1700 if assigned(actionCB
) then actionCB(self
, ly
);
1708 function THCtlCBListBox
.keyEvent (var ev
: THKeyEvent
): Boolean;
1712 result
:= inherited keyEvent(ev
);
1713 if not getFocused
then exit
;
1715 if (ev
= 'Home') or (ev
= 'PageUp') then
1720 if (ev
= 'End') or (ev
= 'PageDown') then
1723 mCurIndex
:= High(mItems
);
1728 if (Length(mItems
) > 0) then
1730 if (mCurIndex
< 0) then mCurIndex
:= Length(mItems
);
1731 while (mCurIndex
> 0) do
1734 if (mItems
[mCurIndex
].varp
<> nil) then break
;
1742 if (ev
= 'Down') then
1745 if (Length(mItems
) > 0) then
1747 if (mCurIndex
< 0) then mCurIndex
:= -1;
1748 while (mCurIndex
< High(mItems
)) do
1751 if (mItems
[mCurIndex
].varp
<> nil) then break
;
1759 if (ev
= 'Space') or (ev
= 'Enter') then
1762 if (mCurIndex
>= 0) and (mCurIndex
< Length(mItems
)) and (mItems
[mCurIndex
].varp
<> nil) then
1764 it
:= @mItems
[mCurIndex
];
1765 it
.varp
^ := not it
.varp
^;
1766 if assigned(it
.actionCB
) then it
.actionCB(self
, Integer(it
.varp
^));
1767 if assigned(actionCB
) then actionCB(self
, mCurIndex
);
1773 // ////////////////////////////////////////////////////////////////////////// //
1774 constructor THCtlBox
.Create (ahoriz
: Boolean);
1781 function THCtlBox
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
1783 if (strEquCI1251(prname
, 'orientation')) or (strEquCI1251(prname
, 'orient')) then
1785 if (par
.eatIdOrStr('horizontal', false)) or (par
.eatIdOrStr('horiz', false)) then mHoriz
:= true
1786 else if (par
.eatIdOrStr('vertical', false)) or (par
.eatIdOrStr('vert', false)) then mHoriz
:= false
1787 else par
.error('`horizontal` or `vertical` expected');
1791 if (strEquCI1251(prname
, 'frame')) then
1793 mHasFrame
:= parseBool(par
);
1794 if (mHasFrame
) then begin mFrameWidth
:= 8; mFrameHeight
:= 8; end else begin mFrameWidth
:= 0; mFrameHeight
:= 0; end;
1798 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) then
1800 mCaption
:= par
.expectStrOrId(true);
1801 mDefSize
:= TLaySize
.Create(Length(mCaption
)*8+2+8*2, 8*2+2);
1805 if (strEquCI1251(prname
, 'children')) then
1811 result
:= inherited parseProperty(prname
, par
);
1815 procedure THCtlBox
.drawControl (sx
, sy
: Integer);
1820 if focused
then begin r
:= 255; g
:= 255; b
:= 255; end else begin r
:= 255; g
:= 255; b
:= 0; end;
1824 drawRectUI(mX
+3, mY
+3, mWidth
-6, mHeight
-6, r
, g
, b
);
1826 if (Length(mCaption
) > 0) then
1828 setScissor(mFrameWidth
+1, 0, mWidth
-mFrameWidth
-2, 8);
1829 tx
:= mX
+((mWidth
-Length(mCaption
)*8) div 2)-1;
1830 if mHasFrame
then fillRect(tx
, mY
, Length(mCaption
)*8+2, 8, 0, 0, 128);
1831 drawText8(tx
+1, mY
, mCaption
, r
, g
, b
);
1836 function THCtlBox
.mouseEvent (var ev
: THMouseEvent
): Boolean;
1840 result
:= inherited mouseEvent(ev
);
1843 if not result
and toLocal(lx
, ly
) then
1850 //TODO: navigation with arrow keys, according to box orientation
1851 function THCtlBox
.keyEvent (var ev
: THKeyEvent
): Boolean;
1853 result
:= inherited keyEvent(ev
);
1857 // ////////////////////////////////////////////////////////////////////////// //
1858 procedure THCtlHBox
.AfterConstruction ();
1860 inherited AfterConstruction();
1865 // ////////////////////////////////////////////////////////////////////////// //
1866 procedure THCtlVBox
.AfterConstruction ();
1868 inherited AfterConstruction();
1872 // ////////////////////////////////////////////////////////////////////////// //
1873 constructor THCtlTextLabel
.Create (const atext
: AnsiString);
1879 mDefSize
:= TLaySize
.Create(Length(atext
)*8, 8);
1883 function THCtlTextLabel
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
1885 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) then
1887 mText
:= par
.expectStrOrId(true);
1888 mDefSize
:= TLaySize
.Create(Length(mText
)*8, 8);
1892 if (strEquCI1251(prname
, 'textalign')) then
1894 parseTextAlign(par
, mHAlign
, mVAlign
);
1898 result
:= inherited parseProperty(prname
, par
);
1902 procedure THCtlTextLabel
.drawControl (sx
, sy
: Integer);
1904 xpos
, ypos
: Integer;
1907 fillRect(sx
, sy
, mWidth
, mHeight
, 96, 96, 0);
1908 drawRectUI(sx
, sy
, mWidth
, mHeight
, 96, 96, 96);
1910 if (Length(mText
) > 0) then
1912 if (mHAlign
< 0) then xpos
:= 0
1913 else if (mHAlign
> 0) then xpos
:= mWidth
-Length(mText
)*8
1914 else xpos
:= (mWidth
-Length(mText
)*8) div 2;
1916 if (mVAlign
< 0) then ypos
:= 0
1917 else if (mVAlign
> 0) then ypos
:= mHeight
-8
1918 else ypos
:= (mHeight
-8) div 2;
1920 drawText8(sx
+xpos
, sy
+ypos
, mText
, 255, 255, 255);
1925 function THCtlTextLabel
.mouseEvent (var ev
: THMouseEvent
): Boolean;
1929 result
:= inherited mouseEvent(ev
);
1932 if not result
and toLocal(lx
, ly
) then
1939 function THCtlTextLabel
.keyEvent (var ev
: THKeyEvent
): Boolean;
1941 result
:= inherited keyEvent(ev
);
1946 registerCtlClass(THCtlBox
, 'box');
1947 registerCtlClass(THCtlHBox
, 'hbox');
1948 registerCtlClass(THCtlVBox
, 'vbox');
1949 registerCtlClass(THCtlTextLabel
, 'label');