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 mStyleLoaded
: Boolean;
68 mCtl4Style
: AnsiString;
69 mBackColor
: array[0..ClrIdxMax
] of TGxRGBA
;
70 mTextColor
: array[0..ClrIdxMax
] of TGxRGBA
;
71 mFrameColor
: array[0..ClrIdxMax
] of TGxRGBA
;
72 mFrameTextColor
: array[0..ClrIdxMax
] of TGxRGBA
;
73 mFrameIconColor
: array[0..ClrIdxMax
] of TGxRGBA
;
74 mSBarFullColor
: array[0..ClrIdxMax
] of TGxRGBA
;
75 mSBarEmptyColor
: array[0..ClrIdxMax
] of TGxRGBA
;
76 mDarken
: array[0..ClrIdxMax
] of Integer; // >255: none
79 procedure updateStyle (); virtual;
80 procedure cacheStyle (root
: TUIStyle
); virtual;
81 function getColorIndex (): Integer; inline;
84 function getEnabled (): Boolean;
85 procedure setEnabled (v
: Boolean); inline;
87 function getFocused (): Boolean; inline;
88 procedure setFocused (v
: Boolean); inline;
90 function getActive (): Boolean; inline;
92 function getCanFocus (): Boolean; inline;
94 function isMyChild (ctl
: TUIControl
): Boolean;
96 function findFirstFocus (): TUIControl
;
97 function findLastFocus (): TUIControl
;
99 function findNextFocus (cur
: TUIControl
; wrap
: Boolean): TUIControl
;
100 function findPrevFocus (cur
: TUIControl
; wrap
: Boolean): TUIControl
;
102 function findCancelControl (): TUIControl
;
103 function findDefaulControl (): TUIControl
;
105 function findControlById (const aid
: AnsiString): TUIControl
;
107 procedure activated (); virtual;
108 procedure blurred (); virtual;
110 procedure calcFullClientSize ();
112 procedure drawFrame (gx
, gy
, resx
, thalign
: Integer; const text: AnsiString; dbl
: Boolean);
115 var savedClip
: TGxRect
; // valid only in `draw*()` calls
116 //WARNING! do not call scissor functions outside `.draw*()` API!
117 // set scissor to this rect (in local coords)
118 procedure setScissor (lx
, ly
, lw
, lh
: Integer); // valid only in `draw*()` calls
122 closeRequestCB
: TCloseRequestCB
;
125 mDefSize
: TLaySize
; // default size
126 mMaxSize
: TLaySize
; // maximum size
133 mLayDefSize
: TLaySize
;
134 mLayMaxSize
: TLaySize
;
140 // layouter interface
141 function getDefSize (): TLaySize
; inline; // default size; <0: use max size
142 //procedure setDefSize (const sz: TLaySize); inline; // default size; <0: use max size
143 function getMargins (): TLayMargins
; inline;
144 function getPadding (): TLaySize
; inline; // children padding (each non-first child will get this on left/top)
145 function getMaxSize (): TLaySize
; inline; // max size; <0: set to some huge value
146 //procedure setMaxSize (const sz: TLaySize); inline; // max size; <0: set to some huge value
147 function getFlex (): Integer; inline; // <=0: not flexible
148 function isHorizBox (): Boolean; inline; // horizontal layout for children?
149 function noPad (): Boolean; inline; // ignore padding in box direction for this control
150 function getAlign (): Integer; inline; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
151 function getExpand (): 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 function getVGroup (): AnsiString; inline; // empty: not grouped
155 procedure setActualSizePos (constref apos
: TLayPos
; constref asize
: TLaySize
); inline;
157 procedure layPrepare (); virtual; // called before registering control in layouter
160 property flex
: Integer read mFlex write mFlex
;
161 property flDefaultSize
: TLaySize read mDefSize write mDefSize
;
162 property flMaxSize
: TLaySize read mMaxSize write mMaxSize
;
163 property flPadding
: TLaySize read mPadding write mPadding
;
164 property flHoriz
: Boolean read mHoriz write mHoriz
;
165 property flAlign
: Integer read mAlign write mAlign
;
166 property flExpand
: Boolean read mExpand write mExpand
;
167 property flHGroup
: AnsiString read mHGroup write mHGroup
;
168 property flVGroup
: AnsiString read mVGroup write mVGroup
;
169 property flNoPad
: Boolean read mNoPad write mNoPad
;
170 property fullSize
: TLaySize read mFullSize
;
173 function parsePos (par
: TTextParser
): TLayPos
;
174 function parseSize (par
: TTextParser
): TLaySize
;
175 function parsePadding (par
: TTextParser
): TLaySize
;
176 function parseHPadding (par
: TTextParser
; def
: Integer): TLaySize
;
177 function parseVPadding (par
: TTextParser
; def
: Integer): TLaySize
;
178 function parseBool (par
: TTextParser
): Boolean;
179 function parseAnyAlign (par
: TTextParser
): Integer;
180 function parseHAlign (par
: TTextParser
): Integer;
181 function parseVAlign (par
: TTextParser
): Integer;
182 function parseOrientation (const prname
: AnsiString; par
: TTextParser
): Boolean;
183 procedure parseTextAlign (par
: TTextParser
; var h
, v
: Integer);
184 procedure parseChildren (par
: TTextParser
); // par should be on '{'; final '}' is eaten
187 // par is on property data
188 // there may be more data in text stream, don't eat it!
189 // return `true` if property name is valid and value was parsed
190 // return `false` if property name is invalid; don't advance parser in this case
191 // throw on property data errors
192 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; virtual;
194 // par should be on '{'; final '}' is eaten
195 procedure parseProperties (par
: TTextParser
);
198 constructor Create ();
199 destructor Destroy (); override;
201 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
203 // `sx` and `sy` are screen coordinates
204 procedure drawControl (gx
, gy
: Integer); virtual;
206 // called after all children drawn
207 procedure drawControlPost (gx
, gy
: Integer); virtual;
209 procedure draw (); virtual;
211 function topLevel (): TUIControl
; inline;
213 // returns `true` if global coords are inside this control
214 function toLocal (var x
, y
: Integer): Boolean;
215 function toLocal (gx
, gy
: Integer; out x
, y
: Integer): Boolean; inline;
216 procedure toGlobal (var x
, y
: Integer);
217 procedure toGlobal (lx
, ly
: Integer; out x
, y
: Integer); inline;
219 procedure getDrawRect (out gx
, gy
, wdt
, hgt
: Integer);
221 // x and y are global coords
222 function controlAtXY (x
, y
: Integer; allowDisabled
: Boolean=false): TUIControl
;
224 function parentScrollX (): Integer; inline;
225 function parentScrollY (): Integer; inline;
227 procedure makeVisibleInParent ();
229 procedure doAction (); virtual; // so user controls can override it
231 procedure mouseEvent (var ev
: THMouseEvent
); virtual; // returns `true` if event was eaten
232 procedure keyEvent (var ev
: THKeyEvent
); virtual; // returns `true` if event was eaten
233 procedure keyEventPre (var ev
: THKeyEvent
); virtual; // will be called before dispatching the event
234 procedure keyEventPost (var ev
: THKeyEvent
); virtual; // will be called after if nobody processed the event
236 function prevSibling (): TUIControl
;
237 function nextSibling (): TUIControl
;
238 function firstChild (): TUIControl
; inline;
239 function lastChild (): TUIControl
; inline;
241 procedure appendChild (ctl
: TUIControl
); virtual;
243 function setActionCBFor (const aid
: AnsiString; cb
: TActionCB
): TActionCB
; // returns previous cb
245 function forEachChildren (cb
: TCtlEnumCB
): TUIControl
; // doesn't recurse
246 function forEachControl (cb
: TCtlEnumCB
; includeSelf
: Boolean=true): TUIControl
;
248 procedure close (); // this closes *top-level* control
251 property id
: AnsiString read mId write mId
;
252 property styleId
: AnsiString read mStyleId
;
253 property scrollX
: Integer read mScrollX write mScrollX
;
254 property scrollY
: Integer read mScrollY write mScrollY
;
255 property x0
: Integer read mX write mX
;
256 property y0
: Integer read mY write mY
;
257 property width
: Integer read mWidth write mWidth
;
258 property height
: Integer read mHeight write mHeight
;
259 property enabled
: Boolean read getEnabled write setEnabled
;
260 property parent
: TUIControl read mParent
;
261 property focused
: Boolean read getFocused write setFocused
;
262 property active
: Boolean read getActive
;
263 property escClose
: Boolean read mEscClose write mEscClose
;
264 property cancel
: Boolean read mCancel write mCancel
;
265 property defctl
: Boolean read mDefault write mDefault
;
266 property canFocus
: Boolean read getCanFocus write mCanFocus
;
267 property ctlById
[const aid
: AnsiString]: TUIControl read findControlById
; default
;
271 TUITopWindow
= class(TUIControl
)
273 type TXMode
= (None
, Drag
, VScroll
, HScroll
);
278 mDragStartX
, mDragStartY
: Integer;
279 mWaitingClose
: Boolean;
281 mFreeOnClose
: Boolean; // default: false
282 mDoCenter
: Boolean; // after layouting
283 mFitToScreen
: Boolean;
286 procedure activated (); override;
287 procedure blurred (); override;
290 closeCB
: TActionCB
; // called after window was removed from ui window list
293 constructor Create (const atitle
: AnsiString);
295 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
297 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
299 procedure flFitToScreen (); // call this before layouting
301 procedure centerInScreen ();
303 // `sx` and `sy` are screen coordinates
304 procedure drawControl (gx
, gy
: Integer); override;
305 procedure drawControlPost (gx
, gy
: Integer); override;
307 procedure keyEvent (var ev
: THKeyEvent
); override; // returns `true` if event was eaten
308 procedure mouseEvent (var ev
: THMouseEvent
); override; // returns `true` if event was eaten
311 property freeOnClose
: Boolean read mFreeOnClose write mFreeOnClose
;
312 property fitToScreen
: Boolean read mFitToScreen write mFitToScreen
;
315 // ////////////////////////////////////////////////////////////////////// //
316 TUIBox
= class(TUIControl
)
319 mCaption
: AnsiString;
320 mHAlign
: Integer; // -1: left; 0: center; 1: right; default: left
323 procedure setCaption (const acap
: AnsiString);
324 procedure setHasFrame (v
: Boolean);
327 constructor Create (ahoriz
: Boolean);
329 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
331 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
333 procedure drawControl (gx
, gy
: Integer); override;
335 procedure mouseEvent (var ev
: THMouseEvent
); override;
336 procedure keyEvent (var ev
: THKeyEvent
); override;
339 property caption
: AnsiString read mCaption write setCaption
;
340 property hasFrame
: Boolean read mHasFrame write setHasFrame
;
341 property captionAlign
: Integer read mHAlign write mHAlign
;
344 TUIHBox
= class(TUIBox
)
346 constructor Create ();
348 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
351 TUIVBox
= class(TUIBox
)
353 constructor Create ();
355 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
358 // ////////////////////////////////////////////////////////////////////// //
359 TUISpan
= class(TUIControl
)
361 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
363 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
365 procedure drawControl (gx
, gy
: Integer); override;
368 // ////////////////////////////////////////////////////////////////////// //
369 TUILine
= class(TUIControl
)
371 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
373 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
375 procedure layPrepare (); override; // called before registering control in layouter
377 procedure drawControl (gx
, gy
: Integer); override;
380 // ////////////////////////////////////////////////////////////////////// //
381 TUIStaticText
= class(TUIControl
)
384 mHAlign
: Integer; // -1: left; 0: center; 1: right; default: left
385 mVAlign
: Integer; // -1: top; 0: center; 1: bottom; default: center
386 mHeader
: Boolean; // true: draw with frame text color
387 mLine
: Boolean; // true: draw horizontal line
390 procedure setText (const atext
: AnsiString);
393 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
395 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
397 procedure drawControl (gx
, gy
: Integer); override;
400 property text: AnsiString read mText write setText
;
401 property halign
: Integer read mHAlign write mHAlign
;
402 property valign
: Integer read mVAlign write mVAlign
;
403 property header
: Boolean read mHeader write mHeader
;
404 property line
: Boolean read mLine write mLine
;
407 // ////////////////////////////////////////////////////////////////////// //
408 TUITextLabel
= class(TUIControl
)
411 mHAlign
: Integer; // -1: left; 0: center; 1: right; default: left
412 mVAlign
: Integer; // -1: top; 0: center; 1: bottom; default: center
414 mHotOfs
: Integer; // from text start, in pixels
415 mHotColor
: array[0..ClrIdxMax
] of TGxRGBA
;
416 mLinkId
: AnsiString; // linked control
419 procedure cacheStyle (root
: TUIStyle
); override;
421 procedure setText (const s
: AnsiString); virtual;
424 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
426 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
428 procedure doAction (); override;
430 procedure drawControl (gx
, gy
: Integer); override;
432 procedure mouseEvent (var ev
: THMouseEvent
); override;
433 procedure keyEventPost (var ev
: THKeyEvent
); override;
436 property text: AnsiString read mText write setText
;
437 property halign
: Integer read mHAlign write mHAlign
;
438 property valign
: Integer read mVAlign write mVAlign
;
441 // ////////////////////////////////////////////////////////////////////// //
442 TUIButton
= class(TUITextLabel
)
444 mSkipLayPrepare
: Boolean;
445 mShadowSize
: Integer;
446 mAddMarkers
: Boolean;
447 mHideMarkers
: Boolean;
451 procedure setText (const s
: AnsiString); override;
453 procedure cacheStyle (root
: TUIStyle
); override;
455 procedure blurred (); override;
458 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
460 procedure layPrepare (); override; // called before registering control in layouter
462 procedure drawControl (gx
, gy
: Integer); override;
464 procedure mouseEvent (var ev
: THMouseEvent
); override;
465 procedure keyEvent (var ev
: THKeyEvent
); override;
468 // ////////////////////////////////////////////////////////////////////// //
469 TUIButtonRound
= class(TUIButton
)
471 procedure setText (const s
: AnsiString); override;
474 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
476 procedure layPrepare (); override; // called before registering control in layouter
478 procedure drawControl (gx
, gy
: Integer); override;
481 // ////////////////////////////////////////////////////////////////////// //
482 TUISwitchBox
= class(TUITextLabel
)
486 mIcon
: TGxContext
.TMarkIcon
;
487 mSwitchColor
: array[0..ClrIdxMax
] of TGxRGBA
;
490 procedure cacheStyle (root
: TUIStyle
); override;
492 procedure setText (const s
: AnsiString); override;
494 function getChecked (): Boolean; virtual;
495 procedure setChecked (v
: Boolean); virtual; abstract;
498 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
500 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
502 procedure drawControl (gx
, gy
: Integer); override;
504 procedure mouseEvent (var ev
: THMouseEvent
); override;
505 procedure keyEvent (var ev
: THKeyEvent
); override;
507 procedure setVar (pvar
: PBoolean);
510 property checked
: Boolean read getChecked write setChecked
;
513 TUICheckBox
= class(TUISwitchBox
)
515 procedure setChecked (v
: Boolean); override;
518 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
520 procedure doAction (); override;
523 TUIRadioBox
= class(TUISwitchBox
)
525 mRadioGroup
: AnsiString;
528 procedure setChecked (v
: Boolean); override;
531 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
533 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
535 procedure doAction (); override;
538 property radioGroup
: AnsiString read mRadioGroup write mRadioGroup
; //FIXME
542 // ////////////////////////////////////////////////////////////////////////// //
543 procedure uiMouseEvent (var evt
: THMouseEvent
);
544 procedure uiKeyEvent (var evt
: THKeyEvent
);
547 procedure uiFocus ();
551 // ////////////////////////////////////////////////////////////////////////// //
552 procedure uiAddWindow (ctl
: TUIControl
);
553 procedure uiRemoveWindow (ctl
: TUIControl
); // will free window if `mFreeOnClose` is `true`
554 function uiVisibleWindow (ctl
: TUIControl
): Boolean;
556 procedure uiUpdateStyles ();
559 // ////////////////////////////////////////////////////////////////////////// //
561 procedure uiLayoutCtl (ctl
: TUIControl
);
564 // ////////////////////////////////////////////////////////////////////////// //
565 procedure uiInitialize ();
566 procedure uiDeinitialize ();
569 // ////////////////////////////////////////////////////////////////////////// //
571 fuiRenderScale
: Single = 1.0;
572 uiContext
: TGxContext
= nil;
582 // ////////////////////////////////////////////////////////////////////////// //
583 procedure uiDeinitialize ();
585 FreeAndNil(uiContext
);
589 procedure uiInitialize ();
591 if (uiContext
<> nil) then raise Exception
.Create('FlexUI already initialized');
592 uiContext
:= TGxContext
.Create();
596 // ////////////////////////////////////////////////////////////////////////// //
598 ctlsToKill
: array of TUIControl
= nil;
601 procedure scheduleKill (ctl
: TUIControl
);
605 if (ctl
= nil) then exit
;
607 for f
:= 0 to High(ctlsToKill
) do
609 if (ctlsToKill
[f
] = ctl
) then exit
;
610 if (ctlsToKill
[f
] = nil) then begin ctlsToKill
[f
] := ctl
; exit
; end;
612 SetLength(ctlsToKill
, Length(ctlsToKill
)+1);
613 ctlsToKill
[High(ctlsToKill
)] := ctl
;
617 procedure processKills ();
622 for f
:= 0 to High(ctlsToKill
) do
624 ctl
:= ctlsToKill
[f
];
625 if (ctl
= nil) then break
;
626 ctlsToKill
[f
] := nil;
629 if (Length(ctlsToKill
) > 0) then ctlsToKill
[0] := nil; // just in case
633 // ////////////////////////////////////////////////////////////////////////// //
635 knownCtlClasses
: array of record
636 klass
: TUIControlClass
;
641 procedure registerCtlClass (aklass
: TUIControlClass
; const aname
: AnsiString);
643 assert(aklass
<> nil);
644 assert(Length(aname
) > 0);
645 SetLength(knownCtlClasses
, Length(knownCtlClasses
)+1);
646 knownCtlClasses
[High(knownCtlClasses
)].klass
:= aklass
;
647 knownCtlClasses
[High(knownCtlClasses
)].name
:= aname
;
651 function findCtlClass (const aname
: AnsiString): TUIControlClass
;
655 for f
:= 0 to High(knownCtlClasses
) do
657 if (strEquCI1251(aname
, knownCtlClasses
[f
].name
)) then
659 result
:= knownCtlClasses
[f
].klass
;
667 // ////////////////////////////////////////////////////////////////////////// //
669 TFlexLayouter
= specialize TFlexLayouterBase
<TUIControl
>;
671 procedure uiLayoutCtl (ctl
: TUIControl
);
675 if (ctl
= nil) then exit
;
676 lay
:= TFlexLayouter
.Create();
678 if (not ctl
.mStyleLoaded
) then ctl
.updateStyle();
679 if (ctl
is TUITopWindow
) and (TUITopWindow(ctl
).fitToScreen
) then TUITopWindow(ctl
).flFitToScreen();
684 //writeln('============================'); lay.dumpFlat();
686 //writeln('=== initial ==='); lay.dump();
688 //lay.calcMaxSizeInternal(0);
691 writeln('=== after first pass ===');
695 writeln('=== after second pass ===');
700 //writeln('=== final ==='); lay.dump();
702 if (ctl
.mParent
= nil) and (ctl
is TUITopWindow
) and (TUITopWindow(ctl
).mDoCenter
) then
704 TUITopWindow(ctl
).centerInScreen();
707 // calculate full size
708 ctl
.calcFullClientSize();
711 if (ctl
.mParent
= nil) then
713 if (ctl
.mFocused
= nil) or (ctl
.mFocused
= ctl
) or (not ctl
.mFocused
.enabled
) then
715 ctl
.mFocused
:= ctl
.findFirstFocus();
725 // ////////////////////////////////////////////////////////////////////////// //
727 uiTopList
: array of TUIControl
= nil;
728 uiGrabCtl
: TUIControl
= nil;
731 procedure uiUpdateStyles ();
735 for ctl
in uiTopList
do ctl
.updateStyle();
739 procedure uiMouseEvent (var evt
: THMouseEvent
);
747 if (evt
.eaten
) or (evt
.cancelled
) then exit
;
749 ev
.x
:= trunc(ev
.x
/fuiRenderScale
);
750 ev
.y
:= trunc(ev
.y
/fuiRenderScale
);
751 ev
.dx
:= trunc(ev
.dx
/fuiRenderScale
); //FIXME
752 ev
.dy
:= trunc(ev
.dy
/fuiRenderScale
); //FIXME
754 if (uiGrabCtl
<> nil) then
756 uiGrabCtl
.mouseEvent(ev
);
757 if (ev
.release
) and ((ev
.bstate
and (not ev
.but
)) = 0) then uiGrabCtl
:= nil;
761 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].mouseEvent(ev
);
762 if (not ev
.eaten
) and (not ev
.cancelled
) and (ev
.press
) then
764 for f
:= High(uiTopList
) downto 0 do
766 if uiTopList
[f
].toLocal(ev
.x
, ev
.y
, lx
, ly
) then
768 if (uiTopList
[f
].enabled
) and (f
<> High(uiTopList
)) then
770 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].blurred();
771 ctmp
:= uiTopList
[f
];
773 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
774 uiTopList
[High(uiTopList
)] := ctmp
;
784 if (ev
.eaten
) then evt
.eat();
785 if (ev
.cancelled
) then evt
.cancel();
790 procedure uiKeyEvent (var evt
: THKeyEvent
);
795 if (evt
.eaten
) or (evt
.cancelled
) then exit
;
797 ev
.x
:= trunc(ev
.x
/fuiRenderScale
);
798 ev
.y
:= trunc(ev
.y
/fuiRenderScale
);
800 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].keyEvent(ev
);
801 //if (ev.release) then begin ev.eat(); exit; end;
803 if (ev
.eaten
) then evt
.eat();
804 if (ev
.cancelled
) then evt
.cancel();
809 procedure uiFocus ();
811 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].activated();
817 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].blurred();
827 //if (uiContext = nil) then uiContext := TGxContext.Create();
828 gxSetContext(uiContext
, fuiRenderScale
);
829 uiContext
.resetClip();
831 for f
:= 0 to High(uiTopList
) do
835 if (f
<> High(uiTopList
)) then
837 cidx
:= ctl
.getColorIndex
;
838 uiContext
.darkenRect(ctl
.x0
, ctl
.y0
, ctl
.width
, ctl
.height
, ctl
.mDarken
[cidx
]);
847 procedure uiAddWindow (ctl
: TUIControl
);
851 if (ctl
= nil) then exit
;
853 if not (ctl
is TUITopWindow
) then exit
; // alas
854 for f
:= 0 to High(uiTopList
) do
856 if (uiTopList
[f
] = ctl
) then
858 if (f
<> High(uiTopList
)) then
860 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].blurred();
861 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
862 uiTopList
[High(uiTopList
)] := ctl
;
868 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].blurred();
869 SetLength(uiTopList
, Length(uiTopList
)+1);
870 uiTopList
[High(uiTopList
)] := ctl
;
871 if (not ctl
.mStyleLoaded
) then ctl
.updateStyle();
876 procedure uiRemoveWindow (ctl
: TUIControl
);
880 if (ctl
= nil) then exit
;
882 if not (ctl
is TUITopWindow
) then exit
; // alas
883 for f
:= 0 to High(uiTopList
) do
885 if (uiTopList
[f
] = ctl
) then
888 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
889 SetLength(uiTopList
, Length(uiTopList
)-1);
890 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].activated();
891 if (ctl
is TUITopWindow
) then
894 if assigned(TUITopWindow(ctl
).closeCB
) then TUITopWindow(ctl
).closeCB(ctl
);
896 if (TUITopWindow(ctl
).mFreeOnClose
) then scheduleKill(ctl
);
905 function uiVisibleWindow (ctl
: TUIControl
): Boolean;
910 if (ctl
= nil) then exit
;
912 if not (ctl
is TUITopWindow
) then exit
; // alas
913 for f
:= 0 to High(uiTopList
) do
915 if (uiTopList
[f
] = ctl
) then begin result
:= true; exit
; end;
920 // ////////////////////////////////////////////////////////////////////////// //
921 constructor TUIControl
.Create ();
926 procedure TUIControl
.AfterConstruction ();
934 mHeight
:= uiContext
.charHeight(' ');
942 mDrawShadow
:= false;
944 // layouter interface
945 //mDefSize := TLaySize.Create(64, uiContext.charHeight(' ')); // default size
946 mDefSize
:= TLaySize
.Create(0, 0); // default size: hidden control
947 mMaxSize
:= TLaySize
.Create(-1, -1); // maximum size
948 mPadding
:= TLaySize
.Create(0, 0);
956 mAlign
:= -1; // left/top
958 mStyleLoaded
:= false;
962 destructor TUIControl
.Destroy ();
966 if (mParent
<> nil) then
969 for f
:= 0 to High(mParent
.mChildren
) do
971 if (mParent
.mChildren
[f
] = self
) then
973 for c
:= f
+1 to High(mParent
.mChildren
) do mParent
.mChildren
[c
-1] := mParent
.mChildren
[c
];
974 SetLength(mParent
.mChildren
, Length(mParent
.mChildren
)-1);
978 for f
:= 0 to High(mChildren
) do
980 mChildren
[f
].mParent
:= nil;
987 function TUIControl
.getColorIndex (): Integer; inline;
989 if (not enabled
) then begin result
:= ClrIdxDisabled
; exit
; end;
990 // top windows: no focus hack
991 if (self
is TUITopWindow
) then
993 if (getActive
) then begin result
:= ClrIdxActive
; exit
; end;
997 // if control cannot be focused, take "active" color scheme for it (it is easier this way)
998 if (not canFocus
) or (getActive
) then begin result
:= ClrIdxActive
; exit
; end;
1000 result
:= ClrIdxInactive
;
1003 procedure TUIControl
.updateStyle ();
1005 stl
: TUIStyle
= nil;
1009 while (ctl
<> nil) do
1011 if (Length(ctl
.mStyleId
) <> 0) then begin stl
:= uiFindStyle(ctl
.mStyleId
); break
; end;
1014 if (stl
= nil) then stl
:= uiFindStyle(''); // default
1016 for ctl
in mChildren
do ctl
.updateStyle();
1017 mStyleLoaded
:= true;
1020 procedure TUIControl
.cacheStyle (root
: TUIStyle
);
1024 //writeln('caching style for <', className, '> (', mCtl4Style, ')...');
1027 mBackColor
[ClrIdxActive
] := root
.get('back-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(0, 0, 128));
1028 mTextColor
[ClrIdxActive
] := root
.get('text-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1029 mFrameColor
[ClrIdxActive
] := root
.get('frame-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1030 mFrameTextColor
[ClrIdxActive
] := root
.get('frame-text-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1031 mFrameIconColor
[ClrIdxActive
] := root
.get('frame-icon-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(0, 255, 0));
1032 mSBarFullColor
[ClrIdxActive
] := root
.get('scrollbar-full-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1033 mSBarEmptyColor
[ClrIdxActive
] := root
.get('scrollbar-empty-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(128, 128, 128));
1034 mDarken
[ClrIdxActive
] := root
.get('darken', 'active', cst
).asInt(666);
1036 mBackColor
[ClrIdxDisabled
] := root
.get('back-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(0, 0, 128));
1037 mTextColor
[ClrIdxDisabled
] := root
.get('text-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(127, 127, 127));
1038 mFrameColor
[ClrIdxDisabled
] := root
.get('frame-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(127, 127, 127));
1039 mFrameTextColor
[ClrIdxDisabled
] := root
.get('frame-text-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(127, 127, 127));
1040 mFrameIconColor
[ClrIdxDisabled
] := root
.get('frame-icon-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(0, 127, 0));
1041 mSBarFullColor
[ClrIdxDisabled
] := root
.get('scrollbar-full-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(127, 127, 127));
1042 mSBarEmptyColor
[ClrIdxDisabled
] := root
.get('scrollbar-empty-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(98, 98, 98));
1043 mDarken
[ClrIdxDisabled
] := root
.get('darken', 'disabled', cst
).asInt(666);
1045 mBackColor
[ClrIdxInactive
] := root
.get('back-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(0, 0, 128));
1046 mTextColor
[ClrIdxInactive
] := root
.get('text-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1047 mFrameColor
[ClrIdxInactive
] := root
.get('frame-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1048 mFrameTextColor
[ClrIdxInactive
] := root
.get('frame-text-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1049 mFrameIconColor
[ClrIdxInactive
] := root
.get('frame-icon-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(0, 255, 0));
1050 mSBarFullColor
[ClrIdxInactive
] := root
.get('scrollbar-full-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1051 mSBarEmptyColor
[ClrIdxInactive
] := root
.get('scrollbar-empty-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(128, 128, 128));
1052 mDarken
[ClrIdxInactive
] := root
.get('darken', 'inactive', cst
).asInt(666);
1056 // ////////////////////////////////////////////////////////////////////////// //
1057 function TUIControl
.getDefSize (): TLaySize
; inline; begin result
:= mLayDefSize
; end;
1058 function TUIControl
.getMaxSize (): TLaySize
; inline; begin result
:= mLayMaxSize
; end;
1059 function TUIControl
.getPadding (): TLaySize
; inline; begin result
:= mPadding
; end;
1060 function TUIControl
.getFlex (): Integer; inline; begin result
:= mFlex
; end;
1061 function TUIControl
.isHorizBox (): Boolean; inline; begin result
:= mHoriz
; end;
1062 function TUIControl
.noPad (): Boolean; inline; begin result
:= mNoPad
; end;
1063 function TUIControl
.getAlign (): Integer; inline; begin result
:= mAlign
; end;
1064 function TUIControl
.getExpand (): Boolean; inline; begin result
:= mExpand
; end;
1065 function TUIControl
.getHGroup (): AnsiString; inline; begin result
:= mHGroup
; end;
1066 function TUIControl
.getVGroup (): AnsiString; inline; begin result
:= mVGroup
; end;
1067 function TUIControl
.getMargins (): TLayMargins
; inline; begin result
:= TLayMargins
.Create(mFrameHeight
, mFrameWidth
, mFrameHeight
, mFrameWidth
); end;
1069 procedure TUIControl
.setActualSizePos (constref apos
: TLayPos
; constref asize
: TLaySize
); inline;
1071 //writeln(self.className, '; pos=', apos.toString, '; size=', asize.toString);
1072 if (mParent
<> nil) then
1079 if (mLayMaxSize
.w
>= 0) then mWidth
:= nmin(mWidth
, mLayMaxSize
.w
);
1080 if (mLayMaxSize
.h
>= 0) then mHeight
:= nmin(mHeight
, mLayMaxSize
.h
);
1083 procedure TUIControl
.layPrepare ();
1085 mLayDefSize
:= mDefSize
;
1086 if (mLayDefSize
.w
<> 0) or (mLayDefSize
.h
<> 0) then
1088 mLayMaxSize
:= mMaxSize
;
1089 if (mLayMaxSize
.w
>= 0) then begin mLayDefSize
.w
+= mFrameWidth
*2; mLayMaxSize
.w
+= mFrameWidth
*2; end;
1090 if (mLayMaxSize
.h
>= 0) then begin mLayDefSize
.h
+= mFrameHeight
*2; mLayMaxSize
.h
+= mFrameHeight
*2; end;
1094 mLayMaxSize
:= TLaySize
.Create(0, 0);
1099 // ////////////////////////////////////////////////////////////////////////// //
1100 function TUIControl
.parsePos (par
: TTextParser
): TLayPos
;
1102 ech
: AnsiChar = ')';
1104 if (par
.eatDelim('[')) then ech
:= ']' else par
.expectDelim('(');
1105 result
.x
:= par
.expectInt();
1106 par
.eatDelim(','); // optional comma
1107 result
.y
:= par
.expectInt();
1108 par
.eatDelim(','); // optional comma
1109 par
.expectDelim(ech
);
1112 function TUIControl
.parseSize (par
: TTextParser
): TLaySize
;
1114 ech
: AnsiChar = ')';
1116 if (par
.eatDelim('[')) then ech
:= ']' else par
.expectDelim('(');
1117 result
.w
:= par
.expectInt();
1118 par
.eatDelim(','); // optional comma
1119 result
.h
:= par
.expectInt();
1120 par
.eatDelim(','); // optional comma
1121 par
.expectDelim(ech
);
1124 function TUIControl
.parsePadding (par
: TTextParser
): TLaySize
;
1126 result
:= parseSize(par
);
1129 function TUIControl
.parseHPadding (par
: TTextParser
; def
: Integer): TLaySize
;
1134 result
.w
:= par
.expectInt();
1138 result
:= parsePadding(par
);
1142 function TUIControl
.parseVPadding (par
: TTextParser
; def
: Integer): TLaySize
;
1147 result
.h
:= par
.expectInt();
1151 result
:= parsePadding(par
);
1155 function TUIControl
.parseBool (par
: TTextParser
): Boolean;
1158 par
.eatIdOrStrCI('true') or
1159 par
.eatIdOrStrCI('yes') or
1160 par
.eatIdOrStrCI('tan');
1163 if (not par
.eatIdOrStrCI('false')) and (not par
.eatIdOrStrCI('no')) and (not par
.eatIdOrStrCI('ona')) then
1165 par
.error('boolean value expected');
1170 function TUIControl
.parseAnyAlign (par
: TTextParser
): Integer;
1172 if (par
.eatIdOrStrCI('left')) or (par
.eatIdOrStrCI('top')) then result
:= -1
1173 else if (par
.eatIdOrStrCI('right')) or (par
.eatIdOrStrCI('bottom')) then result
:= 1
1174 else if (par
.eatIdOrStrCI('center')) then result
:= 0
1175 else par
.error('invalid align value');
1178 function TUIControl
.parseHAlign (par
: TTextParser
): Integer;
1180 if (par
.eatIdOrStrCI('left')) then result
:= -1
1181 else if (par
.eatIdOrStrCI('right')) then result
:= 1
1182 else if (par
.eatIdOrStrCI('center')) then result
:= 0
1183 else par
.error('invalid horizontal align value');
1186 function TUIControl
.parseVAlign (par
: TTextParser
): Integer;
1188 if (par
.eatIdOrStrCI('top')) then result
:= -1
1189 else if (par
.eatIdOrStrCI('bottom')) then result
:= 1
1190 else if (par
.eatIdOrStrCI('center')) then result
:= 0
1191 else par
.error('invalid vertical align value');
1194 procedure TUIControl
.parseTextAlign (par
: TTextParser
; var h
, v
: Integer);
1196 wasH
: Boolean = false;
1197 wasV
: Boolean = false;
1201 if (par
.eatIdOrStrCI('left')) then
1203 if wasH
then par
.error('too many align directives');
1208 if (par
.eatIdOrStrCI('right')) then
1210 if wasH
then par
.error('too many align directives');
1215 if (par
.eatIdOrStrCI('hcenter')) then
1217 if wasH
then par
.error('too many align directives');
1222 if (par
.eatIdOrStrCI('top')) then
1224 if wasV
then par
.error('too many align directives');
1229 if (par
.eatIdOrStrCI('bottom')) then
1231 if wasV
then par
.error('too many align directives');
1236 if (par
.eatIdOrStrCI('vcenter')) then
1238 if wasV
then par
.error('too many align directives');
1243 if (par
.eatIdOrStrCI('center')) then
1245 if wasV
or wasH
then par
.error('too many align directives');
1254 if not wasV
and not wasH
then par
.error('invalid align value');
1257 function TUIControl
.parseOrientation (const prname
: AnsiString; par
: TTextParser
): Boolean;
1259 if (strEquCI1251(prname
, 'orientation')) or (strEquCI1251(prname
, 'orient')) then
1261 if (par
.eatIdOrStrCI('horizontal')) or (par
.eatIdOrStrCI('horiz')) then mHoriz
:= true
1262 else if (par
.eatIdOrStrCI('vertical')) or (par
.eatIdOrStrCI('vert')) then mHoriz
:= false
1263 else par
.error('`horizontal` or `vertical` expected');
1272 // par should be on '{'; final '}' is eaten
1273 procedure TUIControl
.parseProperties (par
: TTextParser
);
1277 if (not par
.eatDelim('{')) then exit
;
1278 while (not par
.eatDelim('}')) do
1280 if (not par
.isIdOrStr
) then par
.error('property name expected');
1283 par
.eatDelim(':'); // optional
1284 if not parseProperty(pn
, par
) then par
.errorfmt('invalid property name ''%s''', [pn
]);
1285 par
.eatDelim(','); // optional
1289 // par should be on '{'
1290 procedure TUIControl
.parseChildren (par
: TTextParser
);
1292 cc
: TUIControlClass
;
1295 par
.expectDelim('{');
1296 while (not par
.eatDelim('}')) do
1298 if (not par
.isIdOrStr
) then par
.error('control name expected');
1299 cc
:= findCtlClass(par
.tokStr
);
1300 if (cc
= nil) then par
.errorfmt('unknown control name: ''%s''', [par
.tokStr
]);
1301 //writeln('children for <', par.tokStr, '>: <', cc.className, '>');
1303 par
.eatDelim(':'); // optional
1305 //writeln(' mHoriz=', ctl.mHoriz);
1307 ctl
.parseProperties(par
);
1312 //writeln(': ', ctl.mDefSize.toString);
1314 par
.eatDelim(','); // optional
1319 function TUIControl
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
1322 if (strEquCI1251(prname
, 'id')) then begin mId
:= par
.expectIdOrStr(true); exit
; end; // allow empty strings
1323 if (strEquCI1251(prname
, 'style')) then begin mStyleId
:= par
.expectIdOrStr(); exit
; end; // no empty strings
1324 if (strEquCI1251(prname
, 'flex')) then begin flex
:= par
.expectInt(); exit
; end;
1326 if (strEquCI1251(prname
, 'defsize')) or (strEquCI1251(prname
, 'size')) then begin mDefSize
:= parseSize(par
); exit
; end;
1327 if (strEquCI1251(prname
, 'maxsize')) then begin mMaxSize
:= parseSize(par
); exit
; end;
1328 if (strEquCI1251(prname
, 'defwidth')) or (strEquCI1251(prname
, 'width')) then begin mDefSize
.w
:= par
.expectInt(); exit
; end;
1329 if (strEquCI1251(prname
, 'defheight')) or (strEquCI1251(prname
, 'height')) then begin mDefSize
.h
:= par
.expectInt(); exit
; end;
1330 if (strEquCI1251(prname
, 'maxwidth')) then begin mMaxSize
.w
:= par
.expectInt(); exit
; end;
1331 if (strEquCI1251(prname
, 'maxheight')) then begin mMaxSize
.h
:= par
.expectInt(); exit
; end;
1333 if (strEquCI1251(prname
, 'padding')) then begin mPadding
:= parsePadding(par
); exit
; end;
1334 if (strEquCI1251(prname
, 'nopad')) then begin mNoPad
:= true; exit
; end;
1336 if (strEquCI1251(prname
, 'expand')) then begin mExpand
:= parseBool(par
); exit
; end;
1338 if (strEquCI1251(prname
, 'align')) then begin mAlign
:= parseAnyAlign(par
); exit
; end;
1339 if (strEquCI1251(prname
, 'hgroup')) then begin mHGroup
:= par
.expectIdOrStr(true); exit
; end; // allow empty strings
1340 if (strEquCI1251(prname
, 'vgroup')) then begin mVGroup
:= par
.expectIdOrStr(true); exit
; end; // allow empty strings
1342 if (strEquCI1251(prname
, 'canfocus')) then begin mCanFocus
:= true; exit
; end;
1343 if (strEquCI1251(prname
, 'nofocus')) then begin mCanFocus
:= false; exit
; end;
1344 if (strEquCI1251(prname
, 'disabled')) then begin mEnabled
:= false; exit
; end;
1345 if (strEquCI1251(prname
, 'enabled')) then begin mEnabled
:= true; exit
; end;
1346 if (strEquCI1251(prname
, 'escclose')) then begin mEscClose
:= not parseBool(par
); exit
; end;
1347 if (strEquCI1251(prname
, 'default')) then begin mDefault
:= true; exit
; end;
1348 if (strEquCI1251(prname
, 'cancel')) then begin mCancel
:= true; exit
; end;
1353 // ////////////////////////////////////////////////////////////////////////// //
1354 procedure TUIControl
.activated ();
1356 makeVisibleInParent();
1360 procedure TUIControl
.blurred ();
1362 if (uiGrabCtl
= self
) then uiGrabCtl
:= nil;
1366 procedure TUIControl
.calcFullClientSize ();
1370 mFullSize
:= TLaySize
.Create(0, 0);
1371 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1372 for ctl
in mChildren
do
1374 ctl
.calcFullClientSize();
1375 mFullSize
.w
:= nmax(mFullSize
.w
, ctl
.mX
-mFrameWidth
+ctl
.mFullSize
.w
);
1376 mFullSize
.h
:= nmax(mFullSize
.h
, ctl
.mY
-mFrameHeight
+ctl
.mFullSize
.h
);
1378 mFullSize
.w
:= nmax(mFullSize
.w
, mWidth
-mFrameWidth
*2);
1379 mFullSize
.h
:= nmax(mFullSize
.h
, mHeight
-mFrameHeight
*2);
1383 function TUIControl
.topLevel (): TUIControl
; inline;
1386 while (result
.mParent
<> nil) do result
:= result
.mParent
;
1390 function TUIControl
.getEnabled (): Boolean;
1395 if (not mEnabled
) then exit
;
1397 while (ctl
<> nil) do
1399 if (not ctl
.mEnabled
) then exit
;
1406 procedure TUIControl
.setEnabled (v
: Boolean); inline;
1408 if (mEnabled
= v
) then exit
;
1410 if (not v
) and focused
then setFocused(false);
1414 function TUIControl
.getFocused (): Boolean; inline;
1416 if (mParent
= nil) then
1418 result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = self
);
1422 result
:= (topLevel
.mFocused
= self
);
1423 if (result
) then result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = topLevel
);
1428 function TUIControl
.getActive (): Boolean; inline;
1432 if (mParent
= nil) then
1434 result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = self
);
1438 ctl
:= topLevel
.mFocused
;
1439 while (ctl
<> nil) and (ctl
<> self
) do ctl
:= ctl
.mParent
;
1440 result
:= (ctl
= self
);
1441 if (result
) then result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = topLevel
);
1446 procedure TUIControl
.setFocused (v
: Boolean); inline;
1453 if (tl
.mFocused
= self
) then
1455 blurred(); // this will reset grab, but still...
1456 if (uiGrabCtl
= self
) then uiGrabCtl
:= nil;
1457 tl
.mFocused
:= tl
.findNextFocus(self
, true);
1458 if (tl
.mFocused
= self
) then tl
.mFocused
:= nil;
1459 if (tl
.mFocused
<> nil) then tl
.mFocused
.activated();
1463 if (not canFocus
) then exit
;
1464 if (tl
.mFocused
<> self
) then
1466 if (tl
.mFocused
<> nil) then tl
.mFocused
.blurred();
1467 tl
.mFocused
:= self
;
1468 if (uiGrabCtl
<> self
) then uiGrabCtl
:= nil;
1474 function TUIControl
.getCanFocus (): Boolean; inline;
1476 result
:= (getEnabled
) and (mCanFocus
) and (mWidth
> 0) and (mHeight
> 0);
1480 function TUIControl
.isMyChild (ctl
: TUIControl
): Boolean;
1483 while (ctl
<> nil) do
1485 if (ctl
.mParent
= self
) then exit
;
1492 // returns `true` if global coords are inside this control
1493 function TUIControl
.toLocal (var x
, y
: Integer): Boolean;
1495 if (mParent
= nil) then
1499 result
:= true; // hack
1503 result
:= mParent
.toLocal(x
, y
);
1504 Inc(x
, mParent
.mScrollX
);
1505 Inc(y
, mParent
.mScrollY
);
1508 if result
then result
:= (x
>= 0) and (y
>= 0) and (x
< mParent
.mWidth
) and (y
< mParent
.mHeight
);
1510 if result
then result
:= (x
>= 0) and (y
>= 0) and (x
< mWidth
) and (y
< mHeight
);
1513 function TUIControl
.toLocal (gx
, gy
: Integer; out x
, y
: Integer): Boolean; inline;
1517 result
:= toLocal(x
, y
);
1521 procedure TUIControl
.toGlobal (var x
, y
: Integer);
1525 if (mParent
<> nil) then
1527 Dec(x
, mParent
.mScrollX
);
1528 Dec(y
, mParent
.mScrollY
);
1529 mParent
.toGlobal(x
, y
);
1533 procedure TUIControl
.toGlobal (lx
, ly
: Integer; out x
, y
: Integer); inline;
1540 procedure TUIControl
.getDrawRect (out gx
, gy
, wdt
, hgt
: Integer);
1544 if (mParent
= nil) then
1553 toGlobal(0, 0, cgx
, cgy
);
1554 mParent
.getDrawRect(gx
, gy
, wdt
, hgt
);
1555 if (wdt
> 0) and (hgt
> 0) then
1557 if (not intersectRect(gx
, gy
, wdt
, hgt
, cgx
, cgy
, mWidth
, mHeight
)) then
1567 // x and y are global coords
1568 function TUIControl
.controlAtXY (x
, y
: Integer; allowDisabled
: Boolean=false): TUIControl
;
1574 if (not allowDisabled
) and (not enabled
) then exit
;
1575 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1576 if not toLocal(x
, y
, lx
, ly
) then exit
;
1577 for f
:= High(mChildren
) downto 0 do
1579 result
:= mChildren
[f
].controlAtXY(x
, y
, allowDisabled
);
1580 if (result
<> nil) then exit
;
1586 function TUIControl
.parentScrollX (): Integer; inline; begin if (mParent
<> nil) then result
:= mParent
.mScrollX
else result
:= 0; end;
1587 function TUIControl
.parentScrollY (): Integer; inline; begin if (mParent
<> nil) then result
:= mParent
.mScrollY
else result
:= 0; end;
1590 procedure TUIControl
.makeVisibleInParent ();
1592 sy
, ey
, cy
: Integer;
1595 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1597 if (p
= nil) then exit
;
1598 if (p
.mFullSize
.w
< 1) or (p
.mFullSize
.h
< 1) then
1604 p
.makeVisibleInParent();
1605 cy
:= mY
-p
.mFrameHeight
;
1607 ey
:= sy
+(p
.mHeight
-p
.mFrameHeight
*2);
1610 p
.mScrollY
:= nmax(0, cy
);
1612 else if (cy
+mHeight
> ey
) then
1614 p
.mScrollY
:= nmax(0, cy
+mHeight
-(p
.mHeight
-p
.mFrameHeight
*2));
1619 // ////////////////////////////////////////////////////////////////////////// //
1620 function TUIControl
.prevSibling (): TUIControl
;
1624 if (mParent
<> nil) then
1626 for f
:= 1 to High(mParent
.mChildren
) do
1628 if (mParent
.mChildren
[f
] = self
) then begin result
:= mParent
.mChildren
[f
-1]; exit
; end;
1634 function TUIControl
.nextSibling (): TUIControl
;
1638 if (mParent
<> nil) then
1640 for f
:= 0 to High(mParent
.mChildren
)-1 do
1642 if (mParent
.mChildren
[f
] = self
) then begin result
:= mParent
.mChildren
[f
+1]; exit
; end;
1648 function TUIControl
.firstChild (): TUIControl
; inline;
1650 if (Length(mChildren
) <> 0) then result
:= mChildren
[0] else result
:= nil;
1653 function TUIControl
.lastChild (): TUIControl
; inline;
1655 if (Length(mChildren
) <> 0) then result
:= mChildren
[High(mChildren
)] else result
:= nil;
1659 function TUIControl
.findFirstFocus (): TUIControl
;
1666 for f
:= 0 to High(mChildren
) do
1668 result
:= mChildren
[f
].findFirstFocus();
1669 if (result
<> nil) then exit
;
1671 if (canFocus
) then result
:= self
;
1676 function TUIControl
.findLastFocus (): TUIControl
;
1683 for f
:= High(mChildren
) downto 0 do
1685 result
:= mChildren
[f
].findLastFocus();
1686 if (result
<> nil) then exit
;
1688 if (canFocus
) then result
:= self
;
1693 function TUIControl
.findNextFocus (cur
: TUIControl
; wrap
: Boolean): TUIControl
;
1695 curHit
: Boolean = false;
1697 function checkFocus (ctl
: TUIControl
): Boolean;
1701 result
:= (ctl
.canFocus
);
1705 curHit
:= (ctl
= cur
);
1706 result
:= false; // don't stop
1714 if not isMyChild(cur
) then
1716 result
:= findFirstFocus();
1720 result
:= forEachControl(checkFocus
);
1721 if (result
= nil) and (wrap
) then result
:= findFirstFocus();
1727 function TUIControl
.findPrevFocus (cur
: TUIControl
; wrap
: Boolean): TUIControl
;
1729 lastCtl
: TUIControl
= nil;
1731 function checkFocus (ctl
: TUIControl
): Boolean;
1740 if (ctl
.canFocus
) then lastCtl
:= ctl
;
1748 if not isMyChild(cur
) then
1750 result
:= findLastFocus();
1754 forEachControl(checkFocus
);
1755 if (lastCtl
= nil) and (wrap
) then lastCtl
:= findLastFocus();
1757 //if (lastCtl <> nil) then writeln('ctl<', lastCtl.className, '>: {', lastCtl.id, '}');
1763 function TUIControl
.findDefaulControl (): TUIControl
;
1769 if (mDefault
) then begin result
:= self
; exit
; end;
1770 for ctl
in mChildren
do
1772 result
:= ctl
.findDefaulControl();
1773 if (result
<> nil) then exit
;
1779 function TUIControl
.findCancelControl (): TUIControl
;
1785 if (mCancel
) then begin result
:= self
; exit
; end;
1786 for ctl
in mChildren
do
1788 result
:= ctl
.findCancelControl();
1789 if (result
<> nil) then exit
;
1796 function TUIControl
.findControlById (const aid
: AnsiString): TUIControl
;
1800 if (strEquCI1251(aid
, mId
)) then begin result
:= self
; exit
; end;
1801 for ctl
in mChildren
do
1803 result
:= ctl
.findControlById(aid
);
1804 if (result
<> nil) then exit
;
1810 procedure TUIControl
.appendChild (ctl
: TUIControl
);
1812 if (ctl
= nil) then exit
;
1813 if (ctl
.mParent
<> nil) then exit
;
1814 SetLength(mChildren
, Length(mChildren
)+1);
1815 mChildren
[High(mChildren
)] := ctl
;
1816 ctl
.mParent
:= self
;
1817 Inc(ctl
.mX
, mFrameWidth
);
1818 Inc(ctl
.mY
, mFrameHeight
);
1819 if (ctl
.mWidth
> 0) and (ctl
.mHeight
> 0) and
1820 (ctl
.mX
+ctl
.mWidth
> mFrameWidth
) and (ctl
.mY
+ctl
.mHeight
> mFrameHeight
) then
1822 if (mWidth
+mFrameWidth
< ctl
.mX
+ctl
.mWidth
) then mWidth
:= ctl
.mX
+ctl
.mWidth
+mFrameWidth
;
1823 if (mHeight
+mFrameHeight
< ctl
.mY
+ctl
.mHeight
) then mHeight
:= ctl
.mY
+ctl
.mHeight
+mFrameHeight
;
1828 function TUIControl
.setActionCBFor (const aid
: AnsiString; cb
: TActionCB
): TActionCB
;
1833 if (ctl
<> nil) then
1835 result
:= ctl
.actionCB
;
1845 function TUIControl
.forEachChildren (cb
: TCtlEnumCB
): TUIControl
;
1850 if (not assigned(cb
)) then exit
;
1851 for ctl
in mChildren
do
1853 if cb(ctl
) then begin result
:= ctl
; exit
; end;
1858 function TUIControl
.forEachControl (cb
: TCtlEnumCB
; includeSelf
: Boolean=true): TUIControl
;
1860 function forChildren (p
: TUIControl
; incSelf
: Boolean): TUIControl
;
1865 if (p
= nil) then exit
;
1866 if (incSelf
) and (cb(p
)) then begin result
:= p
; exit
; end;
1867 for ctl
in p
.mChildren
do
1869 result
:= forChildren(ctl
, true);
1870 if (result
<> nil) then break
;
1876 if (not assigned(cb
)) then exit
;
1877 result
:= forChildren(self
, includeSelf
);
1881 procedure TUIControl
.close (); // this closes *top-level* control
1886 uiRemoveWindow(ctl
);
1887 if (ctl
is TUITopWindow
) and (TUITopWindow(ctl
).mFreeOnClose
) then scheduleKill(ctl
); // just in case
1891 procedure TUIControl
.doAction ();
1893 if assigned(actionCB
) then actionCB(self
);
1897 // ////////////////////////////////////////////////////////////////////////// //
1898 procedure TUIControl
.setScissor (lx
, ly
, lw
, lh
: Integer);
1900 gx
, gy
, wdt
, hgt
, cgx
, cgy
: Integer;
1902 if (not intersectRect(lx
, ly
, lw
, lh
, 0, 0, mWidth
, mHeight
)) then
1904 uiContext
.clip
:= TGxRect
.Create(0, 0, 0, 0);
1908 getDrawRect(gx
, gy
, wdt
, hgt
);
1910 toGlobal(lx
, ly
, cgx
, cgy
);
1911 if (not intersectRect(gx
, gy
, wdt
, hgt
, cgx
, cgy
, lw
, lh
)) then
1913 uiContext
.clip
:= TGxRect
.Create(0, 0, 0, 0);
1917 uiContext
.clip
:= savedClip
;
1918 uiContext
.combineClip(TGxRect
.Create(gx
, gy
, wdt
, hgt
));
1919 //uiContext.clip := TGxRect.Create(gx, gy, wdt, hgt);
1924 // ////////////////////////////////////////////////////////////////////////// //
1925 procedure TUIControl
.drawFrame (gx
, gy
, resx
, thalign
: Integer; const text: AnsiString; dbl
: Boolean);
1927 cidx
, tx
, tw
: Integer;
1929 if (mFrameWidth
< 1) or (mFrameHeight
< 1) then exit
;
1930 cidx
:= getColorIndex
;
1931 uiContext
.color
:= mFrameColor
[cidx
];
1932 case mFrameHeight
of
1937 uiContext
.rect(gx
+3, gy
+3, mWidth
-6, mHeight
-6);
1938 uiContext
.rect(gx
+5, gy
+5, mWidth
-10, mHeight
-10);
1942 uiContext
.rect(gx
+4, gy
+4, mWidth
-8, mHeight
-8);
1949 uiContext
.rect(gx
+3, gy
+3+3, mWidth
-6, mHeight
-6-6);
1950 uiContext
.rect(gx
+5, gy
+5+3, mWidth
-10, mHeight
-10-6);
1954 uiContext
.rect(gx
+4, gy
+4+3, mWidth
-8, mHeight
-8-6);
1961 uiContext
.rect(gx
+3, gy
+3+4, mWidth
-6, mHeight
-6-8);
1962 uiContext
.rect(gx
+5, gy
+5+4, mWidth
-10, mHeight
-10-8);
1966 uiContext
.rect(gx
+4, gy
+4+4, mWidth
-8, mHeight
-8-8);
1982 if (Length(text) > 0) then
1984 if (resx
< 0) then resx
:= 0;
1985 tw
:= uiContext
.textWidth(text);
1986 setScissor(mFrameWidth
+resx
, 0, mWidth
-mFrameWidth
*2-resx
, mFrameHeight
);
1987 if (thalign
< 0) then tx
:= gx
+resx
+mFrameWidth
+2
1988 else if (thalign
> 0) then tx
:= gx
+mWidth
-mFrameWidth
-1-tw
1989 else tx
:= (gx
+resx
+mFrameWidth
)+(mWidth
-mFrameWidth
*2-resx
-tw
) div 2;
1990 uiContext
.color
:= mBackColor
[cidx
];
1991 uiContext
.fillRect(tx
-2, gy
, tw
+4, mFrameHeight
);
1992 uiContext
.color
:= mFrameTextColor
[cidx
];
1993 uiContext
.drawText(tx
, gy
, text);
1998 procedure TUIControl
.draw ();
2003 procedure resetScissor (fullArea
: Boolean); inline;
2005 uiContext
.clip
:= savedClip
;
2006 if (fullArea
) or ((mFrameWidth
= 0) and (mFrameHeight
= 0)) then
2008 setScissor(0, 0, mWidth
, mHeight
);
2012 //writeln('frm: (', mFrameWidth, 'x', mFrameHeight, ')');
2013 setScissor(mFrameWidth
, mFrameHeight
, mWidth
-mFrameWidth
*2, mHeight
-mFrameHeight
*2);
2018 if (mWidth
< 1) or (mHeight
< 1) or (uiContext
= nil) or (not uiContext
.active
) then exit
;
2019 toGlobal(0, 0, gx
, gy
);
2021 savedClip
:= uiContext
.clip
;
2023 resetScissor(true); // full area
2024 drawControl(gx
, gy
);
2025 resetScissor(false); // client area
2026 for f
:= 0 to High(mChildren
) do mChildren
[f
].draw();
2027 resetScissor(true); // full area
2028 if (self
is TUISwitchBox
) then
2030 uiContext
.color
:= TGxRGBA
.Create(255, 0, 0, 255);
2031 //uiContext.fillRect(gx, gy, mWidth, mHeight);
2032 //writeln('frm: (', mFrameWidth, 'x', mFrameHeight, '); sz=(', mWidth, 'x', mHeight, '); clip=', uiContext.clip.toString);
2034 if false and (mId
= 'cbtest') then
2036 uiContext
.color
:= TGxRGBA
.Create(255, 127, 0, 96);
2037 uiContext
.fillRect(gx
, gy
, mWidth
, mHeight
);
2038 if (mFrameWidth
> 0) and (mFrameHeight
> 0) then
2040 uiContext
.color
:= TGxRGBA
.Create(255, 255, 0, 96);
2041 uiContext
.fillRect(gx
+mFrameWidth
, gy
+mFrameHeight
, mWidth
-mFrameWidth
*2, mHeight
-mFrameHeight
*2);
2044 else if false and (self
is TUISwitchBox
) then
2046 uiContext
.color
:= TGxRGBA
.Create(255, 0, 0, 255);
2047 uiContext
.fillRect(gx
, gy
, mWidth
, mHeight
);
2048 //writeln('frm: (', mFrameWidth, 'x', mFrameHeight, ')');
2050 drawControlPost(gx
, gy
);
2052 uiContext
.clip
:= savedClip
;
2056 procedure TUIControl
.drawControl (gx
, gy
: Integer);
2058 //if (mParent = nil) then darkenRect(gx, gy, mWidth, mHeight, 64);
2061 procedure TUIControl
.drawControlPost (gx
, gy
: Integer);
2064 if (mParent
= nil) and (mDrawShadow
) and (mWidth
> 0) and (mHeight
> 0) then
2066 //setScissorGLInternal(gx+8, gy+8, mWidth, mHeight);
2067 uiContext
.resetClip();
2068 uiContext
.darkenRect(gx
+mWidth
, gy
+8, 8, mHeight
, 128);
2069 uiContext
.darkenRect(gx
+8, gy
+mHeight
, mWidth
-8, 8, 128);
2074 // ////////////////////////////////////////////////////////////////////////// //
2075 procedure TUIControl
.mouseEvent (var ev
: THMouseEvent
);
2079 if (not enabled
) then exit
;
2080 if (mWidth
< 1) or (mHeight
< 1) then exit
;
2081 ctl
:= controlAtXY(ev
.x
, ev
.y
);
2082 if (ctl
= nil) then exit
;
2083 if (ctl
.canFocus
) and (ev
.press
) then
2085 if (ctl
<> topLevel
.mFocused
) then ctl
.setFocused(true);
2088 if (ctl
<> self
) then ctl
.mouseEvent(ev
);
2093 procedure TUIControl
.keyEvent (var ev
: THKeyEvent
);
2095 function doPreKey (ctl
: TUIControl
): Boolean;
2097 if (not ctl
.enabled
) then begin result
:= false; exit
; end;
2098 ctl
.keyEventPre(ev
);
2099 result
:= (ev
.eaten
) or (ev
.cancelled
); // stop if event was consumed
2102 function doPostKey (ctl
: TUIControl
): Boolean;
2104 if (not ctl
.enabled
) then begin result
:= false; exit
; end;
2105 ctl
.keyEventPost(ev
);
2106 result
:= (ev
.eaten
) or (ev
.cancelled
); // stop if event was consumed
2112 if (not enabled
) then exit
;
2113 if (ev
.eaten
) or (ev
.cancelled
) then exit
;
2115 if (mParent
= nil) then
2117 forEachControl(doPreKey
);
2118 if (ev
.eaten
) or (ev
.cancelled
) then exit
;
2120 // focused control should process keyboard first
2121 if (topLevel
.mFocused
<> self
) and isMyChild(topLevel
.mFocused
) and (topLevel
.mFocused
.enabled
) then
2123 // bubble keyboard event
2124 ctl
:= topLevel
.mFocused
;
2125 while (ctl
<> nil) and (ctl
<> self
) do
2128 if (ev
.eaten
) or (ev
.cancelled
) then exit
;
2132 // for top-level controls
2133 if (mParent
= nil) then
2135 if (ev
= 'S-Tab') then
2137 ctl
:= findPrevFocus(mFocused
, true);
2138 if (ctl
<> nil) and (ctl
<> mFocused
) then ctl
.setFocused(true);
2142 if (ev
= 'Tab') then
2144 ctl
:= findNextFocus(mFocused
, true);
2145 if (ctl
<> nil) and (ctl
<> mFocused
) then ctl
.setFocused(true);
2149 if (ev
= 'Enter') or (ev
= 'C-Enter') then
2151 ctl
:= findDefaulControl();
2152 if (ctl
<> nil) then
2159 if (ev
= 'Escape') then
2161 ctl
:= findCancelControl();
2162 if (ctl
<> nil) then
2169 if mEscClose
and (ev
= 'Escape') then
2171 if (not assigned(closeRequestCB
)) or (closeRequestCB(self
)) then
2173 uiRemoveWindow(self
);
2179 if (ev
.eaten
) or (ev
.cancelled
) then exit
;
2180 forEachControl(doPostKey
);
2185 procedure TUIControl
.keyEventPre (var ev
: THKeyEvent
);
2190 procedure TUIControl
.keyEventPost (var ev
: THKeyEvent
);
2195 // ////////////////////////////////////////////////////////////////////////// //
2196 constructor TUITopWindow
.Create (const atitle
: AnsiString);
2203 procedure TUITopWindow
.AfterConstruction ();
2206 mFitToScreen
:= true;
2208 mFrameHeight
:= uiContext
.charHeight(#184);
2209 if (mWidth
< mFrameWidth
*2+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
)) then mWidth
:= mFrameWidth
*2+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
);
2210 if (mHeight
< mFrameHeight
*2) then mHeight
:= mFrameHeight
*2;
2211 if (Length(mTitle
) > 0) then
2213 if (mWidth
< uiContext
.textWidth(mTitle
)+mFrameWidth
*2+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
)) then
2215 mWidth
:= uiContext
.textWidth(mTitle
)+mFrameWidth
*2+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
);
2219 mDragScroll
:= TXMode
.None
;
2220 mDrawShadow
:= true;
2221 mWaitingClose
:= false;
2224 mCtl4Style
:= 'window';
2225 mDefSize
.w
:= nmax(1, mDefSize
.w
);
2226 mDefSize
.h
:= nmax(1, mDefSize
.h
);
2230 function TUITopWindow
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2232 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) then
2234 mTitle
:= par
.expectIdOrStr(true);
2238 if (strEquCI1251(prname
, 'children')) then
2244 if (strEquCI1251(prname
, 'position')) then
2246 if (par
.eatIdOrStrCI('default')) then mDoCenter
:= false
2247 else if (par
.eatIdOrStrCI('center')) then mDoCenter
:= true
2248 else par
.error('`center` or `default` expected');
2252 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2253 result
:= inherited parseProperty(prname
, par
);
2257 procedure TUITopWindow
.flFitToScreen ();
2261 nsz
:= TLaySize
.Create(trunc(fuiScrWdt
/fuiRenderScale
)-mFrameWidth
*2-6, trunc(fuiScrHgt
/fuiRenderScale
)-mFrameHeight
*2-6);
2262 if (mMaxSize
.w
< 1) then mMaxSize
.w
:= nsz
.w
;
2263 if (mMaxSize
.h
< 1) then mMaxSize
.h
:= nsz
.h
;
2267 procedure TUITopWindow
.centerInScreen ();
2269 if (mWidth
> 0) and (mHeight
> 0) then
2271 mX
:= trunc((fuiScrWdt
/fuiRenderScale
-mWidth
)/2);
2272 mY
:= trunc((fuiScrHgt
/fuiRenderScale
-mHeight
)/2);
2277 procedure TUITopWindow
.drawControl (gx
, gy
: Integer);
2279 uiContext
.color
:= mBackColor
[getColorIndex
];
2280 uiContext
.fillRect(gx
, gy
, mWidth
, mHeight
);
2283 procedure TUITopWindow
.drawControlPost (gx
, gy
: Integer);
2285 cidx
, iwdt
, ihgt
: Integer;
2286 ybot
, xend
, vhgt
, vwdt
: Integer;
2288 cidx
:= getColorIndex
;
2289 iwdt
:= uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
);
2290 if (mDragScroll
= TXMode
.Drag
) then
2292 //uiContext.color := mFrameColor[cidx];
2293 drawFrame(gx
, gy
, iwdt
, 0, mTitle
, false);
2297 ihgt
:= uiContext
.iconWinHeight(TGxContext
.TWinIcon
.Close
);
2298 //uiContext.color := mFrameColor[cidx];
2299 drawFrame(gx
, gy
, iwdt
, 0, mTitle
, true);
2300 // vertical scroll bar
2301 vhgt
:= mHeight
-mFrameHeight
*2;
2302 if (mFullSize
.h
> vhgt
) then
2304 ybot
:= mScrollY
+vhgt
;
2305 setScissor(0, 0, mWidth
, mHeight
);
2306 uiContext
.drawVSBar(gx
+mWidth
-mFrameWidth
+1, gy
+mFrameHeight
-1, mFrameWidth
-3, vhgt
+2, ybot
, 0, mFullSize
.h
, mSBarFullColor
[cidx
], mSBarEmptyColor
[cidx
]);
2308 // horizontal scroll bar
2309 vwdt
:= mWidth
-mFrameWidth
*2;
2310 if (mFullSize
.w
> vwdt
) then
2312 xend
:= mScrollX
+vwdt
;
2313 setScissor(0, 0, mWidth
, mHeight
);
2314 uiContext
.drawHSBar(gx
+mFrameWidth
+1, gy
+mHeight
-mFrameHeight
+1, vwdt
-2, mFrameHeight
-3, xend
, 0, mFullSize
.w
, mSBarFullColor
[cidx
], mSBarEmptyColor
[cidx
]);
2317 setScissor(mFrameWidth
, 0, iwdt
, ihgt
);
2318 uiContext
.color
:= mBackColor
[cidx
];
2319 uiContext
.fillRect(gx
+mFrameWidth
, gy
, iwdt
, ihgt
);
2320 uiContext
.color
:= mFrameIconColor
[cidx
];
2321 uiContext
.drawIconWin(TGxContext
.TWinIcon
.Close
, gx
+mFrameWidth
, gy
, mInClose
);
2324 inherited drawControlPost(gx
, gy
);
2328 procedure TUITopWindow
.activated ();
2330 if (mFocused
= nil) or (mFocused
= self
) then
2332 mFocused
:= findFirstFocus();
2334 if (mFocused
<> nil) and (mFocused
<> self
) then mFocused
.activated();
2339 procedure TUITopWindow
.blurred ();
2341 mDragScroll
:= TXMode
.None
;
2342 mWaitingClose
:= false;
2344 if (mFocused
<> nil) and (mFocused
<> self
) then mFocused
.blurred();
2349 procedure TUITopWindow
.keyEvent (var ev
: THKeyEvent
);
2351 inherited keyEvent(ev
);
2352 if (ev
.eaten
) or (ev
.cancelled
) or (not enabled
) {or (not getFocused)} then exit
;
2353 if (ev
= 'M-F3') then
2355 if (not assigned(closeRequestCB
)) or (closeRequestCB(self
)) then
2357 uiRemoveWindow(self
);
2365 procedure TUITopWindow
.mouseEvent (var ev
: THMouseEvent
);
2368 vhgt
, ytop
: Integer;
2369 vwdt
, xend
: Integer;
2371 if (not enabled
) then exit
;
2372 if (mWidth
< 1) or (mHeight
< 1) then exit
;
2374 if (mDragScroll
= TXMode
.Drag
) then
2376 mX
+= ev
.x
-mDragStartX
;
2377 mY
+= ev
.y
-mDragStartY
;
2378 mDragStartX
:= ev
.x
;
2379 mDragStartY
:= ev
.y
;
2380 if (ev
.release
) and (ev
.but
= ev
.Left
) then mDragScroll
:= TXMode
.None
;
2385 if (mDragScroll
= TXMode
.VScroll
) then
2388 vhgt
:= mHeight
-mFrameHeight
*2;
2389 ytop
:= uiContext
.sbarPos(ly
, mFrameHeight
-1, vhgt
+2, 0, mFullSize
.h
)-vhgt
;
2390 mScrollY
:= nmax(0, ytop
);
2391 if (ev
.release
) and (ev
.but
= ev
.Left
) then mDragScroll
:= TXMode
.None
;
2396 if (mDragScroll
= TXMode
.HScroll
) then
2399 vwdt
:= mWidth
-mFrameWidth
*2;
2400 xend
:= uiContext
.sbarPos(lx
, mFrameWidth
+1, vwdt
-2, 0, mFullSize
.w
)-vwdt
;
2401 mScrollX
:= nmax(0, xend
);
2402 if (ev
.release
) and (ev
.but
= ev
.Left
) then mDragScroll
:= TXMode
.None
;
2407 if toLocal(ev
.x
, ev
.y
, lx
, ly
) then
2411 if (ly
< mFrameHeight
) then
2414 if (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
)) then
2416 //uiRemoveWindow(self);
2417 mWaitingClose
:= true;
2422 mDragScroll
:= TXMode
.Drag
;
2423 mDragStartX
:= ev
.x
;
2424 mDragStartY
:= ev
.y
;
2429 // check for vertical scrollbar
2430 if (lx
>= mWidth
-mFrameWidth
+1) and (ly
>= mFrameHeight
-1) and (ly
< mHeight
-mFrameHeight
+2) then
2432 vhgt
:= mHeight
-mFrameHeight
*2;
2433 if (mFullSize
.h
> vhgt
) then
2436 mDragScroll
:= TXMode
.VScroll
;
2438 ytop
:= uiContext
.sbarPos(ly
, mFrameHeight
-1, vhgt
+2, 0, mFullSize
.h
)-vhgt
;
2439 mScrollY
:= nmax(0, ytop
);
2443 // check for horizontal scrollbar
2444 if (ly
>= mHeight
-mFrameHeight
+1) and (lx
>= mFrameWidth
+1) and (lx
< mWidth
-mFrameWidth
-1) then
2446 vwdt
:= mWidth
-mFrameWidth
*2;
2447 if (mFullSize
.w
> vwdt
) then
2450 mDragScroll
:= TXMode
.HScroll
;
2452 xend
:= uiContext
.sbarPos(lx
, mFrameWidth
+1, vwdt
-2, 0, mFullSize
.w
)-vwdt
;
2453 mScrollX
:= nmax(0, xend
);
2458 if (lx
< mFrameWidth
) or (lx
>= mWidth
-mFrameWidth
) or (ly
>= mHeight
-mFrameHeight
) then
2461 mDragScroll
:= TXMode
.Drag
;
2462 mDragStartX
:= ev
.x
;
2463 mDragStartY
:= ev
.y
;
2469 if (ev
.release
) then
2471 if mWaitingClose
then
2473 if (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
)) then
2475 if (not assigned(closeRequestCB
)) or (closeRequestCB(self
)) then
2477 uiRemoveWindow(self
);
2480 mWaitingClose
:= false;
2489 if mWaitingClose
then
2491 mInClose
:= (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
));
2497 inherited mouseEvent(ev
);
2502 if (not ev
.motion
) and (mWaitingClose
) then begin ev
.eat(); mWaitingClose
:= false; exit
; end;
2507 // ////////////////////////////////////////////////////////////////////////// //
2508 constructor TUIBox
.Create (ahoriz
: Boolean);
2515 procedure TUIBox
.AfterConstruction ();
2519 mHAlign
:= -1; // left
2520 mCtl4Style
:= 'box';
2521 mDefSize
:= TLaySize
.Create(-1, -1);
2525 procedure TUIBox
.setCaption (const acap
: AnsiString);
2528 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mCaption
)+3, uiContext
.textHeight(mCaption
));
2532 procedure TUIBox
.setHasFrame (v
: Boolean);
2535 if (mHasFrame
) then begin mFrameWidth
:= 8; mFrameHeight
:= uiContext
.charHeight(#184); end else begin mFrameWidth
:= 0; mFrameHeight
:= 0; end;
2536 if (mHasFrame
) then mNoPad
:= true;
2540 function TUIBox
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2542 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2543 if (strEquCI1251(prname
, 'padding')) then
2545 if (mHoriz
) then mPadding
:= parseHPadding(par
, 0) else mPadding
:= parseVPadding(par
, 0);
2549 if (strEquCI1251(prname
, 'frame')) then
2551 setHasFrame(parseBool(par
));
2555 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) then
2557 setCaption(par
.expectIdOrStr(true));
2561 if (strEquCI1251(prname
, 'textalign')) or (strEquCI1251(prname
, 'text-align')) then
2563 mHAlign
:= parseHAlign(par
);
2567 if (strEquCI1251(prname
, 'children')) then
2573 result
:= inherited parseProperty(prname
, par
);
2577 procedure TUIBox
.drawControl (gx
, gy
: Integer);
2582 cidx
:= getColorIndex
;
2583 uiContext
.color
:= mBackColor
[cidx
];
2584 uiContext
.fillRect(gx
, gy
, mWidth
, mHeight
);
2588 drawFrame(gx
, gy
, 0, -1, mCaption
, false);
2589 //uiContext.color := mFrameColor[cidx];
2590 //uiContext.rect(gx+3, gy+3, mWidth-6, mHeight-6);
2592 else if (Length(mCaption
) > 0) then
2595 if (mHAlign
< 0) then xpos
:= 3
2596 else if (mHAlign
> 0) then xpos
:= mWidth
-mFrameWidth
*2-uiContext
.textWidth(mCaption
)
2597 else xpos
:= (mWidth
-mFrameWidth
*2-uiContext
.textWidth(mCaption
)) div 2;
2598 xpos
+= gx
+mFrameWidth
;
2600 setScissor(mFrameWidth
+1, 0, mWidth
-mFrameWidth
-2, uiContext
.textHeight(mCaption
));
2604 uiContext.color := mBackColor[cidx];
2605 uiContext.fillRect(xpos-3, gy, uiContext.textWidth(mCaption)+4, 8);
2608 uiContext
.color
:= mFrameTextColor
[cidx
];
2609 uiContext
.drawText(xpos
, gy
, mCaption
);
2614 procedure TUIBox
.mouseEvent (var ev
: THMouseEvent
);
2618 inherited mouseEvent(ev
);
2619 if (not ev
.eaten
) and (not ev
.cancelled
) and (enabled
) and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
2626 procedure TUIBox
.keyEvent (var ev
: THKeyEvent
);
2629 cur
, ctl
: TUIControl
;
2631 inherited keyEvent(ev
);
2632 if (ev
.eaten
) or (ev
.cancelled
) or (not ev
.press
) or (not enabled
) or (not getActive
) then exit
;
2633 if (Length(mChildren
) = 0) then exit
;
2634 if (mHoriz
) and (ev
= 'Left') then dir
:= -1
2635 else if (mHoriz
) and (ev
= 'Right') then dir
:= 1
2636 else if (not mHoriz
) and (ev
= 'Up') then dir
:= -1
2637 else if (not mHoriz
) and (ev
= 'Down') then dir
:= 1;
2638 if (dir
= 0) then exit
;
2640 cur
:= topLevel
.mFocused
;
2641 while (cur
<> nil) and (cur
.mParent
<> self
) do cur
:= cur
.mParent
;
2642 //if (cur = nil) then writeln('CUR: nil') else writeln('CUR: ', cur.className, '#', cur.id);
2643 if (dir
< 0) then ctl
:= findPrevFocus(cur
, true) else ctl
:= findNextFocus(cur
, true);
2644 //if (ctl = nil) then writeln('CTL: nil') else writeln('CTL: ', ctl.className, '#', ctl.id);
2645 if (ctl
<> nil) and (ctl
<> self
) then
2647 ctl
.focused
:= true;
2652 // ////////////////////////////////////////////////////////////////////////// //
2653 constructor TUIHBox
.Create ();
2658 procedure TUIHBox
.AfterConstruction ();
2665 // ////////////////////////////////////////////////////////////////////////// //
2666 constructor TUIVBox
.Create ();
2671 procedure TUIVBox
.AfterConstruction ();
2678 // ////////////////////////////////////////////////////////////////////////// //
2679 procedure TUISpan
.AfterConstruction ();
2685 mCtl4Style
:= 'span';
2686 mDefSize
:= TLaySize
.Create(-1, -1);
2690 function TUISpan
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2692 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2693 result
:= inherited parseProperty(prname
, par
);
2697 procedure TUISpan
.drawControl (gx
, gy
: Integer);
2702 // ////////////////////////////////////////////////////////////////////// //
2703 procedure TUILine
.AfterConstruction ();
2709 mCtl4Style
:= 'line';
2710 mDefSize
:= TLaySize
.Create(-1, -1);
2714 function TUILine
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2716 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2717 result
:= inherited parseProperty(prname
, par
);
2721 procedure TUILine
.layPrepare ();
2723 inherited layPrepare();
2724 if (mParent
<> nil) then mHoriz
:= not mParent
.mHoriz
;
2727 if (mLayDefSize
.w
< 0) then mLayDefSize
.w
:= 1;
2728 if (mLayDefSize
.h
< 0) then mLayDefSize
.h
:= 7;
2732 if (mLayDefSize
.w
< 0) then mLayDefSize
.w
:= 7;
2733 if (mLayDefSize
.h
< 0) then mLayDefSize
.h
:= 1;
2738 procedure TUILine
.drawControl (gx
, gy
: Integer);
2742 cidx
:= getColorIndex
;
2743 uiContext
.color
:= mTextColor
[cidx
];
2744 if mHoriz
then uiContext
.hline(gx
, gy
+(mHeight
div 2), mWidth
)
2745 else uiContext
.vline(gx
+(mWidth
div 2), gy
, mHeight
);
2749 // ////////////////////////////////////////////////////////////////////////// //
2750 procedure TUIStaticText
.AfterConstruction ();
2756 mHoriz
:= true; // nobody cares
2759 mCtl4Style
:= 'static';
2763 procedure TUIStaticText
.setText (const atext
: AnsiString);
2766 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
), uiContext
.textHeight(mText
));
2770 function TUIStaticText
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2772 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) or (strEquCI1251(prname
, 'text')) then
2774 setText(par
.expectIdOrStr(true));
2778 if (strEquCI1251(prname
, 'textalign')) or (strEquCI1251(prname
, 'text-align')) then
2780 parseTextAlign(par
, mHAlign
, mVAlign
);
2784 if (strEquCI1251(prname
, 'header')) then
2790 if (strEquCI1251(prname
, 'line')) then
2796 result
:= inherited parseProperty(prname
, par
);
2800 procedure TUIStaticText
.drawControl (gx
, gy
: Integer);
2802 xpos
, ypos
: Integer;
2805 cidx
:= getColorIndex
;
2806 uiContext
.color
:= mBackColor
[cidx
];
2807 uiContext
.fillRect(gx
, gy
, mWidth
, mHeight
);
2809 if (mHAlign
< 0) then xpos
:= 0
2810 else if (mHAlign
> 0) then xpos
:= mWidth
-uiContext
.textWidth(mText
)
2811 else xpos
:= (mWidth
-uiContext
.textWidth(mText
)) div 2;
2813 if (Length(mText
) > 0) then
2815 if (mHeader
) then uiContext
.color
:= mFrameTextColor
[cidx
] else uiContext
.color
:= mTextColor
[cidx
];
2817 if (mVAlign
< 0) then ypos
:= 0
2818 else if (mVAlign
> 0) then ypos
:= mHeight
-uiContext
.textHeight(mText
)
2819 else ypos
:= (mHeight
-uiContext
.textHeight(mText
)) div 2;
2821 uiContext
.drawText(gx
+xpos
, gy
+ypos
, mText
);
2826 if (mHeader
) then uiContext
.color
:= mFrameColor
[cidx
] else uiContext
.color
:= mTextColor
[cidx
];
2828 if (mVAlign
< 0) then ypos
:= 0
2829 else if (mVAlign
> 0) then ypos
:= mHeight
-1
2830 else ypos
:= (mHeight
div 2);
2833 if (Length(mText
) = 0) then
2835 uiContext
.hline(gx
, ypos
, mWidth
);
2839 uiContext
.hline(gx
, ypos
, xpos
-1);
2840 uiContext
.hline(gx
+xpos
+uiContext
.textWidth(mText
), ypos
, mWidth
);
2846 // ////////////////////////////////////////////////////////////////////////// //
2847 procedure TUITextLabel
.AfterConstruction ();
2853 mCtl4Style
:= 'label';
2858 procedure TUITextLabel
.cacheStyle (root
: TUIStyle
);
2860 inherited cacheStyle(root
);
2862 mHotColor
[ClrIdxActive
] := root
.get('hot-color', 'active', mCtl4Style
).asRGBADef(TGxRGBA
.Create(0, 128, 0));
2864 mHotColor
[ClrIdxDisabled
] := root
.get('hot-color', 'disabled', mCtl4Style
).asRGBADef(TGxRGBA
.Create(0, 64, 0));
2866 mHotColor
[ClrIdxInactive
] := root
.get('hot-color', 'inactive', mCtl4Style
).asRGBADef(TGxRGBA
.Create(0, 64, 0));
2870 procedure TUITextLabel
.setText (const s
: AnsiString);
2878 while (f
<= Length(s
)) do
2880 if (s
[f
] = '\\') then
2883 if (f
<= Length(s
)) then mText
+= s
[f
];
2886 else if (s
[f
] = '~') then
2889 if (f
<= Length(s
)) then
2891 if (mHotChar
= #0) then
2894 mHotOfs
:= Length(mText
);
2906 // fix hotchar offset
2907 if (mHotChar
<> #0) and (mHotOfs
> 0) then
2909 mHotOfs
:= uiContext
.textWidth(Copy(mText
, 1, mHotOfs
+1))-uiContext
.charWidth(mText
[mHotOfs
+1]);
2912 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
), uiContext
.textHeight(mText
));
2916 function TUITextLabel
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2918 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) or (strEquCI1251(prname
, 'text')) then
2920 setText(par
.expectIdOrStr(true));
2924 if (strEquCI1251(prname
, 'link')) then
2926 mLinkId
:= par
.expectIdOrStr(true);
2930 if (strEquCI1251(prname
, 'textalign')) or (strEquCI1251(prname
, 'text-align')) then
2932 parseTextAlign(par
, mHAlign
, mVAlign
);
2936 result
:= inherited parseProperty(prname
, par
);
2940 procedure TUITextLabel
.drawControl (gx
, gy
: Integer);
2942 xpos
, ypos
: Integer;
2945 cidx
:= getColorIndex
;
2946 uiContext
.color
:= mBackColor
[cidx
];
2947 uiContext
.fillRect(gx
, gy
, mWidth
, mHeight
);
2948 if (Length(mText
) > 0) then
2950 if (mHAlign
< 0) then xpos
:= 0
2951 else if (mHAlign
> 0) then xpos
:= mWidth
-uiContext
.textWidth(mText
)
2952 else xpos
:= (mWidth
-uiContext
.textWidth(mText
)) div 2;
2954 if (mVAlign
< 0) then ypos
:= 0
2955 else if (mVAlign
> 0) then ypos
:= mHeight
-uiContext
.textHeight(mText
)
2956 else ypos
:= (mHeight
-uiContext
.textHeight(mText
)) div 2;
2958 uiContext
.color
:= mTextColor
[cidx
];
2959 uiContext
.drawText(gx
+xpos
, gy
+ypos
, mText
);
2961 if (Length(mLinkId
) > 0) and (mHotChar
<> #0) and (mHotChar
<> ' ') then
2963 uiContext
.color
:= mHotColor
[cidx
];
2964 uiContext
.drawChar(gx
+xpos
+mHotOfs
, gy
+ypos
, mHotChar
);
2970 procedure TUITextLabel
.mouseEvent (var ev
: THMouseEvent
);
2974 inherited mouseEvent(ev
);
2975 if (not ev
.eaten
) and (not ev
.cancelled
) and (enabled
) and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
2982 procedure TUITextLabel
.doAction ();
2986 if (assigned(actionCB
)) then
2992 ctl
:= topLevel
[mLinkId
];
2993 if (ctl
<> nil) then
2995 if (ctl
.canFocus
) then ctl
.focused
:= true;
3001 procedure TUITextLabel
.keyEventPost (var ev
: THKeyEvent
);
3003 if (not enabled
) then exit
;
3004 if (mHotChar
= #0) then exit
;
3005 if (ev
.eaten
) or (ev
.cancelled
) or (not ev
.press
) then exit
;
3006 if (ev
.kstate
<> ev
.ModAlt
) then exit
;
3007 if (not ev
.isHot(mHotChar
)) then exit
;
3009 if (canFocus
) then focused
:= true;
3014 // ////////////////////////////////////////////////////////////////////////// //
3015 procedure TUIButton
.AfterConstruction ();
3022 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+uiContext
.textWidth('[ ]'), uiContext
.textHeight(mText
));
3023 mCtl4Style
:= 'button';
3024 mSkipLayPrepare
:= false;
3025 mAddMarkers
:= false;
3026 mHideMarkers
:= false;
3030 procedure TUIButton
.cacheStyle (root
: TUIStyle
);
3034 inherited cacheStyle(root
);
3036 sz
:= nmax(0, root
.get('shadow-size', 'active', mCtl4Style
).asInt(0));
3037 sz
:= nmax(sz
, root
.get('shadow-size', 'disabled', mCtl4Style
).asInt(0));
3038 sz
:= nmax(sz
, root
.get('shadow-size', 'inactive', mCtl4Style
).asInt(0));
3041 mAddMarkers
:= root
.get('add-markers', 'active', mCtl4Style
).asBool(false);
3042 mAddMarkers
:= mAddMarkers
or root
.get('add-markers', 'disabled', mCtl4Style
).asBool(false);
3043 mAddMarkers
:= mAddMarkers
or root
.get('add-markers', 'inactive', mCtl4Style
).asBool(false);
3045 mHideMarkers
:= root
.get('hide-markers', 'active', mCtl4Style
).asBool(false);
3046 mHideMarkers
:= mHideMarkers
or root
.get('hide-markers', 'disabled', mCtl4Style
).asBool(false);
3047 mHideMarkers
:= mHideMarkers
or root
.get('hide-markers', 'inactive', mCtl4Style
).asBool(false);
3051 procedure TUIButton
.setText (const s
: AnsiString);
3053 inherited setText(s
);
3054 if (mHideMarkers
) then
3056 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+10, uiContext
.textHeight(mText
));
3058 else if (mAddMarkers
) then
3060 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+uiContext
.textWidth('[<>]'), uiContext
.textHeight(mText
));
3064 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+uiContext
.textWidth('<>'), uiContext
.textHeight(mText
));
3069 procedure TUIButton
.layPrepare ();
3074 if (not mSkipLayPrepare
) then
3077 if (ods
.w
<> 0) or (ods
.h
<> 0) then
3079 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
), uiContext
.textHeight(mText
));
3080 if (mHideMarkers
) then
3084 else if (mAddMarkers
) then
3086 if (mDefault
) then ww
:= uiContext
.textWidth('[< >]')
3087 else if (mCancel
) then ww
:= uiContext
.textWidth('[{ }]')
3088 else ww
:= uiContext
.textWidth('[ ]');
3092 ww
:= nmax(0, uiContext
.textWidth('< >'));
3093 ww
:= nmax(ww
, uiContext
.textWidth('{ }'));
3094 ww
:= nmax(ww
, uiContext
.textWidth('[ ]'));
3096 mDefSize
.w
+= ww
+mShadowSize
;
3097 mDefSize
.h
+= mShadowSize
;
3102 ods
:= TLaySize
.Create(0, 0); // fpc is dumb!
3104 inherited layPrepare();
3105 if (not mSkipLayPrepare
) then mDefSize
:= ods
;
3109 procedure TUIButton
.blurred ();
3115 procedure TUIButton
.drawControl (gx
, gy
: Integer);
3118 xpos
, ypos
, xofsl
, xofsr
{, sofs}: Integer;
3121 lstr
, rstr
: AnsiString;
3123 cidx
:= getColorIndex
;
3125 wdt
:= mWidth
-mShadowSize
;
3126 hgt
:= mHeight
-mShadowSize
;
3127 if (mPushed
) {or (cidx = ClrIdxActive)} then
3129 //sofs := mShadowSize;
3136 if (mShadowSize
> 0) then
3138 uiContext
.darkenRect(gx
+mShadowSize
, gy
+hgt
, wdt
, mShadowSize
, 96);
3139 uiContext
.darkenRect(gx
+wdt
, gy
+mShadowSize
, mShadowSize
, hgt
-mShadowSize
, 96);
3143 uiContext
.color
:= mBackColor
[cidx
];
3144 //setScissor(sofs, sofs, wdt, hgt);
3145 uiContext
.fillRect(gx
, gy
, wdt
, hgt
);
3147 if (mVAlign
< 0) then ypos
:= 0
3148 else if (mVAlign
> 0) then ypos
:= hgt
-uiContext
.textHeight(mText
)
3149 else ypos
:= (hgt
-uiContext
.textHeight(mText
)) div 2;
3152 uiContext
.color
:= mTextColor
[cidx
];
3154 if (mHideMarkers
) then
3161 if (mAddMarkers
) then
3163 if (mDefault
) then begin lstr
:= '[< '; rstr
:= ' >]'; end
3164 else if (mCancel
) then begin lstr
:= '[{ '; rstr
:= ' }]'; end
3165 else begin lstr
:= '[ '; rstr
:= ' ]'; end;
3166 xofsl
:= uiContext
.textWidth(lstr
);
3167 xofsr
:= uiContext
.textWidth(rstr
);
3168 uiContext
.drawText(gx
, ypos
, lstr
);
3169 uiContext
.drawText(gx
+wdt
-uiContext
.textWidth(rstr
), ypos
, rstr
);
3173 xofsl
:= nmax(0, uiContext
.textWidth('< '));
3174 xofsl
:= nmax(xofsl
, uiContext
.textWidth('{ '));
3175 xofsl
:= nmax(xofsl
, uiContext
.textWidth('[ '));
3176 xofsr
:= nmax(0, uiContext
.textWidth(' >'));
3177 xofsr
:= nmax(xofsr
, uiContext
.textWidth(' }'));
3178 xofsr
:= nmax(xofsr
, uiContext
.textWidth(' ]'));
3179 if (mDefault
) then begin lch
:= '<'; rch
:= '>'; end
3180 else if (mCancel
) then begin lch
:= '{'; rch
:= '}'; end
3181 else begin lch
:= '['; rch
:= ']'; end;
3182 uiContext
.drawChar(gx
, ypos
, lch
);
3183 uiContext
.drawChar(gx
+wdt
-uiContext
.charWidth(rch
), ypos
, rch
);
3187 if (Length(mText
) > 0) then
3189 if (mHAlign
< 0) then xpos
:= 0
3190 else begin xpos
:= wdt
-xofsl
-xofsr
-uiContext
.textWidth(mText
); if (mHAlign
= 0) then xpos
:= xpos
div 2; end;
3193 //setScissor(xofsl+sofs, sofs, wdt-xofsl-xofsr, hgt);
3194 uiContext
.drawText(gx
+xpos
, ypos
, mText
);
3196 if (mHotChar
<> #0) and (mHotChar
<> ' ') then
3198 uiContext
.color
:= mHotColor
[cidx
];
3199 uiContext
.drawChar(gx
+xpos
+mHotOfs
, ypos
, mHotChar
);
3205 procedure TUIButton
.mouseEvent (var ev
: THMouseEvent
);
3209 inherited mouseEvent(ev
);
3210 if (uiGrabCtl
= self
) then
3213 mPushed
:= toLocal(ev
.x
, ev
.y
, lx
, ly
);
3214 if (ev
= '-lmb') and focused
and mPushed
then
3221 if (ev
.eaten
) or (ev
.cancelled
) or (not enabled
) or not focused
then exit
;
3227 procedure TUIButton
.keyEvent (var ev
: THKeyEvent
);
3229 inherited keyEvent(ev
);
3230 if (not ev
.eaten
) and (not ev
.cancelled
) and (enabled
) then
3232 if (ev
= '+Enter') or (ev
= '+Space') then
3239 if (focused
) and ((ev
= '-Enter') or (ev
= '-Space')) then
3257 // ////////////////////////////////////////////////////////////////////////// //
3258 procedure TUIButtonRound
.AfterConstruction ();
3264 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+8*2, uiContext
.textHeight(mText
)+2);
3265 mCtl4Style
:= 'button-round';
3266 mSkipLayPrepare
:= true;
3270 procedure TUIButtonRound
.setText (const s
: AnsiString);
3272 inherited setText(s
);
3273 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+8*2, uiContext
.textHeight(mText
)+2);
3277 procedure TUIButtonRound
.layPrepare ();
3282 if (ods
.w
<> 0) or (ods
.h
<> 0) then
3284 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+8*2, uiContext
.textHeight(mText
)+2);
3286 inherited layPrepare();
3291 procedure TUIButtonRound
.drawControl (gx
, gy
: Integer);
3293 xpos
, ypos
: Integer;
3296 cidx
:= getColorIndex
;
3298 uiContext
.color
:= mBackColor
[cidx
];
3299 uiContext
.fillRect(gx
+1, gy
, mWidth
-2, mHeight
);
3300 uiContext
.fillRect(gx
, gy
+1, 1, mHeight
-2);
3301 uiContext
.fillRect(gx
+mWidth
-1, gy
+1, 1, mHeight
-2);
3303 if (Length(mText
) > 0) then
3305 if (mHAlign
< 0) then xpos
:= 0
3306 else if (mHAlign
> 0) then xpos
:= mWidth
-uiContext
.textWidth(mText
)
3307 else xpos
:= (mWidth
-uiContext
.textWidth(mText
)) div 2;
3309 if (mVAlign
< 0) then ypos
:= 0
3310 else if (mVAlign
> 0) then ypos
:= mHeight
-uiContext
.textHeight(mText
)
3311 else ypos
:= (mHeight
-uiContext
.textHeight(mText
)) div 2;
3313 setScissor(8, 0, mWidth
-16, mHeight
);
3314 uiContext
.color
:= mTextColor
[cidx
];
3315 uiContext
.drawText(gx
+xpos
+8, gy
+ypos
, mText
);
3317 if (mHotChar
<> #0) and (mHotChar
<> ' ') then
3319 uiContext
.color
:= mHotColor
[cidx
];
3320 uiContext
.drawChar(gx
+xpos
+8+mHotOfs
, gy
+ypos
, mHotChar
);
3326 // ////////////////////////////////////////////////////////////////////////// //
3327 procedure TUISwitchBox
.AfterConstruction ();
3333 mIcon
:= TGxContext
.TMarkIcon
.Checkbox
;
3334 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+3+uiContext
.iconMarkWidth(mIcon
), nmax(uiContext
.iconMarkHeight(mIcon
), uiContext
.textHeight(mText
)));
3335 mCtl4Style
:= 'switchbox';
3337 mBoolVar
:= @mChecked
;
3341 procedure TUISwitchBox
.cacheStyle (root
: TUIStyle
);
3343 inherited cacheStyle(root
);
3345 mSwitchColor
[ClrIdxActive
] := root
.get('switch-color', 'active', mCtl4Style
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
3347 mSwitchColor
[ClrIdxDisabled
] := root
.get('switch-color', 'disabled', mCtl4Style
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
3349 mSwitchColor
[ClrIdxInactive
] := root
.get('switch-color', 'inactive', mCtl4Style
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
3353 procedure TUISwitchBox
.setText (const s
: AnsiString);
3355 inherited setText(s
);
3356 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+3+uiContext
.iconMarkWidth(mIcon
), nmax(uiContext
.iconMarkHeight(mIcon
), uiContext
.textHeight(mText
)));
3360 function TUISwitchBox
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
3362 if (strEquCI1251(prname
, 'checked')) then
3368 result
:= inherited parseProperty(prname
, par
);
3372 function TUISwitchBox
.getChecked (): Boolean;
3374 if (mBoolVar
<> nil) then result
:= mBoolVar
^ else result
:= false;
3378 procedure TUISwitchBox
.setVar (pvar
: PBoolean);
3380 if (pvar
= nil) then pvar
:= @mChecked
;
3381 if (pvar
<> mBoolVar
) then
3384 setChecked(mBoolVar
^);
3389 procedure TUISwitchBox
.drawControl (gx
, gy
: Integer);
3391 xpos
, ypos
, iwdt
, dy
: Integer;
3394 cidx
:= getColorIndex
;
3396 iwdt
:= uiContext
.iconMarkWidth(mIcon
);
3397 if (mHAlign
< 0) then xpos
:= 0
3398 else if (mHAlign
> 0) then xpos
:= mWidth
-(uiContext
.textWidth(mText
)+3+iwdt
)
3399 else xpos
:= (mWidth
-(uiContext
.textWidth(mText
)+3+iwdt
)) div 2;
3401 if (mVAlign
< 0) then ypos
:= 0
3402 else if (mVAlign
> 0) then ypos
:= mHeight
-uiContext
.textHeight(mText
)
3403 else ypos
:= (mHeight
-uiContext
.textHeight(mText
)) div 2;
3405 uiContext
.color
:= mBackColor
[cidx
];
3406 uiContext
.fillRect(gx
, gy
, mWidth
, mHeight
);
3408 uiContext
.color
:= mSwitchColor
[cidx
];
3409 if (uiContext
.iconMarkHeight(mIcon
) < uiContext
.textHeight(mText
)) then
3411 case uiContext
.textHeight(mText
) of
3416 uiContext
.drawIconMark(mIcon
, gx
, gy
+ypos
+uiContext
.textHeight(mText
)-uiContext
.iconMarkHeight(mIcon
)-dy
, checked
);
3420 uiContext
.drawIconMark(mIcon
, gx
, gy
, checked
);
3423 uiContext
.color
:= mTextColor
[cidx
];
3424 uiContext
.drawText(gx
+xpos
+3+iwdt
, gy
+ypos
, mText
);
3426 if (mHotChar
<> #0) and (mHotChar
<> ' ') then
3428 uiContext
.color
:= mHotColor
[cidx
];
3429 uiContext
.drawChar(gx
+xpos
+3+iwdt
+mHotOfs
, gy
+ypos
, mHotChar
);
3434 procedure TUISwitchBox
.mouseEvent (var ev
: THMouseEvent
);
3438 inherited mouseEvent(ev
);
3439 if (uiGrabCtl
= self
) then
3442 if (ev
= '-lmb') and focused
and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
3448 if (ev
.eaten
) or (ev
.cancelled
) or (not enabled
) or not focused
then exit
;
3453 procedure TUISwitchBox
.keyEvent (var ev
: THKeyEvent
);
3455 inherited keyEvent(ev
);
3456 if (not ev
.eaten
) and (not ev
.cancelled
) and (enabled
) then
3458 if (ev
= 'Space') then
3468 // ////////////////////////////////////////////////////////////////////////// //
3469 procedure TUICheckBox
.AfterConstruction ();
3473 mBoolVar
:= @mChecked
;
3474 mIcon
:= TGxContext
.TMarkIcon
.Checkbox
;
3479 procedure TUICheckBox
.setChecked (v
: Boolean);
3485 procedure TUICheckBox
.doAction ();
3487 if (assigned(actionCB
)) then
3493 setChecked(not getChecked
);
3498 // ////////////////////////////////////////////////////////////////////////// //
3499 procedure TUIRadioBox
.AfterConstruction ();
3503 mBoolVar
:= @mChecked
;
3505 mIcon
:= TGxContext
.TMarkIcon
.Radiobox
;
3510 function TUIRadioBox
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
3512 if (strEquCI1251(prname
, 'group')) then
3514 mRadioGroup
:= par
.expectIdOrStr(true);
3515 if (getChecked
) then setChecked(true);
3519 if (strEquCI1251(prname
, 'checked')) then
3525 result
:= inherited parseProperty(prname
, par
);
3529 procedure TUIRadioBox
.setChecked (v
: Boolean);
3531 function resetGroup (ctl
: TUIControl
): Boolean;
3534 if (ctl
<> self
) and (ctl
is TUIRadioBox
) and (TUIRadioBox(ctl
).mRadioGroup
= mRadioGroup
) then
3536 TUIRadioBox(ctl
).mBoolVar
^ := false;
3542 if v
then topLevel
.forEachControl(resetGroup
);
3546 procedure TUIRadioBox
.doAction ();
3548 if (assigned(actionCB
)) then
3559 // ////////////////////////////////////////////////////////////////////////// //
3561 oldFocus
: procedure () = nil;
3562 oldBlur
: procedure () = nil;
3564 procedure onWinFocus (); begin uiFocus(); if (assigned(oldFocus
)) then oldFocus(); end;
3565 procedure onWinBlur (); begin fuiResetKMState(true); uiBlur(); if (assigned(oldBlur
)) then oldBlur(); end;
3568 registerCtlClass(TUIHBox
, 'hbox');
3569 registerCtlClass(TUIVBox
, 'vbox');
3570 registerCtlClass(TUISpan
, 'span');
3571 registerCtlClass(TUILine
, 'line');
3572 registerCtlClass(TUITextLabel
, 'label');
3573 registerCtlClass(TUIStaticText
, 'static');
3574 registerCtlClass(TUIButtonRound
, 'round-button');
3575 registerCtlClass(TUIButton
, 'button');
3576 registerCtlClass(TUICheckBox
, 'checkbox');
3577 registerCtlClass(TUIRadioBox
, 'radiobox');
3579 oldFocus
:= winFocusCB
;
3580 oldBlur
:= winBlurCB
;
3581 winFocusCB
:= onWinFocus
;
3582 winBlurCB
:= onWinBlur
;