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, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 {$INCLUDE ../shared/a_modes.inc}
25 fui_common
, fui_events
, fui_style
,
30 // ////////////////////////////////////////////////////////////////////////// //
32 TUIControlClass
= class of TUIControl
;
36 type TActionCB
= procedure (me
: TUIControl
);
37 type TCloseRequestCB
= function (me
: TUIControl
): Boolean; // top-level windows will call this before closing with icon/keyboard
39 // return `true` to stop
40 type TCtlEnumCB
= function (ctl
: TUIControl
): Boolean is nested
;
43 const ClrIdxActive
= 0;
44 const ClrIdxDisabled
= 1;
45 const ClrIdxInactive
= 2;
53 mWidth
, mHeight
: Integer;
54 mFrameWidth
, mFrameHeight
: Integer;
55 mScrollX
, mScrollY
: Integer;
58 mChildren
: array of TUIControl
;
59 mFocused
: TUIControl
; // valid only for top-level controls
60 mEscClose
: Boolean; // valid only for top-level controls
65 mStyleLoaded
: Boolean;
66 mCtl4Style
: AnsiString;
67 mBackColor
: array[0..ClrIdxMax
] of TGxRGBA
;
68 mTextColor
: array[0..ClrIdxMax
] of TGxRGBA
;
69 mFrameColor
: array[0..ClrIdxMax
] of TGxRGBA
;
70 mFrameTextColor
: array[0..ClrIdxMax
] of TGxRGBA
;
71 mFrameIconColor
: array[0..ClrIdxMax
] of TGxRGBA
;
72 mSBarFullColor
: array[0..ClrIdxMax
] of TGxRGBA
;
73 mSBarEmptyColor
: array[0..ClrIdxMax
] of TGxRGBA
;
74 mDarken
: array[0..ClrIdxMax
] of Integer; // >255: none
77 procedure updateStyle (); virtual;
78 procedure cacheStyle (root
: TUIStyle
); virtual;
79 function getColorIndex (): Integer; inline;
82 function getEnabled (): Boolean;
83 procedure setEnabled (v
: Boolean); inline;
85 function getFocused (): Boolean; inline;
86 procedure setFocused (v
: Boolean); inline;
88 function getActive (): Boolean; inline;
90 function getCanFocus (): Boolean; inline;
92 function isMyChild (ctl
: TUIControl
): Boolean;
94 function findFirstFocus (): TUIControl
;
95 function findLastFocus (): TUIControl
;
97 function findNextFocus (cur
: TUIControl
; wrap
: Boolean): TUIControl
;
98 function findPrevFocus (cur
: TUIControl
; wrap
: Boolean): TUIControl
;
100 function findCancelControl (): TUIControl
;
101 function findDefaulControl (): TUIControl
;
103 function findControlById (const aid
: AnsiString): TUIControl
;
105 procedure activated (); virtual;
106 procedure blurred (); virtual;
108 procedure calcFullClientSize ();
110 procedure drawFrame (gx
, gy
, resx
, thalign
: Integer; const text: AnsiString; dbl
: Boolean);
113 var savedClip
: TGxRect
; // valid only in `draw*()` calls
114 //WARNING! do not call scissor functions outside `.draw*()` API!
115 // set scissor to this rect (in local coords)
116 procedure setScissor (lx
, ly
, lw
, lh
: Integer); // valid only in `draw*()` calls
117 procedure resetScissor (); inline; // only client area, w/o frame
118 procedure resetScissorNC (); inline; // full drawing area, with frame
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 onEvent (var ev
: TFUIEvent
); virtual; // general dispatcher
233 procedure mouseEvent (var ev
: TFUIEvent
); virtual;
234 procedure mouseEventSink (var ev
: TFUIEvent
); virtual;
235 procedure mouseEventBubble (var ev
: TFUIEvent
); virtual;
237 procedure keyEvent (var ev
: TFUIEvent
); virtual;
238 procedure keyEventSink (var ev
: TFUIEvent
); virtual;
239 procedure keyEventBubble (var ev
: TFUIEvent
); virtual;
241 function prevSibling (): TUIControl
;
242 function nextSibling (): TUIControl
;
243 function firstChild (): TUIControl
; inline;
244 function lastChild (): TUIControl
; inline;
246 procedure appendChild (ctl
: TUIControl
); virtual;
248 function setActionCBFor (const aid
: AnsiString; cb
: TActionCB
): TActionCB
; // returns previous cb
250 function forEachChildren (cb
: TCtlEnumCB
): TUIControl
; // doesn't recurse
251 function forEachControl (cb
: TCtlEnumCB
; includeSelf
: Boolean=true): TUIControl
;
253 procedure close (); // this closes *top-level* control
256 property id
: AnsiString read mId write mId
;
257 property styleId
: AnsiString read mStyleId
;
258 property scrollX
: Integer read mScrollX write mScrollX
;
259 property scrollY
: Integer read mScrollY write mScrollY
;
260 property x0
: Integer read mX write mX
;
261 property y0
: Integer read mY write mY
;
262 property width
: Integer read mWidth write mWidth
;
263 property height
: Integer read mHeight write mHeight
;
264 property enabled
: Boolean read getEnabled write setEnabled
;
265 property parent
: TUIControl read mParent
;
266 property focused
: Boolean read getFocused write setFocused
;
267 property active
: Boolean read getActive
;
268 property escClose
: Boolean read mEscClose write mEscClose
;
269 property cancel
: Boolean read mCancel write mCancel
;
270 property defctl
: Boolean read mDefault write mDefault
;
271 property canFocus
: Boolean read getCanFocus write mCanFocus
;
272 property ctlById
[const aid
: AnsiString]: TUIControl read findControlById
; default
;
276 TUITopWindow
= class(TUIControl
)
278 type TXMode
= (None
, Drag
, VScroll
, HScroll
);
283 mDragStartX
, mDragStartY
: Integer;
284 mWaitingClose
: Boolean;
286 mFreeOnClose
: Boolean; // default: false
287 mDoCenter
: Boolean; // after layouting
288 mFitToScreen
: Boolean;
291 procedure activated (); override;
292 procedure blurred (); override;
295 closeCB
: TActionCB
; // called after window was removed from ui window list
298 constructor Create (const atitle
: AnsiString);
300 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
302 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
304 procedure flFitToScreen (); // call this before layouting
306 procedure centerInScreen ();
308 // `sx` and `sy` are screen coordinates
309 procedure drawControl (gx
, gy
: Integer); override;
310 procedure drawControlPost (gx
, gy
: Integer); override;
312 procedure keyEventBubble (var ev
: TFUIEvent
); override; // returns `true` if event was eaten
313 procedure mouseEvent (var ev
: TFUIEvent
); override; // returns `true` if event was eaten
316 property freeOnClose
: Boolean read mFreeOnClose write mFreeOnClose
;
317 property fitToScreen
: Boolean read mFitToScreen write mFitToScreen
;
320 // ////////////////////////////////////////////////////////////////////// //
321 TUIBox
= class(TUIControl
)
324 mCaption
: AnsiString;
325 mHAlign
: Integer; // -1: left; 0: center; 1: right; default: left
328 procedure setCaption (const acap
: AnsiString);
329 procedure setHasFrame (v
: Boolean);
332 constructor Create (ahoriz
: Boolean);
334 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
336 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
338 procedure drawControl (gx
, gy
: Integer); override;
340 procedure mouseEvent (var ev
: TFUIEvent
); override;
341 procedure keyEvent (var ev
: TFUIEvent
); override;
344 property caption
: AnsiString read mCaption write setCaption
;
345 property hasFrame
: Boolean read mHasFrame write setHasFrame
;
346 property captionAlign
: Integer read mHAlign write mHAlign
;
349 TUIHBox
= class(TUIBox
)
351 constructor Create ();
353 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
356 TUIVBox
= class(TUIBox
)
358 constructor Create ();
360 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
363 // ////////////////////////////////////////////////////////////////////// //
364 TUISpan
= class(TUIControl
)
366 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
368 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
371 // ////////////////////////////////////////////////////////////////////// //
372 TUILine
= class(TUIControl
)
374 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
376 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
378 procedure layPrepare (); override; // called before registering control in layouter
380 procedure drawControl (gx
, gy
: Integer); override;
383 // ////////////////////////////////////////////////////////////////////// //
384 TUIStaticText
= class(TUIControl
)
387 mHAlign
: Integer; // -1: left; 0: center; 1: right; default: left
388 mVAlign
: Integer; // -1: top; 0: center; 1: bottom; default: center
389 mHeader
: Boolean; // true: draw with frame text color
390 mLine
: Boolean; // true: draw horizontal line
393 procedure setText (const atext
: AnsiString);
396 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
398 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
400 procedure drawControl (gx
, gy
: Integer); override;
403 property text: AnsiString read mText write setText
;
404 property halign
: Integer read mHAlign write mHAlign
;
405 property valign
: Integer read mVAlign write mVAlign
;
406 property header
: Boolean read mHeader write mHeader
;
407 property line
: Boolean read mLine write mLine
;
410 // ////////////////////////////////////////////////////////////////////// //
411 TUITextLabel
= class(TUIControl
)
414 mHAlign
: Integer; // -1: left; 0: center; 1: right; default: left
415 mVAlign
: Integer; // -1: top; 0: center; 1: bottom; default: center
417 mHotOfs
: Integer; // from text start, in pixels
418 mHotColor
: array[0..ClrIdxMax
] of TGxRGBA
;
419 mLinkId
: AnsiString; // linked control
422 procedure cacheStyle (root
: TUIStyle
); override;
424 procedure setText (const s
: AnsiString); virtual;
427 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
429 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
431 procedure doAction (); override;
433 procedure drawControl (gx
, gy
: Integer); override;
435 procedure mouseEvent (var ev
: TFUIEvent
); override;
436 procedure keyEventBubble (var ev
: TFUIEvent
); override;
439 property text: AnsiString read mText write setText
;
440 property halign
: Integer read mHAlign write mHAlign
;
441 property valign
: Integer read mVAlign write mVAlign
;
444 // ////////////////////////////////////////////////////////////////////// //
445 TUIButton
= class(TUITextLabel
)
447 mSkipLayPrepare
: Boolean;
448 mShadowSize
: Integer;
449 mAddMarkers
: Boolean;
450 mHideMarkers
: Boolean;
454 procedure setText (const s
: AnsiString); override;
456 procedure cacheStyle (root
: TUIStyle
); override;
458 procedure blurred (); override;
461 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
463 procedure layPrepare (); override; // called before registering control in layouter
465 procedure drawControl (gx
, gy
: Integer); override;
467 procedure mouseEvent (var ev
: TFUIEvent
); override;
468 procedure keyEvent (var ev
: TFUIEvent
); override;
471 // ////////////////////////////////////////////////////////////////////// //
472 TUIButtonRound
= class(TUIButton
)
474 procedure setText (const s
: AnsiString); override;
477 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
479 procedure layPrepare (); override; // called before registering control in layouter
481 procedure drawControl (gx
, gy
: Integer); override;
484 // ////////////////////////////////////////////////////////////////////// //
485 TUISwitchBox
= class(TUITextLabel
)
489 mIcon
: TGxContext
.TMarkIcon
;
490 mSwitchColor
: array[0..ClrIdxMax
] of TGxRGBA
;
493 procedure cacheStyle (root
: TUIStyle
); override;
495 procedure setText (const s
: AnsiString); override;
497 function getChecked (): Boolean; virtual;
498 procedure setChecked (v
: Boolean); virtual; abstract;
501 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
503 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
505 procedure drawControl (gx
, gy
: Integer); override;
507 procedure mouseEvent (var ev
: TFUIEvent
); override;
508 procedure keyEvent (var ev
: TFUIEvent
); override;
510 procedure setVar (pvar
: PBoolean);
513 property checked
: Boolean read getChecked write setChecked
;
516 TUICheckBox
= class(TUISwitchBox
)
518 procedure setChecked (v
: Boolean); override;
521 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
523 procedure doAction (); override;
526 TUIRadioBox
= class(TUISwitchBox
)
528 mRadioGroup
: AnsiString;
531 procedure setChecked (v
: Boolean); override;
534 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
536 function parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean; override;
538 procedure doAction (); override;
541 property radioGroup
: AnsiString read mRadioGroup write mRadioGroup
; //FIXME
545 // ////////////////////////////////////////////////////////////////////////// //
546 procedure uiDispatchEvent (var evt
: TFUIEvent
);
549 procedure uiFocus ();
553 // ////////////////////////////////////////////////////////////////////////// //
554 procedure uiAddWindow (ctl
: TUIControl
);
555 procedure uiRemoveWindow (ctl
: TUIControl
); // will free window if `mFreeOnClose` is `true`
556 function uiVisibleWindow (ctl
: TUIControl
): Boolean;
558 // this can return `nil` or disabled control
559 function uiGetFocusedCtl (): TUIControl
;
561 procedure uiUpdateStyles ();
564 // ////////////////////////////////////////////////////////////////////////// //
566 procedure uiLayoutCtl (ctl
: TUIControl
);
569 // ////////////////////////////////////////////////////////////////////////// //
570 procedure uiInitialize ();
571 procedure uiDeinitialize ();
574 // ////////////////////////////////////////////////////////////////////////// //
576 fuiRenderScale
: Single = 1.0;
577 uiContext
: TGxContext
= nil;
588 uiInsideDispatcher
: Boolean = false;
589 uiTopList
: array of TUIControl
= nil;
590 uiGrabCtl
: TUIControl
= nil;
593 // ////////////////////////////////////////////////////////////////////////// //
594 procedure uiDeinitialize ();
596 FreeAndNil(uiContext
);
600 procedure uiInitialize ();
602 if (uiContext
<> nil) then raise Exception
.Create('FlexUI already initialized');
603 uiContext
:= gxCreateContext();
607 // ////////////////////////////////////////////////////////////////////////// //
609 ctlsToKill
: array of TUIControl
= nil;
612 procedure scheduleKill (ctl
: TUIControl
);
616 if (ctl
= nil) then exit
;
618 for f
:= 0 to High(ctlsToKill
) do
620 if (ctlsToKill
[f
] = ctl
) then exit
;
621 if (ctlsToKill
[f
] = nil) then begin ctlsToKill
[f
] := ctl
; exit
; end;
623 SetLength(ctlsToKill
, Length(ctlsToKill
)+1);
624 ctlsToKill
[High(ctlsToKill
)] := ctl
;
628 procedure processKills ();
633 for f
:= 0 to High(ctlsToKill
) do
635 ctl
:= ctlsToKill
[f
];
636 if (ctl
= nil) then break
;
637 if (uiGrabCtl
<> nil) and (ctl
.isMyChild(uiGrabCtl
)) then uiGrabCtl
:= nil; // just in case
638 ctlsToKill
[f
] := nil;
641 if (Length(ctlsToKill
) > 0) then ctlsToKill
[0] := nil; // just in case
645 // ////////////////////////////////////////////////////////////////////////// //
647 knownCtlClasses
: array of record
648 klass
: TUIControlClass
;
653 procedure registerCtlClass (aklass
: TUIControlClass
; const aname
: AnsiString);
655 assert(aklass
<> nil);
656 assert(Length(aname
) > 0);
657 SetLength(knownCtlClasses
, Length(knownCtlClasses
)+1);
658 knownCtlClasses
[High(knownCtlClasses
)].klass
:= aklass
;
659 knownCtlClasses
[High(knownCtlClasses
)].name
:= aname
;
663 function findCtlClass (const aname
: AnsiString): TUIControlClass
;
667 for f
:= 0 to High(knownCtlClasses
) do
669 if (strEquCI1251(aname
, knownCtlClasses
[f
].name
)) then
671 result
:= knownCtlClasses
[f
].klass
;
679 // ////////////////////////////////////////////////////////////////////////// //
681 TFlexLayouter
= specialize TFlexLayouterBase
<TUIControl
>;
683 procedure uiLayoutCtl (ctl
: TUIControl
);
687 if (ctl
= nil) then exit
;
688 lay
:= TFlexLayouter
.Create();
690 if (not ctl
.mStyleLoaded
) then ctl
.updateStyle();
691 if (ctl
is TUITopWindow
) and (TUITopWindow(ctl
).fitToScreen
) then TUITopWindow(ctl
).flFitToScreen();
696 //writeln('============================'); lay.dumpFlat();
698 //writeln('=== initial ==='); lay.dump();
700 //lay.calcMaxSizeInternal(0);
703 writeln('=== after first pass ===');
707 writeln('=== after second pass ===');
712 //writeln('=== final ==='); lay.dump();
714 if (ctl
.mParent
= nil) and (ctl
is TUITopWindow
) and (TUITopWindow(ctl
).mDoCenter
) then
716 TUITopWindow(ctl
).centerInScreen();
719 // calculate full size
720 ctl
.calcFullClientSize();
723 if (ctl
.mParent
= nil) then
725 if (ctl
.mFocused
= nil) or (ctl
.mFocused
= ctl
) or (not ctl
.mFocused
.enabled
) then
727 ctl
.mFocused
:= ctl
.findFirstFocus();
737 // ////////////////////////////////////////////////////////////////////////// //
738 procedure uiUpdateStyles ();
742 for ctl
in uiTopList
do ctl
.updateStyle();
746 procedure uiDispatchEvent (var evt
: TFUIEvent
);
751 procedure doSink (ctl
: TUIControl
);
753 if (ctl
= nil) or (not ev
.alive
) then exit
;
754 if (ctl
.mParent
<> nil) then
757 if (not ev
.alive
) then exit
;
759 //if (ctl = destCtl) then writeln(' SINK: MINE! <', ctl.className, '>');
762 if (ctl
= destCtl
) and (ev
.alive
) then
769 procedure dispatchTo (ctl
: TUIControl
);
771 if (ctl
= nil) then exit
;
776 //ctl := ctl.mParent; // 'cause "mine" is processed in `doSink()`
777 while (ctl
<> nil) and (ev
.alive
) do
785 procedure doMouseEvent ();
793 // pass mouse events to control with grab, if there is any
794 if (uiGrabCtl
<> nil) then
796 //writeln('GRABBED: ', uiGrabCtl.className);
797 doUngrab
:= (ev
.release
) and ((ev
.bstate
and (not ev
.but
)) = 0);
798 dispatchTo(uiGrabCtl
);
799 //FIXME: create API to get grabs, so control can regrab itself event on release
800 if (doUngrab
) and (uiGrabCtl
= destCtl
) then uiGrabCtl
:= nil;
805 if (Length(uiTopList
) > 0) then win
:= uiTopList
[High(uiTopList
)] else win
:= nil;
806 // check if we're still in top window
807 if (ev
.press
) and (win
<> nil) and (not win
.toLocal(0, 0, lx
, ly
)) then
809 // we have other windows too; check for window switching
810 for f
:= High(uiTopList
)-1 downto 0 do
812 if (uiTopList
[f
].enabled
) and (uiTopList
[f
].toLocal(ev
.x
, ev
.y
, lx
, ly
)) then
817 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
818 uiTopList
[High(uiTopList
)] := win
;
825 if (win
<> nil) and (win
.toLocal(ev
.x
, ev
.y
, lx
, ly
)) then
827 ctl
:= win
.controlAtXY(ev
.x
, ev
.y
); // don't allow disabled controls
828 if (ctl
= nil) or (not ctl
.canFocus
) or (not ctl
.enabled
) then ctl
:= win
;
829 // pass focus to another event and set grab, if necessary
832 // pass focus, if necessary
833 if (win
.mFocused
<> ctl
) then
835 if (win
.mFocused
<> nil) then win
.mFocused
.blurred();
838 if (ctl
<> win
) then ctl
.activated();
850 svx
, svy
, svdx
, svdy
: Integer;
855 if (not evt
.alive
) then exit
;
856 odp
:= uiInsideDispatcher
;
857 uiInsideDispatcher
:= true;
858 //writeln('ENTER: FUI DISPATCH');
860 // normalize mouse coordinates
861 svscale
:= fuiRenderScale
;
862 ev
.x
:= trunc(ev
.x
/svscale
);
863 ev
.y
:= trunc(ev
.y
/svscale
);
864 ev
.dx
:= trunc(ev
.dx
/svscale
); //FIXME
865 ev
.dy
:= trunc(ev
.dy
/svscale
); //FIXME
871 // "event grab" eats only mouse events
874 // we need to so some special processing here
879 // simply dispatch to focused control
880 dispatchTo(uiGetFocusedCtl
);
883 uiInsideDispatcher
:= odp
;
884 if (ev
.x
= svx
) and (ev
.y
= svy
) and (ev
.dx
= svdx
) and (ev
.dy
= svdy
) then
886 // due to possible precision loss
901 evt
.x
:= trunc(evt
.x
*svscale
);
902 evt
.y
:= trunc(evt
.y
*svscale
);
903 evt
.dx
:= trunc(evt
.dx
*svscale
);
904 evt
.dy
:= trunc(evt
.dy
*svscale
);
908 //writeln('EXIT: FUI DISPATCH');
911 procedure uiFocus ();
913 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].activated();
919 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].blurred();
929 //if (uiContext = nil) then uiContext := TGxContext.Create();
930 gxSetContext(uiContext
, fuiRenderScale
);
931 uiContext
.resetClip();
933 for f
:= 0 to High(uiTopList
) do
937 if (f
<> High(uiTopList
)) then
939 cidx
:= ctl
.getColorIndex
;
940 uiContext
.darkenRect(ctl
.x0
, ctl
.y0
, ctl
.width
, ctl
.height
, ctl
.mDarken
[cidx
]);
949 function uiGetFocusedCtl (): TUIControl
;
952 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then
954 result
:= uiTopList
[High(uiTopList
)].mFocused
;
955 if (result
= nil) then result
:= uiTopList
[High(uiTopList
)];
960 procedure uiAddWindow (ctl
: TUIControl
);
964 if (ctl
= nil) then exit
;
966 if not (ctl
is TUITopWindow
) then exit
; // alas
967 for f
:= 0 to High(uiTopList
) do
969 if (uiTopList
[f
] = ctl
) then
971 if (f
<> High(uiTopList
)) then
973 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].blurred();
974 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
975 uiTopList
[High(uiTopList
)] := ctl
;
981 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].blurred();
982 SetLength(uiTopList
, Length(uiTopList
)+1);
983 uiTopList
[High(uiTopList
)] := ctl
;
984 if (not ctl
.mStyleLoaded
) then ctl
.updateStyle();
989 procedure uiRemoveWindow (ctl
: TUIControl
);
993 if (ctl
= nil) then exit
;
995 if not (ctl
is TUITopWindow
) then exit
; // alas
996 for f
:= 0 to High(uiTopList
) do
998 if (uiTopList
[f
] = ctl
) then
1001 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
1002 SetLength(uiTopList
, Length(uiTopList
)-1);
1003 if (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then uiTopList
[High(uiTopList
)].activated();
1004 if (ctl
is TUITopWindow
) then
1007 if assigned(TUITopWindow(ctl
).closeCB
) then TUITopWindow(ctl
).closeCB(ctl
);
1009 if (TUITopWindow(ctl
).mFreeOnClose
) then scheduleKill(ctl
);
1018 function uiVisibleWindow (ctl
: TUIControl
): Boolean;
1023 if (ctl
= nil) then exit
;
1024 ctl
:= ctl
.topLevel
;
1025 if not (ctl
is TUITopWindow
) then exit
; // alas
1026 for f
:= 0 to High(uiTopList
) do
1028 if (uiTopList
[f
] = ctl
) then begin result
:= true; exit
; end;
1033 // ////////////////////////////////////////////////////////////////////////// //
1034 constructor TUIControl
.Create ();
1039 procedure TUIControl
.AfterConstruction ();
1047 mHeight
:= uiContext
.charHeight(' ');
1055 mDrawShadow
:= false;
1057 // layouter interface
1058 //mDefSize := TLaySize.Create(64, uiContext.charHeight(' ')); // default size
1059 mDefSize
:= TLaySize
.Create(0, 0); // default size: hidden control
1060 mMaxSize
:= TLaySize
.Create(-1, -1); // maximum size
1061 mPadding
:= TLaySize
.Create(0, 0);
1069 mAlign
:= -1; // left/top
1071 mStyleLoaded
:= false;
1075 destructor TUIControl
.Destroy ();
1078 doActivateOtherWin
: Boolean = false;
1080 if (uiInsideDispatcher
) then raise Exception
.Create('FlexUI: cannot destroy objects in event dispatcher');
1081 if (uiGrabCtl
= self
) then uiGrabCtl
:= nil;
1082 // just in case, check if this is top-level shit
1083 for f
:= 0 to High(uiTopList
) do
1085 if (uiTopList
[f
] = self
) then
1087 if (uiGrabCtl
<> nil) and (isMyChild(uiGrabCtl
)) then uiGrabCtl
:= nil;
1088 for c
:= f
+1 to High(uiTopList
) do uiTopList
[c
-1] := uiTopList
[c
];
1089 SetLength(uiTopList
, Length(uiTopList
)-1);
1090 doActivateOtherWin
:= true;
1094 if (doActivateOtherWin
) and (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)].enabled
) then
1096 uiTopList
[High(uiTopList
)].activated();
1099 if (mParent
<> nil) then
1102 for f
:= 0 to High(mParent
.mChildren
) do
1104 if (mParent
.mChildren
[f
] = self
) then
1106 for c
:= f
+1 to High(mParent
.mChildren
) do mParent
.mChildren
[c
-1] := mParent
.mChildren
[c
];
1107 SetLength(mParent
.mChildren
, Length(mParent
.mChildren
)-1);
1111 for f
:= 0 to High(mChildren
) do
1113 mChildren
[f
].mParent
:= nil;
1114 mChildren
[f
].Free();
1120 function TUIControl
.getColorIndex (): Integer; inline;
1122 if (not enabled
) then begin result
:= ClrIdxDisabled
; exit
; end;
1123 // top windows: no focus hack
1124 if (self
is TUITopWindow
) then
1126 if (getActive
) then begin result
:= ClrIdxActive
; exit
; end;
1130 // if control cannot be focused, take "active" color scheme for it (it is easier this way)
1131 if (not canFocus
) or (getActive
) then begin result
:= ClrIdxActive
; exit
; end;
1133 result
:= ClrIdxInactive
;
1136 procedure TUIControl
.updateStyle ();
1138 stl
: TUIStyle
= nil;
1142 while (ctl
<> nil) do
1144 if (Length(ctl
.mStyleId
) <> 0) then begin stl
:= uiFindStyle(ctl
.mStyleId
); break
; end;
1147 if (stl
= nil) then stl
:= uiFindStyle(''); // default
1149 for ctl
in mChildren
do ctl
.updateStyle();
1150 mStyleLoaded
:= true;
1153 procedure TUIControl
.cacheStyle (root
: TUIStyle
);
1157 //writeln('caching style for <', className, '> (', mCtl4Style, ')...');
1160 mBackColor
[ClrIdxActive
] := root
.get('back-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(0, 0, 128));
1161 mTextColor
[ClrIdxActive
] := root
.get('text-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1162 mFrameColor
[ClrIdxActive
] := root
.get('frame-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1163 mFrameTextColor
[ClrIdxActive
] := root
.get('frame-text-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1164 mFrameIconColor
[ClrIdxActive
] := root
.get('frame-icon-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(0, 255, 0));
1165 mSBarFullColor
[ClrIdxActive
] := root
.get('scrollbar-full-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1166 mSBarEmptyColor
[ClrIdxActive
] := root
.get('scrollbar-empty-color', 'active', cst
).asRGBADef(TGxRGBA
.Create(128, 128, 128));
1167 mDarken
[ClrIdxActive
] := root
.get('darken', 'active', cst
).asInt(666);
1169 mBackColor
[ClrIdxDisabled
] := root
.get('back-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(0, 0, 128));
1170 mTextColor
[ClrIdxDisabled
] := root
.get('text-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(127, 127, 127));
1171 mFrameColor
[ClrIdxDisabled
] := root
.get('frame-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(127, 127, 127));
1172 mFrameTextColor
[ClrIdxDisabled
] := root
.get('frame-text-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(127, 127, 127));
1173 mFrameIconColor
[ClrIdxDisabled
] := root
.get('frame-icon-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(0, 127, 0));
1174 mSBarFullColor
[ClrIdxDisabled
] := root
.get('scrollbar-full-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(127, 127, 127));
1175 mSBarEmptyColor
[ClrIdxDisabled
] := root
.get('scrollbar-empty-color', 'disabled', cst
).asRGBADef(TGxRGBA
.Create(98, 98, 98));
1176 mDarken
[ClrIdxDisabled
] := root
.get('darken', 'disabled', cst
).asInt(666);
1178 mBackColor
[ClrIdxInactive
] := root
.get('back-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(0, 0, 128));
1179 mTextColor
[ClrIdxInactive
] := root
.get('text-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1180 mFrameColor
[ClrIdxInactive
] := root
.get('frame-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1181 mFrameTextColor
[ClrIdxInactive
] := root
.get('frame-text-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1182 mFrameIconColor
[ClrIdxInactive
] := root
.get('frame-icon-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(0, 255, 0));
1183 mSBarFullColor
[ClrIdxInactive
] := root
.get('scrollbar-full-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
1184 mSBarEmptyColor
[ClrIdxInactive
] := root
.get('scrollbar-empty-color', 'inactive', cst
).asRGBADef(TGxRGBA
.Create(128, 128, 128));
1185 mDarken
[ClrIdxInactive
] := root
.get('darken', 'inactive', cst
).asInt(666);
1189 // ////////////////////////////////////////////////////////////////////////// //
1190 function TUIControl
.getDefSize (): TLaySize
; inline; begin result
:= mLayDefSize
; end;
1191 function TUIControl
.getMaxSize (): TLaySize
; inline; begin result
:= mLayMaxSize
; end;
1192 function TUIControl
.getPadding (): TLaySize
; inline; begin result
:= mPadding
; end;
1193 function TUIControl
.getFlex (): Integer; inline; begin result
:= mFlex
; end;
1194 function TUIControl
.isHorizBox (): Boolean; inline; begin result
:= mHoriz
; end;
1195 function TUIControl
.noPad (): Boolean; inline; begin result
:= mNoPad
; end;
1196 function TUIControl
.getAlign (): Integer; inline; begin result
:= mAlign
; end;
1197 function TUIControl
.getExpand (): Boolean; inline; begin result
:= mExpand
; end;
1198 function TUIControl
.getHGroup (): AnsiString; inline; begin result
:= mHGroup
; end;
1199 function TUIControl
.getVGroup (): AnsiString; inline; begin result
:= mVGroup
; end;
1200 function TUIControl
.getMargins (): TLayMargins
; inline; begin result
:= TLayMargins
.Create(mFrameHeight
, mFrameWidth
, mFrameHeight
, mFrameWidth
); end;
1202 procedure TUIControl
.setActualSizePos (constref apos
: TLayPos
; constref asize
: TLaySize
); inline;
1204 //writeln(self.className, '; pos=', apos.toString, '; size=', asize.toString);
1205 if (mParent
<> nil) then
1212 if (mLayMaxSize
.w
>= 0) then mWidth
:= nmin(mWidth
, mLayMaxSize
.w
);
1213 if (mLayMaxSize
.h
>= 0) then mHeight
:= nmin(mHeight
, mLayMaxSize
.h
);
1216 procedure TUIControl
.layPrepare ();
1218 mLayDefSize
:= mDefSize
;
1219 if (mLayDefSize
.w
<> 0) or (mLayDefSize
.h
<> 0) then
1221 mLayMaxSize
:= mMaxSize
;
1222 if (mLayMaxSize
.w
>= 0) then begin mLayDefSize
.w
+= mFrameWidth
*2; mLayMaxSize
.w
+= mFrameWidth
*2; end;
1223 if (mLayMaxSize
.h
>= 0) then begin mLayDefSize
.h
+= mFrameHeight
*2; mLayMaxSize
.h
+= mFrameHeight
*2; end;
1227 mLayMaxSize
:= TLaySize
.Create(0, 0);
1232 // ////////////////////////////////////////////////////////////////////////// //
1233 function TUIControl
.parsePos (par
: TTextParser
): TLayPos
;
1235 ech
: AnsiChar = ')';
1237 if (par
.eatDelim('[')) then ech
:= ']' else par
.expectDelim('(');
1238 result
.x
:= par
.expectInt();
1239 par
.eatDelim(','); // optional comma
1240 result
.y
:= par
.expectInt();
1241 par
.eatDelim(','); // optional comma
1242 par
.expectDelim(ech
);
1245 function TUIControl
.parseSize (par
: TTextParser
): TLaySize
;
1247 ech
: AnsiChar = ')';
1249 if (par
.eatDelim('[')) then ech
:= ']' else par
.expectDelim('(');
1250 result
.w
:= par
.expectInt();
1251 par
.eatDelim(','); // optional comma
1252 result
.h
:= par
.expectInt();
1253 par
.eatDelim(','); // optional comma
1254 par
.expectDelim(ech
);
1257 function TUIControl
.parsePadding (par
: TTextParser
): TLaySize
;
1259 result
:= parseSize(par
);
1262 function TUIControl
.parseHPadding (par
: TTextParser
; def
: Integer): TLaySize
;
1267 result
.w
:= par
.expectInt();
1271 result
:= parsePadding(par
);
1275 function TUIControl
.parseVPadding (par
: TTextParser
; def
: Integer): TLaySize
;
1280 result
.h
:= par
.expectInt();
1284 result
:= parsePadding(par
);
1288 function TUIControl
.parseBool (par
: TTextParser
): Boolean;
1291 par
.eatIdOrStrCI('true') or
1292 par
.eatIdOrStrCI('yes') or
1293 par
.eatIdOrStrCI('tan');
1296 if (not par
.eatIdOrStrCI('false')) and (not par
.eatIdOrStrCI('no')) and (not par
.eatIdOrStrCI('ona')) then
1298 par
.error('boolean value expected');
1303 function TUIControl
.parseAnyAlign (par
: TTextParser
): Integer;
1305 if (par
.eatIdOrStrCI('left')) or (par
.eatIdOrStrCI('top')) then result
:= -1
1306 else if (par
.eatIdOrStrCI('right')) or (par
.eatIdOrStrCI('bottom')) then result
:= 1
1307 else if (par
.eatIdOrStrCI('center')) then result
:= 0
1308 else par
.error('invalid align value');
1311 function TUIControl
.parseHAlign (par
: TTextParser
): Integer;
1313 if (par
.eatIdOrStrCI('left')) then result
:= -1
1314 else if (par
.eatIdOrStrCI('right')) then result
:= 1
1315 else if (par
.eatIdOrStrCI('center')) then result
:= 0
1316 else par
.error('invalid horizontal align value');
1319 function TUIControl
.parseVAlign (par
: TTextParser
): Integer;
1321 if (par
.eatIdOrStrCI('top')) then result
:= -1
1322 else if (par
.eatIdOrStrCI('bottom')) then result
:= 1
1323 else if (par
.eatIdOrStrCI('center')) then result
:= 0
1324 else par
.error('invalid vertical align value');
1327 procedure TUIControl
.parseTextAlign (par
: TTextParser
; var h
, v
: Integer);
1329 wasH
: Boolean = false;
1330 wasV
: Boolean = false;
1334 if (par
.eatIdOrStrCI('left')) then
1336 if wasH
then par
.error('too many align directives');
1341 if (par
.eatIdOrStrCI('right')) then
1343 if wasH
then par
.error('too many align directives');
1348 if (par
.eatIdOrStrCI('hcenter')) then
1350 if wasH
then par
.error('too many align directives');
1355 if (par
.eatIdOrStrCI('top')) then
1357 if wasV
then par
.error('too many align directives');
1362 if (par
.eatIdOrStrCI('bottom')) then
1364 if wasV
then par
.error('too many align directives');
1369 if (par
.eatIdOrStrCI('vcenter')) then
1371 if wasV
then par
.error('too many align directives');
1376 if (par
.eatIdOrStrCI('center')) then
1378 if wasV
or wasH
then par
.error('too many align directives');
1387 if not wasV
and not wasH
then par
.error('invalid align value');
1390 function TUIControl
.parseOrientation (const prname
: AnsiString; par
: TTextParser
): Boolean;
1392 if (strEquCI1251(prname
, 'orientation')) or (strEquCI1251(prname
, 'orient')) then
1394 if (par
.eatIdOrStrCI('horizontal')) or (par
.eatIdOrStrCI('horiz')) then mHoriz
:= true
1395 else if (par
.eatIdOrStrCI('vertical')) or (par
.eatIdOrStrCI('vert')) then mHoriz
:= false
1396 else par
.error('`horizontal` or `vertical` expected');
1405 // par should be on '{'; final '}' is eaten
1406 procedure TUIControl
.parseProperties (par
: TTextParser
);
1410 if (not par
.eatDelim('{')) then exit
;
1411 while (not par
.eatDelim('}')) do
1413 if (not par
.isIdOrStr
) then par
.error('property name expected');
1416 par
.eatDelim(':'); // optional
1417 if not parseProperty(pn
, par
) then par
.errorfmt('invalid property name ''%s''', [pn
]);
1418 par
.eatDelim(','); // optional
1422 // par should be on '{'
1423 procedure TUIControl
.parseChildren (par
: TTextParser
);
1425 cc
: TUIControlClass
;
1428 par
.expectDelim('{');
1429 while (not par
.eatDelim('}')) do
1431 if (not par
.isIdOrStr
) then par
.error('control name expected');
1432 cc
:= findCtlClass(par
.tokStr
);
1433 if (cc
= nil) then par
.errorfmt('unknown control name: ''%s''', [par
.tokStr
]);
1434 //writeln('children for <', par.tokStr, '>: <', cc.className, '>');
1436 par
.eatDelim(':'); // optional
1438 //writeln(' mHoriz=', ctl.mHoriz);
1440 ctl
.parseProperties(par
);
1445 //writeln(': ', ctl.mDefSize.toString);
1447 par
.eatDelim(','); // optional
1452 function TUIControl
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
1455 if (strEquCI1251(prname
, 'id')) then begin mId
:= par
.expectIdOrStr(true); exit
; end; // allow empty strings
1456 if (strEquCI1251(prname
, 'style')) then begin mStyleId
:= par
.expectIdOrStr(); exit
; end; // no empty strings
1457 if (strEquCI1251(prname
, 'flex')) then begin flex
:= par
.expectInt(); exit
; end;
1459 if (strEquCI1251(prname
, 'defsize')) or (strEquCI1251(prname
, 'size')) then begin mDefSize
:= parseSize(par
); exit
; end;
1460 if (strEquCI1251(prname
, 'maxsize')) then begin mMaxSize
:= parseSize(par
); exit
; end;
1461 if (strEquCI1251(prname
, 'defwidth')) or (strEquCI1251(prname
, 'width')) then begin mDefSize
.w
:= par
.expectInt(); exit
; end;
1462 if (strEquCI1251(prname
, 'defheight')) or (strEquCI1251(prname
, 'height')) then begin mDefSize
.h
:= par
.expectInt(); exit
; end;
1463 if (strEquCI1251(prname
, 'maxwidth')) then begin mMaxSize
.w
:= par
.expectInt(); exit
; end;
1464 if (strEquCI1251(prname
, 'maxheight')) then begin mMaxSize
.h
:= par
.expectInt(); exit
; end;
1466 if (strEquCI1251(prname
, 'padding')) then begin mPadding
:= parsePadding(par
); exit
; end;
1467 if (strEquCI1251(prname
, 'nopad')) then begin mNoPad
:= true; exit
; end;
1469 if (strEquCI1251(prname
, 'expand')) then begin mExpand
:= parseBool(par
); exit
; end;
1471 if (strEquCI1251(prname
, 'align')) then begin mAlign
:= parseAnyAlign(par
); exit
; end;
1472 if (strEquCI1251(prname
, 'hgroup')) then begin mHGroup
:= par
.expectIdOrStr(true); exit
; end; // allow empty strings
1473 if (strEquCI1251(prname
, 'vgroup')) then begin mVGroup
:= par
.expectIdOrStr(true); exit
; end; // allow empty strings
1475 if (strEquCI1251(prname
, 'canfocus')) then begin mCanFocus
:= true; exit
; end;
1476 if (strEquCI1251(prname
, 'nofocus')) then begin mCanFocus
:= false; exit
; end;
1477 if (strEquCI1251(prname
, 'disabled')) then begin mEnabled
:= false; exit
; end;
1478 if (strEquCI1251(prname
, 'enabled')) then begin mEnabled
:= true; exit
; end;
1479 if (strEquCI1251(prname
, 'escclose')) then begin mEscClose
:= not parseBool(par
); exit
; end;
1480 if (strEquCI1251(prname
, 'default')) then begin mDefault
:= true; exit
; end;
1481 if (strEquCI1251(prname
, 'cancel')) then begin mCancel
:= true; exit
; end;
1486 // ////////////////////////////////////////////////////////////////////////// //
1487 procedure TUIControl
.activated ();
1489 makeVisibleInParent();
1493 procedure TUIControl
.blurred ();
1495 if (uiGrabCtl
= self
) then uiGrabCtl
:= nil;
1499 procedure TUIControl
.calcFullClientSize ();
1503 mFullSize
:= TLaySize
.Create(0, 0);
1504 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1505 for ctl
in mChildren
do
1507 ctl
.calcFullClientSize();
1508 mFullSize
.w
:= nmax(mFullSize
.w
, ctl
.mX
-mFrameWidth
+ctl
.mFullSize
.w
);
1509 mFullSize
.h
:= nmax(mFullSize
.h
, ctl
.mY
-mFrameHeight
+ctl
.mFullSize
.h
);
1511 mFullSize
.w
:= nmax(mFullSize
.w
, mWidth
-mFrameWidth
*2);
1512 mFullSize
.h
:= nmax(mFullSize
.h
, mHeight
-mFrameHeight
*2);
1516 function TUIControl
.topLevel (): TUIControl
; inline;
1519 while (result
.mParent
<> nil) do result
:= result
.mParent
;
1523 function TUIControl
.getEnabled (): Boolean;
1528 if (not mEnabled
) then exit
;
1530 while (ctl
<> nil) do
1532 if (not ctl
.mEnabled
) then exit
;
1539 procedure TUIControl
.setEnabled (v
: Boolean); inline;
1541 if (mEnabled
= v
) then exit
;
1543 if (not v
) and focused
then setFocused(false);
1547 function TUIControl
.getFocused (): Boolean; inline;
1549 if (mParent
= nil) then
1551 result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = self
);
1555 result
:= (topLevel
.mFocused
= self
);
1556 if (result
) then result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = topLevel
);
1561 function TUIControl
.getActive (): Boolean; inline;
1565 if (mParent
= nil) then
1567 result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = self
);
1571 ctl
:= topLevel
.mFocused
;
1572 while (ctl
<> nil) and (ctl
<> self
) do ctl
:= ctl
.mParent
;
1573 result
:= (ctl
= self
);
1574 if (result
) then result
:= (Length(uiTopList
) > 0) and (uiTopList
[High(uiTopList
)] = topLevel
);
1579 procedure TUIControl
.setFocused (v
: Boolean); inline;
1586 if (tl
.mFocused
= self
) then
1588 blurred(); // this will reset grab, but still...
1589 if (uiGrabCtl
= self
) then uiGrabCtl
:= nil;
1590 tl
.mFocused
:= tl
.findNextFocus(self
, true);
1591 if (tl
.mFocused
= self
) then tl
.mFocused
:= nil;
1592 if (tl
.mFocused
<> nil) then tl
.mFocused
.activated();
1596 if (not canFocus
) then exit
;
1597 if (tl
.mFocused
<> self
) then
1599 if (tl
.mFocused
<> nil) then tl
.mFocused
.blurred();
1600 tl
.mFocused
:= self
;
1601 if (uiGrabCtl
<> self
) then uiGrabCtl
:= nil;
1607 function TUIControl
.getCanFocus (): Boolean; inline;
1609 result
:= (getEnabled
) and (mCanFocus
) and (mWidth
> 0) and (mHeight
> 0);
1613 function TUIControl
.isMyChild (ctl
: TUIControl
): Boolean;
1616 while (ctl
<> nil) do
1618 if (ctl
.mParent
= self
) then exit
;
1625 // returns `true` if global coords are inside this control
1626 function TUIControl
.toLocal (var x
, y
: Integer): Boolean;
1628 if (mParent
= nil) then
1632 result
:= true; // hack
1636 result
:= mParent
.toLocal(x
, y
);
1637 Inc(x
, mParent
.mScrollX
);
1638 Inc(y
, mParent
.mScrollY
);
1641 if result
then result
:= (x
>= 0) and (y
>= 0) and (x
< mParent
.mWidth
) and (y
< mParent
.mHeight
);
1643 if result
then result
:= (x
>= 0) and (y
>= 0) and (x
< mWidth
) and (y
< mHeight
);
1646 function TUIControl
.toLocal (gx
, gy
: Integer; out x
, y
: Integer): Boolean; inline;
1650 result
:= toLocal(x
, y
);
1654 procedure TUIControl
.toGlobal (var x
, y
: Integer);
1658 if (mParent
<> nil) then
1660 Dec(x
, mParent
.mScrollX
);
1661 Dec(y
, mParent
.mScrollY
);
1662 mParent
.toGlobal(x
, y
);
1666 procedure TUIControl
.toGlobal (lx
, ly
: Integer; out x
, y
: Integer); inline;
1673 procedure TUIControl
.getDrawRect (out gx
, gy
, wdt
, hgt
: Integer);
1677 if (mParent
= nil) then
1686 toGlobal(0, 0, cgx
, cgy
);
1687 mParent
.getDrawRect(gx
, gy
, wdt
, hgt
);
1688 if (wdt
> 0) and (hgt
> 0) then
1690 if (not intersectRect(gx
, gy
, wdt
, hgt
, cgx
, cgy
, mWidth
, mHeight
)) then
1700 // x and y are global coords
1701 function TUIControl
.controlAtXY (x
, y
: Integer; allowDisabled
: Boolean=false): TUIControl
;
1707 if (not allowDisabled
) and (not enabled
) then exit
;
1708 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1709 if not toLocal(x
, y
, lx
, ly
) then exit
;
1710 for f
:= High(mChildren
) downto 0 do
1712 result
:= mChildren
[f
].controlAtXY(x
, y
, allowDisabled
);
1713 if (result
<> nil) then exit
;
1719 function TUIControl
.parentScrollX (): Integer; inline; begin if (mParent
<> nil) then result
:= mParent
.mScrollX
else result
:= 0; end;
1720 function TUIControl
.parentScrollY (): Integer; inline; begin if (mParent
<> nil) then result
:= mParent
.mScrollY
else result
:= 0; end;
1723 procedure TUIControl
.makeVisibleInParent ();
1725 sy
, ey
, cy
: Integer;
1728 if (mWidth
< 1) or (mHeight
< 1) then exit
;
1730 if (p
= nil) then exit
;
1731 if (p
.mFullSize
.w
< 1) or (p
.mFullSize
.h
< 1) then
1737 p
.makeVisibleInParent();
1738 cy
:= mY
-p
.mFrameHeight
;
1740 ey
:= sy
+(p
.mHeight
-p
.mFrameHeight
*2);
1743 p
.mScrollY
:= nmax(0, cy
);
1745 else if (cy
+mHeight
> ey
) then
1747 p
.mScrollY
:= nmax(0, cy
+mHeight
-(p
.mHeight
-p
.mFrameHeight
*2));
1752 // ////////////////////////////////////////////////////////////////////////// //
1753 function TUIControl
.prevSibling (): TUIControl
;
1757 if (mParent
<> nil) then
1759 for f
:= 1 to High(mParent
.mChildren
) do
1761 if (mParent
.mChildren
[f
] = self
) then begin result
:= mParent
.mChildren
[f
-1]; exit
; end;
1767 function TUIControl
.nextSibling (): TUIControl
;
1771 if (mParent
<> nil) then
1773 for f
:= 0 to High(mParent
.mChildren
)-1 do
1775 if (mParent
.mChildren
[f
] = self
) then begin result
:= mParent
.mChildren
[f
+1]; exit
; end;
1781 function TUIControl
.firstChild (): TUIControl
; inline;
1783 if (Length(mChildren
) <> 0) then result
:= mChildren
[0] else result
:= nil;
1786 function TUIControl
.lastChild (): TUIControl
; inline;
1788 if (Length(mChildren
) <> 0) then result
:= mChildren
[High(mChildren
)] else result
:= nil;
1792 function TUIControl
.findFirstFocus (): TUIControl
;
1799 for f
:= 0 to High(mChildren
) do
1801 result
:= mChildren
[f
].findFirstFocus();
1802 if (result
<> nil) then exit
;
1804 if (canFocus
) then result
:= self
;
1809 function TUIControl
.findLastFocus (): TUIControl
;
1816 for f
:= High(mChildren
) downto 0 do
1818 result
:= mChildren
[f
].findLastFocus();
1819 if (result
<> nil) then exit
;
1821 if (canFocus
) then result
:= self
;
1826 function TUIControl
.findNextFocus (cur
: TUIControl
; wrap
: Boolean): TUIControl
;
1828 curHit
: Boolean = false;
1830 function checkFocus (ctl
: TUIControl
): Boolean;
1834 result
:= (ctl
.canFocus
);
1838 curHit
:= (ctl
= cur
);
1839 result
:= false; // don't stop
1847 if not isMyChild(cur
) then
1849 result
:= findFirstFocus();
1853 result
:= forEachControl(checkFocus
);
1854 if (result
= nil) and (wrap
) then result
:= findFirstFocus();
1860 function TUIControl
.findPrevFocus (cur
: TUIControl
; wrap
: Boolean): TUIControl
;
1862 lastCtl
: TUIControl
= nil;
1864 function checkFocus (ctl
: TUIControl
): Boolean;
1873 if (ctl
.canFocus
) then lastCtl
:= ctl
;
1881 if not isMyChild(cur
) then
1883 result
:= findLastFocus();
1887 forEachControl(checkFocus
);
1888 if (lastCtl
= nil) and (wrap
) then lastCtl
:= findLastFocus();
1890 //if (lastCtl <> nil) then writeln('ctl<', lastCtl.className, '>: {', lastCtl.id, '}');
1896 function TUIControl
.findDefaulControl (): TUIControl
;
1902 if (mDefault
) then begin result
:= self
; exit
; end;
1903 for ctl
in mChildren
do
1905 result
:= ctl
.findDefaulControl();
1906 if (result
<> nil) then exit
;
1912 function TUIControl
.findCancelControl (): TUIControl
;
1918 if (mCancel
) then begin result
:= self
; exit
; end;
1919 for ctl
in mChildren
do
1921 result
:= ctl
.findCancelControl();
1922 if (result
<> nil) then exit
;
1929 function TUIControl
.findControlById (const aid
: AnsiString): TUIControl
;
1933 if (strEquCI1251(aid
, mId
)) then begin result
:= self
; exit
; end;
1934 for ctl
in mChildren
do
1936 result
:= ctl
.findControlById(aid
);
1937 if (result
<> nil) then exit
;
1943 procedure TUIControl
.appendChild (ctl
: TUIControl
);
1945 if (ctl
= nil) then exit
;
1946 if (ctl
.mParent
<> nil) then exit
;
1947 SetLength(mChildren
, Length(mChildren
)+1);
1948 mChildren
[High(mChildren
)] := ctl
;
1949 ctl
.mParent
:= self
;
1950 Inc(ctl
.mX
, mFrameWidth
);
1951 Inc(ctl
.mY
, mFrameHeight
);
1952 if (ctl
.mWidth
> 0) and (ctl
.mHeight
> 0) and
1953 (ctl
.mX
+ctl
.mWidth
> mFrameWidth
) and (ctl
.mY
+ctl
.mHeight
> mFrameHeight
) then
1955 if (mWidth
+mFrameWidth
< ctl
.mX
+ctl
.mWidth
) then mWidth
:= ctl
.mX
+ctl
.mWidth
+mFrameWidth
;
1956 if (mHeight
+mFrameHeight
< ctl
.mY
+ctl
.mHeight
) then mHeight
:= ctl
.mY
+ctl
.mHeight
+mFrameHeight
;
1961 function TUIControl
.setActionCBFor (const aid
: AnsiString; cb
: TActionCB
): TActionCB
;
1966 if (ctl
<> nil) then
1968 result
:= ctl
.actionCB
;
1978 function TUIControl
.forEachChildren (cb
: TCtlEnumCB
): TUIControl
;
1983 if (not assigned(cb
)) then exit
;
1984 for ctl
in mChildren
do
1986 if cb(ctl
) then begin result
:= ctl
; exit
; end;
1991 function TUIControl
.forEachControl (cb
: TCtlEnumCB
; includeSelf
: Boolean=true): TUIControl
;
1993 function forChildren (p
: TUIControl
; incSelf
: Boolean): TUIControl
;
1998 if (p
= nil) then exit
;
1999 if (incSelf
) and (cb(p
)) then begin result
:= p
; exit
; end;
2000 for ctl
in p
.mChildren
do
2002 result
:= forChildren(ctl
, true);
2003 if (result
<> nil) then break
;
2009 if (not assigned(cb
)) then exit
;
2010 result
:= forChildren(self
, includeSelf
);
2014 procedure TUIControl
.close (); // this closes *top-level* control
2019 uiRemoveWindow(ctl
);
2020 if (ctl
is TUITopWindow
) and (TUITopWindow(ctl
).mFreeOnClose
) then scheduleKill(ctl
); // just in case
2024 procedure TUIControl
.doAction ();
2026 if assigned(actionCB
) then actionCB(self
);
2030 // ////////////////////////////////////////////////////////////////////////// //
2031 procedure TUIControl
.setScissor (lx
, ly
, lw
, lh
: Integer);
2033 gx
, gy
, wdt
, hgt
, cgx
, cgy
: Integer;
2035 if (not intersectRect(lx
, ly
, lw
, lh
, 0, 0, mWidth
, mHeight
)) then
2037 uiContext
.clip
:= TGxRect
.Create(0, 0, 0, 0);
2041 getDrawRect(gx
, gy
, wdt
, hgt
);
2043 toGlobal(lx
, ly
, cgx
, cgy
);
2044 if (not intersectRect(gx
, gy
, wdt
, hgt
, cgx
, cgy
, lw
, lh
)) then
2046 uiContext
.clip
:= TGxRect
.Create(0, 0, 0, 0);
2050 uiContext
.clip
:= savedClip
;
2051 uiContext
.combineClip(TGxRect
.Create(gx
, gy
, wdt
, hgt
));
2052 //uiContext.clip := TGxRect.Create(gx, gy, wdt, hgt);
2055 procedure TUIControl
.resetScissorNC (); inline;
2057 setScissor(0, 0, mWidth
, mHeight
);
2060 procedure TUIControl
.resetScissor (); inline;
2062 if ((mFrameWidth
<= 0) and (mFrameHeight
<= 0)) then
2068 setScissor(mFrameWidth
, mFrameHeight
, mWidth
-mFrameWidth
*2, mHeight
-mFrameHeight
*2);
2073 // ////////////////////////////////////////////////////////////////////////// //
2074 procedure TUIControl
.drawFrame (gx
, gy
, resx
, thalign
: Integer; const text: AnsiString; dbl
: Boolean);
2076 cidx
, tx
, tw
: Integer;
2078 if (mFrameWidth
< 1) or (mFrameHeight
< 1) then exit
;
2079 cidx
:= getColorIndex
;
2080 uiContext
.color
:= mFrameColor
[cidx
];
2081 case mFrameHeight
of
2086 uiContext
.rect(gx
+3, gy
+3, mWidth
-6, mHeight
-6);
2087 uiContext
.rect(gx
+5, gy
+5, mWidth
-10, mHeight
-10);
2091 uiContext
.rect(gx
+4, gy
+4, mWidth
-8, mHeight
-8);
2098 uiContext
.rect(gx
+3, gy
+3+3, mWidth
-6, mHeight
-6-6);
2099 uiContext
.rect(gx
+5, gy
+5+3, mWidth
-10, mHeight
-10-6);
2103 uiContext
.rect(gx
+4, gy
+4+3, mWidth
-8, mHeight
-8-6);
2110 uiContext
.rect(gx
+3, gy
+3+4, mWidth
-6, mHeight
-6-8);
2111 uiContext
.rect(gx
+5, gy
+5+4, mWidth
-10, mHeight
-10-8);
2115 uiContext
.rect(gx
+4, gy
+4+4, mWidth
-8, mHeight
-8-8);
2131 if (Length(text) > 0) then
2133 if (resx
< 0) then resx
:= 0;
2134 tw
:= uiContext
.textWidth(text);
2135 setScissor(mFrameWidth
+resx
, 0, mWidth
-mFrameWidth
*2-resx
, mFrameHeight
);
2136 if (thalign
< 0) then tx
:= gx
+resx
+mFrameWidth
+2
2137 else if (thalign
> 0) then tx
:= gx
+mWidth
-mFrameWidth
-1-tw
2138 else tx
:= (gx
+resx
+mFrameWidth
)+(mWidth
-mFrameWidth
*2-resx
-tw
) div 2;
2139 uiContext
.color
:= mBackColor
[cidx
];
2140 uiContext
.fillRect(tx
-2, gy
, tw
+4, mFrameHeight
);
2141 uiContext
.color
:= mFrameTextColor
[cidx
];
2142 uiContext
.drawText(tx
, gy
, text);
2147 procedure TUIControl
.draw ();
2153 if (mWidth
< 1) or (mHeight
< 1) or (uiContext
= nil) or (not uiContext
.active
) then exit
;
2154 toGlobal(0, 0, gx
, gy
);
2156 savedClip
:= uiContext
.clip
;
2159 drawControl(gx
, gy
);
2161 for f
:= 0 to High(mChildren
) do mChildren
[f
].draw();
2163 drawControlPost(gx
, gy
);
2165 uiContext
.clip
:= savedClip
;
2169 procedure TUIControl
.drawControl (gx
, gy
: Integer);
2173 procedure TUIControl
.drawControlPost (gx
, gy
: Integer);
2175 // shadow for top-level controls
2176 if (mParent
= nil) and (mDrawShadow
) and (mWidth
> 0) and (mHeight
> 0) then
2178 uiContext
.resetClip();
2179 uiContext
.darkenRect(gx
+mWidth
, gy
+8, 8, mHeight
, 128);
2180 uiContext
.darkenRect(gx
+8, gy
+mHeight
, mWidth
-8, 8, 128);
2185 // ////////////////////////////////////////////////////////////////////////// //
2186 procedure TUIControl
.onEvent (var ev
: TFUIEvent
);
2188 if (not ev
.alive
) or (not enabled
) then exit
;
2189 //if (ev.mine) then writeln(' MINE: <', className, '>');
2192 if (ev
.sinking
) then keyEventSink(ev
)
2193 else if (ev
.bubbling
) then keyEventBubble(ev
)
2194 else if (ev
.mine
) then keyEvent(ev
);
2196 else if (ev
.mouse
) then
2198 if (ev
.sinking
) then mouseEventSink(ev
)
2199 else if (ev
.bubbling
) then mouseEventBubble(ev
)
2200 else if (ev
.mine
) then mouseEvent(ev
);
2205 procedure TUIControl
.mouseEventSink (var ev
: TFUIEvent
);
2209 procedure TUIControl
.mouseEventBubble (var ev
: TFUIEvent
);
2213 procedure TUIControl
.mouseEvent (var ev
: TFUIEvent
);
2218 procedure TUIControl
.keyEventSink (var ev
: TFUIEvent
);
2222 if (not enabled
) then exit
;
2223 if (not ev
.alive
) then exit
;
2224 // for top-level controls
2225 if (mParent
<> nil) then exit
;
2226 if (mEscClose
) and (ev
= 'Escape') then
2228 if (not assigned(closeRequestCB
)) or (closeRequestCB(self
)) then
2230 uiRemoveWindow(self
);
2235 if (ev
= 'Enter') or (ev
= 'C-Enter') then
2237 ctl
:= findDefaulControl();
2238 if (ctl
<> nil) then
2245 if (ev
= 'Escape') then
2247 ctl
:= findCancelControl();
2248 if (ctl
<> nil) then
2257 procedure TUIControl
.keyEventBubble (var ev
: TFUIEvent
);
2261 if (not enabled
) then exit
;
2262 if (not ev
.alive
) then exit
;
2263 // for top-level controls
2264 if (mParent
<> nil) then exit
;
2265 if (ev
= 'S-Tab') then
2267 ctl
:= findPrevFocus(mFocused
, true);
2268 if (ctl
<> nil) and (ctl
<> mFocused
) then ctl
.setFocused(true);
2272 if (ev
= 'Tab') then
2274 ctl
:= findNextFocus(mFocused
, true);
2275 if (ctl
<> nil) and (ctl
<> mFocused
) then ctl
.setFocused(true);
2281 procedure TUIControl
.keyEvent (var ev
: TFUIEvent
);
2286 // ////////////////////////////////////////////////////////////////////////// //
2287 constructor TUITopWindow
.Create (const atitle
: AnsiString);
2294 procedure TUITopWindow
.AfterConstruction ();
2297 mFitToScreen
:= true;
2299 mFrameHeight
:= uiContext
.charHeight(#184);
2300 if (mWidth
< mFrameWidth
*2+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
)) then mWidth
:= mFrameWidth
*2+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
);
2301 if (mHeight
< mFrameHeight
*2) then mHeight
:= mFrameHeight
*2;
2302 if (Length(mTitle
) > 0) then
2304 if (mWidth
< uiContext
.textWidth(mTitle
)+mFrameWidth
*2+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
)) then
2306 mWidth
:= uiContext
.textWidth(mTitle
)+mFrameWidth
*2+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
);
2310 mDragScroll
:= TXMode
.None
;
2311 mDrawShadow
:= true;
2312 mWaitingClose
:= false;
2315 mCtl4Style
:= 'window';
2316 mDefSize
.w
:= nmax(1, mDefSize
.w
);
2317 mDefSize
.h
:= nmax(1, mDefSize
.h
);
2321 function TUITopWindow
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2323 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) then
2325 mTitle
:= par
.expectIdOrStr(true);
2329 if (strEquCI1251(prname
, 'children')) then
2335 if (strEquCI1251(prname
, 'position')) then
2337 if (par
.eatIdOrStrCI('default')) then mDoCenter
:= false
2338 else if (par
.eatIdOrStrCI('center')) then mDoCenter
:= true
2339 else par
.error('`center` or `default` expected');
2343 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2344 result
:= inherited parseProperty(prname
, par
);
2348 procedure TUITopWindow
.flFitToScreen ();
2352 nsz
:= TLaySize
.Create(trunc(fuiScrWdt
/fuiRenderScale
)-mFrameWidth
*2-6, trunc(fuiScrHgt
/fuiRenderScale
)-mFrameHeight
*2-6);
2353 if (mMaxSize
.w
< 1) then mMaxSize
.w
:= nsz
.w
;
2354 if (mMaxSize
.h
< 1) then mMaxSize
.h
:= nsz
.h
;
2358 procedure TUITopWindow
.centerInScreen ();
2360 if (mWidth
> 0) and (mHeight
> 0) then
2362 mX
:= trunc((fuiScrWdt
/fuiRenderScale
-mWidth
)/2);
2363 mY
:= trunc((fuiScrHgt
/fuiRenderScale
-mHeight
)/2);
2368 // ////////////////////////////////////////////////////////////////////////// //
2369 procedure TUITopWindow
.drawControl (gx
, gy
: Integer);
2371 uiContext
.color
:= mBackColor
[getColorIndex
];
2372 uiContext
.fillRect(gx
, gy
, mWidth
, mHeight
);
2375 procedure TUITopWindow
.drawControlPost (gx
, gy
: Integer);
2377 cidx
, iwdt
, ihgt
: Integer;
2378 ybot
, xend
, vhgt
, vwdt
: Integer;
2380 cidx
:= getColorIndex
;
2381 iwdt
:= uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
);
2382 if (mDragScroll
= TXMode
.Drag
) then
2384 drawFrame(gx
, gy
, iwdt
, 0, mTitle
, false);
2388 ihgt
:= uiContext
.iconWinHeight(TGxContext
.TWinIcon
.Close
);
2389 drawFrame(gx
, gy
, iwdt
, 0, mTitle
, true);
2390 // vertical scroll bar
2391 vhgt
:= mHeight
-mFrameHeight
*2;
2392 if (mFullSize
.h
> vhgt
) then
2394 ybot
:= mScrollY
+vhgt
;
2396 uiContext
.drawVSBar(gx
+mWidth
-mFrameWidth
+1, gy
+mFrameHeight
-1, mFrameWidth
-3, vhgt
+2, ybot
, 0, mFullSize
.h
, mSBarFullColor
[cidx
], mSBarEmptyColor
[cidx
]);
2398 // horizontal scroll bar
2399 vwdt
:= mWidth
-mFrameWidth
*2;
2400 if (mFullSize
.w
> vwdt
) then
2402 xend
:= mScrollX
+vwdt
;
2404 uiContext
.drawHSBar(gx
+mFrameWidth
+1, gy
+mHeight
-mFrameHeight
+1, vwdt
-2, mFrameHeight
-3, xend
, 0, mFullSize
.w
, mSBarFullColor
[cidx
], mSBarEmptyColor
[cidx
]);
2407 setScissor(mFrameWidth
, 0, iwdt
, ihgt
);
2408 uiContext
.color
:= mBackColor
[cidx
];
2409 uiContext
.fillRect(gx
+mFrameWidth
, gy
, iwdt
, ihgt
);
2410 uiContext
.color
:= mFrameIconColor
[cidx
];
2411 uiContext
.drawIconWin(TGxContext
.TWinIcon
.Close
, gx
+mFrameWidth
, gy
, mInClose
);
2413 // shadow (no need to reset scissor, as draw should do it)
2414 inherited drawControlPost(gx
, gy
);
2418 // ////////////////////////////////////////////////////////////////////////// //
2419 procedure TUITopWindow
.activated ();
2421 if (mFocused
= nil) or (mFocused
= self
) then
2423 mFocused
:= findFirstFocus();
2425 if (mFocused
<> nil) and (mFocused
<> self
) then mFocused
.activated();
2430 procedure TUITopWindow
.blurred ();
2432 mDragScroll
:= TXMode
.None
;
2433 mWaitingClose
:= false;
2435 if (mFocused
<> nil) and (mFocused
<> self
) then mFocused
.blurred();
2440 procedure TUITopWindow
.keyEventBubble (var ev
: TFUIEvent
);
2442 inherited keyEvent(ev
);
2443 if (not ev
.alive
) or (not enabled
) {or (not getFocused)} then exit
;
2444 if (ev
= 'M-F3') then
2446 if (not assigned(closeRequestCB
)) or (closeRequestCB(self
)) then
2448 uiRemoveWindow(self
);
2456 procedure TUITopWindow
.mouseEvent (var ev
: TFUIEvent
);
2459 vhgt
, ytop
: Integer;
2460 vwdt
, xend
: Integer;
2462 if (not enabled
) then exit
;
2463 if (mWidth
< 1) or (mHeight
< 1) then exit
;
2465 if (mDragScroll
= TXMode
.Drag
) then
2467 mX
+= ev
.x
-mDragStartX
;
2468 mY
+= ev
.y
-mDragStartY
;
2469 mDragStartX
:= ev
.x
;
2470 mDragStartY
:= ev
.y
;
2471 if (ev
.release
) and (ev
.but
= ev
.Left
) then mDragScroll
:= TXMode
.None
;
2476 if (mDragScroll
= TXMode
.VScroll
) then
2479 vhgt
:= mHeight
-mFrameHeight
*2;
2480 ytop
:= uiContext
.sbarPos(ly
, mFrameHeight
-1, vhgt
+2, 0, mFullSize
.h
)-vhgt
;
2481 mScrollY
:= nmax(0, ytop
);
2482 if (ev
.release
) and (ev
.but
= ev
.Left
) then mDragScroll
:= TXMode
.None
;
2487 if (mDragScroll
= TXMode
.HScroll
) then
2490 vwdt
:= mWidth
-mFrameWidth
*2;
2491 xend
:= uiContext
.sbarPos(lx
, mFrameWidth
+1, vwdt
-2, 0, mFullSize
.w
)-vwdt
;
2492 mScrollX
:= nmax(0, xend
);
2493 if (ev
.release
) and (ev
.but
= ev
.Left
) then mDragScroll
:= TXMode
.None
;
2498 if toLocal(ev
.x
, ev
.y
, lx
, ly
) then
2502 if (ly
< mFrameHeight
) then
2505 if (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
)) then
2507 //uiRemoveWindow(self);
2508 mWaitingClose
:= true;
2513 mDragScroll
:= TXMode
.Drag
;
2514 mDragStartX
:= ev
.x
;
2515 mDragStartY
:= ev
.y
;
2520 // check for vertical scrollbar
2521 if (lx
>= mWidth
-mFrameWidth
+1) and (ly
>= mFrameHeight
-1) and (ly
< mHeight
-mFrameHeight
+2) then
2523 vhgt
:= mHeight
-mFrameHeight
*2;
2524 if (mFullSize
.h
> vhgt
) then
2527 mDragScroll
:= TXMode
.VScroll
;
2529 ytop
:= uiContext
.sbarPos(ly
, mFrameHeight
-1, vhgt
+2, 0, mFullSize
.h
)-vhgt
;
2530 mScrollY
:= nmax(0, ytop
);
2534 // check for horizontal scrollbar
2535 if (ly
>= mHeight
-mFrameHeight
+1) and (lx
>= mFrameWidth
+1) and (lx
< mWidth
-mFrameWidth
-1) then
2537 vwdt
:= mWidth
-mFrameWidth
*2;
2538 if (mFullSize
.w
> vwdt
) then
2541 mDragScroll
:= TXMode
.HScroll
;
2543 xend
:= uiContext
.sbarPos(lx
, mFrameWidth
+1, vwdt
-2, 0, mFullSize
.w
)-vwdt
;
2544 mScrollX
:= nmax(0, xend
);
2549 if (lx
< mFrameWidth
) or (lx
>= mWidth
-mFrameWidth
) or (ly
>= mHeight
-mFrameHeight
) then
2552 mDragScroll
:= TXMode
.Drag
;
2553 mDragStartX
:= ev
.x
;
2554 mDragStartY
:= ev
.y
;
2560 if (ev
.release
) then
2562 if mWaitingClose
then
2564 if (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
)) then
2566 if (not assigned(closeRequestCB
)) or (closeRequestCB(self
)) then
2568 uiRemoveWindow(self
);
2571 mWaitingClose
:= false;
2580 if mWaitingClose
then
2582 mInClose
:= (lx
>= mFrameWidth
) and (lx
< mFrameWidth
+uiContext
.iconWinWidth(TGxContext
.TWinIcon
.Close
));
2588 inherited mouseEvent(ev
);
2593 if (not ev
.motion
) and (mWaitingClose
) then begin ev
.eat(); mWaitingClose
:= false; exit
; end;
2598 // ////////////////////////////////////////////////////////////////////////// //
2599 constructor TUIBox
.Create (ahoriz
: Boolean);
2606 procedure TUIBox
.AfterConstruction ();
2610 mHAlign
:= -1; // left
2611 mCtl4Style
:= 'box';
2612 mDefSize
:= TLaySize
.Create(-1, -1);
2616 procedure TUIBox
.setCaption (const acap
: AnsiString);
2619 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mCaption
)+3, uiContext
.textHeight(mCaption
));
2623 procedure TUIBox
.setHasFrame (v
: Boolean);
2626 if (mHasFrame
) then begin mFrameWidth
:= 8; mFrameHeight
:= uiContext
.charHeight(#184); end else begin mFrameWidth
:= 0; mFrameHeight
:= 0; end;
2627 if (mHasFrame
) then mNoPad
:= true;
2631 function TUIBox
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2633 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2634 if (strEquCI1251(prname
, 'padding')) then
2636 if (mHoriz
) then mPadding
:= parseHPadding(par
, 0) else mPadding
:= parseVPadding(par
, 0);
2640 if (strEquCI1251(prname
, 'frame')) then
2642 setHasFrame(parseBool(par
));
2646 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) then
2648 setCaption(par
.expectIdOrStr(true));
2652 if (strEquCI1251(prname
, 'textalign')) or (strEquCI1251(prname
, 'text-align')) then
2654 mHAlign
:= parseHAlign(par
);
2658 if (strEquCI1251(prname
, 'children')) then
2664 result
:= inherited parseProperty(prname
, par
);
2668 procedure TUIBox
.drawControl (gx
, gy
: Integer);
2673 cidx
:= getColorIndex
;
2674 uiContext
.color
:= mBackColor
[cidx
];
2675 uiContext
.fillRect(gx
, gy
, mWidth
, mHeight
);
2679 drawFrame(gx
, gy
, 0, mHAlign
, mCaption
, false);
2681 // no frame -- no caption
2683 else if (Length(mCaption) > 0) then
2686 if (mHAlign < 0) then xpos := 3
2687 else if (mHAlign > 0) then xpos := mWidth-mFrameWidth*2-uiContext.textWidth(mCaption)
2688 else xpos := (mWidth-mFrameWidth*2-uiContext.textWidth(mCaption)) div 2;
2689 xpos += gx+mFrameWidth;
2691 setScissor(mFrameWidth+1, 0, mWidth-mFrameWidth-2, uiContext.textHeight(mCaption));
2692 uiContext.color := mFrameTextColor[cidx];
2693 uiContext.drawText(xpos, gy, mCaption);
2699 procedure TUIBox
.mouseEvent (var ev
: TFUIEvent
);
2703 inherited mouseEvent(ev
);
2704 if (ev
.alive
) and (enabled
) and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
2711 procedure TUIBox
.keyEvent (var ev
: TFUIEvent
);
2714 cur
, ctl
: TUIControl
;
2716 inherited keyEvent(ev
);
2717 if (not ev
.alive
) or (not ev
.press
) or (not enabled
) or (not getActive
) then exit
;
2718 if (Length(mChildren
) = 0) then exit
;
2719 if (mHoriz
) and (ev
= 'Left') then dir
:= -1
2720 else if (mHoriz
) and (ev
= 'Right') then dir
:= 1
2721 else if (not mHoriz
) and (ev
= 'Up') then dir
:= -1
2722 else if (not mHoriz
) and (ev
= 'Down') then dir
:= 1;
2723 if (dir
= 0) then exit
;
2725 cur
:= topLevel
.mFocused
;
2726 while (cur
<> nil) and (cur
.mParent
<> self
) do cur
:= cur
.mParent
;
2727 //if (cur = nil) then writeln('CUR: nil') else writeln('CUR: ', cur.className, '#', cur.id);
2728 if (dir
< 0) then ctl
:= findPrevFocus(cur
, true) else ctl
:= findNextFocus(cur
, true);
2729 //if (ctl = nil) then writeln('CTL: nil') else writeln('CTL: ', ctl.className, '#', ctl.id);
2730 if (ctl
<> nil) and (ctl
<> self
) then
2732 ctl
.focused
:= true;
2737 // ////////////////////////////////////////////////////////////////////////// //
2738 constructor TUIHBox
.Create ();
2743 procedure TUIHBox
.AfterConstruction ();
2750 // ////////////////////////////////////////////////////////////////////////// //
2751 constructor TUIVBox
.Create ();
2756 procedure TUIVBox
.AfterConstruction ();
2763 // ////////////////////////////////////////////////////////////////////////// //
2764 procedure TUISpan
.AfterConstruction ();
2770 mCtl4Style
:= 'span';
2771 mDefSize
:= TLaySize
.Create(-1, -1);
2775 function TUISpan
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2777 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2778 result
:= inherited parseProperty(prname
, par
);
2782 // ////////////////////////////////////////////////////////////////////// //
2783 procedure TUILine
.AfterConstruction ();
2789 mCtl4Style
:= 'line';
2790 mDefSize
:= TLaySize
.Create(-1, -1);
2794 function TUILine
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2796 if (parseOrientation(prname
, par
)) then begin result
:= true; exit
; end;
2797 result
:= inherited parseProperty(prname
, par
);
2801 procedure TUILine
.layPrepare ();
2803 inherited layPrepare();
2804 if (mParent
<> nil) then mHoriz
:= not mParent
.mHoriz
;
2807 if (mLayDefSize
.w
< 0) then mLayDefSize
.w
:= 1;
2808 if (mLayDefSize
.h
< 0) then mLayDefSize
.h
:= 7;
2812 if (mLayDefSize
.w
< 0) then mLayDefSize
.w
:= 7;
2813 if (mLayDefSize
.h
< 0) then mLayDefSize
.h
:= 1;
2818 procedure TUILine
.drawControl (gx
, gy
: Integer);
2822 cidx
:= getColorIndex
;
2823 uiContext
.color
:= mTextColor
[cidx
];
2824 if mHoriz
then uiContext
.hline(gx
, gy
+(mHeight
div 2), mWidth
)
2825 else uiContext
.vline(gx
+(mWidth
div 2), gy
, mHeight
);
2829 // ////////////////////////////////////////////////////////////////////////// //
2830 procedure TUIStaticText
.AfterConstruction ();
2836 mHoriz
:= true; // nobody cares
2839 mCtl4Style
:= 'static';
2843 procedure TUIStaticText
.setText (const atext
: AnsiString);
2846 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
), uiContext
.textHeight(mText
));
2850 function TUIStaticText
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2852 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) or (strEquCI1251(prname
, 'text')) then
2854 setText(par
.expectIdOrStr(true));
2858 if (strEquCI1251(prname
, 'textalign')) or (strEquCI1251(prname
, 'text-align')) then
2860 parseTextAlign(par
, mHAlign
, mVAlign
);
2864 if (strEquCI1251(prname
, 'header')) then
2870 if (strEquCI1251(prname
, 'line')) then
2876 result
:= inherited parseProperty(prname
, par
);
2880 procedure TUIStaticText
.drawControl (gx
, gy
: Integer);
2882 xpos
, ypos
: Integer;
2885 cidx
:= getColorIndex
;
2886 uiContext
.color
:= mBackColor
[cidx
];
2887 uiContext
.fillRect(gx
, gy
, mWidth
, mHeight
);
2889 if (mHAlign
< 0) then xpos
:= 0
2890 else if (mHAlign
> 0) then xpos
:= mWidth
-uiContext
.textWidth(mText
)
2891 else xpos
:= (mWidth
-uiContext
.textWidth(mText
)) div 2;
2893 if (Length(mText
) > 0) then
2895 if (mHeader
) then uiContext
.color
:= mFrameTextColor
[cidx
] else uiContext
.color
:= mTextColor
[cidx
];
2897 if (mVAlign
< 0) then ypos
:= 0
2898 else if (mVAlign
> 0) then ypos
:= mHeight
-uiContext
.textHeight(mText
)
2899 else ypos
:= (mHeight
-uiContext
.textHeight(mText
)) div 2;
2901 uiContext
.drawText(gx
+xpos
, gy
+ypos
, mText
);
2906 if (mHeader
) then uiContext
.color
:= mFrameColor
[cidx
] else uiContext
.color
:= mTextColor
[cidx
];
2908 if (mVAlign
< 0) then ypos
:= 0
2909 else if (mVAlign
> 0) then ypos
:= mHeight
-1
2910 else ypos
:= (mHeight
div 2);
2913 if (Length(mText
) = 0) then
2915 uiContext
.hline(gx
, ypos
, mWidth
);
2919 uiContext
.hline(gx
, ypos
, xpos
-1);
2920 uiContext
.hline(gx
+xpos
+uiContext
.textWidth(mText
), ypos
, mWidth
);
2926 // ////////////////////////////////////////////////////////////////////////// //
2927 procedure TUITextLabel
.AfterConstruction ();
2933 mCtl4Style
:= 'label';
2938 procedure TUITextLabel
.cacheStyle (root
: TUIStyle
);
2940 inherited cacheStyle(root
);
2942 mHotColor
[ClrIdxActive
] := root
.get('hot-color', 'active', mCtl4Style
).asRGBADef(TGxRGBA
.Create(0, 128, 0));
2944 mHotColor
[ClrIdxDisabled
] := root
.get('hot-color', 'disabled', mCtl4Style
).asRGBADef(TGxRGBA
.Create(0, 64, 0));
2946 mHotColor
[ClrIdxInactive
] := root
.get('hot-color', 'inactive', mCtl4Style
).asRGBADef(TGxRGBA
.Create(0, 64, 0));
2950 procedure TUITextLabel
.setText (const s
: AnsiString);
2958 while (f
<= Length(s
)) do
2960 if (s
[f
] = '\\') then
2963 if (f
<= Length(s
)) then mText
+= s
[f
];
2966 else if (s
[f
] = '~') then
2969 if (f
<= Length(s
)) then
2971 if (mHotChar
= #0) then
2974 mHotOfs
:= Length(mText
);
2986 // fix hotchar offset
2987 if (mHotChar
<> #0) and (mHotOfs
> 0) then
2989 mHotOfs
:= uiContext
.textWidth(Copy(mText
, 1, mHotOfs
+1))-uiContext
.charWidth(mText
[mHotOfs
+1]);
2992 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
), uiContext
.textHeight(mText
));
2996 function TUITextLabel
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
2998 if (strEquCI1251(prname
, 'title')) or (strEquCI1251(prname
, 'caption')) or (strEquCI1251(prname
, 'text')) then
3000 setText(par
.expectIdOrStr(true));
3004 if (strEquCI1251(prname
, 'link')) then
3006 mLinkId
:= par
.expectIdOrStr(true);
3010 if (strEquCI1251(prname
, 'textalign')) or (strEquCI1251(prname
, 'text-align')) then
3012 parseTextAlign(par
, mHAlign
, mVAlign
);
3016 result
:= inherited parseProperty(prname
, par
);
3020 procedure TUITextLabel
.drawControl (gx
, gy
: Integer);
3022 xpos
, ypos
: Integer;
3025 cidx
:= getColorIndex
;
3026 uiContext
.color
:= mBackColor
[cidx
];
3027 uiContext
.fillRect(gx
, gy
, mWidth
, mHeight
);
3028 if (Length(mText
) > 0) then
3030 if (mHAlign
< 0) then xpos
:= 0
3031 else if (mHAlign
> 0) then xpos
:= mWidth
-uiContext
.textWidth(mText
)
3032 else xpos
:= (mWidth
-uiContext
.textWidth(mText
)) div 2;
3034 if (mVAlign
< 0) then ypos
:= 0
3035 else if (mVAlign
> 0) then ypos
:= mHeight
-uiContext
.textHeight(mText
)
3036 else ypos
:= (mHeight
-uiContext
.textHeight(mText
)) div 2;
3038 uiContext
.color
:= mTextColor
[cidx
];
3039 uiContext
.drawText(gx
+xpos
, gy
+ypos
, mText
);
3041 if (Length(mLinkId
) > 0) and (mHotChar
<> #0) and (mHotChar
<> ' ') then
3043 uiContext
.color
:= mHotColor
[cidx
];
3044 uiContext
.drawChar(gx
+xpos
+mHotOfs
, gy
+ypos
, mHotChar
);
3050 procedure TUITextLabel
.mouseEvent (var ev
: TFUIEvent
);
3054 inherited mouseEvent(ev
);
3055 if (ev
.alive
) and (enabled
) and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
3062 procedure TUITextLabel
.doAction ();
3066 if (assigned(actionCB
)) then
3072 ctl
:= topLevel
[mLinkId
];
3073 if (ctl
<> nil) then
3075 if (ctl
.canFocus
) then ctl
.focused
:= true;
3081 procedure TUITextLabel
.keyEventBubble (var ev
: TFUIEvent
);
3083 if (not enabled
) then exit
;
3084 if (mHotChar
= #0) then exit
;
3085 if (not ev
.alive
) or (not ev
.press
) then exit
;
3086 if (ev
.kstate
<> ev
.ModAlt
) then exit
;
3087 if (not ev
.isHot(mHotChar
)) then exit
;
3089 if (canFocus
) then focused
:= true;
3094 // ////////////////////////////////////////////////////////////////////////// //
3095 procedure TUIButton
.AfterConstruction ();
3102 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+uiContext
.textWidth('[ ]'), uiContext
.textHeight(mText
));
3103 mCtl4Style
:= 'button';
3104 mSkipLayPrepare
:= false;
3105 mAddMarkers
:= false;
3106 mHideMarkers
:= false;
3110 procedure TUIButton
.cacheStyle (root
: TUIStyle
);
3114 inherited cacheStyle(root
);
3116 sz
:= nmax(0, root
.get('shadow-size', 'active', mCtl4Style
).asInt(0));
3117 sz
:= nmax(sz
, root
.get('shadow-size', 'disabled', mCtl4Style
).asInt(0));
3118 sz
:= nmax(sz
, root
.get('shadow-size', 'inactive', mCtl4Style
).asInt(0));
3121 mAddMarkers
:= root
.get('add-markers', 'active', mCtl4Style
).asBool(false);
3122 mAddMarkers
:= mAddMarkers
or root
.get('add-markers', 'disabled', mCtl4Style
).asBool(false);
3123 mAddMarkers
:= mAddMarkers
or root
.get('add-markers', 'inactive', mCtl4Style
).asBool(false);
3125 mHideMarkers
:= root
.get('hide-markers', 'active', mCtl4Style
).asBool(false);
3126 mHideMarkers
:= mHideMarkers
or root
.get('hide-markers', 'disabled', mCtl4Style
).asBool(false);
3127 mHideMarkers
:= mHideMarkers
or root
.get('hide-markers', 'inactive', mCtl4Style
).asBool(false);
3131 procedure TUIButton
.setText (const s
: AnsiString);
3133 inherited setText(s
);
3134 if (mHideMarkers
) then
3136 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+10, uiContext
.textHeight(mText
));
3138 else if (mAddMarkers
) then
3140 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+uiContext
.textWidth('[<>]'), uiContext
.textHeight(mText
));
3144 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+uiContext
.textWidth('<>'), uiContext
.textHeight(mText
));
3149 procedure TUIButton
.layPrepare ();
3154 if (not mSkipLayPrepare
) then
3157 if (ods
.w
<> 0) or (ods
.h
<> 0) then
3159 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
), uiContext
.textHeight(mText
));
3160 if (mHideMarkers
) then
3164 else if (mAddMarkers
) then
3166 if (mDefault
) then ww
:= uiContext
.textWidth('[< >]')
3167 else if (mCancel
) then ww
:= uiContext
.textWidth('[{ }]')
3168 else ww
:= uiContext
.textWidth('[ ]');
3172 ww
:= nmax(0, uiContext
.textWidth('< >'));
3173 ww
:= nmax(ww
, uiContext
.textWidth('{ }'));
3174 ww
:= nmax(ww
, uiContext
.textWidth('[ ]'));
3176 mDefSize
.w
+= ww
+mShadowSize
;
3177 mDefSize
.h
+= mShadowSize
;
3182 ods
:= TLaySize
.Create(0, 0); // fpc is dumb!
3184 inherited layPrepare();
3185 if (not mSkipLayPrepare
) then mDefSize
:= ods
;
3189 procedure TUIButton
.blurred ();
3195 procedure TUIButton
.drawControl (gx
, gy
: Integer);
3198 xpos
, ypos
, xofsl
, xofsr
, sofs
: Integer;
3201 lstr
, rstr
: AnsiString;
3203 cidx
:= getColorIndex
;
3205 wdt
:= mWidth
-mShadowSize
;
3206 hgt
:= mHeight
-mShadowSize
;
3207 if (mPushed
) {or (cidx = ClrIdxActive)} then
3209 sofs
:= mShadowSize
;
3216 if (mShadowSize
> 0) then
3218 uiContext
.darkenRect(gx
+mShadowSize
, gy
+hgt
, wdt
, mShadowSize
, 96);
3219 uiContext
.darkenRect(gx
+wdt
, gy
+mShadowSize
, mShadowSize
, hgt
-mShadowSize
, 96);
3223 uiContext
.color
:= mBackColor
[cidx
];
3224 uiContext
.fillRect(gx
, gy
, wdt
, hgt
);
3226 if (mVAlign
< 0) then ypos
:= 0
3227 else if (mVAlign
> 0) then ypos
:= hgt
-uiContext
.textHeight(mText
)
3228 else ypos
:= (hgt
-uiContext
.textHeight(mText
)) div 2;
3231 uiContext
.color
:= mTextColor
[cidx
];
3233 if (mHideMarkers
) then
3240 if (mAddMarkers
) then
3242 if (mDefault
) then begin lstr
:= '[< '; rstr
:= ' >]'; end
3243 else if (mCancel
) then begin lstr
:= '[{ '; rstr
:= ' }]'; end
3244 else begin lstr
:= '[ '; rstr
:= ' ]'; end;
3245 xofsl
:= uiContext
.textWidth(lstr
);
3246 xofsr
:= uiContext
.textWidth(rstr
);
3247 uiContext
.drawText(gx
, ypos
, lstr
);
3248 uiContext
.drawText(gx
+wdt
-uiContext
.textWidth(rstr
), ypos
, rstr
);
3252 xofsl
:= nmax(0, uiContext
.textWidth('< '));
3253 xofsl
:= nmax(xofsl
, uiContext
.textWidth('{ '));
3254 xofsl
:= nmax(xofsl
, uiContext
.textWidth('[ '));
3255 xofsr
:= nmax(0, uiContext
.textWidth(' >'));
3256 xofsr
:= nmax(xofsr
, uiContext
.textWidth(' }'));
3257 xofsr
:= nmax(xofsr
, uiContext
.textWidth(' ]'));
3258 if (mDefault
) then begin lch
:= '<'; rch
:= '>'; end
3259 else if (mCancel
) then begin lch
:= '{'; rch
:= '}'; end
3260 else begin lch
:= '['; rch
:= ']'; end;
3261 uiContext
.drawChar(gx
, ypos
, lch
);
3262 uiContext
.drawChar(gx
+wdt
-uiContext
.charWidth(rch
), ypos
, rch
);
3266 if (Length(mText
) > 0) then
3268 if (mHAlign
< 0) then xpos
:= 0
3269 else begin xpos
:= wdt
-xofsl
-xofsr
-uiContext
.textWidth(mText
); if (mHAlign
= 0) then xpos
:= xpos
div 2; end;
3272 setScissor(sofs
+xofsl
, sofs
, wdt
-xofsl
-xofsr
, hgt
);
3273 uiContext
.drawText(gx
+xpos
, ypos
, mText
);
3275 if (mHotChar
<> #0) and (mHotChar
<> ' ') then
3277 uiContext
.color
:= mHotColor
[cidx
];
3278 uiContext
.drawChar(gx
+xpos
+mHotOfs
, ypos
, mHotChar
);
3284 procedure TUIButton
.mouseEvent (var ev
: TFUIEvent
);
3288 inherited mouseEvent(ev
);
3289 if (uiGrabCtl
= self
) then
3292 mPushed
:= toLocal(ev
.x
, ev
.y
, lx
, ly
);
3293 if (ev
= '-lmb') and (focused
) and (mPushed
) then
3300 if (not ev
.alive
) or (not enabled
) or (not focused
) then exit
;
3306 procedure TUIButton
.keyEvent (var ev
: TFUIEvent
);
3308 inherited keyEvent(ev
);
3309 if (ev
.alive
) and (enabled
) then
3311 if (ev
= '+Enter') or (ev
= '+Space') then
3318 if (focused
) and ((ev
= '-Enter') or (ev
= '-Space')) then
3336 // ////////////////////////////////////////////////////////////////////////// //
3337 procedure TUIButtonRound
.AfterConstruction ();
3343 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+8*2, uiContext
.textHeight(mText
)+2);
3344 mCtl4Style
:= 'button-round';
3345 mSkipLayPrepare
:= true;
3349 procedure TUIButtonRound
.setText (const s
: AnsiString);
3351 inherited setText(s
);
3352 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+8*2, uiContext
.textHeight(mText
)+2);
3356 procedure TUIButtonRound
.layPrepare ();
3361 if (ods
.w
<> 0) or (ods
.h
<> 0) then
3363 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+8*2, uiContext
.textHeight(mText
)+2);
3365 inherited layPrepare();
3370 procedure TUIButtonRound
.drawControl (gx
, gy
: Integer);
3372 xpos
, ypos
: Integer;
3375 cidx
:= getColorIndex
;
3377 uiContext
.color
:= mBackColor
[cidx
];
3378 uiContext
.fillRect(gx
+1, gy
, mWidth
-2, mHeight
);
3379 uiContext
.fillRect(gx
, gy
+1, 1, mHeight
-2);
3380 uiContext
.fillRect(gx
+mWidth
-1, gy
+1, 1, mHeight
-2);
3382 if (Length(mText
) > 0) then
3384 if (mHAlign
< 0) then xpos
:= 0
3385 else if (mHAlign
> 0) then xpos
:= mWidth
-uiContext
.textWidth(mText
)
3386 else xpos
:= (mWidth
-uiContext
.textWidth(mText
)) div 2;
3388 if (mVAlign
< 0) then ypos
:= 0
3389 else if (mVAlign
> 0) then ypos
:= mHeight
-uiContext
.textHeight(mText
)
3390 else ypos
:= (mHeight
-uiContext
.textHeight(mText
)) div 2;
3392 setScissor(8, 0, mWidth
-16, mHeight
);
3393 uiContext
.color
:= mTextColor
[cidx
];
3394 uiContext
.drawText(gx
+xpos
+8, gy
+ypos
, mText
);
3396 if (mHotChar
<> #0) and (mHotChar
<> ' ') then
3398 uiContext
.color
:= mHotColor
[cidx
];
3399 uiContext
.drawChar(gx
+xpos
+8+mHotOfs
, gy
+ypos
, mHotChar
);
3405 // ////////////////////////////////////////////////////////////////////////// //
3406 procedure TUISwitchBox
.AfterConstruction ();
3412 mIcon
:= TGxContext
.TMarkIcon
.Checkbox
;
3413 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+3+uiContext
.iconMarkWidth(mIcon
), nmax(uiContext
.iconMarkHeight(mIcon
), uiContext
.textHeight(mText
)));
3414 mCtl4Style
:= 'switchbox';
3416 mBoolVar
:= @mChecked
;
3420 procedure TUISwitchBox
.cacheStyle (root
: TUIStyle
);
3422 inherited cacheStyle(root
);
3424 mSwitchColor
[ClrIdxActive
] := root
.get('switch-color', 'active', mCtl4Style
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
3426 mSwitchColor
[ClrIdxDisabled
] := root
.get('switch-color', 'disabled', mCtl4Style
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
3428 mSwitchColor
[ClrIdxInactive
] := root
.get('switch-color', 'inactive', mCtl4Style
).asRGBADef(TGxRGBA
.Create(255, 255, 255));
3432 procedure TUISwitchBox
.setText (const s
: AnsiString);
3434 inherited setText(s
);
3435 mDefSize
:= TLaySize
.Create(uiContext
.textWidth(mText
)+3+uiContext
.iconMarkWidth(mIcon
), nmax(uiContext
.iconMarkHeight(mIcon
), uiContext
.textHeight(mText
)));
3439 function TUISwitchBox
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
3441 if (strEquCI1251(prname
, 'checked')) then
3447 result
:= inherited parseProperty(prname
, par
);
3451 function TUISwitchBox
.getChecked (): Boolean;
3453 if (mBoolVar
<> nil) then result
:= mBoolVar
^ else result
:= false;
3457 procedure TUISwitchBox
.setVar (pvar
: PBoolean);
3459 if (pvar
= nil) then pvar
:= @mChecked
;
3460 if (pvar
<> mBoolVar
) then
3463 setChecked(mBoolVar
^);
3468 procedure TUISwitchBox
.drawControl (gx
, gy
: Integer);
3470 xpos
, ypos
, iwdt
, dy
: Integer;
3473 cidx
:= getColorIndex
;
3475 iwdt
:= uiContext
.iconMarkWidth(mIcon
);
3476 if (mHAlign
< 0) then xpos
:= 0
3477 else if (mHAlign
> 0) then xpos
:= mWidth
-(uiContext
.textWidth(mText
)+3+iwdt
)
3478 else xpos
:= (mWidth
-(uiContext
.textWidth(mText
)+3+iwdt
)) div 2;
3480 if (mVAlign
< 0) then ypos
:= 0
3481 else if (mVAlign
> 0) then ypos
:= mHeight
-uiContext
.textHeight(mText
)
3482 else ypos
:= (mHeight
-uiContext
.textHeight(mText
)) div 2;
3484 uiContext
.color
:= mBackColor
[cidx
];
3485 uiContext
.fillRect(gx
, gy
, mWidth
, mHeight
);
3487 uiContext
.color
:= mSwitchColor
[cidx
];
3488 if (uiContext
.iconMarkHeight(mIcon
) < uiContext
.textHeight(mText
)) then
3490 case uiContext
.textHeight(mText
) of
3495 uiContext
.drawIconMark(mIcon
, gx
, gy
+ypos
+uiContext
.textHeight(mText
)-uiContext
.iconMarkHeight(mIcon
)-dy
, checked
);
3499 uiContext
.drawIconMark(mIcon
, gx
, gy
, checked
);
3502 uiContext
.color
:= mTextColor
[cidx
];
3503 uiContext
.drawText(gx
+xpos
+3+iwdt
, gy
+ypos
, mText
);
3505 if (mHotChar
<> #0) and (mHotChar
<> ' ') then
3507 uiContext
.color
:= mHotColor
[cidx
];
3508 uiContext
.drawChar(gx
+xpos
+3+iwdt
+mHotOfs
, gy
+ypos
, mHotChar
);
3513 procedure TUISwitchBox
.mouseEvent (var ev
: TFUIEvent
);
3517 inherited mouseEvent(ev
);
3518 if (uiGrabCtl
= self
) then
3521 if (ev
= '-lmb') and focused
and toLocal(ev
.x
, ev
.y
, lx
, ly
) then
3527 if (not ev
.alive
) or (not enabled
) or not focused
then exit
;
3532 procedure TUISwitchBox
.keyEvent (var ev
: TFUIEvent
);
3534 inherited keyEvent(ev
);
3535 if (ev
.alive
) and (enabled
) then
3537 if (ev
= 'Space') then
3547 // ////////////////////////////////////////////////////////////////////////// //
3548 procedure TUICheckBox
.AfterConstruction ();
3552 mBoolVar
:= @mChecked
;
3553 mIcon
:= TGxContext
.TMarkIcon
.Checkbox
;
3558 procedure TUICheckBox
.setChecked (v
: Boolean);
3564 procedure TUICheckBox
.doAction ();
3566 if (assigned(actionCB
)) then
3572 setChecked(not getChecked
);
3577 // ////////////////////////////////////////////////////////////////////////// //
3578 procedure TUIRadioBox
.AfterConstruction ();
3582 mBoolVar
:= @mChecked
;
3584 mIcon
:= TGxContext
.TMarkIcon
.Radiobox
;
3589 function TUIRadioBox
.parseProperty (const prname
: AnsiString; par
: TTextParser
): Boolean;
3591 if (strEquCI1251(prname
, 'group')) then
3593 mRadioGroup
:= par
.expectIdOrStr(true);
3594 if (getChecked
) then setChecked(true);
3598 if (strEquCI1251(prname
, 'checked')) then
3604 result
:= inherited parseProperty(prname
, par
);
3608 procedure TUIRadioBox
.setChecked (v
: Boolean);
3610 function resetGroup (ctl
: TUIControl
): Boolean;
3613 if (ctl
<> self
) and (ctl
is TUIRadioBox
) and (TUIRadioBox(ctl
).mRadioGroup
= mRadioGroup
) then
3615 TUIRadioBox(ctl
).mBoolVar
^ := false;
3621 if v
then topLevel
.forEachControl(resetGroup
);
3625 procedure TUIRadioBox
.doAction ();
3627 if (assigned(actionCB
)) then
3638 // ////////////////////////////////////////////////////////////////////////// //
3640 oldFocus
: procedure () = nil;
3641 oldBlur
: procedure () = nil;
3643 procedure onWinFocus (); begin uiFocus(); if (assigned(oldFocus
)) then oldFocus(); end;
3644 procedure onWinBlur (); begin fuiResetKMState(true); uiBlur(); if (assigned(oldBlur
)) then oldBlur(); end;
3647 registerCtlClass(TUIHBox
, 'hbox');
3648 registerCtlClass(TUIVBox
, 'vbox');
3649 registerCtlClass(TUISpan
, 'span');
3650 registerCtlClass(TUILine
, 'line');
3651 registerCtlClass(TUITextLabel
, 'label');
3652 registerCtlClass(TUIStaticText
, 'static');
3653 registerCtlClass(TUIButtonRound
, 'round-button');
3654 registerCtlClass(TUIButton
, 'button');
3655 registerCtlClass(TUICheckBox
, 'checkbox');
3656 registerCtlClass(TUIRadioBox
, 'radiobox');
3658 oldFocus
:= winFocusCB
;
3659 oldBlur
:= winBlurCB
;
3660 winFocusCB
:= onWinFocus
;
3661 winBlurCB
:= onWinBlur
;