1 (* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 {$INCLUDE ../shared/a_modes.inc}
32 // ////////////////////////////////////////////////////////////////////////// //
34 TUIControlClass
= class of TUIControl
;
38 type TActionCB
= procedure (me
: TUIControl
; uinfo
: Integer);
39 type TCloseRequestCB
= function (me
: TUIControl
): Boolean; // top-level windows will call this before closing with icon/keyboard
42 const ClrIdxActive
= 0;
43 const ClrIdxDisabled
= 1;
44 const ClrIdxInactive
= 2;
52 mWidth
, mHeight
: Integer;
53 mFrameWidth
, mFrameHeight
: Integer;
56 mChildren
: array of TUIControl
;
57 mFocused
: TUIControl
; // valid only for top-level controls
58 mEscClose
: Boolean; // valid only for top-level controls
64 mCtl4Style
: AnsiString;
65 mBackColor
: array[0..ClrIdxMax
] of TGxRGBA
;
66 mTextColor
: array[0..ClrIdxMax
] of TGxRGBA
;
67 mFrameColor
: array[0..ClrIdxMax
] of TGxRGBA
;
68 mFrameTextColor
: array[0..ClrIdxMax
] of TGxRGBA
;
69 mFrameIconColor
: array[0..ClrIdxMax
] of TGxRGBA
;
70 mDarken
: array[0..ClrIdxMax
] of Integer; // -1: none
77 procedure updateStyle (); virtual;
78 procedure cacheStyle (root
: TUIStyle
); virtual;
79 function getColorIndex (): Integer; inline;
82 function getEnabled (): Boolean;
83 procedure setEnabled (v
: Boolean); inline;
85 function getFocused (): Boolean; inline;
86 procedure setFocused (v
: Boolean); inline;
88 function getCanFocus (): Boolean; inline;
90 function isMyChild (ctl
: TUIControl
): Boolean;
92 function findFirstFocus (): TUIControl
;
93 function findLastFocus (): TUIControl
;
95 function findNextFocus (cur
: TUIControl
): TUIControl
;
96 function findPrevFocus (cur
: TUIControl
): TUIControl
;
98 function findCancelControl (): TUIControl
;
99 function findDefaulControl (): TUIControl
;
101 function findControlById (const aid
: AnsiString): TUIControl
;
103 procedure activated (); virtual;
104 procedure blurred (); virtual;
106 //WARNING! do not call scissor functions outside `.draw*()` API!
107 // set scissor to this rect (in local coords)
108 procedure setScissor (lx
, ly
, lw
, lh
: Integer);
109 // reset scissor to whole control
110 procedure resetScissor (fullArea
: Boolean); inline; // "full area" means "with frame"
113 // set scissor to this rect (in global coords)
114 procedure setScissorGLInternal (x
, y
, w
, h
: Integer);
118 closeRequestCB
: TCloseRequestCB
;
121 mDefSize
: TLaySize
; // default size
122 mMaxSize
: TLaySize
; // maximum size
131 mLayDefSize
: TLaySize
;
132 mLayMaxSize
: TLaySize
;
135 // layouter interface
136 function getDefSize (): TLaySize
; inline; // default size; <0: use max size
137 //procedure setDefSize (const sz: TLaySize); inline; // default size; <0: use max size
138 function getMargins (): TLayMargins
; inline;
139 function getMaxSize (): TLaySize
; inline; // max size; <0: set to some huge value
140 //procedure setMaxSize (const sz: TLaySize); inline; // max size; <0: set to some huge value
141 function getFlex (): Integer; inline; // <=0: not flexible
142 function isHorizBox (): Boolean; inline; // horizontal layout for children?
143 procedure setHorizBox (v
: Boolean); inline; // horizontal layout for children?
144 function canWrap (): Boolean; inline; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
145 procedure setCanWrap (v
: Boolean); inline; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
146 function isLineStart (): Boolean; inline; // `true` if this ctl should start a new line; ignored for vertical boxes
147 procedure setLineStart (v
: Boolean); inline; // `true` if this ctl should start a new line; ignored for vertical boxes
148 function getAlign (): Integer; inline; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
149 procedure setAlign (v
: Integer); inline; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
150 function getExpand (): Boolean; inline; // expanding in non-main direction: `true` will ignore align and eat all available space
151 procedure setExpand (v
: Boolean); inline; // expanding in non-main direction: `true` will ignore align and eat all available space
152 function getHGroup (): AnsiString; inline; // empty: not grouped
153 procedure setHGroup (const v
: AnsiString); inline; // empty: not grouped
154 function getVGroup (): AnsiString; inline; // empty: not grouped
155 procedure setVGroup (const v
: AnsiString); inline; // empty: not grouped
157 procedure setActualSizePos (constref apos
: TLayPos
; constref asize
: TLaySize
); inline;
159 procedure layPrepare (); virtual; // called before registering control in layouter
162 property flex
: Integer read mFlex write mFlex
;
163 property flDefaultSize
: TLaySize read mDefSize write mDefSize
;
164 property flMaxSize
: TLaySize read mMaxSize write mMaxSize
;
165 property flHoriz
: Boolean read isHorizBox write setHorizBox
;
166 property flCanWrap
: Boolean read canWrap write setCanWrap
;
167 property flLineStart
: Boolean read isLineStart write setLineStart
;
168 property flAlign
: Integer read getAlign write setAlign
;
169 property flExpand
: Boolean read getExpand write setExpand
;
170 property flHGroup
: AnsiString read getHGroup write setHGroup
;
171 property flVGroup
: AnsiString read getVGroup write setVGroup
;
174 function parsePos (par
: TTextParser
): TLayPos
;
175 function parseSize (par
: TTextParser
): TLaySize
;
176 function parseBool (par
: TTextParser
): Boolean;
177 function parseAnyAlign (par
: TTextParser
): Integer;
178 function parseHAlign (par
: TTextParser
): Integer;
179 function parseVAlign (par
: TTextParser
): Integer;
180 function parseOrientation (const prname
: AnsiString; par
: TTextParser
): Boolean;
181 procedure parseTextAlign (par
: TTextParser
; var h
, v
: Integer);
182 procedure parseChildren (par
: TTextParser
); // par should be on '{'; final '}' is eaten
185 // par is on property data
186 // there may be more data in text stream, don't eat it!
187 // return `true` if property name is valid and value was parsed
188 // return `false` if property name is invalid; don't advance parser in this case
189 // throw on property data errors
190 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; virtual;
192 // par should be on '{'; final '}' is eaten
193 procedure parseProperties (par
: TTextParser
);
196 constructor Create ();
197 constructor Create (ax
, ay
, aw
, ah
: Integer);
198 destructor Destroy (); override;
200 // `sx` and `sy` are screen coordinates
201 procedure drawControl (gx
, gy
: Integer); virtual;
203 // called after all children drawn
204 procedure drawControlPost (gx
, gy
: Integer); virtual;
206 procedure draw (); virtual;
208 function topLevel (): TUIControl
; inline;
210 // returns `true` if global coords are inside this control
211 function toLocal (var x
, y
: Integer): Boolean;
212 function toLocal (gx
, gy
: Integer; out x
, y
: Integer): Boolean; inline;
213 procedure toGlobal (var x
, y
: Integer);
214 procedure toGlobal (lx
, ly
: Integer; out x
, y
: Integer); inline;
216 // x and y are global coords
217 function controlAtXY (x
, y
: Integer; allowDisabled
: Boolean=false): TUIControl
;
219 procedure doAction ();
221 procedure mouseEvent (var ev
: THMouseEvent
); virtual; // returns `true` if event was eaten
222 procedure keyEvent (var ev
: THKeyEvent
); virtual; // returns `true` if event was eaten
224 function prevSibling (): TUIControl
;
225 function nextSibling (): TUIControl
;
226 function firstChild (): TUIControl
; inline;
227 function lastChild (): TUIControl
; inline;
229 procedure appendChild (ctl
: TUIControl
); virtual;
231 procedure close (); // this closes *top-level* control
234 property id
: AnsiString read mId
;
235 property styleId
: AnsiString read mStyleId
;
236 property x0
: Integer read mX
;
237 property y0
: Integer read mY
;
238 property height
: Integer read mHeight
;
239 property width
: Integer read mWidth
;
240 property enabled
: Boolean read getEnabled write setEnabled
;
241 property parent
: TUIControl read mParent
;
242 property focused
: Boolean read getFocused write setFocused
;
243 property escClose
: Boolean read mEscClose write mEscClose
;
244 property eatKeys
: Boolean read mEatKeys write mEatKeys
;
245 property cancel
: Boolean read mCancel write mCancel
;
246 property defctl
: Boolean read mDefault write mDefault
;
247 property canFocus
: Boolean read getCanFocus write mCanFocus
;
248 property ctlById
[const aid
: AnsiString]: TUIControl read findControlById
; default
;
252 TUITopWindow
= class(TUIControl
)
256 mDragStartX
, mDragStartY
: Integer;
257 mWaitingClose
: Boolean;
259 mFreeOnClose
: Boolean; // default: false
260 mDoCenter
: Boolean; // after layouting
263 procedure cacheStyle (root
: TUIStyle
); override;
266 procedure activated (); override;
267 procedure blurred (); override;
270 closeCB
: TActionCB
; // called after window was removed from ui window list
273 constructor Create (const atitle
: AnsiString; ax
, ay
: Integer; aw
: Integer=-1; ah
: Integer=-1);
275 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
277 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
279 procedure centerInScreen ();
281 // `sx` and `sy` are screen coordinates
282 procedure drawControl (gx
, gy
: Integer); override;
283 procedure drawControlPost (gx
, gy
: Integer); override;
285 procedure keyEvent (var ev
: THKeyEvent
); override; // returns `true` if event was eaten
286 procedure mouseEvent (var ev
: THMouseEvent
); override; // returns `true` if event was eaten
289 property freeOnClose
: Boolean read mFreeOnClose write mFreeOnClose
;
293 TUISimpleText
= class(TUIControl
)
303 mItems
: array of TItem
;
306 constructor Create (ax
, ay
: Integer);
307 destructor Destroy (); override;
309 procedure appendItem (const atext
: AnsiString; acentered
: Boolean=false; ahline
: Boolean=false);
311 procedure drawControl (gx
, gy
: Integer); override;
313 procedure mouseEvent (var ev
: THMouseEvent
); override;
317 TUICBListBox
= class(TUIControl
)
327 mItems
: array of TItem
;
331 constructor Create (ax
, ay
: Integer);
332 destructor Destroy (); override;
334 procedure appendItem (const atext
: AnsiString; bv
: PBoolean; aaction
: TActionCB
=nil);
336 procedure drawControl (gx
, gy
: Integer); override;
338 procedure mouseEvent (var ev
: THMouseEvent
); override;
339 procedure keyEvent (var ev
: THKeyEvent
); override;
342 // ////////////////////////////////////////////////////////////////////// //
343 TUIBox
= class(TUIControl
)
346 mCaption
: AnsiString;
349 constructor Create (ahoriz
: Boolean);
351 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
353 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
355 procedure drawControl (gx
, gy
: Integer); override;
357 procedure mouseEvent (var ev
: THMouseEvent
); override;
358 procedure keyEvent (var ev
: THKeyEvent
); override;
361 TUIHBox
= class(TUIBox
)
363 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
366 TUIVBox
= class(TUIBox
)
368 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
371 // ////////////////////////////////////////////////////////////////////// //
372 TUISpan
= class(TUIControl
)
374 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
376 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
378 procedure drawControl (gx
, gy
: Integer); override;
381 // ////////////////////////////////////////////////////////////////////// //
382 TUILine
= class(TUIControl
)
384 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
386 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
388 procedure drawControl (gx
, gy
: Integer); override;
391 TUIHLine
= class(TUILine
)
393 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
396 TUIVLine
= class(TUILine
)
398 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
401 // ////////////////////////////////////////////////////////////////////// //
402 TUITextLabel
= class(TUIControl
)
405 mHAlign
: Integer; // -1: left; 0: center; 1: right; default: left
406 mVAlign
: Integer; // -1: top; 0: center; 1: bottom; default: center
409 constructor Create (const atext
: AnsiString);
411 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
413 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
415 procedure drawControl (gx
, gy
: Integer); override;
417 procedure mouseEvent (var ev
: THMouseEvent
); override;
420 // ////////////////////////////////////////////////////////////////////// //
421 TUIButton
= class(TUITextLabel
)
423 constructor Create (const atext
: AnsiString);
425 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
427 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
429 procedure drawControl (gx
, gy
: Integer); override;
431 procedure mouseEvent (var ev
: THMouseEvent
); override;
432 procedure keyEvent (var ev
: THKeyEvent
); override;
436 // ////////////////////////////////////////////////////////////////////////// //
437 procedure uiMouseEvent (var evt
: THMouseEvent
);
438 procedure uiKeyEvent (var evt
: THKeyEvent
);
442 // ////////////////////////////////////////////////////////////////////////// //
443 procedure uiAddWindow (ctl
: TUIControl
);
444 procedure uiRemoveWindow (ctl
: TUIControl
); // will free window if `mFreeOnClose` is `true`
445 function uiVisibleWindow (ctl
: TUIControl
): Boolean;
447 procedure uiUpdateStyles ();
450 // ////////////////////////////////////////////////////////////////////////// //
452 procedure uiLayoutCtl (ctl
: TUIControl
);
455 // ////////////////////////////////////////////////////////////////////////// //
457 gh_ui_scale
: Single = 1.0;
467 // ////////////////////////////////////////////////////////////////////////// //
469 ctlsToKill
: array of TUIControl
= nil;
472 procedure scheduleKill (ctl
: TUIControl
);
476 if (ctl
= nil) then exit
;
478 for f
:= 0 to High(ctlsToKill
) do
480 if (ctlsToKill
[f
] = ctl
) then exit
;
481 if (ctlsToKill
[f
] = nil) then begin ctlsToKill
[f
] := ctl
; exit
; end;
483 SetLength(ctlsToKill
, Length(ctlsToKill
)+1);
484 ctlsToKill
[High(ctlsToKill
)] := ctl
;
488 procedure processKills ();
493 for f
:= 0 to High(ctlsToKill
) do
495 ctl
:= ctlsToKill
[f
];
496 if (ctl
= nil) then break
;
497 ctlsToKill
[f
] := nil;
500 if (Length(ctlsToKill
) > 0) then ctlsToKill
[0] := nil; // just in case
504 // ////////////////////////////////////////////////////////////////////////// //
506 knownCtlClasses
: array of record
507 klass
: TUIControlClass
;
512 procedure registerCtlClass (aklass
: TUIControlClass
; const aname
: AnsiString);
514 assert(aklass
<> nil);
515 assert(Length(aname
) > 0);
516 SetLength(knownCtlClasses
, Length(knownCtlClasses
)+1);
517 knownCtlClasses
[High(knownCtlClasses
)].klass
:= aklass
;
518 knownCtlClasses
[High(knownCtlClasses
)].name
:= aname
;
522 function findCtlClass (const aname
: AnsiString): TUIControlClass
;
526 for f
:= 0 to High(knownCtlClasses
) do
528 if (strEquCI1251(aname
, knownCtlClasses
[f
].name
)) then
530 result
:= knownCtlClasses
[f
].klass
;
538 // ////////////////////////////////////////////////////////////////////////// //
540 TFlexLayouter
= specialize TFlexLayouterBase
<TUIControl
>;
542 procedure uiLayoutCtl (ctl
: TUIControl
);
546 if (ctl
= nil) then exit
;
547 lay
:= TFlexLayouter
.Create();
552 //writeln('============================'); lay.dumpFlat();
554 //writeln('=== initial ==='); lay.dump();
556 //lay.calcMaxSizeInternal(0);
559 writeln('=== after first pass ===');
563 writeln('=== after second pass ===');
568 //writeln('=== final ==='); lay.dump();
570 if (ctl
.mParent
= nil) and (ctl
is TUITopWindow
) and (TUITopWindow(ctl
).mDoCenter
) then
572 TUITopWindow(ctl
).centerInScreen();
581 // ////////////////////////////////////////////////////////////////////////// //
583 uiTopList
: array of TUIControl
= nil;
584 uiGrabCtl
: TUIControl
= nil;
587 procedure uiUpdateStyles ();
591 for ctl
in uiTopList
do ctl
.updateStyle();
595 procedure uiMouseEvent (var evt
: THMouseEvent
);
603 if (evt
.eaten
) or (evt
.cancelled
) then exit
;
605 ev
.x
:= trunc(ev
.x
/gh_ui_scale
);
606 ev
.y
:= trunc(ev
.y
/gh_ui_scale
);
607 ev
.dx
:= trunc(ev
.dx
/gh_ui_scale
); //FIXME
608 ev
.dy
:= trunc(ev
.dy
/gh_ui_scale
); //FIXME
610 if (uiGrabCtl
<> nil) then
612 uiGrabCtl
.mouseEvent(ev
);
613 if (ev
.release
) then uiGrabCtl
:= nil;
617 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].mEnabled
) then uiTopList
[High(uiTopList
)].mouseEvent(ev
);
618 if (not ev
.eaten
) and (not ev
.cancelled
) and (ev
.press
) then
620 for f
:= High(uiTopList
) downto 0 do
622 if uiTopList
[f
].toLocal(ev
.x
, ev
.y
, lx
, ly
) then
624 if (uiTopList
[f
].mEnabled
) and (f
<> High(uiTopList
)) then
626 uiTopList
[High(uiTopList
)].blurred();
627 ctmp
:= uiTopList
[f
];
629 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
630 uiTopList
[High(uiTopList
)] := ctmp
;
640 if (ev
.eaten
) then evt
.eat();
641 if (ev
.cancelled
) then evt
.cancel();
646 procedure uiKeyEvent (var evt
: THKeyEvent
);
651 if (evt
.eaten
) or (evt
.cancelled
) then exit
;
653 ev
.x
:= trunc(ev
.x
/gh_ui_scale
);
654 ev
.y
:= trunc(ev
.y
/gh_ui_scale
);
656 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].mEnabled
) then uiTopList
[High(uiTopList
)].keyEvent(ev
);
657 //if (ev.release) then begin ev.eat(); exit; end;
659 if (ev
.eaten
) then evt
.eat();
660 if (ev
.cancelled
) then evt
.cancel();
671 glMatrixMode(GL_MODELVIEW
);
675 glScalef(gh_ui_scale
, gh_ui_scale
, 1);
676 for f
:= 0 to High(uiTopList
) do
680 cidx
:= ctl
.getColorIndex
;
681 //if (f <> High(uiTopList)) then darkenRect(ctl.x0, ctl.y0, ctl.width, ctl.height, 128);
682 if (ctl
.mDarken
[cidx
] > 0) then darkenRect(ctl
.x0
, ctl
.y0
, ctl
.width
, ctl
.height
, ctl
.mDarken
[cidx
]);
685 glMatrixMode(GL_MODELVIEW
);
691 procedure uiAddWindow (ctl
: TUIControl
);
695 if (ctl
= nil) then exit
;
697 if not (ctl
is TUITopWindow
) then exit
; // alas
698 for f
:= 0 to High(uiTopList
) do
700 if (uiTopList
[f
] = ctl
) then
702 if (f
<> High(uiTopList
)) then
704 uiTopList
[High(uiTopList
)].blurred();
705 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
706 uiTopList
[High(uiTopList
)] := ctl
;
712 if (Length(uiTopList
) > 0) then uiTopList
[High(uiTopList
)].blurred();
713 SetLength(uiTopList
, Length(uiTopList
)+1);
714 uiTopList
[High(uiTopList
)] := ctl
;
720 procedure uiRemoveWindow (ctl
: TUIControl
);
724 if (ctl
= nil) then exit
;
726 if not (ctl
is TUITopWindow
) then exit
; // alas
727 for f
:= 0 to High(uiTopList
) do
729 if (uiTopList
[f
] = ctl
) then
732 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
733 SetLength(uiTopList
, Length(uiTopList
)-1);
734 if (ctl
is TUITopWindow
) then
737 if assigned(TUITopWindow(ctl
).closeCB
) then TUITopWindow(ctl
).closeCB(ctl
, 0);
739 if (TUITopWindow(ctl
).mFreeOnClose
) then scheduleKill(ctl
);
748 function uiVisibleWindow (ctl
: TUIControl
): Boolean;
753 if (ctl
= nil) then exit
;
755 if not (ctl
is TUITopWindow
) then exit
; // alas
756 for f
:= 0 to High(uiTopList
) do
758 if (uiTopList
[f
] = ctl
) then begin result
:= true; exit
; end;
763 // ////////////////////////////////////////////////////////////////////////// //
764 constructor TUIControl
.Create ();
781 mDrawShadow
:= false;
783 // layouter interface
784 //mDefSize := TLaySize.Create(64, 8); // default size
785 mDefSize
:= TLaySize
.Create(0, 0); // default size
786 mMaxSize
:= TLaySize
.Create(-1, -1); // maximum size
795 mAlign
:= -1; // left/top
800 constructor TUIControl
.Create (ax
, ay
, aw
, ah
: Integer);
810 destructor TUIControl
.Destroy ();
814 if (mParent
<> nil) then
817 for f
:= 0 to High(mParent
.mChildren
) do
819 if (mParent
.mChildren
[f
] = self
) then
821 for c
:= f
+1 to High(mParent
.mChildren
) do mParent
.mChildren
[c
-1] := mParent
.mChildren
[c
];
822 SetLength(mParent
.mChildren
, Length(mParent
.mChildren
)-1);
826 for f
:= 0 to High(mChildren
) do
828 mChildren
[f
].mParent
:= nil;
835 function TUIControl
.getColorIndex (): Integer; inline;
837 if (not mEnabled
) then begin result
:= ClrIdxDisabled
; exit
; end;
838 if (getFocused
) then begin result
:= ClrIdxActive
; exit
; end;
839 result
:= ClrIdxInactive
;
842 procedure TUIControl
.updateStyle ();
848 while (ctl
<> nil) do
850 if (Length(ctl
.mStyleId
) <> 0) then begin stl
:= uiFindStyle(ctl
.mStyleId
); break
; end;
853 if (stl
= nil) then stl
:= uiFindStyle(''); // default
855 for ctl
in mChildren
do ctl
.updateStyle();
858 procedure TUIControl
.cacheStyle (root
: TUIStyle
);
860 cst
: AnsiString = '';
862 //writeln('caching style for <', className, '> (', mCtl4Style, ')...');
863 if (Length(mCtl4Style
) > 0) then
866 if (cst
[1] <> '@') then cst
:= '@'+cst
;
869 mBackColor
[ClrIdxActive
] := root
['back-color'+cst
].asRGBADef(TGxRGBA
.Create(0, 0, 128));
870 mTextColor
[ClrIdxActive
] := root
['text-color'+cst
].asRGBADef(TGxRGBA
.Create(255, 255, 255));
871 mFrameColor
[ClrIdxActive
] := root
['frame-color'+cst
].asRGBADef(TGxRGBA
.Create(255, 255, 255));
872 mFrameTextColor
[ClrIdxActive
] := root
['frame-text-color'+cst
].asRGBADef(TGxRGBA
.Create(255, 255, 255));
873 mFrameIconColor
[ClrIdxActive
] := root
['frame-icon-color'+cst
].asRGBADef(TGxRGBA
.Create(0, 255, 0));
874 mDarken
[ClrIdxActive
] := root
['darken'+cst
].asIntDef(-1);
876 mBackColor
[ClrIdxDisabled
] := root
['back-color#disabled'+cst
].asRGBADef(TGxRGBA
.Create(0, 0, 128));
877 mTextColor
[ClrIdxDisabled
] := root
['text-color#disabled'+cst
].asRGBADef(TGxRGBA
.Create(127, 127, 127));
878 mFrameColor
[ClrIdxDisabled
] := root
['frame-color#disabled'+cst
].asRGBADef(TGxRGBA
.Create(127, 127, 127));
879 mFrameTextColor
[ClrIdxDisabled
] := root
['frame-text-color#disabled'+cst
].asRGBADef(TGxRGBA
.Create(127, 127, 127));
880 mFrameIconColor
[ClrIdxDisabled
] := root
['frame-icon-color#disabled'+cst
].asRGBADef(TGxRGBA
.Create(0, 127, 0));
881 mDarken
[ClrIdxDisabled
] := root
['darken#disabled'+cst
].asIntDef(128);
883 mBackColor
[ClrIdxInactive
] := root
['back-color#inactive'+cst
].asRGBADef(TGxRGBA
.Create(0, 0, 128));
884 mTextColor
[ClrIdxInactive
] := root
['text-color#inactive'+cst
].asRGBADef(TGxRGBA
.Create(255, 255, 255));
885 mFrameColor
[ClrIdxInactive
] := root
['frame-color#inactive'+cst
].asRGBADef(TGxRGBA
.Create(255, 255, 255));
886 mFrameTextColor
[ClrIdxInactive
] := root
['frame-text-color#inactive'+cst
].asRGBADef(TGxRGBA
.Create(255, 255, 255));
887 mFrameIconColor
[ClrIdxInactive
] := root
['frame-icon-color#inactive'+cst
].asRGBADef(TGxRGBA
.Create(0, 255, 0));
888 mDarken
[ClrIdxInactive
] := root
['darken#inactive'+cst
].asIntDef(128);
892 // ////////////////////////////////////////////////////////////////////////// //
893 function TUIControl
.getDefSize (): TLaySize
; inline; begin result
:= mLayDefSize
; end;
894 function TUIControl
.getMaxSize (): TLaySize
; inline; begin result
:= mLayMaxSize
; end;
895 function TUIControl
.getFlex (): Integer; inline; begin result
:= mFlex
; end;
896 function TUIControl
.isHorizBox (): Boolean; inline; begin result
:= mHoriz
; end;
897 procedure TUIControl
.setHorizBox (v
: Boolean); inline; begin mHoriz
:= v
; end;
898 function TUIControl
.canWrap (): Boolean; inline; begin result
:= mCanWrap
; end;
899 procedure TUIControl
.setCanWrap (v
: Boolean); inline; begin mCanWrap
:= v
; end;
900 function TUIControl
.isLineStart (): Boolean; inline; begin result
:= mLineStart
; end;
901 procedure TUIControl
.setLineStart (v
: Boolean); inline; begin mLineStart
:= v
; end;
902 function TUIControl
.getAlign (): Integer; inline; begin result
:= mAlign
; end;
903 procedure TUIControl
.setAlign (v
: Integer); inline; begin mAlign
:= v
; end;
904 function TUIControl
.getExpand (): Boolean; inline; begin result
:= mExpand
; end;
905 procedure TUIControl
.setExpand (v
: Boolean); inline; begin mExpand
:= v
; end;
906 function TUIControl
.getHGroup (): AnsiString; inline; begin result
:= mHGroup
; end;
907 procedure TUIControl
.setHGroup (const v
: AnsiString); inline; begin mHGroup
:= v
; end;
908 function TUIControl
.getVGroup (): AnsiString; inline; begin result
:= mVGroup
; end;
909 procedure TUIControl
.setVGroup (const v
: AnsiString); inline; begin mVGroup
:= v
; end;
911 function TUIControl
.getMargins (): TLayMargins
; inline;
913 result
:= TLayMargins
.Create(mFrameHeight
, mFrameWidth
, mFrameHeight
, mFrameWidth
);
916 procedure TUIControl
.setActualSizePos (constref apos
: TLayPos
; constref asize
: TLaySize
); inline; begin
917 //writeln(self.className, '; pos=', apos.toString, '; size=', asize.toString);
918 if (mParent
<> nil) then
927 procedure TUIControl
.layPrepare ();
929 mLayDefSize
:= mDefSize
;
930 mLayMaxSize
:= mMaxSize
;
931 if (mLayMaxSize
.w
>= 0) then mLayMaxSize
.w
+= mFrameWidth
*2;
932 if (mLayMaxSize
.h
>= 0) then mLayMaxSize
.h
+= mFrameHeight
*2;
936 // ////////////////////////////////////////////////////////////////////////// //
937 function TUIControl
.parsePos (par
: TTextParser
): TLayPos
;
941 if (par
.eatDelim('[')) then ech
:= ']' else par
.expectDelim('(');
942 result
.x
:= par
.expectInt();
943 par
.eatDelim(','); // optional comma
944 result
.y
:= par
.expectInt();
945 par
.eatDelim(','); // optional comma
946 par
.expectDelim(ech
);
949 function TUIControl
.parseSize (par
: TTextParser
): TLaySize
;
953 if (par
.eatDelim('[')) then ech
:= ']' else par
.expectDelim('(');
954 result
.w
:= par
.expectInt();
955 par
.eatDelim(','); // optional comma
956 result
.h
:= par
.expectInt();
957 par
.eatDelim(','); // optional comma
958 par
.expectDelim(ech
);
961 function TUIControl
.parseBool (par
: TTextParser
): Boolean;
964 par
.eatIdOrStrCI('true') or
965 par
.eatIdOrStrCI('yes') or
966 par
.eatIdOrStrCI('tan');
969 if (not par
.eatIdOrStrCI('false')) and (not par
.eatIdOrStrCI('no')) and (not par
.eatIdOrStrCI('ona')) then
971 par
.error('boolean value expected');
976 function TUIControl
.parseAnyAlign (par
: TTextParser
): Integer;
978 if (par
.eatIdOrStrCI('left')) or (par
.eatIdOrStrCI('top')) then result
:= -1
979 else if (par
.eatIdOrStrCI('right')) or (par
.eatIdOrStrCI('bottom')) then result
:= 1
980 else if (par
.eatIdOrStrCI('center')) then result
:= 0
981 else par
.error('invalid align value');
984 function TUIControl
.parseHAlign (par
: TTextParser
): Integer;
986 if (par
.eatIdOrStrCI('left')) then result
:= -1
987 else if (par
.eatIdOrStrCI('right')) then result
:= 1
988 else if (par
.eatIdOrStrCI('center')) then result
:= 0
989 else par
.error('invalid horizontal align value');
992 function TUIControl
.parseVAlign (par
: TTextParser
): Integer;
994 if (par
.eatIdOrStrCI('top')) then result
:= -1
995 else if (par
.eatIdOrStrCI('bottom')) then result
:= 1
996 else if (par
.eatIdOrStrCI('center')) then result
:= 0
997 else par
.error('invalid vertical align value');
1000 procedure TUIControl
.parseTextAlign (par
: TTextParser
; var h
, v
: Integer);
1002 wasH
: Boolean = false;
1003 wasV
: Boolean = false;
1007 if (par
.eatIdOrStrCI('left')) then
1009 if wasH
then par
.error('too many align directives');
1014 if (par
.eatIdOrStrCI('right')) then
1016 if wasH
then par
.error('too many align directives');
1021 if (par
.eatIdOrStrCI('hcenter')) then
1023 if wasH
then par
.error('too many align directives');
1028 if (par
.eatIdOrStrCI('top')) then
1030 if wasV
then par
.error('too many align directives');
1035 if (par
.eatIdOrStrCI('bottom')) then
1037 if wasV
then par
.error('too many align directives');
1042 if (par
.eatIdOrStrCI('vcenter')) then
1044 if wasV
then par
.error('too many align directives');
1049 if (par
.eatIdOrStrCI('center')) then
1051 if wasV
or wasH
then par
.error('too many align directives');
1060 if not wasV
and not wasH
then par
.error('invalid align value');
1063 function TUIControl
.parseOrientation (const prname
: AnsiString; par
: TTextParser
): Boolean;
1065 if (strEquCI1251(prname
, 'orientation')) or (strEquCI1251(prname
, 'orient')) then
1067 if (par
.eatIdOrStrCI('horizontal')) or (par
.eatIdOrStrCI('horiz')) then mHoriz
:= true
1068 else if (par
.eatIdOrStrCI('vertical')) or (par
.eatIdOrStrCI('vert')) then mHoriz
:= false
1069 else par
.error('`horizontal` or `vertical` expected');
1078 // par should be on '{'; final '}' is eaten
1079 procedure TUIControl
.parseProperties (par
: TTextParser
);
1083 if (not par
.eatDelim('{')) then exit
;
1084 while (not par
.eatDelim('}')) do
1086 if (not par
.isIdOrStr
) then par
.error('property name expected');
1089 par
.eatDelim(':'); // optional
1090 if not parseProperty(pn
, par
) then par
.errorfmt('invalid property name ''%s''', [pn
]);
1091 par
.eatDelim(','); // optional
1095 // par should be on '{'
1096 procedure TUIControl
.parseChildren (par
: TTextParser
);
1098 cc
: TUIControlClass
;
1101 par
.expectDelim('{');
1102 while (not par
.eatDelim('}')) do
1104 if (not par
.isIdOrStr
) then par
.error('control name expected');
1105 cc
:= findCtlClass(par
.tokStr
);
1106 if (cc
= nil) then par
.errorfmt('unknown control name: ''%s''', [par
.tokStr
]);
1107 //writeln('children for <', par.tokStr, '>: <', cc.className, '>');
1109 par
.eatDelim(':'); // optional
1111 //writeln(' mHoriz=', ctl.mHoriz);
1113 ctl
.parseProperties(par
);
1118 //writeln(': ', ctl.mDefSize.toString);
1120 par
.eatDelim(','); // optional
1125 function TUIControl
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
1128 if (strEquCI1251(prname
, 'id')) then begin mId
:= par
.expectIdOrStr(true); exit
; end; // allow empty strings
1129 if (strEquCI1251(prname
, 'style')) then begin mStyleId
:= par
.expectIdOrStr(); exit
; end; // no empty strings
1130 if (strEquCI1251(prname
, 'flex')) then begin flex
:= par
.expectInt(); exit
; end;
1132 if (strEquCI1251(prname
, 'defsize')) or (strEquCI1251(prname
, 'size')) then begin mDefSize
:= parseSize(par
); exit
; end;
1133 if (strEquCI1251(prname
, 'maxsize')) then begin mMaxSize
:= parseSize(par
); exit
; end;
1134 if (strEquCI1251(prname
, 'defwidth')) or (strEquCI1251(prname
, 'width')) then begin mDefSize
.w
:= par
.expectInt(); exit
; end;
1135 if (strEquCI1251(prname
, 'defheight')) or (strEquCI1251(prname
, 'height')) then begin mDefSize
.h
:= par
.expectInt(); exit
; end;
1136 if (strEquCI1251(prname
, 'maxwidth')) then begin mMaxSize
.w
:= par
.expectInt(); exit
; end;
1137 if (strEquCI1251(prname
, 'maxheight')) then begin mMaxSize
.h
:= par
.expectInt(); exit
; end;
1139 if (strEquCI1251(prname
, 'wrap')) then begin mCanWrap
:= parseBool(par
); exit
; end;
1140 if (strEquCI1251(prname
, 'linestart')) then begin mLineStart
:= parseBool(par
); exit
; end;
1141 if (strEquCI1251(prname
, 'expand')) then begin mExpand
:= parseBool(par
); exit
; end;
1143 if (strEquCI1251(prname
, 'align')) then begin mAlign
:= parseAnyAlign(par
); exit
; end;
1144 if (strEquCI1251(prname
, 'hgroup')) then begin mHGroup
:= par
.expectIdOrStr(true); exit
; end; // allow empty strings
1145 if (strEquCI1251(prname
, 'vgroup')) then begin mVGroup
:= par
.expectIdOrStr(true); exit
; end; // allow empty strings
1147 if (strEquCI1251(prname
, 'canfocus')) then begin mCanFocus
:= parseBool(par
); exit
; end;
1148 if (strEquCI1251(prname
, 'enabled')) then begin mEnabled
:= parseBool(par
); exit
; end;
1149 if (strEquCI1251(prname
, 'disabled')) then begin mEnabled
:= not parseBool(par
); exit
; end;
1150 if (strEquCI1251(prname
, 'escclose')) then begin mEscClose
:= not parseBool(par
); exit
; end;
1151 if (strEquCI1251(prname
, 'eatkeys')) then begin mEatKeys
:= not parseBool(par
); exit
; end;
1152 if (strEquCI1251(prname
, 'default')) then begin mDefault
:= true; exit
; end;
1153 if (strEquCI1251(prname
, 'cancel')) then begin mCancel
:= true; exit
; end;
1158 // ////////////////////////////////////////////////////////////////////////// //
1159 procedure TUIControl
.activated ();
1164 procedure TUIControl
.blurred ();
1166 if (uiGrabCtl
= self
) then uiGrabCtl
:= nil;
1170 function TUIControl
.topLevel (): TUIControl
; inline;
1173 while (result
.mParent
<> nil) do result
:= result
.mParent
;
1177 function TUIControl
.getEnabled (): Boolean;
1182 if (not mEnabled
) then exit
;
1184 while (ctl
<> nil) do
1186 if (not ctl
.mEnabled
) then exit
;
1193 procedure TUIControl
.setEnabled (v
: Boolean); inline;
1195 if (mEnabled
= v
) then exit
;
1197 if (not v
) and focused
then setFocused(false);
1201 function TUIControl
.getFocused (): Boolean; inline;
1203 if (mParent
= nil) then
1205 result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = self
);
1209 result
:= (topLevel
.mFocused
= self
);
1210 if (result
) then result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = topLevel
);
1215 procedure TUIControl
.setFocused (v
: Boolean); inline;
1222 if (tl
.mFocused
= self
) then
1225 tl
.mFocused
:= tl
.findNextFocus(self
);
1226 if (tl
.mFocused
= self
) then tl
.mFocused
:= nil;
1230 if (not mEnabled
) or (not canFocus
) then exit
;
1231 if (tl
.mFocused
<> self
) then
1233 if (tl
.mFocused
<> nil) and (tl
.mFocused
<> nil) then tl
.mFocused
.blurred();
1234 tl
.mFocused
:= self
;
1235 if (uiGrabCtl
<> self
) then uiGrabCtl
:= nil;
1241 function TUIControl
.getCanFocus (): Boolean; inline;
1243 result
:= (mCanFocus
) and (mWidth
> 0) and (mHeight
> 0);
1247 function TUIControl
.isMyChild (ctl
: TUIControl
): Boolean;
1250 while (ctl
<> nil) do
1252 if (ctl
.mParent
= self
) then exit
;
1259 // returns `true` if global coords are inside this control
1260 function TUIControl
.toLocal (var x
, y
: Integer): Boolean;
1265 while (ctl
<> nil) do
1271 result
:= (x
>= 0) and (y
>= 0) and (x
< mWidth
) and (y
< mHeight
);
1274 function TUIControl
.toLocal (gx
, gy
: Integer; out x
, y
: Integer): Boolean; inline;
1278 result
:= toLocal(x
, y
);
1281 procedure TUIControl
.toGlobal (var x
, y
: Integer);
1286 while (ctl
<> nil) do
1294 procedure TUIControl
.toGlobal (lx
, ly
: Integer; out x
, y
: Integer); inline;
1302 // x and y are global coords
1303 function TUIControl
.controlAtXY (x
, y
: Integer; allowDisabled
: Boolean=false): TUIControl
;
1309 if (not allowDisabled
) and (not mEnabled
) then exit
;
1310 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1311 if not toLocal(x
, y
, lx
, ly
) then exit
;
1312 for f
:= High(mChildren
) downto 0 do
1314 result
:= mChildren
[f
].controlAtXY(x
, y
, allowDisabled
);
1315 if (result
<> nil) then exit
;
1321 function TUIControl
.prevSibling (): TUIControl
;
1325 if (mParent
<> nil) then
1327 for f
:= 1 to High(mParent
.mChildren
) do
1329 if (mParent
.mChildren
[f
] = self
) then begin result
:= mParent
.mChildren
[f
-1]; exit
; end;
1335 function TUIControl
.nextSibling (): TUIControl
;
1339 if (mParent
<> nil) then
1341 for f
:= 0 to High(mParent
.mChildren
)-1 do
1343 if (mParent
.mChildren
[f
] = self
) then begin result
:= mParent
.mChildren
[f
+1]; exit
; end;
1349 function TUIControl
.firstChild (): TUIControl
; inline;
1351 if (Length(mChildren
) <> 0) then result
:= mChildren
[0] else result
:= nil;
1354 function TUIControl
.lastChild (): TUIControl
; inline;
1356 if (Length(mChildren
) <> 0) then result
:= mChildren
[High(mChildren
)] else result
:= nil;
1360 function TUIControl
.findFirstFocus (): TUIControl
;
1367 for f
:= 0 to High(mChildren
) do
1369 result
:= mChildren
[f
].findFirstFocus();
1370 if (result
<> nil) then exit
;
1372 if canFocus
then result
:= self
;
1377 function TUIControl
.findLastFocus (): TUIControl
;
1384 for f
:= High(mChildren
) downto 0 do
1386 result
:= mChildren
[f
].findLastFocus();
1387 if (result
<> nil) then exit
;
1389 if canFocus
then result
:= self
;
1394 function TUIControl
.findNextFocus (cur
: TUIControl
): TUIControl
;
1399 if not isMyChild(cur
) then cur
:= nil;
1400 if (cur
= nil) then begin result
:= findFirstFocus(); exit
; end;
1401 result
:= cur
.findFirstFocus();
1402 if (result
<> nil) and (result
<> cur
) then exit
;
1405 cur
:= cur
.nextSibling
;
1406 if (cur
= nil) then break
;
1407 result
:= cur
.findFirstFocus();
1408 if (result
<> nil) then exit
;
1410 result
:= findFirstFocus();
1415 function TUIControl
.findPrevFocus (cur
: TUIControl
): TUIControl
;
1420 if not isMyChild(cur
) then cur
:= nil;
1421 if (cur
= nil) then begin result
:= findLastFocus(); exit
; end;
1423 result
:= cur
.findLastFocus();
1424 if (result
<> nil) and (result
<> cur
) then exit
;
1427 cur
:= cur
.prevSibling
;
1428 if (cur
= nil) then break
;
1429 result
:= cur
.findLastFocus();
1430 if (result
<> nil) then exit
;
1432 result
:= findLastFocus();
1437 function TUIControl
.findDefaulControl (): TUIControl
;
1441 if mDefault
then begin result
:= self
; exit
; end;
1442 for ctl
in mChildren
do
1444 result
:= ctl
.findDefaulControl();
1445 if (result
<> nil) then exit
;
1450 function TUIControl
.findCancelControl (): TUIControl
;
1454 if mCancel
then begin result
:= self
; exit
; end;
1455 for ctl
in mChildren
do
1457 result
:= ctl
.findCancelControl();
1458 if (result
<> nil) then exit
;
1464 function TUIControl
.findControlById (const aid
: AnsiString): TUIControl
;
1468 if (strEquCI1251(aid
, mId
)) then begin result
:= self
; exit
; end;
1469 for ctl
in mChildren
do
1471 result
:= ctl
.findControlById(aid
);
1472 if (result
<> nil) then exit
;
1478 procedure TUIControl
.appendChild (ctl
: TUIControl
);
1480 if (ctl
= nil) then exit
;
1481 if (ctl
.mParent
<> nil) then exit
;
1482 SetLength(mChildren
, Length(mChildren
)+1);
1483 mChildren
[High(mChildren
)] := ctl
;
1484 ctl
.mParent
:= self
;
1485 Inc(ctl
.mX
, mFrameWidth
);
1486 Inc(ctl
.mY
, mFrameHeight
);
1487 if (ctl
.mWidth
> 0) and (ctl
.mHeight
> 0) and
1488 (ctl
.mX
+ctl
.mWidth
> mFrameWidth
) and (ctl
.mY
+ctl
.mHeight
> mFrameHeight
) then
1490 if (mWidth
+mFrameWidth
< ctl
.mX
+ctl
.mWidth
) then mWidth
:= ctl
.mX
+ctl
.mWidth
+mFrameWidth
;
1491 if (mHeight
+mFrameHeight
< ctl
.mY
+ctl
.mHeight
) then mHeight
:= ctl
.mY
+ctl
.mHeight
+mFrameHeight
;
1496 procedure TUIControl
.close (); // this closes *top-level* control
1501 uiRemoveWindow(ctl
);
1502 if (ctl
is TUITopWindow
) and (TUITopWindow(ctl
).mFreeOnClose
) then scheduleKill(ctl
); // just in case
1506 procedure TUIControl
.doAction ();
1508 if assigned(actionCB
) then actionCB(self
, 0);
1512 // ////////////////////////////////////////////////////////////////////////// //
1513 procedure TUIControl
.setScissorGLInternal (x
, y
, w
, h
: Integer);
1515 if not scallowed
then exit
;
1516 x
:= trunc(x
*gh_ui_scale
);
1517 y
:= trunc(y
*gh_ui_scale
);
1518 w
:= trunc(w
*gh_ui_scale
);
1519 h
:= trunc(h
*gh_ui_scale
);
1520 scis
.combineRect(x
, y
, w
, h
);
1523 procedure TUIControl
.setScissor (lx
, ly
, lw
, lh
: Integer);
1526 //ox, oy, ow, oh: Integer;
1528 if not scallowed
then exit
;
1529 //ox := lx; oy := ly; ow := lw; oh := lh;
1530 if not intersectRect(lx
, ly
, lw
, lh
, 0, 0, mWidth
, mHeight
) then
1532 //writeln('oops: <', self.className, '>: old=(', ox, ',', oy, ')-[', ow, ',', oh, ']');
1533 glScissor(0, 0, 0, 0);
1536 toGlobal(lx
, ly
, gx
, gy
);
1537 setScissorGLInternal(gx
, gy
, lw
, lh
);
1540 procedure TUIControl
.resetScissor (fullArea
: Boolean); inline;
1542 if not scallowed
then exit
;
1545 setScissor(0, 0, mWidth
, mHeight
);
1549 setScissor(mFrameWidth
, mFrameHeight
, mWidth
-mFrameWidth
*2, mHeight
-mFrameHeight
*2);
1554 // ////////////////////////////////////////////////////////////////////////// //
1555 procedure TUIControl
.draw ();
1560 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1561 toGlobal(0, 0, gx
, gy
);
1562 //conwritefln('[%s]: (%d,%d)-(%d,%d) (%d,%d)', [ClassName, mX, mY, mWidth, mHeight, x, y]);
1564 scis
.save(true); // scissoring enabled
1567 resetScissor(true); // full area
1568 drawControl(gx
, gy
);
1569 resetScissor(false); // client area
1570 for f
:= 0 to High(mChildren
) do mChildren
[f
].draw();
1571 resetScissor(true); // full area
1572 drawControlPost(gx
, gy
);
1579 procedure TUIControl
.drawControl (gx
, gy
: Integer);
1581 //if (mParent = nil) then darkenRect(gx, gy, mWidth, mHeight, 64);
1584 procedure TUIControl
.drawControlPost (gx
, gy
: Integer);
1587 if mDrawShadow
and (mWidth
> 0) and (mHeight
> 0) then
1589 setScissorGLInternal(gx
+8, gy
+8, mWidth
, mHeight
);
1590 darkenRect(gx
+mWidth
, gy
+8, 8, mHeight
, 128);
1591 darkenRect(gx
+8, gy
+mHeight
, mWidth
-8, 8, 128);
1596 // ////////////////////////////////////////////////////////////////////////// //
1597 procedure TUIControl
.mouseEvent (var ev
: THMouseEvent
);
1601 if (not mEnabled
) then exit
;
1602 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1603 ctl
:= controlAtXY(ev
.x
, ev
.y
);
1604 if (ctl
= nil) then exit
;
1605 if (ctl
.canFocus
) and (ev
.press
) then
1607 if (ctl
<> topLevel
.mFocused
) then ctl
.setFocused(true);
1610 if (ctl
<> self
) then ctl
.mouseEvent(ev
);
1615 procedure TUIControl
.keyEvent (var ev
: THKeyEvent
);
1619 if (not mEnabled
) then exit
;
1620 // focused control should process keyboard first
1621 if (topLevel
.mFocused
<> self
) and isMyChild(topLevel
.mFocused
) and (topLevel
.mFocused
.mEnabled
) then
1623 topLevel
.mFocused
.keyEvent(ev
);
1625 // for top-level controls
1626 if (mParent
= nil) and (not ev
.eaten
) and (not ev
.cancelled
) then
1628 if (ev
= 'S-Tab') then
1630 ctl
:= findPrevFocus(mFocused
);
1631 if (ctl
<> mFocused
) then ctl
.setFocused(true);
1635 if (ev
= 'Tab') then
1637 ctl
:= findNextFocus(mFocused
);
1638 if (ctl
<> mFocused
) then ctl
.setFocused(true);
1642 if (ev
= 'Enter') or (ev
= 'C-Enter') then
1644 ctl
:= findDefaulControl();
1645 if (ctl
<> nil) then
1652 if (ev
= 'Escape') then
1654 ctl
:= findCancelControl();
1655 if (ctl
<> nil) then
1662 if mEscClose
and (ev
= 'Escape') then
1664 if (not assigned(closeRequestCB
)) or (closeRequestCB(self
)) then
1666 uiRemoveWindow(self
);
1672 if mEatKeys
then ev
.eat();
1676 // ////////////////////////////////////////////////////////////////////////// //
1677 constructor TUITopWindow
.Create (const atitle
: AnsiString; ax
, ay
: Integer; aw
: Integer=-1; ah
: Integer=-1);
1679 inherited Create(ax
, ay
, aw
, ah
);
1685 procedure TUITopWindow
.AfterConstruction ();
1687 inherited AfterConstruction();
1688 if (mWidth
< mFrameWidth
*2+3*8) then mWidth
:= mFrameWidth
*2+3*8;
1689 if (mHeight
< mFrameHeight
*2) then mHeight
:= mFrameHeight
*2;
1690 if (Length(mTitle
) > 0) then
1692 if (mWidth
< Length(mTitle
)*8+mFrameWidth
*2+3*8) then mWidth
:= Length(mTitle
)*8+mFrameWidth
*2+3*8;
1695 mDrawShadow
:= true;
1696 mWaitingClose
:= false;
1703 function TUITopWindow
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
1705 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) then
1707 mTitle
:= par
.expectIdOrStr(true);
1711 if (strEquCI1251(prname
, 'children')) then
1717 if (strEquCI1251(prname
, 'position')) then
1719 if (par
.eatIdOrStrCI('default')) then mDoCenter
:= false
1720 else if (par
.eatIdOrStrCI('center')) then mDoCenter
:= true
1721 else par
.error('`center` or `default` expected');
1725 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
1726 result
:= inherited parseProperty(prname
, par
);
1730 procedure TUITopWindow
.cacheStyle (root
: TUIStyle
);
1732 inherited cacheStyle(root
);
1736 procedure TUITopWindow
.centerInScreen ();
1738 if (mWidth
> 0) and (mHeight
> 0) then
1740 mX
:= trunc((gScrWidth
/gh_ui_scale
-mWidth
)/2);
1741 mY
:= trunc((gScrHeight
/gh_ui_scale
-mHeight
)/2);
1746 procedure TUITopWindow
.drawControl (gx
, gy
: Integer);
1748 fillRect(gx
, gy
, mWidth
, mHeight
, mBackColor
[getColorIndex
]);
1752 procedure TUITopWindow
.drawControlPost (gx
, gy
: Integer);
1757 cidx
:= getColorIndex
;
1760 drawRectUI(mX
+4, mY
+4, mWidth
-8, mHeight
-8, mFrameColor
[cidx
]);
1764 drawRectUI(mX
+3, mY
+3, mWidth
-6, mHeight
-6, mFrameColor
[cidx
]);
1765 drawRectUI(mX
+5, mY
+5, mWidth
-10, mHeight
-10, mFrameColor
[cidx
]);
1766 setScissor(mFrameWidth
, 0, 3*8, 8);
1767 fillRect(mX
+mFrameWidth
, mY
, 3*8, 8, mBackColor
[cidx
]);
1768 drawText8(mX
+mFrameWidth
, mY
, '[ ]', mFrameColor
[cidx
]);
1769 if mInClose
then drawText8(mX
+mFrameWidth
+7, mY
, '#', mFrameIconColor
[cidx
])
1770 else drawText8(mX
+mFrameWidth
+7, mY
, '*', mFrameIconColor
[cidx
]);
1772 if (Length(mTitle
) > 0) then
1774 setScissor(mFrameWidth
+3*8, 0, mWidth
-mFrameWidth
*2-3*8, 8);
1775 tx
:= (mX
+3*8)+((mWidth
-3*8)-Length(mTitle
)*8) div 2;
1776 fillRect(tx
-3, mY
, Length(mTitle
)*8+3+2, 8, mBackColor
[cidx
]);
1777 drawText8(tx
, mY
, mTitle
, mFrameTextColor
[cidx
]);
1779 inherited drawControlPost(gx
, gy
);
1783 procedure TUITopWindow
.activated ();
1785 if (mFocused
= nil) or (mFocused
= self
) then
1787 mFocused
:= findFirstFocus();
1788 if (mFocused
<> nil) and (mFocused
<> self
) then mFocused
.activated();
1794 procedure TUITopWindow
.blurred ();
1797 mWaitingClose
:= false;
1803 procedure TUITopWindow
.keyEvent (var ev
: THKeyEvent
);
1805 inherited keyEvent(ev
);
1806 if (ev
.eaten
) or (ev
.cancelled
) or (not mEnabled
) or (not getFocused
) then exit
;
1807 if (ev
= 'M-F3') then
1809 if (not assigned(closeRequestCB
)) or (closeRequestCB(self
)) then
1811 uiRemoveWindow(self
);
1819 procedure TUITopWindow
.mouseEvent (var ev
: THMouseEvent
);
1823 if (not mEnabled
) then exit
;
1824 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1828 mX
+= ev
.x
-mDragStartX
;
1829 mY
+= ev
.y
-mDragStartY
;
1830 mDragStartX
:= ev
.x
;
1831 mDragStartY
:= ev
.y
;
1832 if (ev
.release
) then mDragging
:= false;
1837 if toLocal(ev
.x
, ev
.y
, lx
, ly
) then
1844 if (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+3*8) then
1846 //uiRemoveWindow(self);
1847 mWaitingClose
:= true;
1853 mDragStartX
:= ev
.x
;
1854 mDragStartY
:= ev
.y
;
1859 if (lx
< mFrameWidth
) or (lx
>= mWidth
-mFrameWidth
) or (ly
>= mHeight
-mFrameHeight
) then
1863 mDragStartX
:= ev
.x
;
1864 mDragStartY
:= ev
.y
;
1870 if (ev
.release
) then
1872 if mWaitingClose
then
1874 if (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+3*8) then
1876 if (not assigned(closeRequestCB
)) or (closeRequestCB(self
)) then
1878 uiRemoveWindow(self
);
1881 mWaitingClose
:= false;
1890 if mWaitingClose
then
1892 mInClose
:= (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+3*8);
1898 inherited mouseEvent(ev
);
1903 if (not ev
.motion
) and (mWaitingClose
) then begin ev
.eat(); mWaitingClose
:= false; exit
; end;
1908 // ////////////////////////////////////////////////////////////////////////// //
1909 constructor TUISimpleText
.Create (ax
, ay
: Integer);
1912 inherited Create(ax
, ay
, 4, 4);
1916 destructor TUISimpleText
.Destroy ();
1923 procedure TUISimpleText
.appendItem (const atext
: AnsiString; acentered
: Boolean=false; ahline
: Boolean=false);
1927 if (Length(atext
)*8+3*8+2 > mWidth
) then mWidth
:= Length(atext
)*8+3*8+2;
1928 SetLength(mItems
, Length(mItems
)+1);
1929 it
:= @mItems
[High(mItems
)];
1931 it
.centered
:= acentered
;
1933 if (Length(mItems
)*8 > mHeight
) then mHeight
:= Length(mItems
)*8;
1937 procedure TUISimpleText
.drawControl (gx
, gy
: Integer);
1943 for f
:= 0 to High(mItems
) do
1950 if it
.centered
then begin b
:= 255; tx
:= gx
+(mWidth
-Length(it
.title
)*8) div 2; end;
1954 if (Length(it
.title
) = 0) then
1956 drawHLine(gx
+4, gy
+3, mWidth
-8, TGxRGBA
.Create(r
, g
, b
));
1958 else if (tx
-3 > gx
+4) then
1960 drawHLine(gx
+4, gy
+3, tx
-3-(gx
+3), TGxRGBA
.Create(r
, g
, b
));
1961 drawHLine(tx
+Length(it
.title
)*8, gy
+3, mWidth
-4, TGxRGBA
.Create(r
, g
, b
));
1964 drawText8(tx
, gy
, it
.title
, TGxRGBA
.Create(r
, g
, b
));
1970 procedure TUISimpleText
.mouseEvent (var ev
: THMouseEvent
);
1974 inherited mouseEvent(ev
);
1975 if (not ev
.eaten
) and (not ev
.cancelled
) and (mEnabled
) and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
1982 // ////////////////////////////////////////////////////////////////////////// //
1983 constructor TUICBListBox
.Create (ax
, ay
: Integer);
1987 inherited Create(ax
, ay
, 4, 4);
1991 destructor TUICBListBox
.Destroy ();
1998 procedure TUICBListBox
.appendItem (const atext
: AnsiString; bv
: PBoolean; aaction
: TActionCB
=nil);
2002 if (Length(atext
)*8+3*8+2 > mWidth
) then mWidth
:= Length(atext
)*8+3*8+2;
2003 SetLength(mItems
, Length(mItems
)+1);
2004 it
:= @mItems
[High(mItems
)];
2007 it
.actionCB
:= aaction
;
2008 if (Length(mItems
)*8 > mHeight
) then mHeight
:= Length(mItems
)*8;
2009 if (mCurIndex
< 0) then mCurIndex
:= 0;
2013 procedure TUICBListBox
.drawControl (gx
, gy
: Integer);
2018 for f
:= 0 to High(mItems
) do
2021 if (mCurIndex
= f
) then fillRect(gx
, gy
, mWidth
, 8, TGxRGBA
.Create(0, 128, 0));
2022 if (it
.varp
<> nil) then
2024 if it
.varp
^ then drawText8(gx
, gy
, '[x]', TGxRGBA
.Create(255, 255, 255)) else drawText8(gx
, gy
, '[ ]', TGxRGBA
.Create(255, 255, 255));
2025 drawText8(gx
+3*8+2, gy
, it
.title
, TGxRGBA
.Create(255, 255, 0));
2027 else if (Length(it
.title
) > 0) then
2029 tx
:= gx
+(mWidth
-Length(it
.title
)*8) div 2;
2030 if (tx
-3 > gx
+4) then
2032 drawHLine(gx
+4, gy
+3, tx
-3-(gx
+3), TGxRGBA
.Create(255, 255, 255));
2033 drawHLine(tx
+Length(it
.title
)*8, gy
+3, mWidth
-4, TGxRGBA
.Create(255, 255, 255));
2035 drawText8(tx
, gy
, it
.title
, TGxRGBA
.Create(255, 255, 255));
2039 drawHLine(gx
+4, gy
+3, mWidth
-8, TGxRGBA
.Create(255, 255, 255));
2046 procedure TUICBListBox
.mouseEvent (var ev
: THMouseEvent
);
2051 inherited mouseEvent(ev
);
2052 if (not ev
.eaten
) and (not ev
.cancelled
) and (mEnabled
) and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
2055 if (ev
= 'lmb') then
2058 if (ly
>= 0) and (ly
< Length(mItems
)) then
2061 if (it
.varp
<> nil) then
2064 it
.varp
^ := not it
.varp
^;
2065 if assigned(it
.actionCB
) then it
.actionCB(self
, Integer(it
.varp
^));
2066 if assigned(actionCB
) then actionCB(self
, ly
);
2074 procedure TUICBListBox
.keyEvent (var ev
: THKeyEvent
);
2078 inherited keyEvent(ev
);
2079 if (ev
.eaten
) or (ev
.cancelled
) or (not mEnabled
) or (not getFocused
) then exit
;
2081 if (ev
= 'Home') or (ev
= 'PageUp') then
2086 if (ev
= 'End') or (ev
= 'PageDown') then
2089 mCurIndex
:= High(mItems
);
2094 if (Length(mItems
) > 0) then
2096 if (mCurIndex
< 0) then mCurIndex
:= Length(mItems
);
2097 while (mCurIndex
> 0) do
2100 if (mItems
[mCurIndex
].varp
<> nil) then break
;
2108 if (ev
= 'Down') then
2111 if (Length(mItems
) > 0) then
2113 if (mCurIndex
< 0) then mCurIndex
:= -1;
2114 while (mCurIndex
< High(mItems
)) do
2117 if (mItems
[mCurIndex
].varp
<> nil) then break
;
2125 if (ev
= 'Space') or (ev
= 'Enter') then
2128 if (mCurIndex
>= 0) and (mCurIndex
< Length(mItems
)) and (mItems
[mCurIndex
].varp
<> nil) then
2130 it
:= @mItems
[mCurIndex
];
2131 it
.varp
^ := not it
.varp
^;
2132 if assigned(it
.actionCB
) then it
.actionCB(self
, Integer(it
.varp
^));
2133 if assigned(actionCB
) then actionCB(self
, mCurIndex
);
2139 // ////////////////////////////////////////////////////////////////////////// //
2140 constructor TUIBox
.Create (ahoriz
: Boolean);
2147 procedure TUIBox
.AfterConstruction ();
2149 inherited AfterConstruction();
2151 mCtl4Style
:= 'box';
2155 function TUIBox
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2157 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2158 if (strEquCI1251(prname
, 'frame')) then
2160 mHasFrame
:= parseBool(par
);
2161 if (mHasFrame
) then begin mFrameWidth
:= 8; mFrameHeight
:= 8; end else begin mFrameWidth
:= 0; mFrameHeight
:= 0; end;
2165 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) then
2167 mCaption
:= par
.expectIdOrStr(true);
2168 mDefSize
:= TLaySize
.Create(Length(mCaption
)*8+3, 8);
2172 if (strEquCI1251(prname
, 'children')) then
2178 result
:= inherited parseProperty(prname
, par
);
2182 procedure TUIBox
.drawControl (gx
, gy
: Integer);
2187 cidx
:= getColorIndex
;
2188 fillRect(gx
, gy
, mWidth
, mHeight
, mBackColor
[cidx
]);
2192 drawRectUI(gx
+3, gy
+3, mWidth
-6, mHeight
-6, mFrameColor
[cidx
]);
2195 if (Length(mCaption
) > 0) then
2197 setScissor(mFrameWidth
+1, 0, mWidth
-mFrameWidth
-2, 8);
2198 tx
:= gx
+((mWidth
-Length(mCaption
)*8) div 2);
2199 if mHasFrame
then fillRect(tx
-2, gy
, Length(mCaption
)*8+3, 8, mBackColor
[cidx
]);
2200 drawText8(tx
, gy
, mCaption
, mFrameTextColor
[cidx
]);
2205 procedure TUIBox
.mouseEvent (var ev
: THMouseEvent
);
2209 inherited mouseEvent(ev
);
2210 if (not ev
.eaten
) and (not ev
.cancelled
) and (mEnabled
) and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
2217 //TODO: navigation with arrow keys, according to box orientation
2218 procedure TUIBox
.keyEvent (var ev
: THKeyEvent
);
2220 inherited keyEvent(ev
);
2221 if (ev
.eaten
) or (ev
.cancelled
) or (not mEnabled
) or (not getFocused
) then exit
;
2225 // ////////////////////////////////////////////////////////////////////////// //
2226 procedure TUIHBox
.AfterConstruction ();
2228 inherited AfterConstruction();
2233 // ////////////////////////////////////////////////////////////////////////// //
2234 procedure TUIVBox
.AfterConstruction ();
2236 inherited AfterConstruction();
2241 // ////////////////////////////////////////////////////////////////////////// //
2242 procedure TUISpan
.AfterConstruction ();
2244 inherited AfterConstruction();
2247 mCtl4Style
:= 'span';
2251 function TUISpan
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2253 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2254 result
:= inherited parseProperty(prname
, par
);
2258 procedure TUISpan
.drawControl (gx
, gy
: Integer);
2263 // ////////////////////////////////////////////////////////////////////// //
2264 procedure TUILine
.AfterConstruction ();
2266 inherited AfterConstruction();
2269 mCtl4Style
:= 'line';
2273 function TUILine
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2275 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2276 result
:= inherited parseProperty(prname
, par
);
2280 procedure TUILine
.drawControl (gx
, gy
: Integer);
2284 cidx
:= getColorIndex
;
2287 drawHLine(gx
, gy
+(mHeight
div 2), mWidth
, mTextColor
[cidx
]);
2291 drawVLine(gx
+(mWidth
div 2), gy
, mHeight
, mTextColor
[cidx
]);
2296 // ////////////////////////////////////////////////////////////////////////// //
2297 procedure TUIHLine
.AfterConstruction ();
2299 inherited AfterConstruction();
2305 // ////////////////////////////////////////////////////////////////////////// //
2306 procedure TUIVLine
.AfterConstruction ();
2308 inherited AfterConstruction();
2314 // ////////////////////////////////////////////////////////////////////////// //
2315 constructor TUITextLabel
.Create (const atext
: AnsiString);
2319 mDefSize
:= TLaySize
.Create(Length(atext
)*8, 8);
2323 procedure TUITextLabel
.AfterConstruction ();
2325 inherited AfterConstruction();
2329 if (mDefSize
.h
<= 0) then mDefSize
.h
:= 8;
2330 mCtl4Style
:= 'label';
2334 function TUITextLabel
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2336 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) then
2338 mText
:= par
.expectIdOrStr(true);
2339 mDefSize
:= TLaySize
.Create(Length(mText
)*8, 8);
2343 if (strEquCI1251(prname
, 'textalign')) then
2345 parseTextAlign(par
, mHAlign
, mVAlign
);
2349 result
:= inherited parseProperty(prname
, par
);
2353 procedure TUITextLabel
.drawControl (gx
, gy
: Integer);
2355 xpos
, ypos
: Integer;
2358 cidx
:= getColorIndex
;
2359 fillRect(gx
, gy
, mWidth
, mHeight
, mBackColor
[cidx
]);
2360 if (Length(mText
) > 0) then
2362 if (mHAlign
< 0) then xpos
:= 0
2363 else if (mHAlign
> 0) then xpos
:= mWidth
-Length(mText
)*8
2364 else xpos
:= (mWidth
-Length(mText
)*8) div 2;
2366 if (mVAlign
< 0) then ypos
:= 0
2367 else if (mVAlign
> 0) then ypos
:= mHeight
-8
2368 else ypos
:= (mHeight
-8) div 2;
2370 drawText8(gx
+xpos
, gy
+ypos
, mText
, mTextColor
[cidx
]);
2375 procedure TUITextLabel
.mouseEvent (var ev
: THMouseEvent
);
2379 inherited mouseEvent(ev
);
2380 if (not ev
.eaten
) and (not ev
.cancelled
) and (mEnabled
) and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
2387 // ////////////////////////////////////////////////////////////////////////// //
2388 constructor TUIButton
.Create (const atext
: AnsiString);
2390 inherited Create(atext
);
2394 procedure TUIButton
.AfterConstruction ();
2396 inherited AfterConstruction();
2400 mDefSize
:= TLaySize
.Create(Length(mText
)*8+8, 8);
2401 mCtl4Style
:= 'button';
2405 function TUIButton
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2407 result
:= inherited parseProperty(prname
, par
);
2410 mDefSize
:= TLaySize
.Create(Length(mText
)*8+8*2, 8);
2415 procedure TUIButton
.drawControl (gx
, gy
: Integer);
2417 xpos
, ypos
: Integer;
2421 cidx
:= getColorIndex
;
2422 fillRect(gx
, gy
, mWidth
, mHeight
, mBackColor
[cidx
]);
2424 if (mDefault
) then begin lch
:= '<'; rch
:= '>'; end
2425 else if (mCancel
) then begin lch
:= '{'; rch
:= '}'; end
2426 else begin lch
:= '['; rch
:= ']'; end;
2428 if (mVAlign
< 0) then ypos
:= 0
2429 else if (mVAlign
> 0) then ypos
:= mHeight
-8
2430 else ypos
:= (mHeight
-8) div 2;
2432 drawText8(gx
, gy
+ypos
, lch
, mTextColor
[cidx
]);
2433 drawText8(gx
+mWidth
-8, gy
+ypos
, rch
, mTextColor
[cidx
]);
2435 if (Length(mText
) > 0) then
2437 if (mHAlign
< 0) then xpos
:= 0
2438 else if (mHAlign
> 0) then xpos
:= mWidth
-Length(mText
)*8
2439 else xpos
:= (mWidth
-Length(mText
)*8) div 2;
2441 setScissor(8, 0, mWidth
-16, mHeight
);
2442 drawText8(gx
+xpos
+8, gy
+ypos
, mText
, mTextColor
[cidx
]);
2447 procedure TUIButton
.mouseEvent (var ev
: THMouseEvent
);
2451 inherited mouseEvent(ev
);
2452 if (uiGrabCtl
= self
) then
2455 if (ev
= '-lmb') and focused
and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
2461 if (ev
.eaten
) or (ev
.cancelled
) or (not mEnabled
) or not focused
then exit
;
2466 procedure TUIButton
.keyEvent (var ev
: THKeyEvent
);
2468 inherited keyEvent(ev
);
2469 if (not ev
.eaten
) and (not ev
.cancelled
) and (mEnabled
) then
2471 if (ev
= 'Enter') or (ev
= 'Space') then
2482 registerCtlClass(TUIHBox
, 'hbox');
2483 registerCtlClass(TUIVBox
, 'vbox');
2484 registerCtlClass(TUISpan
, 'span');
2485 registerCtlClass(TUIHLine
, 'hline');
2486 registerCtlClass(TUIVLine
, 'vline');
2487 registerCtlClass(TUITextLabel
, 'label');
2488 registerCtlClass(TUIButton
, 'button');