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}
27 fui_common
, fui_events
, fui_style
,
32 // ////////////////////////////////////////////////////////////////////////// //
34 TUIControlClass
= class of TUIControl
;
38 type TActionCB
= procedure (me
: TUIControl
);
39 type TCloseRequestCB
= function (me
: TUIControl
): Boolean; // top-level windows will call this before closing with icon/keyboard
41 // return `true` to stop
42 type TCtlEnumCB
= function (ctl
: TUIControl
): Boolean is nested
;
45 const ClrIdxActive
= 0;
46 const ClrIdxDisabled
= 1;
47 const ClrIdxInactive
= 2;
55 mWidth
, mHeight
: Integer;
56 mFrameWidth
, mFrameHeight
: Integer;
57 mScrollX
, mScrollY
: Integer;
60 mChildren
: array of TUIControl
;
61 mFocused
: TUIControl
; // valid only for top-level controls
62 mEscClose
: Boolean; // valid only for top-level controls
67 mCtl4Style
: AnsiString;
68 mBackColor
: array[0..ClrIdxMax
] of TGxRGBA
;
69 mTextColor
: array[0..ClrIdxMax
] of TGxRGBA
;
70 mFrameColor
: array[0..ClrIdxMax
] of TGxRGBA
;
71 mFrameTextColor
: array[0..ClrIdxMax
] of TGxRGBA
;
72 mFrameIconColor
: array[0..ClrIdxMax
] of TGxRGBA
;
73 mDarken
: array[0..ClrIdxMax
] of Integer; // -1: none
80 procedure updateStyle (); virtual;
81 procedure cacheStyle (root
: TUIStyle
); virtual;
82 function getColorIndex (): Integer; inline;
85 function getEnabled (): Boolean;
86 procedure setEnabled (v
: Boolean); inline;
88 function getFocused (): Boolean; inline;
89 procedure setFocused (v
: Boolean); inline;
91 function getActive (): Boolean; inline;
93 function getCanFocus (): Boolean; inline;
95 function isMyChild (ctl
: TUIControl
): Boolean;
97 function findFirstFocus (): TUIControl
;
98 function findLastFocus (): TUIControl
;
100 function findNextFocus (cur
: TUIControl
; wrap
: Boolean): TUIControl
;
101 function findPrevFocus (cur
: TUIControl
; wrap
: Boolean): TUIControl
;
103 function findCancelControl (): TUIControl
;
104 function findDefaulControl (): TUIControl
;
106 function findControlById (const aid
: AnsiString): TUIControl
;
108 procedure activated (); virtual;
109 procedure blurred (); virtual;
111 procedure calcFullClientSize ();
113 //WARNING! do not call scissor functions outside `.draw*()` API!
114 // set scissor to this rect (in local coords)
115 procedure setScissor (lx
, ly
, lw
, lh
: Integer);
116 // reset scissor to whole control
117 procedure resetScissor (fullArea
: Boolean); inline; // "full area" means "with frame"
120 // set scissor to this rect (in global coords)
121 procedure setScissorGLInternal (x
, y
, w
, h
: Integer);
125 closeRequestCB
: TCloseRequestCB
;
128 mDefSize
: TLaySize
; // default size
129 mMaxSize
: TLaySize
; // maximum size
138 mLayDefSize
: TLaySize
;
139 mLayMaxSize
: TLaySize
;
145 // layouter interface
146 function getDefSize (): TLaySize
; inline; // default size; <0: use max size
147 //procedure setDefSize (const sz: TLaySize); inline; // default size; <0: use max size
148 function getMargins (): TLayMargins
; inline;
149 function getPadding (): TLaySize
; inline; // children padding (each non-first child will get this on left/top)
150 function getMaxSize (): TLaySize
; inline; // max size; <0: set to some huge value
151 //procedure setMaxSize (const sz: TLaySize); inline; // max size; <0: set to some huge value
152 function getFlex (): Integer; inline; // <=0: not flexible
153 function isHorizBox (): Boolean; inline; // horizontal layout for children?
154 function canWrap (): Boolean; inline; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
155 function noPad (): Boolean; inline; // ignore padding in box direction for this control
156 function isLineStart (): Boolean; inline; // `true` if this ctl should start a new line; ignored for vertical boxes
157 function getAlign (): Integer; inline; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
158 function getExpand (): Boolean; inline; // expanding in non-main direction: `true` will ignore align and eat all available space
159 function getHGroup (): AnsiString; inline; // empty: not grouped
160 function getVGroup (): AnsiString; inline; // empty: not grouped
162 procedure setActualSizePos (constref apos
: TLayPos
; constref asize
: TLaySize
); inline;
164 procedure layPrepare (); virtual; // called before registering control in layouter
167 property flex
: Integer read mFlex write mFlex
;
168 property flDefaultSize
: TLaySize read mDefSize write mDefSize
;
169 property flMaxSize
: TLaySize read mMaxSize write mMaxSize
;
170 property flPadding
: TLaySize read mPadding write mPadding
;
171 property flHoriz
: Boolean read mHoriz write mHoriz
;
172 property flCanWrap
: Boolean read mCanWrap write mCanWrap
;
173 property flLineStart
: Boolean read mLineStart write mLineStart
;
174 property flAlign
: Integer read mAlign write mAlign
;
175 property flExpand
: Boolean read mExpand write mExpand
;
176 property flHGroup
: AnsiString read mHGroup write mHGroup
;
177 property flVGroup
: AnsiString read mVGroup write mVGroup
;
178 property flNoPad
: Boolean read mNoPad write mNoPad
;
179 property fullSize
: TLaySize read mFullSize
;
182 function parsePos (par
: TTextParser
): TLayPos
;
183 function parseSize (par
: TTextParser
): TLaySize
;
184 function parsePadding (par
: TTextParser
): TLaySize
;
185 function parseHPadding (par
: TTextParser
; def
: Integer): TLaySize
;
186 function parseVPadding (par
: TTextParser
; def
: Integer): TLaySize
;
187 function parseBool (par
: TTextParser
): Boolean;
188 function parseAnyAlign (par
: TTextParser
): Integer;
189 function parseHAlign (par
: TTextParser
): Integer;
190 function parseVAlign (par
: TTextParser
): Integer;
191 function parseOrientation (const prname
: AnsiString; par
: TTextParser
): Boolean;
192 procedure parseTextAlign (par
: TTextParser
; var h
, v
: Integer);
193 procedure parseChildren (par
: TTextParser
); // par should be on '{'; final '}' is eaten
196 // par is on property data
197 // there may be more data in text stream, don't eat it!
198 // return `true` if property name is valid and value was parsed
199 // return `false` if property name is invalid; don't advance parser in this case
200 // throw on property data errors
201 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; virtual;
203 // par should be on '{'; final '}' is eaten
204 procedure parseProperties (par
: TTextParser
);
207 constructor Create ();
208 destructor Destroy (); override;
210 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
212 // `sx` and `sy` are screen coordinates
213 procedure drawControl (gx
, gy
: Integer); virtual;
215 // called after all children drawn
216 procedure drawControlPost (gx
, gy
: Integer); virtual;
218 procedure draw (); virtual;
220 function topLevel (): TUIControl
; inline;
222 // returns `true` if global coords are inside this control
223 function toLocal (var x
, y
: Integer): Boolean;
224 function toLocal (gx
, gy
: Integer; out x
, y
: Integer): Boolean; inline;
225 procedure toGlobal (var x
, y
: Integer);
226 procedure toGlobal (lx
, ly
: Integer; out x
, y
: Integer); inline;
228 procedure getDrawRect (out gx
, gy
, wdt
, hgt
: Integer);
230 // x and y are global coords
231 function controlAtXY (x
, y
: Integer; allowDisabled
: Boolean=false): TUIControl
;
233 function parentScrollX (): Integer; inline;
234 function parentScrollY (): Integer; inline;
236 procedure makeVisibleInParent ();
238 procedure doAction (); virtual; // so user controls can override it
240 procedure mouseEvent (var ev
: THMouseEvent
); virtual; // returns `true` if event was eaten
241 procedure keyEvent (var ev
: THKeyEvent
); virtual; // returns `true` if event was eaten
242 procedure keyEventPre (var ev
: THKeyEvent
); virtual; // will be called before dispatching the event
243 procedure keyEventPost (var ev
: THKeyEvent
); virtual; // will be called after if nobody processed the event
245 function prevSibling (): TUIControl
;
246 function nextSibling (): TUIControl
;
247 function firstChild (): TUIControl
; inline;
248 function lastChild (): TUIControl
; inline;
250 procedure appendChild (ctl
: TUIControl
); virtual;
252 function setActionCBFor (const aid
: AnsiString; cb
: TActionCB
): TActionCB
; // returns previous cb
254 function forEachChildren (cb
: TCtlEnumCB
): TUIControl
; // doesn't recurse
255 function forEachControl (cb
: TCtlEnumCB
; includeSelf
: Boolean=true): TUIControl
;
257 procedure close (); // this closes *top-level* control
260 property id
: AnsiString read mId
;
261 property styleId
: AnsiString read mStyleId
;
262 property scrollX
: Integer read mScrollX write mScrollX
;
263 property scrollY
: Integer read mScrollY write mScrollY
;
264 property x0
: Integer read mX write mX
;
265 property y0
: Integer read mY write mY
;
266 property width
: Integer read mWidth write mWidth
;
267 property height
: Integer read mHeight write mHeight
;
268 property enabled
: Boolean read getEnabled write setEnabled
;
269 property parent
: TUIControl read mParent
;
270 property focused
: Boolean read getFocused write setFocused
;
271 property active
: Boolean read getActive
;
272 property escClose
: Boolean read mEscClose write mEscClose
;
273 property cancel
: Boolean read mCancel write mCancel
;
274 property defctl
: Boolean read mDefault write mDefault
;
275 property canFocus
: Boolean read getCanFocus write mCanFocus
;
276 property ctlById
[const aid
: AnsiString]: TUIControl read findControlById
; default
;
280 TUITopWindow
= class(TUIControl
)
282 type TXMode
= (None
, Drag
, Scroll
);
287 mDragStartX
, mDragStartY
: Integer;
288 mWaitingClose
: Boolean;
290 mFreeOnClose
: Boolean; // default: false
291 mDoCenter
: Boolean; // after layouting
292 mFitToScreen
: Boolean;
295 procedure activated (); override;
296 procedure blurred (); override;
299 closeCB
: TActionCB
; // called after window was removed from ui window list
302 constructor Create (const atitle
: AnsiString);
304 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
306 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
308 procedure flFitToScreen (); // call this before layouting
310 procedure centerInScreen ();
312 // `sx` and `sy` are screen coordinates
313 procedure drawControl (gx
, gy
: Integer); override;
314 procedure drawControlPost (gx
, gy
: Integer); override;
316 procedure keyEvent (var ev
: THKeyEvent
); override; // returns `true` if event was eaten
317 procedure mouseEvent (var ev
: THMouseEvent
); override; // returns `true` if event was eaten
320 property freeOnClose
: Boolean read mFreeOnClose write mFreeOnClose
;
321 property fitToScreen
: Boolean read mFitToScreen write mFitToScreen
;
324 // ////////////////////////////////////////////////////////////////////// //
325 TUIBox
= class(TUIControl
)
328 mCaption
: AnsiString;
329 mHAlign
: Integer; // -1: left; 0: center; 1: right; default: left
332 procedure setCaption (const acap
: AnsiString);
333 procedure setHasFrame (v
: Boolean);
336 constructor Create (ahoriz
: Boolean);
338 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
340 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
342 procedure drawControl (gx
, gy
: Integer); override;
344 procedure mouseEvent (var ev
: THMouseEvent
); override;
345 procedure keyEvent (var ev
: THKeyEvent
); override;
348 property caption
: AnsiString read mCaption write setCaption
;
349 property hasFrame
: Boolean read mHasFrame write setHasFrame
;
350 property captionAlign
: Integer read mHAlign write mHAlign
;
353 TUIHBox
= class(TUIBox
)
355 constructor Create ();
357 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
360 TUIVBox
= class(TUIBox
)
362 constructor Create ();
364 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
367 // ////////////////////////////////////////////////////////////////////// //
368 TUISpan
= class(TUIControl
)
370 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
372 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
374 procedure drawControl (gx
, gy
: Integer); override;
377 // ////////////////////////////////////////////////////////////////////// //
378 TUILine
= class(TUIControl
)
380 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
382 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
384 procedure drawControl (gx
, gy
: Integer); override;
387 TUIHLine
= class(TUILine
)
389 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
392 TUIVLine
= class(TUILine
)
394 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
397 // ////////////////////////////////////////////////////////////////////// //
398 TUIStaticText
= class(TUIControl
)
401 mHAlign
: Integer; // -1: left; 0: center; 1: right; default: left
402 mVAlign
: Integer; // -1: top; 0: center; 1: bottom; default: center
403 mHeader
: Boolean; // true: draw with frame text color
404 mLine
: Boolean; // true: draw horizontal line
407 procedure setText (const atext
: AnsiString);
410 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
412 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
414 procedure drawControl (gx
, gy
: Integer); override;
417 property text: AnsiString read mText write setText
;
418 property halign
: Integer read mHAlign write mHAlign
;
419 property valign
: Integer read mVAlign write mVAlign
;
420 property header
: Boolean read mHeader write mHeader
;
421 property line
: Boolean read mLine write mLine
;
424 // ////////////////////////////////////////////////////////////////////// //
425 TUITextLabel
= class(TUIControl
)
428 mHAlign
: Integer; // -1: left; 0: center; 1: right; default: left
429 mVAlign
: Integer; // -1: top; 0: center; 1: bottom; default: center
431 mHotOfs
: Integer; // from text start, in pixels
432 mHotColor
: array[0..ClrIdxMax
] of TGxRGBA
;
433 mLinkId
: AnsiString; // linked control
436 procedure cacheStyle (root
: TUIStyle
); override;
438 procedure setText (const s
: AnsiString); virtual;
441 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
443 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
445 procedure doAction (); override;
447 procedure drawControl (gx
, gy
: Integer); override;
449 procedure mouseEvent (var ev
: THMouseEvent
); override;
450 procedure keyEventPost (var ev
: THKeyEvent
); override;
453 property text: AnsiString read mText write setText
;
454 property halign
: Integer read mHAlign write mHAlign
;
455 property valign
: Integer read mVAlign write mVAlign
;
458 // ////////////////////////////////////////////////////////////////////// //
459 TUIButton
= class(TUITextLabel
)
461 procedure setText (const s
: AnsiString); override;
464 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
466 procedure drawControl (gx
, gy
: Integer); override;
468 procedure mouseEvent (var ev
: THMouseEvent
); override;
469 procedure keyEvent (var ev
: THKeyEvent
); override;
472 // ////////////////////////////////////////////////////////////////////// //
473 TUISwitchBox
= class(TUITextLabel
)
477 mCheckedStr
: AnsiString;
478 mUncheckedStr
: AnsiString;
479 mSwitchColor
: array[0..ClrIdxMax
] of TGxRGBA
;
482 procedure cacheStyle (root
: TUIStyle
); override;
484 procedure setText (const s
: AnsiString); override;
486 function getChecked (): Boolean; virtual;
487 procedure setChecked (v
: Boolean); virtual; abstract;
490 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
492 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
494 procedure drawControl (gx
, gy
: Integer); override;
496 procedure mouseEvent (var ev
: THMouseEvent
); override;
497 procedure keyEvent (var ev
: THKeyEvent
); override;
499 procedure setVar (pvar
: PBoolean);
502 property checked
: Boolean read getChecked write setChecked
;
505 TUICheckBox
= class(TUISwitchBox
)
507 procedure setChecked (v
: Boolean); override;
510 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
512 procedure doAction (); override;
515 TUIRadioBox
= class(TUISwitchBox
)
517 mRadioGroup
: AnsiString;
520 procedure setChecked (v
: Boolean); override;
523 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
525 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
527 procedure doAction (); override;
530 property radioGroup
: AnsiString read mRadioGroup write mRadioGroup
; //FIXME
534 // ////////////////////////////////////////////////////////////////////////// //
535 procedure uiMouseEvent (var evt
: THMouseEvent
);
536 procedure uiKeyEvent (var evt
: THKeyEvent
);
540 // ////////////////////////////////////////////////////////////////////////// //
541 procedure uiAddWindow (ctl
: TUIControl
);
542 procedure uiRemoveWindow (ctl
: TUIControl
); // will free window if `mFreeOnClose` is `true`
543 function uiVisibleWindow (ctl
: TUIControl
): Boolean;
545 procedure uiUpdateStyles ();
548 // ////////////////////////////////////////////////////////////////////////// //
550 procedure uiLayoutCtl (ctl
: TUIControl
);
553 // ////////////////////////////////////////////////////////////////////////// //
555 fuiRenderScale
: Single = 1.0;
565 // ////////////////////////////////////////////////////////////////////////// //
567 ctlsToKill
: array of TUIControl
= nil;
570 procedure scheduleKill (ctl
: TUIControl
);
574 if (ctl
= nil) then exit
;
576 for f
:= 0 to High(ctlsToKill
) do
578 if (ctlsToKill
[f
] = ctl
) then exit
;
579 if (ctlsToKill
[f
] = nil) then begin ctlsToKill
[f
] := ctl
; exit
; end;
581 SetLength(ctlsToKill
, Length(ctlsToKill
)+1);
582 ctlsToKill
[High(ctlsToKill
)] := ctl
;
586 procedure processKills ();
591 for f
:= 0 to High(ctlsToKill
) do
593 ctl
:= ctlsToKill
[f
];
594 if (ctl
= nil) then break
;
595 ctlsToKill
[f
] := nil;
598 if (Length(ctlsToKill
) > 0) then ctlsToKill
[0] := nil; // just in case
602 // ////////////////////////////////////////////////////////////////////////// //
604 knownCtlClasses
: array of record
605 klass
: TUIControlClass
;
610 procedure registerCtlClass (aklass
: TUIControlClass
; const aname
: AnsiString);
612 assert(aklass
<> nil);
613 assert(Length(aname
) > 0);
614 SetLength(knownCtlClasses
, Length(knownCtlClasses
)+1);
615 knownCtlClasses
[High(knownCtlClasses
)].klass
:= aklass
;
616 knownCtlClasses
[High(knownCtlClasses
)].name
:= aname
;
620 function findCtlClass (const aname
: AnsiString): TUIControlClass
;
624 for f
:= 0 to High(knownCtlClasses
) do
626 if (strEquCI1251(aname
, knownCtlClasses
[f
].name
)) then
628 result
:= knownCtlClasses
[f
].klass
;
636 // ////////////////////////////////////////////////////////////////////////// //
638 TFlexLayouter
= specialize TFlexLayouterBase
<TUIControl
>;
640 procedure uiLayoutCtl (ctl
: TUIControl
);
644 if (ctl
= nil) then exit
;
645 lay
:= TFlexLayouter
.Create();
647 if (ctl
is TUITopWindow
) and (TUITopWindow(ctl
).fitToScreen
) then TUITopWindow(ctl
).flFitToScreen();
652 //writeln('============================'); lay.dumpFlat();
654 //writeln('=== initial ==='); lay.dump();
656 //lay.calcMaxSizeInternal(0);
659 writeln('=== after first pass ===');
663 writeln('=== after second pass ===');
668 //writeln('=== final ==='); lay.dump();
670 if (ctl
.mParent
= nil) and (ctl
is TUITopWindow
) and (TUITopWindow(ctl
).mDoCenter
) then
672 TUITopWindow(ctl
).centerInScreen();
675 // calculate full size
676 ctl
.calcFullClientSize();
679 if (ctl
.mParent
= nil) then
681 if (ctl
.mFocused
= nil) or (ctl
.mFocused
= ctl
) or (not ctl
.mFocused
.enabled
) then
683 ctl
.mFocused
:= ctl
.findFirstFocus();
693 // ////////////////////////////////////////////////////////////////////////// //
695 uiTopList
: array of TUIControl
= nil;
696 uiGrabCtl
: TUIControl
= nil;
699 procedure uiUpdateStyles ();
703 for ctl
in uiTopList
do ctl
.updateStyle();
707 procedure uiMouseEvent (var evt
: THMouseEvent
);
715 if (evt
.eaten
) or (evt
.cancelled
) then exit
;
717 ev
.x
:= trunc(ev
.x
/fuiRenderScale
);
718 ev
.y
:= trunc(ev
.y
/fuiRenderScale
);
719 ev
.dx
:= trunc(ev
.dx
/fuiRenderScale
); //FIXME
720 ev
.dy
:= trunc(ev
.dy
/fuiRenderScale
); //FIXME
722 if (uiGrabCtl
<> nil) then
724 uiGrabCtl
.mouseEvent(ev
);
725 if (ev
.release
) and ((ev
.bstate
and (not ev
.but
)) = 0) then uiGrabCtl
:= nil;
729 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].mouseEvent(ev
);
730 if (not ev
.eaten
) and (not ev
.cancelled
) and (ev
.press
) then
732 for f
:= High(uiTopList
) downto 0 do
734 if uiTopList
[f
].toLocal(ev
.x
, ev
.y
, lx
, ly
) then
736 if (uiTopList
[f
].enabled
) and (f
<> High(uiTopList
)) then
738 uiTopList
[High(uiTopList
)].blurred();
739 ctmp
:= uiTopList
[f
];
741 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
742 uiTopList
[High(uiTopList
)] := ctmp
;
752 if (ev
.eaten
) then evt
.eat();
753 if (ev
.cancelled
) then evt
.cancel();
758 procedure uiKeyEvent (var evt
: THKeyEvent
);
763 if (evt
.eaten
) or (evt
.cancelled
) then exit
;
765 ev
.x
:= trunc(ev
.x
/fuiRenderScale
);
766 ev
.y
:= trunc(ev
.y
/fuiRenderScale
);
768 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].keyEvent(ev
);
769 //if (ev.release) then begin ev.eat(); exit; end;
771 if (ev
.eaten
) then evt
.eat();
772 if (ev
.cancelled
) then evt
.cancel();
783 gxBeginUIDraw(fuiRenderScale
);
785 for f
:= 0 to High(uiTopList
) do
789 if (f
<> High(uiTopList
)) then
791 cidx
:= ctl
.getColorIndex
;
792 if (ctl
.mDarken
[cidx
] > 0) then darkenRect(ctl
.x0
, ctl
.y0
, ctl
.width
, ctl
.height
, ctl
.mDarken
[cidx
]);
801 procedure uiAddWindow (ctl
: TUIControl
);
805 if (ctl
= nil) then exit
;
807 if not (ctl
is TUITopWindow
) then exit
; // alas
808 for f
:= 0 to High(uiTopList
) do
810 if (uiTopList
[f
] = ctl
) then
812 if (f
<> High(uiTopList
)) then
814 uiTopList
[High(uiTopList
)].blurred();
815 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
816 uiTopList
[High(uiTopList
)] := ctl
;
822 if (Length(uiTopList
) > 0) then uiTopList
[High(uiTopList
)].blurred();
823 SetLength(uiTopList
, Length(uiTopList
)+1);
824 uiTopList
[High(uiTopList
)] := ctl
;
830 procedure uiRemoveWindow (ctl
: TUIControl
);
834 if (ctl
= nil) then exit
;
836 if not (ctl
is TUITopWindow
) then exit
; // alas
837 for f
:= 0 to High(uiTopList
) do
839 if (uiTopList
[f
] = ctl
) then
842 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
843 SetLength(uiTopList
, Length(uiTopList
)-1);
844 if (ctl
is TUITopWindow
) then
847 if assigned(TUITopWindow(ctl
).closeCB
) then TUITopWindow(ctl
).closeCB(ctl
);
849 if (TUITopWindow(ctl
).mFreeOnClose
) then scheduleKill(ctl
);
858 function uiVisibleWindow (ctl
: TUIControl
): Boolean;
863 if (ctl
= nil) then exit
;
865 if not (ctl
is TUITopWindow
) then exit
; // alas
866 for f
:= 0 to High(uiTopList
) do
868 if (uiTopList
[f
] = ctl
) then begin result
:= true; exit
; end;
873 // ////////////////////////////////////////////////////////////////////////// //
874 constructor TUIControl
.Create ();
879 procedure TUIControl
.AfterConstruction ();
896 mDrawShadow
:= false;
898 // layouter interface
899 //mDefSize := TLaySize.Create(64, 8); // default size
900 mDefSize
:= TLaySize
.Create(0, 0); // default size
901 mMaxSize
:= TLaySize
.Create(-1, -1); // maximum size
902 mPadding
:= TLaySize
.Create(0, 0);
912 mAlign
:= -1; // left/top
917 destructor TUIControl
.Destroy ();
921 if (mParent
<> nil) then
924 for f
:= 0 to High(mParent
.mChildren
) do
926 if (mParent
.mChildren
[f
] = self
) then
928 for c
:= f
+1 to High(mParent
.mChildren
) do mParent
.mChildren
[c
-1] := mParent
.mChildren
[c
];
929 SetLength(mParent
.mChildren
, Length(mParent
.mChildren
)-1);
933 for f
:= 0 to High(mChildren
) do
935 mChildren
[f
].mParent
:= nil;
942 function TUIControl
.getColorIndex (): Integer; inline;
944 if (not enabled
) then begin result
:= ClrIdxDisabled
; exit
; end;
945 // top windows: no focus hack
946 if (self
is TUITopWindow
) then
948 if (getActive
) then begin result
:= ClrIdxActive
; exit
; end;
952 // if control cannot be focused, take "active" color scheme for it (it is easier this way)
953 if (not canFocus
) or (getActive
) then begin result
:= ClrIdxActive
; exit
; end;
955 result
:= ClrIdxInactive
;
958 procedure TUIControl
.updateStyle ();
964 while (ctl
<> nil) do
966 if (Length(ctl
.mStyleId
) <> 0) then begin stl
:= uiFindStyle(ctl
.mStyleId
); break
; end;
969 if (stl
= nil) then stl
:= uiFindStyle(''); // default
971 for ctl
in mChildren
do ctl
.updateStyle();
974 procedure TUIControl
.cacheStyle (root
: TUIStyle
);
978 //writeln('caching style for <', className, '> (', mCtl4Style, ')...');
981 mBackColor
[ClrIdxActive
] := root
.get('back-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(0, 0, 128));
982 mTextColor
[ClrIdxActive
] := root
.get('text-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
983 mFrameColor
[ClrIdxActive
] := root
.get('frame-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
984 mFrameTextColor
[ClrIdxActive
] := root
.get('frame-text-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
985 mFrameIconColor
[ClrIdxActive
] := root
.get('frame-icon-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(0, 255, 0));
986 mDarken
[ClrIdxActive
] := root
.get('darken', 'active', cst
).asInt(-1);
988 mBackColor
[ClrIdxDisabled
] := root
.get('back-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(0, 0, 128));
989 mTextColor
[ClrIdxDisabled
] := root
.get('text-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(127, 127, 127));
990 mFrameColor
[ClrIdxDisabled
] := root
.get('frame-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(127, 127, 127));
991 mFrameTextColor
[ClrIdxDisabled
] := root
.get('frame-text-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(127, 127, 127));
992 mFrameIconColor
[ClrIdxDisabled
] := root
.get('frame-icon-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(0, 127, 0));
993 mDarken
[ClrIdxDisabled
] := root
.get('darken', 'disabled', cst
).asInt(-1);
995 mBackColor
[ClrIdxInactive
] := root
.get('back-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(0, 0, 128));
996 mTextColor
[ClrIdxInactive
] := root
.get('text-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
997 mFrameColor
[ClrIdxInactive
] := root
.get('frame-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
998 mFrameTextColor
[ClrIdxInactive
] := root
.get('frame-text-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
999 mFrameIconColor
[ClrIdxInactive
] := root
.get('frame-icon-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(0, 255, 0));
1000 mDarken
[ClrIdxInactive
] := root
.get('darken', 'inactive', cst
).asInt(-1);
1004 // ////////////////////////////////////////////////////////////////////////// //
1005 function TUIControl
.getDefSize (): TLaySize
; inline; begin result
:= mLayDefSize
; end;
1006 function TUIControl
.getMaxSize (): TLaySize
; inline; begin result
:= mLayMaxSize
; end;
1007 function TUIControl
.getPadding (): TLaySize
; inline; begin result
:= mPadding
; end;
1008 function TUIControl
.getFlex (): Integer; inline; begin result
:= mFlex
; end;
1009 function TUIControl
.isHorizBox (): Boolean; inline; begin result
:= mHoriz
; end;
1010 function TUIControl
.canWrap (): Boolean; inline; begin result
:= mCanWrap
; end;
1011 function TUIControl
.noPad (): Boolean; inline; begin result
:= mNoPad
; end;
1012 function TUIControl
.isLineStart (): Boolean; inline; begin result
:= mLineStart
; end;
1013 function TUIControl
.getAlign (): Integer; inline; begin result
:= mAlign
; end;
1014 function TUIControl
.getExpand (): Boolean; inline; begin result
:= mExpand
; end;
1015 function TUIControl
.getHGroup (): AnsiString; inline; begin result
:= mHGroup
; end;
1016 function TUIControl
.getVGroup (): AnsiString; inline; begin result
:= mVGroup
; end;
1017 function TUIControl
.getMargins (): TLayMargins
; inline; begin result
:= TLayMargins
.Create(mFrameHeight
, mFrameWidth
, mFrameHeight
, mFrameWidth
); end;
1019 procedure TUIControl
.setActualSizePos (constref apos
: TLayPos
; constref asize
: TLaySize
); inline;
1021 //writeln(self.className, '; pos=', apos.toString, '; size=', asize.toString);
1022 if (mParent
<> nil) then
1031 procedure TUIControl
.layPrepare ();
1033 mLayDefSize
:= mDefSize
;
1034 mLayMaxSize
:= mMaxSize
;
1035 if (mLayMaxSize
.w
>= 0) then mLayMaxSize
.w
+= mFrameWidth
*2;
1036 if (mLayMaxSize
.h
>= 0) then mLayMaxSize
.h
+= mFrameHeight
*2;
1040 // ////////////////////////////////////////////////////////////////////////// //
1041 function TUIControl
.parsePos (par
: TTextParser
): TLayPos
;
1043 ech
: AnsiChar = ')';
1045 if (par
.eatDelim('[')) then ech
:= ']' else par
.expectDelim('(');
1046 result
.x
:= par
.expectInt();
1047 par
.eatDelim(','); // optional comma
1048 result
.y
:= par
.expectInt();
1049 par
.eatDelim(','); // optional comma
1050 par
.expectDelim(ech
);
1053 function TUIControl
.parseSize (par
: TTextParser
): TLaySize
;
1055 ech
: AnsiChar = ')';
1057 if (par
.eatDelim('[')) then ech
:= ']' else par
.expectDelim('(');
1058 result
.w
:= par
.expectInt();
1059 par
.eatDelim(','); // optional comma
1060 result
.h
:= par
.expectInt();
1061 par
.eatDelim(','); // optional comma
1062 par
.expectDelim(ech
);
1065 function TUIControl
.parsePadding (par
: TTextParser
): TLaySize
;
1067 result
:= parseSize(par
);
1070 function TUIControl
.parseHPadding (par
: TTextParser
; def
: Integer): TLaySize
;
1075 result
.w
:= par
.expectInt();
1079 result
:= parsePadding(par
);
1083 function TUIControl
.parseVPadding (par
: TTextParser
; def
: Integer): TLaySize
;
1088 result
.h
:= par
.expectInt();
1092 result
:= parsePadding(par
);
1096 function TUIControl
.parseBool (par
: TTextParser
): Boolean;
1099 par
.eatIdOrStrCI('true') or
1100 par
.eatIdOrStrCI('yes') or
1101 par
.eatIdOrStrCI('tan');
1104 if (not par
.eatIdOrStrCI('false')) and (not par
.eatIdOrStrCI('no')) and (not par
.eatIdOrStrCI('ona')) then
1106 par
.error('boolean value expected');
1111 function TUIControl
.parseAnyAlign (par
: TTextParser
): Integer;
1113 if (par
.eatIdOrStrCI('left')) or (par
.eatIdOrStrCI('top')) then result
:= -1
1114 else if (par
.eatIdOrStrCI('right')) or (par
.eatIdOrStrCI('bottom')) then result
:= 1
1115 else if (par
.eatIdOrStrCI('center')) then result
:= 0
1116 else par
.error('invalid align value');
1119 function TUIControl
.parseHAlign (par
: TTextParser
): Integer;
1121 if (par
.eatIdOrStrCI('left')) then result
:= -1
1122 else if (par
.eatIdOrStrCI('right')) then result
:= 1
1123 else if (par
.eatIdOrStrCI('center')) then result
:= 0
1124 else par
.error('invalid horizontal align value');
1127 function TUIControl
.parseVAlign (par
: TTextParser
): Integer;
1129 if (par
.eatIdOrStrCI('top')) then result
:= -1
1130 else if (par
.eatIdOrStrCI('bottom')) then result
:= 1
1131 else if (par
.eatIdOrStrCI('center')) then result
:= 0
1132 else par
.error('invalid vertical align value');
1135 procedure TUIControl
.parseTextAlign (par
: TTextParser
; var h
, v
: Integer);
1137 wasH
: Boolean = false;
1138 wasV
: Boolean = false;
1142 if (par
.eatIdOrStrCI('left')) then
1144 if wasH
then par
.error('too many align directives');
1149 if (par
.eatIdOrStrCI('right')) then
1151 if wasH
then par
.error('too many align directives');
1156 if (par
.eatIdOrStrCI('hcenter')) then
1158 if wasH
then par
.error('too many align directives');
1163 if (par
.eatIdOrStrCI('top')) then
1165 if wasV
then par
.error('too many align directives');
1170 if (par
.eatIdOrStrCI('bottom')) then
1172 if wasV
then par
.error('too many align directives');
1177 if (par
.eatIdOrStrCI('vcenter')) then
1179 if wasV
then par
.error('too many align directives');
1184 if (par
.eatIdOrStrCI('center')) then
1186 if wasV
or wasH
then par
.error('too many align directives');
1195 if not wasV
and not wasH
then par
.error('invalid align value');
1198 function TUIControl
.parseOrientation (const prname
: AnsiString; par
: TTextParser
): Boolean;
1200 if (strEquCI1251(prname
, 'orientation')) or (strEquCI1251(prname
, 'orient')) then
1202 if (par
.eatIdOrStrCI('horizontal')) or (par
.eatIdOrStrCI('horiz')) then mHoriz
:= true
1203 else if (par
.eatIdOrStrCI('vertical')) or (par
.eatIdOrStrCI('vert')) then mHoriz
:= false
1204 else par
.error('`horizontal` or `vertical` expected');
1213 // par should be on '{'; final '}' is eaten
1214 procedure TUIControl
.parseProperties (par
: TTextParser
);
1218 if (not par
.eatDelim('{')) then exit
;
1219 while (not par
.eatDelim('}')) do
1221 if (not par
.isIdOrStr
) then par
.error('property name expected');
1224 par
.eatDelim(':'); // optional
1225 if not parseProperty(pn
, par
) then par
.errorfmt('invalid property name ''%s''', [pn
]);
1226 par
.eatDelim(','); // optional
1230 // par should be on '{'
1231 procedure TUIControl
.parseChildren (par
: TTextParser
);
1233 cc
: TUIControlClass
;
1236 par
.expectDelim('{');
1237 while (not par
.eatDelim('}')) do
1239 if (not par
.isIdOrStr
) then par
.error('control name expected');
1240 cc
:= findCtlClass(par
.tokStr
);
1241 if (cc
= nil) then par
.errorfmt('unknown control name: ''%s''', [par
.tokStr
]);
1242 //writeln('children for <', par.tokStr, '>: <', cc.className, '>');
1244 par
.eatDelim(':'); // optional
1246 //writeln(' mHoriz=', ctl.mHoriz);
1248 ctl
.parseProperties(par
);
1253 //writeln(': ', ctl.mDefSize.toString);
1255 par
.eatDelim(','); // optional
1260 function TUIControl
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
1263 if (strEquCI1251(prname
, 'id')) then begin mId
:= par
.expectIdOrStr(true); exit
; end; // allow empty strings
1264 if (strEquCI1251(prname
, 'style')) then begin mStyleId
:= par
.expectIdOrStr(); exit
; end; // no empty strings
1265 if (strEquCI1251(prname
, 'flex')) then begin flex
:= par
.expectInt(); exit
; end;
1267 if (strEquCI1251(prname
, 'defsize')) or (strEquCI1251(prname
, 'size')) then begin mDefSize
:= parseSize(par
); exit
; end;
1268 if (strEquCI1251(prname
, 'maxsize')) then begin mMaxSize
:= parseSize(par
); exit
; end;
1269 if (strEquCI1251(prname
, 'defwidth')) or (strEquCI1251(prname
, 'width')) then begin mDefSize
.w
:= par
.expectInt(); exit
; end;
1270 if (strEquCI1251(prname
, 'defheight')) or (strEquCI1251(prname
, 'height')) then begin mDefSize
.h
:= par
.expectInt(); exit
; end;
1271 if (strEquCI1251(prname
, 'maxwidth')) then begin mMaxSize
.w
:= par
.expectInt(); exit
; end;
1272 if (strEquCI1251(prname
, 'maxheight')) then begin mMaxSize
.h
:= par
.expectInt(); exit
; end;
1274 if (strEquCI1251(prname
, 'padding')) then begin mPadding
:= parsePadding(par
); exit
; end;
1275 if (strEquCI1251(prname
, 'nopad')) then begin mNoPad
:= true; exit
; end;
1277 if (strEquCI1251(prname
, 'wrap')) then begin mCanWrap
:= parseBool(par
); exit
; end;
1278 if (strEquCI1251(prname
, 'linestart')) then begin mLineStart
:= parseBool(par
); exit
; end;
1279 if (strEquCI1251(prname
, 'expand')) then begin mExpand
:= parseBool(par
); exit
; end;
1281 if (strEquCI1251(prname
, 'align')) then begin mAlign
:= parseAnyAlign(par
); exit
; end;
1282 if (strEquCI1251(prname
, 'hgroup')) then begin mHGroup
:= par
.expectIdOrStr(true); exit
; end; // allow empty strings
1283 if (strEquCI1251(prname
, 'vgroup')) then begin mVGroup
:= par
.expectIdOrStr(true); exit
; end; // allow empty strings
1285 if (strEquCI1251(prname
, 'canfocus')) then begin mCanFocus
:= true; exit
; end;
1286 if (strEquCI1251(prname
, 'nofocus')) then begin mCanFocus
:= false; exit
; end;
1287 if (strEquCI1251(prname
, 'disabled')) then begin mEnabled
:= false; exit
; end;
1288 if (strEquCI1251(prname
, 'enabled')) then begin mEnabled
:= true; exit
; end;
1289 if (strEquCI1251(prname
, 'escclose')) then begin mEscClose
:= not parseBool(par
); exit
; end;
1290 if (strEquCI1251(prname
, 'default')) then begin mDefault
:= true; exit
; end;
1291 if (strEquCI1251(prname
, 'cancel')) then begin mCancel
:= true; exit
; end;
1296 // ////////////////////////////////////////////////////////////////////////// //
1297 procedure TUIControl
.activated ();
1299 makeVisibleInParent();
1303 procedure TUIControl
.blurred ();
1305 if (uiGrabCtl
= self
) then uiGrabCtl
:= nil;
1309 procedure TUIControl
.calcFullClientSize ();
1313 mFullSize
:= TLaySize
.Create(0, 0);
1314 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1315 for ctl
in mChildren
do
1317 ctl
.calcFullClientSize();
1318 mFullSize
.w
:= nmax(mFullSize
.w
, ctl
.mX
-mFrameWidth
+ctl
.mFullSize
.w
);
1319 mFullSize
.h
:= nmax(mFullSize
.h
, ctl
.mY
-mFrameHeight
+ctl
.mFullSize
.h
);
1321 mFullSize
.w
:= nmax(mFullSize
.w
, mWidth
-mFrameWidth
*2);
1322 mFullSize
.h
:= nmax(mFullSize
.h
, mHeight
-mFrameHeight
*2);
1326 function TUIControl
.topLevel (): TUIControl
; inline;
1329 while (result
.mParent
<> nil) do result
:= result
.mParent
;
1333 function TUIControl
.getEnabled (): Boolean;
1338 if (not mEnabled
) then exit
;
1340 while (ctl
<> nil) do
1342 if (not ctl
.mEnabled
) then exit
;
1349 procedure TUIControl
.setEnabled (v
: Boolean); inline;
1351 if (mEnabled
= v
) then exit
;
1353 if (not v
) and focused
then setFocused(false);
1357 function TUIControl
.getFocused (): Boolean; inline;
1359 if (mParent
= nil) then
1361 result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = self
);
1365 result
:= (topLevel
.mFocused
= self
);
1366 if (result
) then result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = topLevel
);
1371 function TUIControl
.getActive (): Boolean; inline;
1375 if (mParent
= nil) then
1377 result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = self
);
1381 ctl
:= topLevel
.mFocused
;
1382 while (ctl
<> nil) and (ctl
<> self
) do ctl
:= ctl
.mParent
;
1383 result
:= (ctl
= self
);
1384 if (result
) then result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = topLevel
);
1389 procedure TUIControl
.setFocused (v
: Boolean); inline;
1396 if (tl
.mFocused
= self
) then
1398 blurred(); // this will reset grab, but still...
1399 if (uiGrabCtl
= self
) then uiGrabCtl
:= nil;
1400 tl
.mFocused
:= tl
.findNextFocus(self
, true);
1401 if (tl
.mFocused
= self
) then tl
.mFocused
:= nil;
1402 if (tl
.mFocused
<> nil) then tl
.mFocused
.activated();
1406 if (not canFocus
) then exit
;
1407 if (tl
.mFocused
<> self
) then
1409 if (tl
.mFocused
<> nil) then tl
.mFocused
.blurred();
1410 tl
.mFocused
:= self
;
1411 if (uiGrabCtl
<> self
) then uiGrabCtl
:= nil;
1417 function TUIControl
.getCanFocus (): Boolean; inline;
1419 result
:= (getEnabled
) and (mCanFocus
) and (mWidth
> 0) and (mHeight
> 0);
1423 function TUIControl
.isMyChild (ctl
: TUIControl
): Boolean;
1426 while (ctl
<> nil) do
1428 if (ctl
.mParent
= self
) then exit
;
1435 // returns `true` if global coords are inside this control
1436 function TUIControl
.toLocal (var x
, y
: Integer): Boolean;
1438 if (mParent
= nil) then
1442 result
:= true; // hack
1446 result
:= mParent
.toLocal(x
, y
);
1447 Inc(x
, mParent
.mScrollX
);
1448 Inc(y
, mParent
.mScrollY
);
1451 if result
then result
:= (x
>= 0) and (y
>= 0) and (x
< mParent
.mWidth
) and (y
< mParent
.mHeight
);
1453 if result
then result
:= (x
>= 0) and (y
>= 0) and (x
< mWidth
) and (y
< mHeight
);
1456 function TUIControl
.toLocal (gx
, gy
: Integer; out x
, y
: Integer): Boolean; inline;
1460 result
:= toLocal(x
, y
);
1464 procedure TUIControl
.toGlobal (var x
, y
: Integer);
1468 if (mParent
<> nil) then
1470 Dec(x
, mParent
.mScrollX
);
1471 Dec(y
, mParent
.mScrollY
);
1472 mParent
.toGlobal(x
, y
);
1476 procedure TUIControl
.toGlobal (lx
, ly
: Integer; out x
, y
: Integer); inline;
1483 procedure TUIControl
.getDrawRect (out gx
, gy
, wdt
, hgt
: Integer);
1487 if (mParent
= nil) then
1496 toGlobal(0, 0, cgx
, cgy
);
1497 mParent
.getDrawRect(gx
, gy
, wdt
, hgt
);
1498 if (wdt
> 0) and (hgt
> 0) then
1500 if not intersectRect(gx
, gy
, wdt
, hgt
, cgx
, cgy
, mWidth
, mHeight
) then
1510 // x and y are global coords
1511 function TUIControl
.controlAtXY (x
, y
: Integer; allowDisabled
: Boolean=false): TUIControl
;
1517 if (not allowDisabled
) and (not enabled
) then exit
;
1518 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1519 if not toLocal(x
, y
, lx
, ly
) then exit
;
1520 for f
:= High(mChildren
) downto 0 do
1522 result
:= mChildren
[f
].controlAtXY(x
, y
, allowDisabled
);
1523 if (result
<> nil) then exit
;
1529 function TUIControl
.parentScrollX (): Integer; inline; begin if (mParent
<> nil) then result
:= mParent
.mScrollX
else result
:= 0; end;
1530 function TUIControl
.parentScrollY (): Integer; inline; begin if (mParent
<> nil) then result
:= mParent
.mScrollY
else result
:= 0; end;
1533 procedure TUIControl
.makeVisibleInParent ();
1535 sy
, ey
, cy
: Integer;
1538 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1540 if (p
= nil) then exit
;
1541 if (p
.mFullSize
.w
< 1) or (p
.mFullSize
.h
< 1) then
1547 p
.makeVisibleInParent();
1548 cy
:= mY
-p
.mFrameHeight
;
1550 ey
:= sy
+(p
.mHeight
-p
.mFrameHeight
*2);
1553 p
.mScrollY
:= nmax(0, cy
);
1555 else if (cy
+mHeight
> ey
) then
1557 p
.mScrollY
:= nmax(0, cy
+mHeight
-(p
.mHeight
-p
.mFrameHeight
*2));
1562 // ////////////////////////////////////////////////////////////////////////// //
1563 function TUIControl
.prevSibling (): TUIControl
;
1567 if (mParent
<> nil) then
1569 for f
:= 1 to High(mParent
.mChildren
) do
1571 if (mParent
.mChildren
[f
] = self
) then begin result
:= mParent
.mChildren
[f
-1]; exit
; end;
1577 function TUIControl
.nextSibling (): TUIControl
;
1581 if (mParent
<> nil) then
1583 for f
:= 0 to High(mParent
.mChildren
)-1 do
1585 if (mParent
.mChildren
[f
] = self
) then begin result
:= mParent
.mChildren
[f
+1]; exit
; end;
1591 function TUIControl
.firstChild (): TUIControl
; inline;
1593 if (Length(mChildren
) <> 0) then result
:= mChildren
[0] else result
:= nil;
1596 function TUIControl
.lastChild (): TUIControl
; inline;
1598 if (Length(mChildren
) <> 0) then result
:= mChildren
[High(mChildren
)] else result
:= nil;
1602 function TUIControl
.findFirstFocus (): TUIControl
;
1609 for f
:= 0 to High(mChildren
) do
1611 result
:= mChildren
[f
].findFirstFocus();
1612 if (result
<> nil) then exit
;
1614 if (canFocus
) then result
:= self
;
1619 function TUIControl
.findLastFocus (): TUIControl
;
1626 for f
:= High(mChildren
) downto 0 do
1628 result
:= mChildren
[f
].findLastFocus();
1629 if (result
<> nil) then exit
;
1631 if (canFocus
) then result
:= self
;
1636 function TUIControl
.findNextFocus (cur
: TUIControl
; wrap
: Boolean): TUIControl
;
1638 curHit
: Boolean = false;
1640 function checkFocus (ctl
: TUIControl
): Boolean;
1644 result
:= (ctl
.canFocus
);
1648 curHit
:= (ctl
= cur
);
1649 result
:= false; // don't stop
1657 if not isMyChild(cur
) then
1659 result
:= findFirstFocus();
1663 result
:= forEachControl(checkFocus
);
1664 if (result
= nil) and (wrap
) then result
:= findFirstFocus();
1670 function TUIControl
.findPrevFocus (cur
: TUIControl
; wrap
: Boolean): TUIControl
;
1672 lastCtl
: TUIControl
= nil;
1674 function checkFocus (ctl
: TUIControl
): Boolean;
1683 if (ctl
.canFocus
) then lastCtl
:= ctl
;
1691 if not isMyChild(cur
) then
1693 result
:= findLastFocus();
1697 forEachControl(checkFocus
);
1698 if (lastCtl
= nil) and (wrap
) then lastCtl
:= findLastFocus();
1700 //if (lastCtl <> nil) then writeln('ctl<', lastCtl.className, '>: {', lastCtl.id, '}');
1706 function TUIControl
.findDefaulControl (): TUIControl
;
1712 if (mDefault
) then begin result
:= self
; exit
; end;
1713 for ctl
in mChildren
do
1715 result
:= ctl
.findDefaulControl();
1716 if (result
<> nil) then exit
;
1722 function TUIControl
.findCancelControl (): TUIControl
;
1728 if (mCancel
) then begin result
:= self
; exit
; end;
1729 for ctl
in mChildren
do
1731 result
:= ctl
.findCancelControl();
1732 if (result
<> nil) then exit
;
1739 function TUIControl
.findControlById (const aid
: AnsiString): TUIControl
;
1743 if (strEquCI1251(aid
, mId
)) then begin result
:= self
; exit
; end;
1744 for ctl
in mChildren
do
1746 result
:= ctl
.findControlById(aid
);
1747 if (result
<> nil) then exit
;
1753 procedure TUIControl
.appendChild (ctl
: TUIControl
);
1755 if (ctl
= nil) then exit
;
1756 if (ctl
.mParent
<> nil) then exit
;
1757 SetLength(mChildren
, Length(mChildren
)+1);
1758 mChildren
[High(mChildren
)] := ctl
;
1759 ctl
.mParent
:= self
;
1760 Inc(ctl
.mX
, mFrameWidth
);
1761 Inc(ctl
.mY
, mFrameHeight
);
1762 if (ctl
.mWidth
> 0) and (ctl
.mHeight
> 0) and
1763 (ctl
.mX
+ctl
.mWidth
> mFrameWidth
) and (ctl
.mY
+ctl
.mHeight
> mFrameHeight
) then
1765 if (mWidth
+mFrameWidth
< ctl
.mX
+ctl
.mWidth
) then mWidth
:= ctl
.mX
+ctl
.mWidth
+mFrameWidth
;
1766 if (mHeight
+mFrameHeight
< ctl
.mY
+ctl
.mHeight
) then mHeight
:= ctl
.mY
+ctl
.mHeight
+mFrameHeight
;
1771 function TUIControl
.setActionCBFor (const aid
: AnsiString; cb
: TActionCB
): TActionCB
;
1776 if (ctl
<> nil) then
1778 result
:= ctl
.actionCB
;
1788 function TUIControl
.forEachChildren (cb
: TCtlEnumCB
): TUIControl
;
1793 if (not assigned(cb
)) then exit
;
1794 for ctl
in mChildren
do
1796 if cb(ctl
) then begin result
:= ctl
; exit
; end;
1801 function TUIControl
.forEachControl (cb
: TCtlEnumCB
; includeSelf
: Boolean=true): TUIControl
;
1803 function forChildren (p
: TUIControl
; incSelf
: Boolean): TUIControl
;
1808 if (p
= nil) then exit
;
1809 if (incSelf
) and (cb(p
)) then begin result
:= p
; exit
; end;
1810 for ctl
in p
.mChildren
do
1812 result
:= forChildren(ctl
, true);
1813 if (result
<> nil) then break
;
1819 if (not assigned(cb
)) then exit
;
1820 result
:= forChildren(self
, includeSelf
);
1824 procedure TUIControl
.close (); // this closes *top-level* control
1829 uiRemoveWindow(ctl
);
1830 if (ctl
is TUITopWindow
) and (TUITopWindow(ctl
).mFreeOnClose
) then scheduleKill(ctl
); // just in case
1834 procedure TUIControl
.doAction ();
1836 if assigned(actionCB
) then actionCB(self
);
1840 // ////////////////////////////////////////////////////////////////////////// //
1841 procedure TUIControl
.setScissorGLInternal (x
, y
, w
, h
: Integer);
1843 if not scallowed
then exit
;
1844 x
:= trunc(x
*fuiRenderScale
);
1845 y
:= trunc(y
*fuiRenderScale
);
1846 w
:= trunc(w
*fuiRenderScale
);
1847 h
:= trunc(h
*fuiRenderScale
);
1848 scis
.combineRect(x
, y
, w
, h
);
1851 procedure TUIControl
.setScissor (lx
, ly
, lw
, lh
: Integer);
1853 gx
, gy
, wdt
, hgt
, cgx
, cgy
: Integer;
1855 if not scallowed
then exit
;
1857 if not intersectRect(lx
, ly
, lw
, lh
, 0, 0, mWidth
, mHeight
) then
1859 scis
.combineRect(0, 0, 0, 0);
1863 getDrawRect(gx
, gy
, wdt
, hgt
);
1864 toGlobal(lx
, ly
, cgx
, cgy
);
1865 if not intersectRect(gx
, gy
, wdt
, hgt
, cgx
, cgy
, lw
, lh
) then
1867 scis
.combineRect(0, 0, 0, 0);
1871 setScissorGLInternal(gx
, gy
, wdt
, hgt
);
1874 procedure TUIControl
.resetScissor (fullArea
: Boolean); inline;
1876 if not scallowed
then exit
;
1879 setScissor(0, 0, mWidth
, mHeight
);
1883 setScissor(mFrameWidth
, mFrameHeight
, mWidth
-mFrameWidth
*2, mHeight
-mFrameHeight
*2);
1888 // ////////////////////////////////////////////////////////////////////////// //
1889 procedure TUIControl
.draw ();
1894 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1895 toGlobal(0, 0, gx
, gy
);
1897 scis
.save(true); // scissoring enabled
1900 resetScissor(true); // full area
1901 drawControl(gx
, gy
);
1902 resetScissor(false); // client area
1903 for f
:= 0 to High(mChildren
) do mChildren
[f
].draw();
1904 resetScissor(true); // full area
1905 drawControlPost(gx
, gy
);
1912 procedure TUIControl
.drawControl (gx
, gy
: Integer);
1914 //if (mParent = nil) then darkenRect(gx, gy, mWidth, mHeight, 64);
1917 procedure TUIControl
.drawControlPost (gx
, gy
: Integer);
1920 if mDrawShadow
and (mWidth
> 0) and (mHeight
> 0) then
1922 setScissorGLInternal(gx
+8, gy
+8, mWidth
, mHeight
);
1923 darkenRect(gx
+mWidth
, gy
+8, 8, mHeight
, 128);
1924 darkenRect(gx
+8, gy
+mHeight
, mWidth
-8, 8, 128);
1929 // ////////////////////////////////////////////////////////////////////////// //
1930 procedure TUIControl
.mouseEvent (var ev
: THMouseEvent
);
1934 if (not enabled
) then exit
;
1935 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1936 ctl
:= controlAtXY(ev
.x
, ev
.y
);
1937 if (ctl
= nil) then exit
;
1938 if (ctl
.canFocus
) and (ev
.press
) then
1940 if (ctl
<> topLevel
.mFocused
) then ctl
.setFocused(true);
1943 if (ctl
<> self
) then ctl
.mouseEvent(ev
);
1948 procedure TUIControl
.keyEvent (var ev
: THKeyEvent
);
1950 function doPreKey (ctl
: TUIControl
): Boolean;
1952 if (not ctl
.enabled
) then begin result
:= false; exit
; end;
1953 ctl
.keyEventPre(ev
);
1954 result
:= (ev
.eaten
) or (ev
.cancelled
); // stop if event was consumed
1957 function doPostKey (ctl
: TUIControl
): Boolean;
1959 if (not ctl
.enabled
) then begin result
:= false; exit
; end;
1960 ctl
.keyEventPost(ev
);
1961 result
:= (ev
.eaten
) or (ev
.cancelled
); // stop if event was consumed
1967 if (not enabled
) then exit
;
1968 if (ev
.eaten
) or (ev
.cancelled
) then exit
;
1970 if (mParent
= nil) then
1972 forEachControl(doPreKey
);
1973 if (ev
.eaten
) or (ev
.cancelled
) then exit
;
1975 // focused control should process keyboard first
1976 if (topLevel
.mFocused
<> self
) and isMyChild(topLevel
.mFocused
) and (topLevel
.mFocused
.enabled
) then
1978 // bubble keyboard event
1979 ctl
:= topLevel
.mFocused
;
1980 while (ctl
<> nil) and (ctl
<> self
) do
1983 if (ev
.eaten
) or (ev
.cancelled
) then exit
;
1987 // for top-level controls
1988 if (mParent
= nil) then
1990 if (ev
= 'S-Tab') then
1992 ctl
:= findPrevFocus(mFocused
, true);
1993 if (ctl
<> nil) and (ctl
<> mFocused
) then ctl
.setFocused(true);
1997 if (ev
= 'Tab') then
1999 ctl
:= findNextFocus(mFocused
, true);
2000 if (ctl
<> nil) and (ctl
<> mFocused
) then ctl
.setFocused(true);
2004 if (ev
= 'Enter') or (ev
= 'C-Enter') then
2006 ctl
:= findDefaulControl();
2007 if (ctl
<> nil) then
2014 if (ev
= 'Escape') then
2016 ctl
:= findCancelControl();
2017 if (ctl
<> nil) then
2024 if mEscClose
and (ev
= 'Escape') then
2026 if (not assigned(closeRequestCB
)) or (closeRequestCB(self
)) then
2028 uiRemoveWindow(self
);
2034 if (ev
.eaten
) or (ev
.cancelled
) then exit
;
2035 forEachControl(doPostKey
);
2040 procedure TUIControl
.keyEventPre (var ev
: THKeyEvent
);
2045 procedure TUIControl
.keyEventPost (var ev
: THKeyEvent
);
2050 // ////////////////////////////////////////////////////////////////////////// //
2051 constructor TUITopWindow
.Create (const atitle
: AnsiString);
2058 procedure TUITopWindow
.AfterConstruction ();
2061 mFitToScreen
:= true;
2064 if (mWidth
< mFrameWidth
*2+3*8) then mWidth
:= mFrameWidth
*2+3*8;
2065 if (mHeight
< mFrameHeight
*2) then mHeight
:= mFrameHeight
*2;
2066 if (Length(mTitle
) > 0) then
2068 if (mWidth
< Length(mTitle
)*8+mFrameWidth
*2+3*8) then mWidth
:= Length(mTitle
)*8+mFrameWidth
*2+3*8;
2071 mDragScroll
:= TXMode
.None
;
2072 mDrawShadow
:= true;
2073 mWaitingClose
:= false;
2076 mCtl4Style
:= 'window';
2080 function TUITopWindow
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2082 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) then
2084 mTitle
:= par
.expectIdOrStr(true);
2088 if (strEquCI1251(prname
, 'children')) then
2094 if (strEquCI1251(prname
, 'position')) then
2096 if (par
.eatIdOrStrCI('default')) then mDoCenter
:= false
2097 else if (par
.eatIdOrStrCI('center')) then mDoCenter
:= true
2098 else par
.error('`center` or `default` expected');
2102 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2103 result
:= inherited parseProperty(prname
, par
);
2107 procedure TUITopWindow
.flFitToScreen ();
2111 nsz
:= TLaySize
.Create(trunc(fuiScrWdt
/fuiRenderScale
)-mFrameWidth
*2-6, trunc(fuiScrHgt
/fuiRenderScale
)-mFrameHeight
*2-6);
2112 if (mMaxSize
.w
< 1) then mMaxSize
.w
:= nsz
.w
;
2113 if (mMaxSize
.h
< 1) then mMaxSize
.h
:= nsz
.h
;
2117 procedure TUITopWindow
.centerInScreen ();
2119 if (mWidth
> 0) and (mHeight
> 0) then
2121 mX
:= trunc((fuiScrWdt
/fuiRenderScale
-mWidth
)/2);
2122 mY
:= trunc((fuiScrHgt
/fuiRenderScale
-mHeight
)/2);
2127 procedure TUITopWindow
.drawControl (gx
, gy
: Integer);
2129 fillRect(gx
, gy
, mWidth
, mHeight
, mBackColor
[getColorIndex
]);
2133 procedure TUITopWindow
.drawControlPost (gx
, gy
: Integer);
2136 tx
, hgt
, sbhgt
: Integer;
2138 cidx
:= getColorIndex
;
2139 if (mDragScroll
= TXMode
.Drag
) then
2141 drawRectUI(gx
+4, gy
+4, mWidth
-8, mHeight
-8, mFrameColor
[cidx
]);
2145 drawRectUI(gx
+3, gy
+3, mWidth
-6, mHeight
-6, mFrameColor
[cidx
]);
2146 drawRectUI(gx
+5, gy
+5, mWidth
-10, mHeight
-10, mFrameColor
[cidx
]);
2147 // vertical scroll bar
2148 hgt
:= mHeight
-mFrameHeight
*2;
2149 if (hgt
> 0) and (mFullSize
.h
> hgt
) then
2151 //writeln(mTitle, ': height=', mHeight-mFrameHeight*2, '; fullsize=', mFullSize.toString);
2152 sbhgt
:= mHeight
-mFrameHeight
*2+2;
2153 fillRect(gx
+mWidth
-mFrameWidth
+1, gy
+7, mFrameWidth
-3, sbhgt
, mFrameColor
[cidx
]);
2155 if (hgt
> mFullSize
.h
) then hgt
:= mFullSize
.h
;
2156 hgt
:= sbhgt
*hgt
div mFullSize
.h
;
2159 setScissor(mWidth
-mFrameWidth
+1, 7, mFrameWidth
-3, sbhgt
);
2160 darkenRect(gx
+mWidth
-mFrameWidth
+1, gy
+7+hgt
, mFrameWidth
-3, sbhgt
, 128);
2164 setScissor(mFrameWidth
, 0, 3*8, 8);
2165 fillRect(gx
+mFrameWidth
, gy
, 3*8, 8, mBackColor
[cidx
]);
2166 drawText8(gx
+mFrameWidth
, gy
, '[ ]', mFrameColor
[cidx
]);
2167 if mInClose
then drawText8(gx
+mFrameWidth
+7, gy
, '#', mFrameIconColor
[cidx
])
2168 else drawText8(gx
+mFrameWidth
+7, gy
, '*', mFrameIconColor
[cidx
]);
2171 if (Length(mTitle
) > 0) then
2173 setScissor(mFrameWidth
+3*8, 0, mWidth
-mFrameWidth
*2-3*8, 8);
2174 tx
:= (gx
+3*8)+((mWidth
-3*8)-Length(mTitle
)*8) div 2;
2175 fillRect(tx
-3, gy
, Length(mTitle
)*8+3+2, 8, mBackColor
[cidx
]);
2176 drawText8(tx
, gy
, mTitle
, mFrameTextColor
[cidx
]);
2179 inherited drawControlPost(gx
, gy
);
2183 procedure TUITopWindow
.activated ();
2185 if (mFocused
= nil) or (mFocused
= self
) then
2187 mFocused
:= findFirstFocus();
2189 if (mFocused
<> nil) and (mFocused
<> self
) then mFocused
.activated();
2194 procedure TUITopWindow
.blurred ();
2196 mDragScroll
:= TXMode
.None
;
2197 mWaitingClose
:= false;
2199 if (mFocused
<> nil) and (mFocused
<> self
) then mFocused
.blurred();
2204 procedure TUITopWindow
.keyEvent (var ev
: THKeyEvent
);
2206 inherited keyEvent(ev
);
2207 if (ev
.eaten
) or (ev
.cancelled
) or (not enabled
) {or (not getFocused)} then exit
;
2208 if (ev
= 'M-F3') then
2210 if (not assigned(closeRequestCB
)) or (closeRequestCB(self
)) then
2212 uiRemoveWindow(self
);
2220 procedure TUITopWindow
.mouseEvent (var ev
: THMouseEvent
);
2223 hgt
, sbhgt
: Integer;
2225 if (not enabled
) then exit
;
2226 if (mWidth
< 1) or (mHeight
< 1) then exit
;
2228 if (mDragScroll
= TXMode
.Drag
) then
2230 mX
+= ev
.x
-mDragStartX
;
2231 mY
+= ev
.y
-mDragStartY
;
2232 mDragStartX
:= ev
.x
;
2233 mDragStartY
:= ev
.y
;
2234 if (ev
.release
) and (ev
.but
= ev
.Left
) then mDragScroll
:= TXMode
.None
;
2239 if (mDragScroll
= TXMode
.Scroll
) then
2241 // check for vertical scrollbar
2249 sbhgt
:= mHeight
-mFrameHeight
*2+2;
2250 hgt
:= mHeight
-mFrameHeight
*2;
2251 if (hgt
> 0) and (mFullSize
.h
> hgt
) then
2253 hgt
:= (mFullSize
.h
*(ly
-7) div (sbhgt
-1))-(mHeight
-mFrameHeight
*2);
2254 mScrollY
:= nmax(0, hgt
);
2255 hgt
:= mHeight
-mFrameHeight
*2;
2256 if (mScrollY
+hgt
> mFullSize
.h
) then mScrollY
:= nmax(0, mFullSize
.h
-hgt
);
2259 if (ev
.release
) and (ev
.but
= ev
.Left
) then mDragScroll
:= TXMode
.None
;
2264 if toLocal(ev
.x
, ev
.y
, lx
, ly
) then
2271 if (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+3*8) then
2273 //uiRemoveWindow(self);
2274 mWaitingClose
:= true;
2279 mDragScroll
:= TXMode
.Drag
;
2280 mDragStartX
:= ev
.x
;
2281 mDragStartY
:= ev
.y
;
2286 // check for vertical scrollbar
2287 if (lx
>= mWidth
-mFrameWidth
+1) and (ly
>= 7) and (ly
< mHeight
-mFrameHeight
+1) then
2289 sbhgt
:= mHeight
-mFrameHeight
*2+2;
2290 hgt
:= mHeight
-mFrameHeight
*2;
2291 if (hgt
> 0) and (mFullSize
.h
> hgt
) then
2293 hgt
:= (mFullSize
.h
*(ly
-7) div (sbhgt
-1))-(mHeight
-mFrameHeight
*2);
2294 mScrollY
:= nmax(0, hgt
);
2296 mDragScroll
:= TXMode
.Scroll
;
2302 if (lx
< mFrameWidth
) or (lx
>= mWidth
-mFrameWidth
) or (ly
>= mHeight
-mFrameHeight
) then
2305 mDragScroll
:= TXMode
.Drag
;
2306 mDragStartX
:= ev
.x
;
2307 mDragStartY
:= ev
.y
;
2313 if (ev
.release
) then
2315 if mWaitingClose
then
2317 if (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+3*8) then
2319 if (not assigned(closeRequestCB
)) or (closeRequestCB(self
)) then
2321 uiRemoveWindow(self
);
2324 mWaitingClose
:= false;
2333 if mWaitingClose
then
2335 mInClose
:= (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+3*8);
2341 inherited mouseEvent(ev
);
2346 if (not ev
.motion
) and (mWaitingClose
) then begin ev
.eat(); mWaitingClose
:= false; exit
; end;
2351 // ////////////////////////////////////////////////////////////////////////// //
2352 constructor TUIBox
.Create (ahoriz
: Boolean);
2359 procedure TUIBox
.AfterConstruction ();
2363 mHAlign
:= -1; // left
2364 mCtl4Style
:= 'box';
2368 procedure TUIBox
.setCaption (const acap
: AnsiString);
2371 mDefSize
:= TLaySize
.Create(Length(mCaption
)*8+3, 8);
2375 procedure TUIBox
.setHasFrame (v
: Boolean);
2378 if (mHasFrame
) then begin mFrameWidth
:= 8; mFrameHeight
:= 8; end else begin mFrameWidth
:= 0; mFrameHeight
:= 0; end;
2379 if (mHasFrame
) then mNoPad
:= true;
2383 function TUIBox
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2385 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2386 if (strEquCI1251(prname
, 'padding')) then
2388 if (mHoriz
) then mPadding
:= parseHPadding(par
, 0) else mPadding
:= parseVPadding(par
, 0);
2392 if (strEquCI1251(prname
, 'frame')) then
2394 setHasFrame(parseBool(par
));
2398 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) then
2400 setCaption(par
.expectIdOrStr(true));
2404 if (strEquCI1251(prname
, 'textalign')) or (strEquCI1251(prname
, 'text-align')) then
2406 mHAlign
:= parseHAlign(par
);
2410 if (strEquCI1251(prname
, 'children')) then
2416 result
:= inherited parseProperty(prname
, par
);
2420 procedure TUIBox
.drawControl (gx
, gy
: Integer);
2425 cidx
:= getColorIndex
;
2426 fillRect(gx
, gy
, mWidth
, mHeight
, mBackColor
[cidx
]);
2430 drawRectUI(gx
+3, gy
+3, mWidth
-6, mHeight
-6, mFrameColor
[cidx
]);
2433 if (Length(mCaption
) > 0) then
2435 if (mHAlign
< 0) then xpos
:= 3
2436 else if (mHAlign
> 0) then xpos
:= mWidth
-mFrameWidth
*2-Length(mCaption
)*8
2437 else xpos
:= (mWidth
-mFrameWidth
*2-Length(mCaption
)*8) div 2;
2438 xpos
+= gx
+mFrameWidth
;
2440 setScissor(mFrameWidth
+1, 0, mWidth
-mFrameWidth
-2, 8);
2441 if mHasFrame
then fillRect(xpos
-3, gy
, Length(mCaption
)*8+4, 8, mBackColor
[cidx
]);
2442 drawText8(xpos
, gy
, mCaption
, mFrameTextColor
[cidx
]);
2447 procedure TUIBox
.mouseEvent (var ev
: THMouseEvent
);
2451 inherited mouseEvent(ev
);
2452 if (not ev
.eaten
) and (not ev
.cancelled
) and (enabled
) and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
2459 procedure TUIBox
.keyEvent (var ev
: THKeyEvent
);
2462 cur
, ctl
: TUIControl
;
2464 inherited keyEvent(ev
);
2465 if (ev
.eaten
) or (ev
.cancelled
) or (not ev
.press
) or (not enabled
) or (not getActive
) then exit
;
2466 if (Length(mChildren
) = 0) then exit
;
2467 if (mHoriz
) and (ev
= 'Left') then dir
:= -1
2468 else if (mHoriz
) and (ev
= 'Right') then dir
:= 1
2469 else if (not mHoriz
) and (ev
= 'Up') then dir
:= -1
2470 else if (not mHoriz
) and (ev
= 'Down') then dir
:= 1;
2471 if (dir
= 0) then exit
;
2473 cur
:= topLevel
.mFocused
;
2474 while (cur
<> nil) and (cur
.mParent
<> self
) do cur
:= cur
.mParent
;
2475 //if (cur = nil) then writeln('CUR: nil') else writeln('CUR: ', cur.className, '#', cur.id);
2476 if (dir
< 0) then ctl
:= findPrevFocus(cur
, true) else ctl
:= findNextFocus(cur
, true);
2477 //if (ctl = nil) then writeln('CTL: nil') else writeln('CTL: ', ctl.className, '#', ctl.id);
2478 if (ctl
<> nil) and (ctl
<> self
) then
2480 ctl
.focused
:= true;
2485 // ////////////////////////////////////////////////////////////////////////// //
2486 constructor TUIHBox
.Create ();
2491 procedure TUIHBox
.AfterConstruction ();
2498 // ////////////////////////////////////////////////////////////////////////// //
2499 constructor TUIVBox
.Create ();
2504 procedure TUIVBox
.AfterConstruction ();
2511 // ////////////////////////////////////////////////////////////////////////// //
2512 procedure TUISpan
.AfterConstruction ();
2518 mCtl4Style
:= 'span';
2522 function TUISpan
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2524 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2525 result
:= inherited parseProperty(prname
, par
);
2529 procedure TUISpan
.drawControl (gx
, gy
: Integer);
2534 // ////////////////////////////////////////////////////////////////////// //
2535 procedure TUILine
.AfterConstruction ();
2541 mCtl4Style
:= 'line';
2545 function TUILine
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2547 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2548 result
:= inherited parseProperty(prname
, par
);
2552 procedure TUILine
.drawControl (gx
, gy
: Integer);
2556 cidx
:= getColorIndex
;
2559 drawHLine(gx
, gy
+(mHeight
div 2), mWidth
, mTextColor
[cidx
]);
2563 drawVLine(gx
+(mWidth
div 2), gy
, mHeight
, mTextColor
[cidx
]);
2568 // ////////////////////////////////////////////////////////////////////////// //
2569 procedure TUIHLine
.AfterConstruction ();
2577 // ////////////////////////////////////////////////////////////////////////// //
2578 procedure TUIVLine
.AfterConstruction ();
2586 // ////////////////////////////////////////////////////////////////////////// //
2587 procedure TUIStaticText
.AfterConstruction ();
2593 mHoriz
:= true; // nobody cares
2597 mCtl4Style
:= 'static';
2601 procedure TUIStaticText
.setText (const atext
: AnsiString);
2604 mDefSize
:= TLaySize
.Create(Length(mText
)*8, 8);
2608 function TUIStaticText
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2610 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) or (strEquCI1251(prname
, 'text')) then
2612 setText(par
.expectIdOrStr(true));
2616 if (strEquCI1251(prname
, 'textalign')) or (strEquCI1251(prname
, 'text-align')) then
2618 parseTextAlign(par
, mHAlign
, mVAlign
);
2622 if (strEquCI1251(prname
, 'header')) then
2628 if (strEquCI1251(prname
, 'line')) then
2634 result
:= inherited parseProperty(prname
, par
);
2638 procedure TUIStaticText
.drawControl (gx
, gy
: Integer);
2640 xpos
, ypos
: Integer;
2644 cidx
:= getColorIndex
;
2645 fillRect(gx
, gy
, mWidth
, mHeight
, mBackColor
[cidx
]);
2647 if (mHAlign
< 0) then xpos
:= 0
2648 else if (mHAlign
> 0) then xpos
:= mWidth
-Length(mText
)*8
2649 else xpos
:= (mWidth
-Length(mText
)*8) div 2;
2651 if (Length(mText
) > 0) then
2653 if (mHeader
) then clr
:= mFrameTextColor
[cidx
] else clr
:= mTextColor
[cidx
];
2655 if (mVAlign
< 0) then ypos
:= 0
2656 else if (mVAlign
> 0) then ypos
:= mHeight
-8
2657 else ypos
:= (mHeight
-8) div 2;
2659 drawText8(gx
+xpos
, gy
+ypos
, mText
, clr
);
2664 if (mHeader
) then clr
:= mFrameColor
[cidx
] else clr
:= mTextColor
[cidx
];
2666 if (mVAlign
< 0) then ypos
:= 0
2667 else if (mVAlign
> 0) then ypos
:= mHeight
-1
2668 else ypos
:= (mHeight
div 2);
2671 if (Length(mText
) = 0) then
2673 drawHLine(gx
, ypos
, mWidth
, clr
);
2677 drawHLine(gx
, ypos
, xpos
-1, clr
);
2678 drawHLine(gx
+xpos
+Length(mText
)*8, ypos
, mWidth
, clr
);
2684 // ////////////////////////////////////////////////////////////////////////// //
2685 procedure TUITextLabel
.AfterConstruction ();
2691 mDefSize
:= TLaySize
.Create(Length(mText
)*8, 8);
2692 mCtl4Style
:= 'label';
2697 procedure TUITextLabel
.cacheStyle (root
: TUIStyle
);
2699 inherited cacheStyle(root
);
2701 mHotColor
[ClrIdxActive
] := root
.get('hot-color', 'active', mCtl4Style
).asRGBADef(TGxRGBA
.Create(0, 128, 0));
2703 mHotColor
[ClrIdxDisabled
] := root
.get('hot-color', 'disabled', mCtl4Style
).asRGBADef(TGxRGBA
.Create(0, 64, 0));
2705 mHotColor
[ClrIdxInactive
] := root
.get('hot-color', 'inactive', mCtl4Style
).asRGBADef(TGxRGBA
.Create(0, 64, 0));
2709 procedure TUITextLabel
.setText (const s
: AnsiString);
2717 while (f
<= Length(s
)) do
2719 if (s
[f
] = '\\') then
2722 if (f
<= Length(s
)) then mText
+= s
[f
];
2725 else if (s
[f
] = '~') then
2728 if (f
<= Length(s
)) then
2730 if (mHotChar
= #0) then
2733 mHotOfs
:= Length(mText
)*8;
2745 mDefSize
:= TLaySize
.Create(Length(mText
)*8, 8);
2749 function TUITextLabel
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2751 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) or (strEquCI1251(prname
, 'text')) then
2753 setText(par
.expectIdOrStr(true));
2757 if (strEquCI1251(prname
, 'link')) then
2759 mLinkId
:= par
.expectIdOrStr(true);
2763 if (strEquCI1251(prname
, 'textalign')) or (strEquCI1251(prname
, 'text-align')) then
2765 parseTextAlign(par
, mHAlign
, mVAlign
);
2769 result
:= inherited parseProperty(prname
, par
);
2773 procedure TUITextLabel
.drawControl (gx
, gy
: Integer);
2775 xpos
, ypos
: Integer;
2778 cidx
:= getColorIndex
;
2779 fillRect(gx
, gy
, mWidth
, mHeight
, mBackColor
[cidx
]);
2780 if (Length(mText
) > 0) then
2782 if (mHAlign
< 0) then xpos
:= 0
2783 else if (mHAlign
> 0) then xpos
:= mWidth
-Length(mText
)*8
2784 else xpos
:= (mWidth
-Length(mText
)*8) div 2;
2786 if (mVAlign
< 0) then ypos
:= 0
2787 else if (mVAlign
> 0) then ypos
:= mHeight
-8
2788 else ypos
:= (mHeight
-8) div 2;
2790 drawText8(gx
+xpos
, gy
+ypos
, mText
, mTextColor
[cidx
]);
2792 if (Length(mLinkId
) > 0) and (mHotChar
<> #0) and (mHotChar
<> ' ') then
2794 drawText8(gx
+xpos
+8+mHotOfs
, gy
+ypos
, mHotChar
, mHotColor
[cidx
]);
2800 procedure TUITextLabel
.mouseEvent (var ev
: THMouseEvent
);
2804 inherited mouseEvent(ev
);
2805 if (not ev
.eaten
) and (not ev
.cancelled
) and (enabled
) and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
2812 procedure TUITextLabel
.doAction ();
2816 if (assigned(actionCB
)) then
2822 ctl
:= topLevel
[mLinkId
];
2823 if (ctl
<> nil) then
2825 if (ctl
.canFocus
) then ctl
.focused
:= true;
2831 procedure TUITextLabel
.keyEventPost (var ev
: THKeyEvent
);
2833 if (not enabled
) then exit
;
2834 if (mHotChar
= #0) then exit
;
2835 if (ev
.eaten
) or (ev
.cancelled
) or (not ev
.press
) then exit
;
2836 if (ev
.kstate
<> ev
.ModAlt
) then exit
;
2837 if (not ev
.isHot(mHotChar
)) then exit
;
2839 if (canFocus
) then focused
:= true;
2844 // ////////////////////////////////////////////////////////////////////////// //
2845 procedure TUIButton
.AfterConstruction ();
2851 mDefSize
:= TLaySize
.Create(Length(mText
)*8+8, 10);
2852 mCtl4Style
:= 'button';
2856 procedure TUIButton
.setText (const s
: AnsiString);
2858 inherited setText(s
);
2859 mDefSize
:= TLaySize
.Create(Length(mText
)*8+8*2, 10);
2863 procedure TUIButton
.drawControl (gx
, gy
: Integer);
2865 xpos
, ypos
: Integer;
2868 cidx
:= getColorIndex
;
2870 fillRect(gx
+1, gy
, mWidth
-2, mHeight
, mBackColor
[cidx
]);
2871 fillRect(gx
, gy
+1, 1, mHeight
-2, mBackColor
[cidx
]);
2872 fillRect(gx
+mWidth
-1, gy
+1, 1, mHeight
-2, mBackColor
[cidx
]);
2874 if (Length(mText
) > 0) then
2876 if (mHAlign
< 0) then xpos
:= 0
2877 else if (mHAlign
> 0) then xpos
:= mWidth
-Length(mText
)*8
2878 else xpos
:= (mWidth
-Length(mText
)*8) div 2;
2880 if (mVAlign
< 0) then ypos
:= 0
2881 else if (mVAlign
> 0) then ypos
:= mHeight
-8
2882 else ypos
:= (mHeight
-8) div 2;
2884 setScissor(8, 0, mWidth
-16, mHeight
);
2885 drawText8(gx
+xpos
+8, gy
+ypos
, mText
, mTextColor
[cidx
]);
2887 if (mHotChar
<> #0) and (mHotChar
<> ' ') then drawText8(gx
+xpos
+8+mHotOfs
, gy
+ypos
, mHotChar
, mHotColor
[cidx
]);
2892 procedure TUIButton
.mouseEvent (var ev
: THMouseEvent
);
2896 inherited mouseEvent(ev
);
2897 if (uiGrabCtl
= self
) then
2900 if (ev
= '-lmb') and focused
and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
2906 if (ev
.eaten
) or (ev
.cancelled
) or (not enabled
) or not focused
then exit
;
2911 procedure TUIButton
.keyEvent (var ev
: THKeyEvent
);
2913 inherited keyEvent(ev
);
2914 if (not ev
.eaten
) and (not ev
.cancelled
) and (enabled
) then
2916 if (ev
= 'Enter') or (ev
= 'Space') then
2926 // ////////////////////////////////////////////////////////////////////////// //
2927 procedure TUISwitchBox
.AfterConstruction ();
2933 mDefSize
:= TLaySize
.Create(Length(mText
)*8+8*3, 8);
2934 mCtl4Style
:= 'switchbox';
2936 mBoolVar
:= @mChecked
;
2940 procedure TUISwitchBox
.cacheStyle (root
: TUIStyle
);
2942 inherited cacheStyle(root
);
2944 mSwitchColor
[ClrIdxActive
] := root
.get('switch-color', 'active', mCtl4Style
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
2946 mSwitchColor
[ClrIdxDisabled
] := root
.get('switch-color', 'disabled', mCtl4Style
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
2948 mSwitchColor
[ClrIdxInactive
] := root
.get('switch-color', 'inactive', mCtl4Style
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
2952 procedure TUISwitchBox
.setText (const s
: AnsiString);
2954 inherited setText(s
);
2955 mDefSize
:= TLaySize
.Create(Length(mText
)*8+8*3, 8);
2959 function TUISwitchBox
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2961 if (strEquCI1251(prname
, 'checked')) then
2967 result
:= inherited parseProperty(prname
, par
);
2971 function TUISwitchBox
.getChecked (): Boolean;
2973 if (mBoolVar
<> nil) then result
:= mBoolVar
^ else result
:= false;
2977 procedure TUISwitchBox
.setVar (pvar
: PBoolean);
2979 if (pvar
= nil) then pvar
:= @mChecked
;
2980 if (pvar
<> mBoolVar
) then
2983 setChecked(mBoolVar
^);
2988 procedure TUISwitchBox
.drawControl (gx
, gy
: Integer);
2990 xpos
, ypos
: Integer;
2993 cidx
:= getColorIndex
;
2995 if (mHAlign
< 0) then xpos
:= 0
2996 else if (mHAlign
> 0) then xpos
:= mWidth
-(Length(mText
)+4)*8
2997 else xpos
:= (mWidth
-(Length(mText
)+4)*8) div 2;
2999 if (mVAlign
< 0) then ypos
:= 0
3000 else if (mVAlign
> 0) then ypos
:= mHeight
-8
3001 else ypos
:= (mHeight
-8) div 2;
3004 fillRect(gx
, gy
, mWidth
, mHeight
, mBackColor
[cidx
]);
3008 if (Length(mCheckedStr
) <> 3) or (mCheckedStr
[2] <> '*') then
3010 drawText8(gx
+xpos
, gy
+ypos
, mCheckedStr
, mSwitchColor
[cidx
]);
3014 drawText8(gx
+xpos
, gy
+ypos
, mCheckedStr
[1], mSwitchColor
[cidx
]);
3015 drawText8(gx
+xpos
+2*8, gy
+ypos
, mCheckedStr
[3], mSwitchColor
[cidx
]);
3016 drawText8(gx
+xpos
+7, gy
+ypos
, '*', mSwitchColor
[cidx
]);
3021 drawText8(gx
+xpos
, gy
+ypos
, mUncheckedStr
, mSwitchColor
[cidx
]);
3024 drawText8(gx
+xpos
+8*3, gy
+ypos
, mText
, mTextColor
[cidx
]);
3026 if (mHotChar
<> #0) and (mHotChar
<> ' ') then drawText8(gx
+xpos
+8*3+mHotOfs
, gy
+ypos
, mHotChar
, mHotColor
[cidx
]);
3030 procedure TUISwitchBox
.mouseEvent (var ev
: THMouseEvent
);
3034 inherited mouseEvent(ev
);
3035 if (uiGrabCtl
= self
) then
3038 if (ev
= '-lmb') and focused
and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
3044 if (ev
.eaten
) or (ev
.cancelled
) or (not enabled
) or not focused
then exit
;
3049 procedure TUISwitchBox
.keyEvent (var ev
: THKeyEvent
);
3051 inherited keyEvent(ev
);
3052 if (not ev
.eaten
) and (not ev
.cancelled
) and (enabled
) then
3054 if (ev
= 'Space') then
3064 // ////////////////////////////////////////////////////////////////////////// //
3065 procedure TUICheckBox
.AfterConstruction ();
3069 mBoolVar
:= @mChecked
;
3070 mCheckedStr
:= '[x]';
3071 mUncheckedStr
:= '[ ]';
3075 procedure TUICheckBox
.setChecked (v
: Boolean);
3081 procedure TUICheckBox
.doAction ();
3083 if (assigned(actionCB
)) then
3089 setChecked(not getChecked
);
3094 // ////////////////////////////////////////////////////////////////////////// //
3095 procedure TUIRadioBox
.AfterConstruction ();
3099 mBoolVar
:= @mChecked
;
3100 mCheckedStr
:= '(*)';
3101 mUncheckedStr
:= '( )';
3106 function TUIRadioBox
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
3108 if (strEquCI1251(prname
, 'group')) then
3110 mRadioGroup
:= par
.expectIdOrStr(true);
3111 if (getChecked
) then setChecked(true);
3115 if (strEquCI1251(prname
, 'checked')) then
3121 result
:= inherited parseProperty(prname
, par
);
3125 procedure TUIRadioBox
.setChecked (v
: Boolean);
3127 function resetGroup (ctl
: TUIControl
): Boolean;
3130 if (ctl
<> self
) and (ctl
is TUIRadioBox
) and (TUIRadioBox(ctl
).mRadioGroup
= mRadioGroup
) then
3132 TUIRadioBox(ctl
).mBoolVar
^ := false;
3138 if v
then topLevel
.forEachControl(resetGroup
);
3142 procedure TUIRadioBox
.doAction ();
3144 if (assigned(actionCB
)) then
3155 // ////////////////////////////////////////////////////////////////////////// //
3157 registerCtlClass(TUIHBox
, 'hbox');
3158 registerCtlClass(TUIVBox
, 'vbox');
3159 registerCtlClass(TUISpan
, 'span');
3160 registerCtlClass(TUIHLine
, 'hline');
3161 registerCtlClass(TUIVLine
, 'vline');
3162 registerCtlClass(TUITextLabel
, 'label');
3163 registerCtlClass(TUIStaticText
, 'static');
3164 registerCtlClass(TUIButton
, 'button');
3165 registerCtlClass(TUICheckBox
, 'checkbox');
3166 registerCtlClass(TUIRadioBox
, 'radiobox');