DEADSOFTWARE

Holmes: UI cosmetix
[d2df-sdl.git] / src / gx / gh_ui.pas
1 (* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
3 *
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.
8 *
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.
13 *
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/>.
16 *)
17 {$INCLUDE ../shared/a_modes.inc}
18 {$M+}
19 unit gh_ui;
21 interface
23 uses
24 SysUtils, Classes,
25 SDL2,
26 gh_ui_common,
27 gh_ui_style,
28 sdlcarcass, glgfx,
29 xparser;
32 // ////////////////////////////////////////////////////////////////////////// //
33 type
34 TUIControlClass = class of TUIControl;
36 TUIControl = class
37 public
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;
44 public
45 const ClrIdxActive = 0;
46 const ClrIdxDisabled = 1;
47 const ClrIdxInactive = 2;
48 const ClrIdxMax = 2;
50 private
51 mParent: TUIControl;
52 mId: AnsiString;
53 mStyleId: AnsiString;
54 mX, mY: Integer;
55 mWidth, mHeight: Integer;
56 mFrameWidth, mFrameHeight: Integer;
57 mScrollX, mScrollY: Integer;
58 mEnabled: Boolean;
59 mCanFocus: Boolean;
60 mChildren: array of TUIControl;
61 mFocused: TUIControl; // valid only for top-level controls
62 mEscClose: Boolean; // valid only for top-level controls
63 mDrawShadow: Boolean;
64 mCancel: Boolean;
65 mDefault: Boolean;
66 // colors
67 mCtl4Style: AnsiString;
68 mBackColor: array[0..ClrIdxMax] of TGxRGBA;
69 mTextColor: array[0..ClrIdxMax] of TGxRGBA;
70 mFrameColor: array[0..ClrIdxMax] of TGxRGBA;
71 mFrameTextColor: array[0..ClrIdxMax] of TGxRGBA;
72 mFrameIconColor: array[0..ClrIdxMax] of TGxRGBA;
73 mDarken: array[0..ClrIdxMax] of Integer; // -1: none
75 private
76 scis: TScissorSave;
77 scallowed: Boolean;
79 protected
80 procedure updateStyle (); virtual;
81 procedure cacheStyle (root: TUIStyle); virtual;
82 function getColorIndex (): Integer; inline;
84 protected
85 function getEnabled (): Boolean;
86 procedure setEnabled (v: Boolean); inline;
88 function getFocused (): Boolean; inline;
89 procedure setFocused (v: Boolean); inline;
91 function getActive (): Boolean; inline;
93 function getCanFocus (): Boolean; inline;
95 function isMyChild (ctl: TUIControl): Boolean;
97 function findFirstFocus (): TUIControl;
98 function findLastFocus (): TUIControl;
100 function findNextFocus (cur: TUIControl; wrap: Boolean): TUIControl;
101 function findPrevFocus (cur: TUIControl; wrap: Boolean): TUIControl;
103 function findCancelControl (): TUIControl;
104 function findDefaulControl (): TUIControl;
106 function findControlById (const aid: AnsiString): TUIControl;
108 procedure activated (); virtual;
109 procedure blurred (); virtual;
111 procedure calcFullClientSize ();
113 //WARNING! do not call scissor functions outside `.draw*()` API!
114 // set scissor to this rect (in local coords)
115 procedure setScissor (lx, ly, lw, lh: Integer);
116 // reset scissor to whole control
117 procedure resetScissor (fullArea: Boolean); inline; // "full area" means "with frame"
119 // DO NOT USE!
120 // set scissor to this rect (in global coords)
121 procedure setScissorGLInternal (x, y, w, h: Integer);
123 public
124 actionCB: TActionCB;
125 closeRequestCB: TCloseRequestCB;
127 private
128 mDefSize: TLaySize; // default size
129 mMaxSize: TLaySize; // maximum size
130 mFlex: Integer;
131 mHoriz: Boolean;
132 mCanWrap: Boolean;
133 mLineStart: Boolean;
134 mHGroup: AnsiString;
135 mVGroup: AnsiString;
136 mAlign: Integer;
137 mExpand: Boolean;
138 mLayDefSize: TLaySize;
139 mLayMaxSize: TLaySize;
140 mFullSize: TLaySize;
142 public
143 // layouter interface
144 function getDefSize (): TLaySize; inline; // default size; <0: use max size
145 //procedure setDefSize (const sz: TLaySize); inline; // default size; <0: use max size
146 function getMargins (): TLayMargins; inline;
147 function getMaxSize (): TLaySize; inline; // max size; <0: set to some huge value
148 //procedure setMaxSize (const sz: TLaySize); inline; // max size; <0: set to some huge value
149 function getFlex (): Integer; inline; // <=0: not flexible
150 function isHorizBox (): Boolean; inline; // horizontal layout for children?
151 procedure setHorizBox (v: Boolean); inline; // horizontal layout for children?
152 function canWrap (): Boolean; inline; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
153 procedure setCanWrap (v: Boolean); inline; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
154 function isLineStart (): Boolean; inline; // `true` if this ctl should start a new line; ignored for vertical boxes
155 procedure setLineStart (v: Boolean); inline; // `true` if this ctl should start a new line; ignored for vertical boxes
156 function getAlign (): Integer; inline; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
157 procedure setAlign (v: Integer); inline; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
158 function getExpand (): Boolean; inline; // expanding in non-main direction: `true` will ignore align and eat all available space
159 procedure setExpand (v: Boolean); inline; // expanding in non-main direction: `true` will ignore align and eat all available space
160 function getHGroup (): AnsiString; inline; // empty: not grouped
161 procedure setHGroup (const v: AnsiString); inline; // empty: not grouped
162 function getVGroup (): AnsiString; inline; // empty: not grouped
163 procedure setVGroup (const v: AnsiString); inline; // empty: not grouped
165 procedure setActualSizePos (constref apos: TLayPos; constref asize: TLaySize); inline;
167 procedure layPrepare (); virtual; // called before registering control in layouter
169 public
170 property flex: Integer read mFlex write mFlex;
171 property flDefaultSize: TLaySize read mDefSize write mDefSize;
172 property flMaxSize: TLaySize read mMaxSize write mMaxSize;
173 property flHoriz: Boolean read isHorizBox write setHorizBox;
174 property flCanWrap: Boolean read canWrap write setCanWrap;
175 property flLineStart: Boolean read isLineStart write setLineStart;
176 property flAlign: Integer read getAlign write setAlign;
177 property flExpand: Boolean read getExpand write setExpand;
178 property flHGroup: AnsiString read getHGroup write setHGroup;
179 property flVGroup: AnsiString read getVGroup write setVGroup;
180 property fullSize: TLaySize read mFullSize;
182 protected
183 function parsePos (par: TTextParser): TLayPos;
184 function parseSize (par: TTextParser): TLaySize;
185 function parseBool (par: TTextParser): Boolean;
186 function parseAnyAlign (par: TTextParser): Integer;
187 function parseHAlign (par: TTextParser): Integer;
188 function parseVAlign (par: TTextParser): Integer;
189 function parseOrientation (const prname: AnsiString; par: TTextParser): Boolean;
190 procedure parseTextAlign (par: TTextParser; var h, v: Integer);
191 procedure parseChildren (par: TTextParser); // par should be on '{'; final '}' is eaten
193 public
194 // par is on property data
195 // there may be more data in text stream, don't eat it!
196 // return `true` if property name is valid and value was parsed
197 // return `false` if property name is invalid; don't advance parser in this case
198 // throw on property data errors
199 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; virtual;
201 // par should be on '{'; final '}' is eaten
202 procedure parseProperties (par: TTextParser);
204 public
205 constructor Create ();
206 destructor Destroy (); override;
208 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
210 // `sx` and `sy` are screen coordinates
211 procedure drawControl (gx, gy: Integer); virtual;
213 // called after all children drawn
214 procedure drawControlPost (gx, gy: Integer); virtual;
216 procedure draw (); virtual;
218 function topLevel (): TUIControl; inline;
220 // returns `true` if global coords are inside this control
221 function toLocal (var x, y: Integer): Boolean;
222 function toLocal (gx, gy: Integer; out x, y: Integer): Boolean; inline;
223 procedure toGlobal (var x, y: Integer);
224 procedure toGlobal (lx, ly: Integer; out x, y: Integer); inline;
226 procedure getDrawRect (out gx, gy, wdt, hgt: Integer);
228 // x and y are global coords
229 function controlAtXY (x, y: Integer; allowDisabled: Boolean=false): TUIControl;
231 function parentScrollX (): Integer; inline;
232 function parentScrollY (): Integer; inline;
234 procedure makeVisibleInParent ();
236 procedure doAction (); virtual; // so user controls can override it
238 procedure mouseEvent (var ev: THMouseEvent); virtual; // returns `true` if event was eaten
239 procedure keyEvent (var ev: THKeyEvent); virtual; // returns `true` if event was eaten
240 procedure keyEventPre (var ev: THKeyEvent); virtual; // will be called before dispatching the event
241 procedure keyEventPost (var ev: THKeyEvent); virtual; // will be called after if nobody processed the event
243 function prevSibling (): TUIControl;
244 function nextSibling (): TUIControl;
245 function firstChild (): TUIControl; inline;
246 function lastChild (): TUIControl; inline;
248 procedure appendChild (ctl: TUIControl); virtual;
250 function setActionCBFor (const aid: AnsiString; cb: TActionCB): TActionCB; // returns previous cb
252 function forEachChildren (cb: TCtlEnumCB): TUIControl; // doesn't recurse
253 function forEachControl (cb: TCtlEnumCB; includeSelf: Boolean=true): TUIControl;
255 procedure close (); // this closes *top-level* control
257 public
258 property id: AnsiString read mId;
259 property styleId: AnsiString read mStyleId;
260 property scrollX: Integer read mScrollX write mScrollX;
261 property scrollY: Integer read mScrollY write mScrollY;
262 property x0: Integer read mX write mX;
263 property y0: Integer read mY write mY;
264 property width: Integer read mWidth write mWidth;
265 property height: Integer read mHeight write mHeight;
266 property enabled: Boolean read getEnabled write setEnabled;
267 property parent: TUIControl read mParent;
268 property focused: Boolean read getFocused write setFocused;
269 property active: Boolean read getActive;
270 property escClose: Boolean read mEscClose write mEscClose;
271 property cancel: Boolean read mCancel write mCancel;
272 property defctl: Boolean read mDefault write mDefault;
273 property canFocus: Boolean read getCanFocus write mCanFocus;
274 property ctlById[const aid: AnsiString]: TUIControl read findControlById; default;
275 end;
278 TUITopWindow = class(TUIControl)
279 private
280 type TXMode = (None, Drag, Scroll);
282 private
283 mTitle: AnsiString;
284 mDragScroll: TXMode;
285 mDragStartX, mDragStartY: Integer;
286 mWaitingClose: Boolean;
287 mInClose: Boolean;
288 mFreeOnClose: Boolean; // default: false
289 mDoCenter: Boolean; // after layouting
291 protected
292 procedure activated (); override;
293 procedure blurred (); override;
295 public
296 closeCB: TActionCB; // called after window was removed from ui window list
298 public
299 constructor Create (const atitle: AnsiString);
301 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
303 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
305 procedure centerInScreen ();
307 // `sx` and `sy` are screen coordinates
308 procedure drawControl (gx, gy: Integer); override;
309 procedure drawControlPost (gx, gy: Integer); override;
311 procedure keyEvent (var ev: THKeyEvent); override; // returns `true` if event was eaten
312 procedure mouseEvent (var ev: THMouseEvent); override; // returns `true` if event was eaten
314 public
315 property freeOnClose: Boolean read mFreeOnClose write mFreeOnClose;
316 end;
318 // ////////////////////////////////////////////////////////////////////// //
319 TUIBox = class(TUIControl)
320 private
321 mHasFrame: Boolean;
322 mCaption: AnsiString;
323 mHAlign: Integer; // -1: left; 0: center; 1: right; default: left
325 protected
326 procedure setCaption (const acap: AnsiString);
327 procedure setHasFrame (v: Boolean);
329 public
330 constructor Create (ahoriz: Boolean);
332 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
334 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
336 procedure drawControl (gx, gy: Integer); override;
338 procedure mouseEvent (var ev: THMouseEvent); override;
339 procedure keyEvent (var ev: THKeyEvent); override;
341 public
342 property caption: AnsiString read mCaption write setCaption;
343 property hasFrame: Boolean read mHasFrame write setHasFrame;
344 property captionAlign: Integer read mHAlign write mHAlign;
345 end;
347 TUIHBox = class(TUIBox)
348 public
349 constructor Create ();
351 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
352 end;
354 TUIVBox = class(TUIBox)
355 public
356 constructor Create ();
358 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
359 end;
361 // ////////////////////////////////////////////////////////////////////// //
362 TUISpan = class(TUIControl)
363 public
364 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
366 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
368 procedure drawControl (gx, gy: Integer); override;
369 end;
371 // ////////////////////////////////////////////////////////////////////// //
372 TUILine = class(TUIControl)
373 public
374 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
376 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
378 procedure drawControl (gx, gy: Integer); override;
379 end;
381 TUIHLine = class(TUILine)
382 public
383 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
384 end;
386 TUIVLine = class(TUILine)
387 public
388 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
389 end;
391 // ////////////////////////////////////////////////////////////////////// //
392 TUIStaticText = class(TUIControl)
393 private
394 mText: AnsiString;
395 mHAlign: Integer; // -1: left; 0: center; 1: right; default: left
396 mVAlign: Integer; // -1: top; 0: center; 1: bottom; default: center
397 mHeader: Boolean; // true: draw with frame text color
398 mLine: Boolean; // true: draw horizontal line
400 private
401 procedure setText (const atext: AnsiString);
403 public
404 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
406 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
408 procedure drawControl (gx, gy: Integer); override;
410 public
411 property text: AnsiString read mText write setText;
412 property halign: Integer read mHAlign write mHAlign;
413 property valign: Integer read mVAlign write mVAlign;
414 property header: Boolean read mHeader write mHeader;
415 property line: Boolean read mLine write mLine;
416 end;
418 // ////////////////////////////////////////////////////////////////////// //
419 TUITextLabel = class(TUIControl)
420 private
421 mText: AnsiString;
422 mHAlign: Integer; // -1: left; 0: center; 1: right; default: left
423 mVAlign: Integer; // -1: top; 0: center; 1: bottom; default: center
424 mHotChar: AnsiChar;
425 mHotOfs: Integer; // from text start, in pixels
426 mHotColor: array[0..ClrIdxMax] of TGxRGBA;
427 mLinkId: AnsiString; // linked control
429 protected
430 procedure cacheStyle (root: TUIStyle); override;
432 procedure setText (const s: AnsiString); virtual;
434 public
435 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
437 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
439 procedure drawControl (gx, gy: Integer); override;
441 procedure mouseEvent (var ev: THMouseEvent); override;
442 procedure keyEventPost (var ev: THKeyEvent); override;
444 public
445 property text: AnsiString read mText write setText;
446 property halign: Integer read mHAlign write mHAlign;
447 property valign: Integer read mVAlign write mVAlign;
448 end;
450 // ////////////////////////////////////////////////////////////////////// //
451 TUIButton = class(TUITextLabel)
452 protected
453 procedure setText (const s: AnsiString); override;
455 public
456 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
458 procedure drawControl (gx, gy: Integer); override;
460 procedure mouseEvent (var ev: THMouseEvent); override;
461 procedure keyEvent (var ev: THKeyEvent); override;
462 procedure keyEventPost (var ev: THKeyEvent); override;
463 end;
465 // ////////////////////////////////////////////////////////////////////// //
466 TUISwitchBox = class(TUITextLabel)
467 protected
468 mBoolVar: PBoolean;
469 mChecked: Boolean;
470 mCheckedStr: AnsiString;
471 mUncheckedStr: AnsiString;
472 mSwitchColor: array[0..ClrIdxMax] of TGxRGBA;
474 protected
475 procedure cacheStyle (root: TUIStyle); override;
477 procedure setText (const s: AnsiString); override;
479 function getChecked (): Boolean; virtual;
480 procedure setChecked (v: Boolean); virtual; abstract;
482 public
483 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
485 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
487 procedure drawControl (gx, gy: Integer); override;
489 procedure mouseEvent (var ev: THMouseEvent); override;
490 procedure keyEvent (var ev: THKeyEvent); override;
491 procedure keyEventPost (var ev: THKeyEvent); override;
493 procedure setVar (pvar: PBoolean);
495 public
496 property checked: Boolean read getChecked write setChecked;
497 end;
499 TUICheckBox = class(TUISwitchBox)
500 protected
501 procedure setChecked (v: Boolean); override;
503 public
504 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
506 procedure doAction (); override;
507 end;
509 TUIRadioBox = class(TUISwitchBox)
510 private
511 mRadioGroup: AnsiString;
513 protected
514 procedure setChecked (v: Boolean); override;
516 public
517 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
519 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
521 procedure doAction (); override;
523 public
524 property radioGroup: AnsiString read mRadioGroup write mRadioGroup; //FIXME
525 end;
528 // ////////////////////////////////////////////////////////////////////////// //
529 procedure uiMouseEvent (var evt: THMouseEvent);
530 procedure uiKeyEvent (var evt: THKeyEvent);
531 procedure uiDraw ();
534 // ////////////////////////////////////////////////////////////////////////// //
535 procedure uiAddWindow (ctl: TUIControl);
536 procedure uiRemoveWindow (ctl: TUIControl); // will free window if `mFreeOnClose` is `true`
537 function uiVisibleWindow (ctl: TUIControl): Boolean;
539 procedure uiUpdateStyles ();
542 // ////////////////////////////////////////////////////////////////////////// //
543 // do layouting
544 procedure uiLayoutCtl (ctl: TUIControl);
547 // ////////////////////////////////////////////////////////////////////////// //
548 var
549 gh_ui_scale: Single = 1.0;
552 implementation
554 uses
555 gh_flexlay,
556 utils;
559 // ////////////////////////////////////////////////////////////////////////// //
560 var
561 ctlsToKill: array of TUIControl = nil;
564 procedure scheduleKill (ctl: TUIControl);
565 var
566 f: Integer;
567 begin
568 if (ctl = nil) then exit;
569 ctl := ctl.topLevel;
570 for f := 0 to High(ctlsToKill) do
571 begin
572 if (ctlsToKill[f] = ctl) then exit;
573 if (ctlsToKill[f] = nil) then begin ctlsToKill[f] := ctl; exit; end;
574 end;
575 SetLength(ctlsToKill, Length(ctlsToKill)+1);
576 ctlsToKill[High(ctlsToKill)] := ctl;
577 end;
580 procedure processKills ();
581 var
582 f: Integer;
583 ctl: TUIControl;
584 begin
585 for f := 0 to High(ctlsToKill) do
586 begin
587 ctl := ctlsToKill[f];
588 if (ctl = nil) then break;
589 ctlsToKill[f] := nil;
590 FreeAndNil(ctl);
591 end;
592 if (Length(ctlsToKill) > 0) then ctlsToKill[0] := nil; // just in case
593 end;
596 // ////////////////////////////////////////////////////////////////////////// //
597 var
598 knownCtlClasses: array of record
599 klass: TUIControlClass;
600 name: AnsiString;
601 end = nil;
604 procedure registerCtlClass (aklass: TUIControlClass; const aname: AnsiString);
605 begin
606 assert(aklass <> nil);
607 assert(Length(aname) > 0);
608 SetLength(knownCtlClasses, Length(knownCtlClasses)+1);
609 knownCtlClasses[High(knownCtlClasses)].klass := aklass;
610 knownCtlClasses[High(knownCtlClasses)].name := aname;
611 end;
614 function findCtlClass (const aname: AnsiString): TUIControlClass;
615 var
616 f: Integer;
617 begin
618 for f := 0 to High(knownCtlClasses) do
619 begin
620 if (strEquCI1251(aname, knownCtlClasses[f].name)) then
621 begin
622 result := knownCtlClasses[f].klass;
623 exit;
624 end;
625 end;
626 result := nil;
627 end;
630 // ////////////////////////////////////////////////////////////////////////// //
631 type
632 TFlexLayouter = specialize TFlexLayouterBase<TUIControl>;
634 procedure uiLayoutCtl (ctl: TUIControl);
635 var
636 lay: TFlexLayouter;
637 begin
638 if (ctl = nil) then exit;
639 lay := TFlexLayouter.Create();
640 try
641 lay.setup(ctl);
642 //lay.layout();
644 //writeln('============================'); lay.dumpFlat();
646 //writeln('=== initial ==='); lay.dump();
648 //lay.calcMaxSizeInternal(0);
650 lay.firstPass();
651 writeln('=== after first pass ===');
652 lay.dump();
654 lay.secondPass();
655 writeln('=== after second pass ===');
656 lay.dump();
659 lay.layout();
660 //writeln('=== final ==='); lay.dump();
662 if (ctl.mParent = nil) and (ctl is TUITopWindow) and (TUITopWindow(ctl).mDoCenter) then
663 begin
664 TUITopWindow(ctl).centerInScreen();
665 end;
667 // calculate full size
668 ctl.calcFullClientSize();
670 // fix focus
671 if (ctl.mParent = nil) then
672 begin
673 if (ctl.mFocused = nil) or (ctl.mFocused = ctl) or (not ctl.mFocused.enabled) then
674 begin
675 ctl.mFocused := ctl.findFirstFocus();
676 end;
677 end;
679 finally
680 FreeAndNil(lay);
681 end;
682 end;
685 // ////////////////////////////////////////////////////////////////////////// //
686 var
687 uiTopList: array of TUIControl = nil;
688 uiGrabCtl: TUIControl = nil;
691 procedure uiUpdateStyles ();
692 var
693 ctl: TUIControl;
694 begin
695 for ctl in uiTopList do ctl.updateStyle();
696 end;
699 procedure uiMouseEvent (var evt: THMouseEvent);
700 var
701 ev: THMouseEvent;
702 f, c: Integer;
703 lx, ly: Integer;
704 ctmp: TUIControl;
705 begin
706 processKills();
707 if (evt.eaten) or (evt.cancelled) then exit;
708 ev := evt;
709 ev.x := trunc(ev.x/gh_ui_scale);
710 ev.y := trunc(ev.y/gh_ui_scale);
711 ev.dx := trunc(ev.dx/gh_ui_scale); //FIXME
712 ev.dy := trunc(ev.dy/gh_ui_scale); //FIXME
713 try
714 if (uiGrabCtl <> nil) then
715 begin
716 uiGrabCtl.mouseEvent(ev);
717 if (ev.release) and ((ev.bstate and (not ev.but)) = 0) then uiGrabCtl := nil;
718 ev.eat();
719 exit;
720 end;
721 if (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)].enabled) then uiTopList[High(uiTopList)].mouseEvent(ev);
722 if (not ev.eaten) and (not ev.cancelled) and (ev.press) then
723 begin
724 for f := High(uiTopList) downto 0 do
725 begin
726 if uiTopList[f].toLocal(ev.x, ev.y, lx, ly) then
727 begin
728 if (uiTopList[f].enabled) and (f <> High(uiTopList)) then
729 begin
730 uiTopList[High(uiTopList)].blurred();
731 ctmp := uiTopList[f];
732 uiGrabCtl := nil;
733 for c := f+1 to High(uiTopList) do uiTopList[c-1] := uiTopList[c];
734 uiTopList[High(uiTopList)] := ctmp;
735 ctmp.activated();
736 ctmp.mouseEvent(ev);
737 end;
738 ev.eat();
739 exit;
740 end;
741 end;
742 end;
743 finally
744 if (ev.eaten) then evt.eat();
745 if (ev.cancelled) then evt.cancel();
746 end;
747 end;
750 procedure uiKeyEvent (var evt: THKeyEvent);
751 var
752 ev: THKeyEvent;
753 begin
754 processKills();
755 if (evt.eaten) or (evt.cancelled) then exit;
756 ev := evt;
757 ev.x := trunc(ev.x/gh_ui_scale);
758 ev.y := trunc(ev.y/gh_ui_scale);
759 try
760 if (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)].enabled) then uiTopList[High(uiTopList)].keyEvent(ev);
761 //if (ev.release) then begin ev.eat(); exit; end;
762 finally
763 if (ev.eaten) then evt.eat();
764 if (ev.cancelled) then evt.cancel();
765 end;
766 end;
769 procedure uiDraw ();
770 var
771 f, cidx: Integer;
772 ctl: TUIControl;
773 begin
774 processKills();
775 gxBeginUIDraw(gh_ui_scale);
776 try
777 for f := 0 to High(uiTopList) do
778 begin
779 ctl := uiTopList[f];
780 ctl.draw();
781 if (f <> High(uiTopList)) then
782 begin
783 cidx := ctl.getColorIndex;
784 if (ctl.mDarken[cidx] > 0) then darkenRect(ctl.x0, ctl.y0, ctl.width, ctl.height, ctl.mDarken[cidx]);
785 end;
786 end;
787 finally
788 gxEndUIDraw();
789 end;
790 end;
793 procedure uiAddWindow (ctl: TUIControl);
794 var
795 f, c: Integer;
796 begin
797 if (ctl = nil) then exit;
798 ctl := ctl.topLevel;
799 if not (ctl is TUITopWindow) then exit; // alas
800 for f := 0 to High(uiTopList) do
801 begin
802 if (uiTopList[f] = ctl) then
803 begin
804 if (f <> High(uiTopList)) then
805 begin
806 uiTopList[High(uiTopList)].blurred();
807 for c := f+1 to High(uiTopList) do uiTopList[c-1] := uiTopList[c];
808 uiTopList[High(uiTopList)] := ctl;
809 ctl.activated();
810 end;
811 exit;
812 end;
813 end;
814 if (Length(uiTopList) > 0) then uiTopList[High(uiTopList)].blurred();
815 SetLength(uiTopList, Length(uiTopList)+1);
816 uiTopList[High(uiTopList)] := ctl;
817 ctl.updateStyle();
818 ctl.activated();
819 end;
822 procedure uiRemoveWindow (ctl: TUIControl);
823 var
824 f, c: Integer;
825 begin
826 if (ctl = nil) then exit;
827 ctl := ctl.topLevel;
828 if not (ctl is TUITopWindow) then exit; // alas
829 for f := 0 to High(uiTopList) do
830 begin
831 if (uiTopList[f] = ctl) then
832 begin
833 ctl.blurred();
834 for c := f+1 to High(uiTopList) do uiTopList[c-1] := uiTopList[c];
835 SetLength(uiTopList, Length(uiTopList)-1);
836 if (ctl is TUITopWindow) then
837 begin
838 try
839 if assigned(TUITopWindow(ctl).closeCB) then TUITopWindow(ctl).closeCB(ctl);
840 finally
841 if (TUITopWindow(ctl).mFreeOnClose) then scheduleKill(ctl);
842 end;
843 end;
844 exit;
845 end;
846 end;
847 end;
850 function uiVisibleWindow (ctl: TUIControl): Boolean;
851 var
852 f: Integer;
853 begin
854 result := false;
855 if (ctl = nil) then exit;
856 ctl := ctl.topLevel;
857 if not (ctl is TUITopWindow) then exit; // alas
858 for f := 0 to High(uiTopList) do
859 begin
860 if (uiTopList[f] = ctl) then begin result := true; exit; end;
861 end;
862 end;
865 // ////////////////////////////////////////////////////////////////////////// //
866 constructor TUIControl.Create ();
867 begin
868 end;
871 procedure TUIControl.AfterConstruction ();
872 begin
873 inherited;
874 mParent := nil;
875 mId := '';
876 mX := 0;
877 mY := 0;
878 mWidth := 64;
879 mHeight := 8;
880 mFrameWidth := 0;
881 mFrameHeight := 0;
882 mEnabled := true;
883 mCanFocus := true;
884 mChildren := nil;
885 mFocused := nil;
886 mEscClose := false;
887 scallowed := false;
888 mDrawShadow := false;
889 actionCB := nil;
890 // layouter interface
891 //mDefSize := TLaySize.Create(64, 8); // default size
892 mDefSize := TLaySize.Create(0, 0); // default size
893 mMaxSize := TLaySize.Create(-1, -1); // maximum size
894 mFlex := 0;
895 mHoriz := true;
896 mCanWrap := false;
897 mLineStart := false;
898 mHGroup := '';
899 mVGroup := '';
900 mStyleId := '';
901 mCtl4Style := '';
902 mAlign := -1; // left/top
903 mExpand := false;
904 end;
907 destructor TUIControl.Destroy ();
908 var
909 f, c: Integer;
910 begin
911 if (mParent <> nil) then
912 begin
913 setFocused(false);
914 for f := 0 to High(mParent.mChildren) do
915 begin
916 if (mParent.mChildren[f] = self) then
917 begin
918 for c := f+1 to High(mParent.mChildren) do mParent.mChildren[c-1] := mParent.mChildren[c];
919 SetLength(mParent.mChildren, Length(mParent.mChildren)-1);
920 end;
921 end;
922 end;
923 for f := 0 to High(mChildren) do
924 begin
925 mChildren[f].mParent := nil;
926 mChildren[f].Free();
927 end;
928 mChildren := nil;
929 end;
932 function TUIControl.getColorIndex (): Integer; inline;
933 begin
934 if (not enabled) then begin result := ClrIdxDisabled; exit; end;
935 // top windows: no focus hack
936 if (self is TUITopWindow) then
937 begin
938 if (getActive) then begin result := ClrIdxActive; exit; end;
939 end
940 else
941 begin
942 // if control cannot be focused, take "active" color scheme for it (it is easier this way)
943 if (not canFocus) or (getActive) then begin result := ClrIdxActive; exit; end;
944 end;
945 result := ClrIdxInactive;
946 end;
948 procedure TUIControl.updateStyle ();
949 var
950 stl: TUIStyle = nil;
951 ctl: TUIControl;
952 begin
953 ctl := self;
954 while (ctl <> nil) do
955 begin
956 if (Length(ctl.mStyleId) <> 0) then begin stl := uiFindStyle(ctl.mStyleId); break; end;
957 ctl := ctl.mParent;
958 end;
959 if (stl = nil) then stl := uiFindStyle(''); // default
960 cacheStyle(stl);
961 for ctl in mChildren do ctl.updateStyle();
962 end;
964 procedure TUIControl.cacheStyle (root: TUIStyle);
965 var
966 cst: AnsiString;
967 begin
968 //writeln('caching style for <', className, '> (', mCtl4Style, ')...');
969 cst := mCtl4Style;
970 // active
971 mBackColor[ClrIdxActive] := root.get('back-color', 'active', cst).asRGBADef(TGxRGBA.Create(0, 0, 128));
972 mTextColor[ClrIdxActive] := root.get('text-color', 'active', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
973 mFrameColor[ClrIdxActive] := root.get('frame-color', 'active', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
974 mFrameTextColor[ClrIdxActive] := root.get('frame-text-color', 'active', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
975 mFrameIconColor[ClrIdxActive] := root.get('frame-icon-color', 'active', cst).asRGBADef(TGxRGBA.Create(0, 255, 0));
976 mDarken[ClrIdxActive] := root.get('darken', 'active', cst).asInt(-1);
977 // disabled
978 mBackColor[ClrIdxDisabled] := root.get('back-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(0, 0, 128));
979 mTextColor[ClrIdxDisabled] := root.get('text-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(127, 127, 127));
980 mFrameColor[ClrIdxDisabled] := root.get('frame-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(127, 127, 127));
981 mFrameTextColor[ClrIdxDisabled] := root.get('frame-text-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(127, 127, 127));
982 mFrameIconColor[ClrIdxDisabled] := root.get('frame-icon-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(0, 127, 0));
983 mDarken[ClrIdxDisabled] := root.get('darken', 'disabled', cst).asInt(-1);
984 // inactive
985 mBackColor[ClrIdxInactive] := root.get('back-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(0, 0, 128));
986 mTextColor[ClrIdxInactive] := root.get('text-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
987 mFrameColor[ClrIdxInactive] := root.get('frame-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
988 mFrameTextColor[ClrIdxInactive] := root.get('frame-text-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
989 mFrameIconColor[ClrIdxInactive] := root.get('frame-icon-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(0, 255, 0));
990 mDarken[ClrIdxInactive] := root.get('darken', 'inactive', cst).asInt(-1);
991 end;
994 // ////////////////////////////////////////////////////////////////////////// //
995 function TUIControl.getDefSize (): TLaySize; inline; begin result := mLayDefSize; end;
996 function TUIControl.getMaxSize (): TLaySize; inline; begin result := mLayMaxSize; end;
997 function TUIControl.getFlex (): Integer; inline; begin result := mFlex; end;
998 function TUIControl.isHorizBox (): Boolean; inline; begin result := mHoriz; end;
999 procedure TUIControl.setHorizBox (v: Boolean); inline; begin mHoriz := v; end;
1000 function TUIControl.canWrap (): Boolean; inline; begin result := mCanWrap; end;
1001 procedure TUIControl.setCanWrap (v: Boolean); inline; begin mCanWrap := v; end;
1002 function TUIControl.isLineStart (): Boolean; inline; begin result := mLineStart; end;
1003 procedure TUIControl.setLineStart (v: Boolean); inline; begin mLineStart := v; end;
1004 function TUIControl.getAlign (): Integer; inline; begin result := mAlign; end;
1005 procedure TUIControl.setAlign (v: Integer); inline; begin mAlign := v; end;
1006 function TUIControl.getExpand (): Boolean; inline; begin result := mExpand; end;
1007 procedure TUIControl.setExpand (v: Boolean); inline; begin mExpand := v; end;
1008 function TUIControl.getHGroup (): AnsiString; inline; begin result := mHGroup; end;
1009 procedure TUIControl.setHGroup (const v: AnsiString); inline; begin mHGroup := v; end;
1010 function TUIControl.getVGroup (): AnsiString; inline; begin result := mVGroup; end;
1011 procedure TUIControl.setVGroup (const v: AnsiString); inline; begin mVGroup := v; end;
1013 function TUIControl.getMargins (): TLayMargins; inline;
1014 begin
1015 result := TLayMargins.Create(mFrameHeight, mFrameWidth, mFrameHeight, mFrameWidth);
1016 end;
1018 procedure TUIControl.setActualSizePos (constref apos: TLayPos; constref asize: TLaySize); inline;
1019 begin
1020 //writeln(self.className, '; pos=', apos.toString, '; size=', asize.toString);
1021 if (mParent <> nil) then
1022 begin
1023 mX := apos.x;
1024 mY := apos.y;
1025 end;
1026 mWidth := asize.w;
1027 mHeight := asize.h;
1028 end;
1030 procedure TUIControl.layPrepare ();
1031 begin
1032 mLayDefSize := mDefSize;
1033 mLayMaxSize := mMaxSize;
1034 if (mLayMaxSize.w >= 0) then mLayMaxSize.w += mFrameWidth*2;
1035 if (mLayMaxSize.h >= 0) then mLayMaxSize.h += mFrameHeight*2;
1036 end;
1039 // ////////////////////////////////////////////////////////////////////////// //
1040 function TUIControl.parsePos (par: TTextParser): TLayPos;
1041 var
1042 ech: AnsiChar = ')';
1043 begin
1044 if (par.eatDelim('[')) then ech := ']' else par.expectDelim('(');
1045 result.x := par.expectInt();
1046 par.eatDelim(','); // optional comma
1047 result.y := par.expectInt();
1048 par.eatDelim(','); // optional comma
1049 par.expectDelim(ech);
1050 end;
1052 function TUIControl.parseSize (par: TTextParser): TLaySize;
1053 var
1054 ech: AnsiChar = ')';
1055 begin
1056 if (par.eatDelim('[')) then ech := ']' else par.expectDelim('(');
1057 result.w := par.expectInt();
1058 par.eatDelim(','); // optional comma
1059 result.h := par.expectInt();
1060 par.eatDelim(','); // optional comma
1061 par.expectDelim(ech);
1062 end;
1064 function TUIControl.parseBool (par: TTextParser): Boolean;
1065 begin
1066 result :=
1067 par.eatIdOrStrCI('true') or
1068 par.eatIdOrStrCI('yes') or
1069 par.eatIdOrStrCI('tan');
1070 if not result then
1071 begin
1072 if (not par.eatIdOrStrCI('false')) and (not par.eatIdOrStrCI('no')) and (not par.eatIdOrStrCI('ona')) then
1073 begin
1074 par.error('boolean value expected');
1075 end;
1076 end;
1077 end;
1079 function TUIControl.parseAnyAlign (par: TTextParser): Integer;
1080 begin
1081 if (par.eatIdOrStrCI('left')) or (par.eatIdOrStrCI('top')) then result := -1
1082 else if (par.eatIdOrStrCI('right')) or (par.eatIdOrStrCI('bottom')) then result := 1
1083 else if (par.eatIdOrStrCI('center')) then result := 0
1084 else par.error('invalid align value');
1085 end;
1087 function TUIControl.parseHAlign (par: TTextParser): Integer;
1088 begin
1089 if (par.eatIdOrStrCI('left')) then result := -1
1090 else if (par.eatIdOrStrCI('right')) then result := 1
1091 else if (par.eatIdOrStrCI('center')) then result := 0
1092 else par.error('invalid horizontal align value');
1093 end;
1095 function TUIControl.parseVAlign (par: TTextParser): Integer;
1096 begin
1097 if (par.eatIdOrStrCI('top')) then result := -1
1098 else if (par.eatIdOrStrCI('bottom')) then result := 1
1099 else if (par.eatIdOrStrCI('center')) then result := 0
1100 else par.error('invalid vertical align value');
1101 end;
1103 procedure TUIControl.parseTextAlign (par: TTextParser; var h, v: Integer);
1104 var
1105 wasH: Boolean = false;
1106 wasV: Boolean = false;
1107 begin
1108 while true do
1109 begin
1110 if (par.eatIdOrStrCI('left')) then
1111 begin
1112 if wasH then par.error('too many align directives');
1113 wasH := true;
1114 h := -1;
1115 continue;
1116 end;
1117 if (par.eatIdOrStrCI('right')) then
1118 begin
1119 if wasH then par.error('too many align directives');
1120 wasH := true;
1121 h := 1;
1122 continue;
1123 end;
1124 if (par.eatIdOrStrCI('hcenter')) then
1125 begin
1126 if wasH then par.error('too many align directives');
1127 wasH := true;
1128 h := 0;
1129 continue;
1130 end;
1131 if (par.eatIdOrStrCI('top')) then
1132 begin
1133 if wasV then par.error('too many align directives');
1134 wasV := true;
1135 v := -1;
1136 continue;
1137 end;
1138 if (par.eatIdOrStrCI('bottom')) then
1139 begin
1140 if wasV then par.error('too many align directives');
1141 wasV := true;
1142 v := 1;
1143 continue;
1144 end;
1145 if (par.eatIdOrStrCI('vcenter')) then
1146 begin
1147 if wasV then par.error('too many align directives');
1148 wasV := true;
1149 v := 0;
1150 continue;
1151 end;
1152 if (par.eatIdOrStrCI('center')) then
1153 begin
1154 if wasV or wasH then par.error('too many align directives');
1155 wasV := true;
1156 wasH := true;
1157 h := 0;
1158 v := 0;
1159 continue;
1160 end;
1161 break;
1162 end;
1163 if not wasV and not wasH then par.error('invalid align value');
1164 end;
1166 function TUIControl.parseOrientation (const prname: AnsiString; par: TTextParser): Boolean;
1167 begin
1168 if (strEquCI1251(prname, 'orientation')) or (strEquCI1251(prname, 'orient')) then
1169 begin
1170 if (par.eatIdOrStrCI('horizontal')) or (par.eatIdOrStrCI('horiz')) then mHoriz := true
1171 else if (par.eatIdOrStrCI('vertical')) or (par.eatIdOrStrCI('vert')) then mHoriz := false
1172 else par.error('`horizontal` or `vertical` expected');
1173 result := true;
1174 end
1175 else
1176 begin
1177 result := false;
1178 end;
1179 end;
1181 // par should be on '{'; final '}' is eaten
1182 procedure TUIControl.parseProperties (par: TTextParser);
1183 var
1184 pn: AnsiString;
1185 begin
1186 if (not par.eatDelim('{')) then exit;
1187 while (not par.eatDelim('}')) do
1188 begin
1189 if (not par.isIdOrStr) then par.error('property name expected');
1190 pn := par.tokStr;
1191 par.skipToken();
1192 par.eatDelim(':'); // optional
1193 if not parseProperty(pn, par) then par.errorfmt('invalid property name ''%s''', [pn]);
1194 par.eatDelim(','); // optional
1195 end;
1196 end;
1198 // par should be on '{'
1199 procedure TUIControl.parseChildren (par: TTextParser);
1200 var
1201 cc: TUIControlClass;
1202 ctl: TUIControl;
1203 begin
1204 par.expectDelim('{');
1205 while (not par.eatDelim('}')) do
1206 begin
1207 if (not par.isIdOrStr) then par.error('control name expected');
1208 cc := findCtlClass(par.tokStr);
1209 if (cc = nil) then par.errorfmt('unknown control name: ''%s''', [par.tokStr]);
1210 //writeln('children for <', par.tokStr, '>: <', cc.className, '>');
1211 par.skipToken();
1212 par.eatDelim(':'); // optional
1213 ctl := cc.Create();
1214 //writeln(' mHoriz=', ctl.mHoriz);
1215 try
1216 ctl.parseProperties(par);
1217 except
1218 FreeAndNil(ctl);
1219 raise;
1220 end;
1221 //writeln(': ', ctl.mDefSize.toString);
1222 appendChild(ctl);
1223 par.eatDelim(','); // optional
1224 end;
1225 end;
1228 function TUIControl.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
1229 begin
1230 result := true;
1231 if (strEquCI1251(prname, 'id')) then begin mId := par.expectIdOrStr(true); exit; end; // allow empty strings
1232 if (strEquCI1251(prname, 'style')) then begin mStyleId := par.expectIdOrStr(); exit; end; // no empty strings
1233 if (strEquCI1251(prname, 'flex')) then begin flex := par.expectInt(); exit; end;
1234 // sizes
1235 if (strEquCI1251(prname, 'defsize')) or (strEquCI1251(prname, 'size')) then begin mDefSize := parseSize(par); exit; end;
1236 if (strEquCI1251(prname, 'maxsize')) then begin mMaxSize := parseSize(par); exit; end;
1237 if (strEquCI1251(prname, 'defwidth')) or (strEquCI1251(prname, 'width')) then begin mDefSize.w := par.expectInt(); exit; end;
1238 if (strEquCI1251(prname, 'defheight')) or (strEquCI1251(prname, 'height')) then begin mDefSize.h := par.expectInt(); exit; end;
1239 if (strEquCI1251(prname, 'maxwidth')) then begin mMaxSize.w := par.expectInt(); exit; end;
1240 if (strEquCI1251(prname, 'maxheight')) then begin mMaxSize.h := par.expectInt(); exit; end;
1241 // flags
1242 if (strEquCI1251(prname, 'wrap')) then begin mCanWrap := parseBool(par); exit; end;
1243 if (strEquCI1251(prname, 'linestart')) then begin mLineStart := parseBool(par); exit; end;
1244 if (strEquCI1251(prname, 'expand')) then begin mExpand := parseBool(par); exit; end;
1245 // align
1246 if (strEquCI1251(prname, 'align')) then begin mAlign := parseAnyAlign(par); exit; end;
1247 if (strEquCI1251(prname, 'hgroup')) then begin mHGroup := par.expectIdOrStr(true); exit; end; // allow empty strings
1248 if (strEquCI1251(prname, 'vgroup')) then begin mVGroup := par.expectIdOrStr(true); exit; end; // allow empty strings
1249 // other
1250 if (strEquCI1251(prname, 'canfocus')) then begin mCanFocus := true; exit; end;
1251 if (strEquCI1251(prname, 'nofocus')) then begin mCanFocus := false; exit; end;
1252 if (strEquCI1251(prname, 'disabled')) then begin mEnabled := false; exit; end;
1253 if (strEquCI1251(prname, 'enabled')) then begin mEnabled := true; exit; end;
1254 if (strEquCI1251(prname, 'escclose')) then begin mEscClose := not parseBool(par); exit; end;
1255 if (strEquCI1251(prname, 'default')) then begin mDefault := true; exit; end;
1256 if (strEquCI1251(prname, 'cancel')) then begin mCancel := true; exit; end;
1257 result := false;
1258 end;
1261 // ////////////////////////////////////////////////////////////////////////// //
1262 procedure TUIControl.activated ();
1263 begin
1264 makeVisibleInParent();
1265 end;
1268 procedure TUIControl.blurred ();
1269 begin
1270 if (uiGrabCtl = self) then uiGrabCtl := nil;
1271 end;
1274 procedure TUIControl.calcFullClientSize ();
1275 var
1276 ctl: TUIControl;
1277 begin
1278 mFullSize := TLaySize.Create(0, 0);
1279 if (mWidth < 1) or (mHeight < 1) then exit;
1280 for ctl in mChildren do
1281 begin
1282 ctl.calcFullClientSize();
1283 mFullSize.w := nmax(mFullSize.w, ctl.mX-mFrameWidth+ctl.mFullSize.w);
1284 mFullSize.h := nmax(mFullSize.h, ctl.mY-mFrameHeight+ctl.mFullSize.h);
1285 end;
1286 mFullSize.w := nmax(mFullSize.w, mWidth-mFrameWidth*2);
1287 mFullSize.h := nmax(mFullSize.h, mHeight-mFrameHeight*2);
1288 end;
1291 function TUIControl.topLevel (): TUIControl; inline;
1292 begin
1293 result := self;
1294 while (result.mParent <> nil) do result := result.mParent;
1295 end;
1298 function TUIControl.getEnabled (): Boolean;
1299 var
1300 ctl: TUIControl;
1301 begin
1302 result := false;
1303 if (not mEnabled) then exit;
1304 ctl := mParent;
1305 while (ctl <> nil) do
1306 begin
1307 if (not ctl.mEnabled) then exit;
1308 ctl := ctl.mParent;
1309 end;
1310 result := true;
1311 end;
1314 procedure TUIControl.setEnabled (v: Boolean); inline;
1315 begin
1316 if (mEnabled = v) then exit;
1317 mEnabled := v;
1318 if (not v) and focused then setFocused(false);
1319 end;
1322 function TUIControl.getFocused (): Boolean; inline;
1323 begin
1324 if (mParent = nil) then
1325 begin
1326 result := (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)] = self);
1327 end
1328 else
1329 begin
1330 result := (topLevel.mFocused = self);
1331 if (result) then result := (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)] = topLevel);
1332 end;
1333 end;
1336 function TUIControl.getActive (): Boolean; inline;
1337 var
1338 ctl: TUIControl;
1339 begin
1340 if (mParent = nil) then
1341 begin
1342 result := (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)] = self);
1343 end
1344 else
1345 begin
1346 ctl := topLevel.mFocused;
1347 while (ctl <> nil) and (ctl <> self) do ctl := ctl.mParent;
1348 result := (ctl = self);
1349 if (result) then result := (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)] = topLevel);
1350 end;
1351 end;
1354 procedure TUIControl.setFocused (v: Boolean); inline;
1355 var
1356 tl: TUIControl;
1357 begin
1358 tl := topLevel;
1359 if (not v) then
1360 begin
1361 if (tl.mFocused = self) then
1362 begin
1363 blurred(); // this will reset grab, but still...
1364 if (uiGrabCtl = self) then uiGrabCtl := nil;
1365 tl.mFocused := tl.findNextFocus(self, true);
1366 if (tl.mFocused = self) then tl.mFocused := nil;
1367 if (tl.mFocused <> nil) then tl.mFocused.activated();
1368 end;
1369 exit;
1370 end;
1371 if (not canFocus) then exit;
1372 if (tl.mFocused <> self) then
1373 begin
1374 if (tl.mFocused <> nil) then tl.mFocused.blurred();
1375 tl.mFocused := self;
1376 if (uiGrabCtl <> self) then uiGrabCtl := nil;
1377 activated();
1378 end;
1379 end;
1382 function TUIControl.getCanFocus (): Boolean; inline;
1383 begin
1384 result := (getEnabled) and (mCanFocus) and (mWidth > 0) and (mHeight > 0);
1385 end;
1388 function TUIControl.isMyChild (ctl: TUIControl): Boolean;
1389 begin
1390 result := true;
1391 while (ctl <> nil) do
1392 begin
1393 if (ctl.mParent = self) then exit;
1394 ctl := ctl.mParent;
1395 end;
1396 result := false;
1397 end;
1400 // returns `true` if global coords are inside this control
1401 function TUIControl.toLocal (var x, y: Integer): Boolean;
1402 begin
1403 if (mParent = nil) then
1404 begin
1405 Dec(x, mX);
1406 Dec(y, mY);
1407 result := true; // hack
1408 end
1409 else
1410 begin
1411 result := mParent.toLocal(x, y);
1412 Inc(x, mParent.mScrollX);
1413 Inc(y, mParent.mScrollY);
1414 Dec(x, mX);
1415 Dec(y, mY);
1416 if result then result := (x >= 0) and (y >= 0) and (x < mParent.mWidth) and (y < mParent.mHeight);
1417 end;
1418 if result then result := (x >= 0) and (y >= 0) and (x < mWidth) and (y < mHeight);
1419 end;
1421 function TUIControl.toLocal (gx, gy: Integer; out x, y: Integer): Boolean; inline;
1422 begin
1423 x := gx;
1424 y := gy;
1425 result := toLocal(x, y);
1426 end;
1429 procedure TUIControl.toGlobal (var x, y: Integer);
1430 begin
1431 Inc(x, mX);
1432 Inc(y, mY);
1433 if (mParent <> nil) then
1434 begin
1435 Dec(x, mParent.mScrollX);
1436 Dec(y, mParent.mScrollY);
1437 mParent.toGlobal(x, y);
1438 end;
1439 end;
1441 procedure TUIControl.toGlobal (lx, ly: Integer; out x, y: Integer); inline;
1442 begin
1443 x := lx;
1444 y := ly;
1445 toGlobal(x, y);
1446 end;
1448 procedure TUIControl.getDrawRect (out gx, gy, wdt, hgt: Integer);
1449 var
1450 cgx, cgy: Integer;
1451 begin
1452 if (mParent = nil) then
1453 begin
1454 gx := mX;
1455 gy := mY;
1456 wdt := mWidth;
1457 hgt := mHeight;
1458 end
1459 else
1460 begin
1461 toGlobal(0, 0, cgx, cgy);
1462 mParent.getDrawRect(gx, gy, wdt, hgt);
1463 if (wdt > 0) and (hgt > 0) then
1464 begin
1465 if not intersectRect(gx, gy, wdt, hgt, cgx, cgy, mWidth, mHeight) then
1466 begin
1467 wdt := 0;
1468 hgt := 0;
1469 end;
1470 end;
1471 end;
1472 end;
1475 // x and y are global coords
1476 function TUIControl.controlAtXY (x, y: Integer; allowDisabled: Boolean=false): TUIControl;
1477 var
1478 lx, ly: Integer;
1479 f: Integer;
1480 begin
1481 result := nil;
1482 if (not allowDisabled) and (not enabled) then exit;
1483 if (mWidth < 1) or (mHeight < 1) then exit;
1484 if not toLocal(x, y, lx, ly) then exit;
1485 for f := High(mChildren) downto 0 do
1486 begin
1487 result := mChildren[f].controlAtXY(x, y, allowDisabled);
1488 if (result <> nil) then exit;
1489 end;
1490 result := self;
1491 end;
1494 function TUIControl.parentScrollX (): Integer; inline; begin if (mParent <> nil) then result := mParent.mScrollX else result := 0; end;
1495 function TUIControl.parentScrollY (): Integer; inline; begin if (mParent <> nil) then result := mParent.mScrollY else result := 0; end;
1498 procedure TUIControl.makeVisibleInParent ();
1499 var
1500 sy, ey, cy: Integer;
1501 p: TUIControl;
1502 begin
1503 if (mWidth < 1) or (mHeight < 1) then exit;
1504 p := mParent;
1505 if (p = nil) then exit;
1506 if (p.mFullSize.w < 1) or (p.mFullSize.h < 1) then
1507 begin
1508 p.mScrollX := 0;
1509 p.mScrollY := 0;
1510 exit;
1511 end;
1512 p.makeVisibleInParent();
1513 cy := mY-p.mFrameHeight;
1514 sy := p.mScrollY;
1515 ey := sy+(p.mHeight-p.mFrameHeight*2);
1516 if (cy < sy) then
1517 begin
1518 p.mScrollY := nmax(0, cy);
1519 end
1520 else if (cy+mHeight > ey) then
1521 begin
1522 p.mScrollY := nmax(0, cy+mHeight-(p.mHeight-p.mFrameHeight*2));
1523 end;
1524 end;
1527 // ////////////////////////////////////////////////////////////////////////// //
1528 function TUIControl.prevSibling (): TUIControl;
1529 var
1530 f: Integer;
1531 begin
1532 if (mParent <> nil) then
1533 begin
1534 for f := 1 to High(mParent.mChildren) do
1535 begin
1536 if (mParent.mChildren[f] = self) then begin result := mParent.mChildren[f-1]; exit; end;
1537 end;
1538 end;
1539 result := nil;
1540 end;
1542 function TUIControl.nextSibling (): TUIControl;
1543 var
1544 f: Integer;
1545 begin
1546 if (mParent <> nil) then
1547 begin
1548 for f := 0 to High(mParent.mChildren)-1 do
1549 begin
1550 if (mParent.mChildren[f] = self) then begin result := mParent.mChildren[f+1]; exit; end;
1551 end;
1552 end;
1553 result := nil;
1554 end;
1556 function TUIControl.firstChild (): TUIControl; inline;
1557 begin
1558 if (Length(mChildren) <> 0) then result := mChildren[0] else result := nil;
1559 end;
1561 function TUIControl.lastChild (): TUIControl; inline;
1562 begin
1563 if (Length(mChildren) <> 0) then result := mChildren[High(mChildren)] else result := nil;
1564 end;
1567 function TUIControl.findFirstFocus (): TUIControl;
1568 var
1569 f: Integer;
1570 begin
1571 result := nil;
1572 if enabled then
1573 begin
1574 for f := 0 to High(mChildren) do
1575 begin
1576 result := mChildren[f].findFirstFocus();
1577 if (result <> nil) then exit;
1578 end;
1579 if (canFocus) then result := self;
1580 end;
1581 end;
1584 function TUIControl.findLastFocus (): TUIControl;
1585 var
1586 f: Integer;
1587 begin
1588 result := nil;
1589 if enabled then
1590 begin
1591 for f := High(mChildren) downto 0 do
1592 begin
1593 result := mChildren[f].findLastFocus();
1594 if (result <> nil) then exit;
1595 end;
1596 if (canFocus) then result := self;
1597 end;
1598 end;
1601 function TUIControl.findNextFocus (cur: TUIControl; wrap: Boolean): TUIControl;
1602 var
1603 curHit: Boolean = false;
1605 function checkFocus (ctl: TUIControl): Boolean;
1606 begin
1607 if curHit then
1608 begin
1609 result := (ctl.canFocus);
1610 end
1611 else
1612 begin
1613 curHit := (ctl = cur);
1614 result := false; // don't stop
1615 end;
1616 end;
1618 begin
1619 result := nil;
1620 if enabled then
1621 begin
1622 if not isMyChild(cur) then
1623 begin
1624 result := findFirstFocus();
1625 end
1626 else
1627 begin
1628 result := forEachControl(checkFocus);
1629 if (result = nil) and (wrap) then result := findFirstFocus();
1630 end;
1631 end;
1632 end;
1635 function TUIControl.findPrevFocus (cur: TUIControl; wrap: Boolean): TUIControl;
1636 var
1637 lastCtl: TUIControl = nil;
1639 function checkFocus (ctl: TUIControl): Boolean;
1640 begin
1641 if (ctl = cur) then
1642 begin
1643 result := true;
1644 end
1645 else
1646 begin
1647 result := false;
1648 if (ctl.canFocus) then lastCtl := ctl;
1649 end;
1650 end;
1652 begin
1653 result := nil;
1654 if enabled then
1655 begin
1656 if not isMyChild(cur) then
1657 begin
1658 result := findLastFocus();
1659 end
1660 else
1661 begin
1662 forEachControl(checkFocus);
1663 if (lastCtl = nil) and (wrap) then lastCtl := findLastFocus();
1664 result := lastCtl;
1665 //if (lastCtl <> nil) then writeln('ctl<', lastCtl.className, '>: {', lastCtl.id, '}');
1666 end;
1667 end;
1668 end;
1671 function TUIControl.findDefaulControl (): TUIControl;
1672 var
1673 ctl: TUIControl;
1674 begin
1675 if (enabled) then
1676 begin
1677 if (mDefault) then begin result := self; exit; end;
1678 for ctl in mChildren do
1679 begin
1680 result := ctl.findDefaulControl();
1681 if (result <> nil) then exit;
1682 end;
1683 end;
1684 result := nil;
1685 end;
1687 function TUIControl.findCancelControl (): TUIControl;
1688 var
1689 ctl: TUIControl;
1690 begin
1691 if (enabled) then
1692 begin
1693 if (mCancel) then begin result := self; exit; end;
1694 for ctl in mChildren do
1695 begin
1696 result := ctl.findCancelControl();
1697 if (result <> nil) then exit;
1698 end;
1699 end;
1700 result := nil;
1701 end;
1704 function TUIControl.findControlById (const aid: AnsiString): TUIControl;
1705 var
1706 ctl: TUIControl;
1707 begin
1708 if (strEquCI1251(aid, mId)) then begin result := self; exit; end;
1709 for ctl in mChildren do
1710 begin
1711 result := ctl.findControlById(aid);
1712 if (result <> nil) then exit;
1713 end;
1714 result := nil;
1715 end;
1718 procedure TUIControl.appendChild (ctl: TUIControl);
1719 begin
1720 if (ctl = nil) then exit;
1721 if (ctl.mParent <> nil) then exit;
1722 SetLength(mChildren, Length(mChildren)+1);
1723 mChildren[High(mChildren)] := ctl;
1724 ctl.mParent := self;
1725 Inc(ctl.mX, mFrameWidth);
1726 Inc(ctl.mY, mFrameHeight);
1727 if (ctl.mWidth > 0) and (ctl.mHeight > 0) and
1728 (ctl.mX+ctl.mWidth > mFrameWidth) and (ctl.mY+ctl.mHeight > mFrameHeight) then
1729 begin
1730 if (mWidth+mFrameWidth < ctl.mX+ctl.mWidth) then mWidth := ctl.mX+ctl.mWidth+mFrameWidth;
1731 if (mHeight+mFrameHeight < ctl.mY+ctl.mHeight) then mHeight := ctl.mY+ctl.mHeight+mFrameHeight;
1732 end;
1733 end;
1736 function TUIControl.setActionCBFor (const aid: AnsiString; cb: TActionCB): TActionCB;
1737 var
1738 ctl: TUIControl;
1739 begin
1740 ctl := self[aid];
1741 if (ctl <> nil) then
1742 begin
1743 result := ctl.actionCB;
1744 ctl.actionCB := cb;
1745 end
1746 else
1747 begin
1748 result := nil;
1749 end;
1750 end;
1753 function TUIControl.forEachChildren (cb: TCtlEnumCB): TUIControl;
1754 var
1755 ctl: TUIControl;
1756 begin
1757 result := nil;
1758 if (not assigned(cb)) then exit;
1759 for ctl in mChildren do
1760 begin
1761 if cb(ctl) then begin result := ctl; exit; end;
1762 end;
1763 end;
1766 function TUIControl.forEachControl (cb: TCtlEnumCB; includeSelf: Boolean=true): TUIControl;
1768 function forChildren (p: TUIControl; incSelf: Boolean): TUIControl;
1769 var
1770 ctl: TUIControl;
1771 begin
1772 result := nil;
1773 if (p = nil) then exit;
1774 if (incSelf) and (cb(p)) then begin result := p; exit; end;
1775 for ctl in p.mChildren do
1776 begin
1777 result := forChildren(ctl, true);
1778 if (result <> nil) then break;
1779 end;
1780 end;
1782 begin
1783 result := nil;
1784 if (not assigned(cb)) then exit;
1785 result := forChildren(self, includeSelf);
1786 end;
1789 procedure TUIControl.close (); // this closes *top-level* control
1790 var
1791 ctl: TUIControl;
1792 begin
1793 ctl := topLevel;
1794 uiRemoveWindow(ctl);
1795 if (ctl is TUITopWindow) and (TUITopWindow(ctl).mFreeOnClose) then scheduleKill(ctl); // just in case
1796 end;
1799 procedure TUIControl.doAction ();
1800 begin
1801 if assigned(actionCB) then actionCB(self);
1802 end;
1805 // ////////////////////////////////////////////////////////////////////////// //
1806 procedure TUIControl.setScissorGLInternal (x, y, w, h: Integer);
1807 begin
1808 if not scallowed then exit;
1809 x := trunc(x*gh_ui_scale);
1810 y := trunc(y*gh_ui_scale);
1811 w := trunc(w*gh_ui_scale);
1812 h := trunc(h*gh_ui_scale);
1813 scis.combineRect(x, y, w, h);
1814 end;
1816 procedure TUIControl.setScissor (lx, ly, lw, lh: Integer);
1817 var
1818 gx, gy, wdt, hgt, cgx, cgy: Integer;
1819 begin
1820 if not scallowed then exit;
1822 if not intersectRect(lx, ly, lw, lh, 0, 0, mWidth, mHeight) then
1823 begin
1824 scis.combineRect(0, 0, 0, 0);
1825 exit;
1826 end;
1828 getDrawRect(gx, gy, wdt, hgt);
1829 toGlobal(lx, ly, cgx, cgy);
1830 if not intersectRect(gx, gy, wdt, hgt, cgx, cgy, lw, lh) then
1831 begin
1832 scis.combineRect(0, 0, 0, 0);
1833 exit;
1834 end;
1836 setScissorGLInternal(gx, gy, wdt, hgt);
1837 end;
1839 procedure TUIControl.resetScissor (fullArea: Boolean); inline;
1840 begin
1841 if not scallowed then exit;
1842 if (fullArea) then
1843 begin
1844 setScissor(0, 0, mWidth, mHeight);
1845 end
1846 else
1847 begin
1848 setScissor(mFrameWidth, mFrameHeight, mWidth-mFrameWidth*2, mHeight-mFrameHeight*2);
1849 end;
1850 end;
1853 // ////////////////////////////////////////////////////////////////////////// //
1854 procedure TUIControl.draw ();
1855 var
1856 f: Integer;
1857 gx, gy: Integer;
1858 begin
1859 if (mWidth < 1) or (mHeight < 1) then exit;
1860 toGlobal(0, 0, gx, gy);
1862 scis.save(true); // scissoring enabled
1863 try
1864 scallowed := true;
1865 resetScissor(true); // full area
1866 drawControl(gx, gy);
1867 resetScissor(false); // client area
1868 for f := 0 to High(mChildren) do mChildren[f].draw();
1869 resetScissor(true); // full area
1870 drawControlPost(gx, gy);
1871 finally
1872 scis.restore();
1873 scallowed := false;
1874 end;
1875 end;
1877 procedure TUIControl.drawControl (gx, gy: Integer);
1878 begin
1879 //if (mParent = nil) then darkenRect(gx, gy, mWidth, mHeight, 64);
1880 end;
1882 procedure TUIControl.drawControlPost (gx, gy: Integer);
1883 begin
1884 // shadow
1885 if mDrawShadow and (mWidth > 0) and (mHeight > 0) then
1886 begin
1887 setScissorGLInternal(gx+8, gy+8, mWidth, mHeight);
1888 darkenRect(gx+mWidth, gy+8, 8, mHeight, 128);
1889 darkenRect(gx+8, gy+mHeight, mWidth-8, 8, 128);
1890 end;
1891 end;
1894 // ////////////////////////////////////////////////////////////////////////// //
1895 procedure TUIControl.mouseEvent (var ev: THMouseEvent);
1896 var
1897 ctl: TUIControl;
1898 begin
1899 if (not enabled) then exit;
1900 if (mWidth < 1) or (mHeight < 1) then exit;
1901 ctl := controlAtXY(ev.x, ev.y);
1902 if (ctl = nil) then exit;
1903 if (ctl.canFocus) and (ev.press) then
1904 begin
1905 if (ctl <> topLevel.mFocused) then ctl.setFocused(true);
1906 uiGrabCtl := ctl;
1907 end;
1908 if (ctl <> self) then ctl.mouseEvent(ev);
1909 //ev.eat();
1910 end;
1913 procedure TUIControl.keyEvent (var ev: THKeyEvent);
1915 function doPreKey (ctl: TUIControl): Boolean;
1916 begin
1917 if (not ctl.enabled) then begin result := false; exit; end;
1918 ctl.keyEventPre(ev);
1919 result := (ev.eaten) or (ev.cancelled); // stop if event was consumed
1920 end;
1922 function doPostKey (ctl: TUIControl): Boolean;
1923 begin
1924 if (not ctl.enabled) then begin result := false; exit; end;
1925 ctl.keyEventPost(ev);
1926 result := (ev.eaten) or (ev.cancelled); // stop if event was consumed
1927 end;
1929 var
1930 ctl: TUIControl;
1931 begin
1932 if (not enabled) then exit;
1933 if (ev.eaten) or (ev.cancelled) then exit;
1934 // call pre-key
1935 if (mParent = nil) then
1936 begin
1937 forEachControl(doPreKey);
1938 if (ev.eaten) or (ev.cancelled) then exit;
1939 end;
1940 // focused control should process keyboard first
1941 if (topLevel.mFocused <> self) and isMyChild(topLevel.mFocused) and (topLevel.mFocused.enabled) then
1942 begin
1943 // bubble keyboard event
1944 ctl := topLevel.mFocused;
1945 while (ctl <> nil) and (ctl <> self) do
1946 begin
1947 ctl.keyEvent(ev);
1948 if (ev.eaten) or (ev.cancelled) then exit;
1949 ctl := ctl.mParent;
1950 end;
1951 end;
1952 // for top-level controls
1953 if (mParent = nil) then
1954 begin
1955 if (ev = 'S-Tab') then
1956 begin
1957 ctl := findPrevFocus(mFocused, true);
1958 if (ctl <> nil) and (ctl <> mFocused) then ctl.setFocused(true);
1959 ev.eat();
1960 exit;
1961 end;
1962 if (ev = 'Tab') then
1963 begin
1964 ctl := findNextFocus(mFocused, true);
1965 if (ctl <> nil) and (ctl <> mFocused) then ctl.setFocused(true);
1966 ev.eat();
1967 exit;
1968 end;
1969 if (ev = 'Enter') or (ev = 'C-Enter') then
1970 begin
1971 ctl := findDefaulControl();
1972 if (ctl <> nil) then
1973 begin
1974 ev.eat();
1975 ctl.doAction();
1976 exit;
1977 end;
1978 end;
1979 if (ev = 'Escape') then
1980 begin
1981 ctl := findCancelControl();
1982 if (ctl <> nil) then
1983 begin
1984 ev.eat();
1985 ctl.doAction();
1986 exit;
1987 end;
1988 end;
1989 if mEscClose and (ev = 'Escape') then
1990 begin
1991 if (not assigned(closeRequestCB)) or (closeRequestCB(self)) then
1992 begin
1993 uiRemoveWindow(self);
1994 end;
1995 ev.eat();
1996 exit;
1997 end;
1998 // call post-keys
1999 if (ev.eaten) or (ev.cancelled) then exit;
2000 forEachControl(doPostKey);
2001 end;
2002 end;
2005 procedure TUIControl.keyEventPre (var ev: THKeyEvent);
2006 begin
2007 end;
2010 procedure TUIControl.keyEventPost (var ev: THKeyEvent);
2011 begin
2012 end;
2015 // ////////////////////////////////////////////////////////////////////////// //
2016 constructor TUITopWindow.Create (const atitle: AnsiString);
2017 begin
2018 inherited Create();
2019 mTitle := atitle;
2020 end;
2023 procedure TUITopWindow.AfterConstruction ();
2024 begin
2025 inherited;
2026 mFrameWidth := 8;
2027 mFrameHeight := 8;
2028 if (mWidth < mFrameWidth*2+3*8) then mWidth := mFrameWidth*2+3*8;
2029 if (mHeight < mFrameHeight*2) then mHeight := mFrameHeight*2;
2030 if (Length(mTitle) > 0) then
2031 begin
2032 if (mWidth < Length(mTitle)*8+mFrameWidth*2+3*8) then mWidth := Length(mTitle)*8+mFrameWidth*2+3*8;
2033 end;
2034 mCanFocus := false;
2035 mDragScroll := TXMode.None;
2036 mDrawShadow := true;
2037 mWaitingClose := false;
2038 mInClose := false;
2039 closeCB := nil;
2040 mCtl4Style := 'window';
2041 end;
2044 function TUITopWindow.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2045 begin
2046 if (strEquCI1251(prname, 'title')) or (strEquCI1251(prname, 'caption')) then
2047 begin
2048 mTitle := par.expectIdOrStr(true);
2049 result := true;
2050 exit;
2051 end;
2052 if (strEquCI1251(prname, 'children')) then
2053 begin
2054 parseChildren(par);
2055 result := true;
2056 exit;
2057 end;
2058 if (strEquCI1251(prname, 'position')) then
2059 begin
2060 if (par.eatIdOrStrCI('default')) then mDoCenter := false
2061 else if (par.eatIdOrStrCI('center')) then mDoCenter := true
2062 else par.error('`center` or `default` expected');
2063 result := true;
2064 exit;
2065 end;
2066 if (parseOrientation(prname, par)) then begin result := true; exit; end;
2067 result := inherited parseProperty(prname, par);
2068 end;
2071 procedure TUITopWindow.centerInScreen ();
2072 begin
2073 if (mWidth > 0) and (mHeight > 0) then
2074 begin
2075 mX := trunc((gScrWidth/gh_ui_scale-mWidth)/2);
2076 mY := trunc((gScrHeight/gh_ui_scale-mHeight)/2);
2077 end;
2078 end;
2081 procedure TUITopWindow.drawControl (gx, gy: Integer);
2082 begin
2083 fillRect(gx, gy, mWidth, mHeight, mBackColor[getColorIndex]);
2084 end;
2087 procedure TUITopWindow.drawControlPost (gx, gy: Integer);
2088 var
2089 cidx: Integer;
2090 tx, hgt, sbhgt: Integer;
2091 begin
2092 cidx := getColorIndex;
2093 if (mDragScroll = TXMode.Drag) then
2094 begin
2095 drawRectUI(gx+4, gy+4, mWidth-8, mHeight-8, mFrameColor[cidx]);
2096 end
2097 else
2098 begin
2099 drawRectUI(gx+3, gy+3, mWidth-6, mHeight-6, mFrameColor[cidx]);
2100 drawRectUI(gx+5, gy+5, mWidth-10, mHeight-10, mFrameColor[cidx]);
2101 // vertical scroll bar
2102 hgt := mHeight-mFrameHeight*2;
2103 if (hgt > 0) and (mFullSize.h > hgt) then
2104 begin
2105 //writeln(mTitle, ': height=', mHeight-mFrameHeight*2, '; fullsize=', mFullSize.toString);
2106 sbhgt := mHeight-mFrameHeight*2+2;
2107 fillRect(gx+mWidth-mFrameWidth+1, gy+7, mFrameWidth-3, sbhgt, mFrameColor[cidx]);
2108 hgt += mScrollY;
2109 if (hgt > mFullSize.h) then hgt := mFullSize.h;
2110 hgt := sbhgt*hgt div mFullSize.h;
2111 if (hgt > 0) then
2112 begin
2113 setScissor(mWidth-mFrameWidth+1, 7, mFrameWidth-3, sbhgt);
2114 darkenRect(gx+mWidth-mFrameWidth+1, gy+7+hgt, mFrameWidth-3, sbhgt, 128);
2115 end;
2116 end;
2117 // frame icon
2118 setScissor(mFrameWidth, 0, 3*8, 8);
2119 fillRect(gx+mFrameWidth, gy, 3*8, 8, mBackColor[cidx]);
2120 drawText8(gx+mFrameWidth, gy, '[ ]', mFrameColor[cidx]);
2121 if mInClose then drawText8(gx+mFrameWidth+7, gy, '#', mFrameIconColor[cidx])
2122 else drawText8(gx+mFrameWidth+7, gy, '*', mFrameIconColor[cidx]);
2123 end;
2124 // title
2125 if (Length(mTitle) > 0) then
2126 begin
2127 setScissor(mFrameWidth+3*8, 0, mWidth-mFrameWidth*2-3*8, 8);
2128 tx := (gx+3*8)+((mWidth-3*8)-Length(mTitle)*8) div 2;
2129 fillRect(tx-3, gy, Length(mTitle)*8+3+2, 8, mBackColor[cidx]);
2130 drawText8(tx, gy, mTitle, mFrameTextColor[cidx]);
2131 end;
2132 // shadow
2133 inherited drawControlPost(gx, gy);
2134 end;
2137 procedure TUITopWindow.activated ();
2138 begin
2139 if (mFocused = nil) or (mFocused = self) then
2140 begin
2141 mFocused := findFirstFocus();
2142 end;
2143 if (mFocused <> nil) and (mFocused <> self) then mFocused.activated();
2144 inherited;
2145 end;
2148 procedure TUITopWindow.blurred ();
2149 begin
2150 mDragScroll := TXMode.None;
2151 mWaitingClose := false;
2152 mInClose := false;
2153 if (mFocused <> nil) and (mFocused <> self) then mFocused.blurred();
2154 inherited;
2155 end;
2158 procedure TUITopWindow.keyEvent (var ev: THKeyEvent);
2159 begin
2160 inherited keyEvent(ev);
2161 if (ev.eaten) or (ev.cancelled) or (not enabled) {or (not getFocused)} then exit;
2162 if (ev = 'M-F3') then
2163 begin
2164 if (not assigned(closeRequestCB)) or (closeRequestCB(self)) then
2165 begin
2166 uiRemoveWindow(self);
2167 end;
2168 ev.eat();
2169 exit;
2170 end;
2171 end;
2174 procedure TUITopWindow.mouseEvent (var ev: THMouseEvent);
2175 var
2176 lx, ly: Integer;
2177 hgt, sbhgt: Integer;
2178 begin
2179 if (not enabled) then exit;
2180 if (mWidth < 1) or (mHeight < 1) then exit;
2182 if (mDragScroll = TXMode.Drag) then
2183 begin
2184 mX += ev.x-mDragStartX;
2185 mY += ev.y-mDragStartY;
2186 mDragStartX := ev.x;
2187 mDragStartY := ev.y;
2188 if (ev.release) and (ev.but = ev.Left) then mDragScroll := TXMode.None;
2189 ev.eat();
2190 exit;
2191 end;
2193 if (mDragScroll = TXMode.Scroll) then
2194 begin
2195 // check for vertical scrollbar
2196 ly := ev.y-mY;
2197 if (ly < 7) then
2198 begin
2199 mScrollY := 0;
2200 end
2201 else
2202 begin
2203 sbhgt := mHeight-mFrameHeight*2+2;
2204 hgt := mHeight-mFrameHeight*2;
2205 if (hgt > 0) and (mFullSize.h > hgt) then
2206 begin
2207 hgt := (mFullSize.h*(ly-7) div (sbhgt-1))-(mHeight-mFrameHeight*2);
2208 mScrollY := nmax(0, hgt);
2209 hgt := mHeight-mFrameHeight*2;
2210 if (mScrollY+hgt > mFullSize.h) then mScrollY := nmax(0, mFullSize.h-hgt);
2211 end;
2212 end;
2213 if (ev.release) and (ev.but = ev.Left) then mDragScroll := TXMode.None;
2214 ev.eat();
2215 exit;
2216 end;
2218 if toLocal(ev.x, ev.y, lx, ly) then
2219 begin
2220 if (ev.press) then
2221 begin
2222 if (ly < 8) then
2223 begin
2224 uiGrabCtl := self;
2225 if (lx >= mFrameWidth) and (lx < mFrameWidth+3*8) then
2226 begin
2227 //uiRemoveWindow(self);
2228 mWaitingClose := true;
2229 mInClose := true;
2230 end
2231 else
2232 begin
2233 mDragScroll := TXMode.Drag;
2234 mDragStartX := ev.x;
2235 mDragStartY := ev.y;
2236 end;
2237 ev.eat();
2238 exit;
2239 end;
2240 // check for vertical scrollbar
2241 if (lx >= mWidth-mFrameWidth+1) and (ly >= 7) and (ly < mHeight-mFrameHeight+1) then
2242 begin
2243 sbhgt := mHeight-mFrameHeight*2+2;
2244 hgt := mHeight-mFrameHeight*2;
2245 if (hgt > 0) and (mFullSize.h > hgt) then
2246 begin
2247 hgt := (mFullSize.h*(ly-7) div (sbhgt-1))-(mHeight-mFrameHeight*2);
2248 mScrollY := nmax(0, hgt);
2249 uiGrabCtl := self;
2250 mDragScroll := TXMode.Scroll;
2251 ev.eat();
2252 exit;
2253 end;
2254 end;
2255 // drag
2256 if (lx < mFrameWidth) or (lx >= mWidth-mFrameWidth) or (ly >= mHeight-mFrameHeight) then
2257 begin
2258 uiGrabCtl := self;
2259 mDragScroll := TXMode.Drag;
2260 mDragStartX := ev.x;
2261 mDragStartY := ev.y;
2262 ev.eat();
2263 exit;
2264 end;
2265 end;
2267 if (ev.release) then
2268 begin
2269 if mWaitingClose then
2270 begin
2271 if (lx >= mFrameWidth) and (lx < mFrameWidth+3*8) then
2272 begin
2273 if (not assigned(closeRequestCB)) or (closeRequestCB(self)) then
2274 begin
2275 uiRemoveWindow(self);
2276 end;
2277 end;
2278 mWaitingClose := false;
2279 mInClose := false;
2280 ev.eat();
2281 exit;
2282 end;
2283 end;
2285 if (ev.motion) then
2286 begin
2287 if mWaitingClose then
2288 begin
2289 mInClose := (lx >= mFrameWidth) and (lx < mFrameWidth+3*8);
2290 ev.eat();
2291 exit;
2292 end;
2293 end;
2295 inherited mouseEvent(ev);
2296 end
2297 else
2298 begin
2299 mInClose := false;
2300 if (not ev.motion) and (mWaitingClose) then begin ev.eat(); mWaitingClose := false; exit; end;
2301 end;
2302 end;
2305 // ////////////////////////////////////////////////////////////////////////// //
2306 constructor TUIBox.Create (ahoriz: Boolean);
2307 begin
2308 inherited Create();
2309 mHoriz := ahoriz;
2310 end;
2313 procedure TUIBox.AfterConstruction ();
2314 begin
2315 inherited;
2316 mCanFocus := false;
2317 mHAlign := -1; // left
2318 mCtl4Style := 'box';
2319 end;
2322 procedure TUIBox.setCaption (const acap: AnsiString);
2323 begin
2324 mCaption := acap;
2325 mDefSize := TLaySize.Create(Length(mCaption)*8+3, 8);
2326 end;
2329 procedure TUIBox.setHasFrame (v: Boolean);
2330 begin
2331 mHasFrame := v;
2332 if (mHasFrame) then begin mFrameWidth := 8; mFrameHeight := 8; end else begin mFrameWidth := 0; mFrameHeight := 0; end;
2333 end;
2336 function TUIBox.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2337 begin
2338 if (parseOrientation(prname, par)) then begin result := true; exit; end;
2339 if (strEquCI1251(prname, 'frame')) then
2340 begin
2341 setHasFrame(parseBool(par));
2342 result := true;
2343 exit;
2344 end;
2345 if (strEquCI1251(prname, 'title')) or (strEquCI1251(prname, 'caption')) then
2346 begin
2347 setCaption(par.expectIdOrStr(true));
2348 result := true;
2349 exit;
2350 end;
2351 if (strEquCI1251(prname, 'textalign')) or (strEquCI1251(prname, 'text-align')) then
2352 begin
2353 mHAlign := parseHAlign(par);
2354 result := true;
2355 exit;
2356 end;
2357 if (strEquCI1251(prname, 'children')) then
2358 begin
2359 parseChildren(par);
2360 result := true;
2361 exit;
2362 end;
2363 result := inherited parseProperty(prname, par);
2364 end;
2367 procedure TUIBox.drawControl (gx, gy: Integer);
2368 var
2369 cidx: Integer;
2370 xpos: Integer;
2371 begin
2372 cidx := getColorIndex;
2373 fillRect(gx, gy, mWidth, mHeight, mBackColor[cidx]);
2374 if mHasFrame then
2375 begin
2376 // draw frame
2377 drawRectUI(gx+3, gy+3, mWidth-6, mHeight-6, mFrameColor[cidx]);
2378 end;
2379 // draw caption
2380 if (Length(mCaption) > 0) then
2381 begin
2382 if (mHAlign < 0) then xpos := 3
2383 else if (mHAlign > 0) then xpos := mWidth-mFrameWidth*2-Length(mCaption)*8
2384 else xpos := (mWidth-mFrameWidth*2-Length(mCaption)*8) div 2;
2385 xpos += gx+mFrameWidth;
2387 setScissor(mFrameWidth+1, 0, mWidth-mFrameWidth-2, 8);
2388 if mHasFrame then fillRect(xpos-3, gy, Length(mCaption)*8+4, 8, mBackColor[cidx]);
2389 drawText8(xpos, gy, mCaption, mFrameTextColor[cidx]);
2390 end;
2391 end;
2394 procedure TUIBox.mouseEvent (var ev: THMouseEvent);
2395 var
2396 lx, ly: Integer;
2397 begin
2398 inherited mouseEvent(ev);
2399 if (not ev.eaten) and (not ev.cancelled) and (enabled) and toLocal(ev.x, ev.y, lx, ly) then
2400 begin
2401 ev.eat();
2402 end;
2403 end;
2406 procedure TUIBox.keyEvent (var ev: THKeyEvent);
2407 var
2408 dir: Integer = 0;
2409 cur, ctl: TUIControl;
2410 begin
2411 inherited keyEvent(ev);
2412 if (ev.eaten) or (ev.cancelled) or (not ev.press) or (not enabled) or (not getActive) then exit;
2413 if (Length(mChildren) = 0) then exit;
2414 if (mHoriz) and (ev = 'Left') then dir := -1
2415 else if (mHoriz) and (ev = 'Right') then dir := 1
2416 else if (not mHoriz) and (ev = 'Up') then dir := -1
2417 else if (not mHoriz) and (ev = 'Down') then dir := 1;
2418 if (dir = 0) then exit;
2419 ev.eat();
2420 cur := topLevel.mFocused;
2421 while (cur <> nil) and (cur.mParent <> self) do cur := cur.mParent;
2422 //if (cur = nil) then writeln('CUR: nil') else writeln('CUR: ', cur.className, '#', cur.id);
2423 if (dir < 0) then ctl := findPrevFocus(cur, true) else ctl := findNextFocus(cur, true);
2424 //if (ctl = nil) then writeln('CTL: nil') else writeln('CTL: ', ctl.className, '#', ctl.id);
2425 if (ctl <> nil) and (ctl <> self) then
2426 begin
2427 ctl.focused := true;
2428 end;
2429 end;
2432 // ////////////////////////////////////////////////////////////////////////// //
2433 constructor TUIHBox.Create ();
2434 begin
2435 end;
2438 procedure TUIHBox.AfterConstruction ();
2439 begin
2440 inherited;
2441 mHoriz := true;
2442 end;
2445 // ////////////////////////////////////////////////////////////////////////// //
2446 constructor TUIVBox.Create ();
2447 begin
2448 end;
2451 procedure TUIVBox.AfterConstruction ();
2452 begin
2453 inherited;
2454 mHoriz := false;
2455 writeln('VBOX: ', canFocus, ':', enabled);
2456 end;
2459 // ////////////////////////////////////////////////////////////////////////// //
2460 procedure TUISpan.AfterConstruction ();
2461 begin
2462 inherited;
2463 mExpand := true;
2464 mCanFocus := false;
2465 mCtl4Style := 'span';
2466 end;
2469 function TUISpan.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2470 begin
2471 if (parseOrientation(prname, par)) then begin result := true; exit; end;
2472 result := inherited parseProperty(prname, par);
2473 end;
2476 procedure TUISpan.drawControl (gx, gy: Integer);
2477 begin
2478 end;
2481 // ////////////////////////////////////////////////////////////////////// //
2482 procedure TUILine.AfterConstruction ();
2483 begin
2484 inherited;
2485 mCanFocus := false;
2486 mExpand := true;
2487 mCanFocus := false;
2488 mCtl4Style := 'line';
2489 end;
2492 function TUILine.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2493 begin
2494 if (parseOrientation(prname, par)) then begin result := true; exit; end;
2495 result := inherited parseProperty(prname, par);
2496 end;
2499 procedure TUILine.drawControl (gx, gy: Integer);
2500 var
2501 cidx: Integer;
2502 begin
2503 cidx := getColorIndex;
2504 if mHoriz then
2505 begin
2506 drawHLine(gx, gy+(mHeight div 2), mWidth, mTextColor[cidx]);
2507 end
2508 else
2509 begin
2510 drawVLine(gx+(mWidth div 2), gy, mHeight, mTextColor[cidx]);
2511 end;
2512 end;
2515 // ////////////////////////////////////////////////////////////////////////// //
2516 procedure TUIHLine.AfterConstruction ();
2517 begin
2518 inherited;
2519 mHoriz := true;
2520 mDefSize.h := 7;
2521 end;
2524 // ////////////////////////////////////////////////////////////////////////// //
2525 procedure TUIVLine.AfterConstruction ();
2526 begin
2527 inherited;
2528 mHoriz := false;
2529 mDefSize.w := 7;
2530 end;
2533 // ////////////////////////////////////////////////////////////////////////// //
2534 procedure TUIStaticText.AfterConstruction ();
2535 begin
2536 inherited;
2537 mCanFocus := false;
2538 mHAlign := -1;
2539 mVAlign := 0;
2540 mHoriz := true; // nobody cares
2541 mHeader := false;
2542 mLine := false;
2543 mDefSize.h := 8;
2544 mCtl4Style := 'static';
2545 end;
2548 procedure TUIStaticText.setText (const atext: AnsiString);
2549 begin
2550 mText := atext;
2551 mDefSize := TLaySize.Create(Length(mText)*8, 8);
2552 end;
2555 function TUIStaticText.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2556 begin
2557 if (strEquCI1251(prname, 'title')) or (strEquCI1251(prname, 'caption')) or (strEquCI1251(prname, 'text')) then
2558 begin
2559 setText(par.expectIdOrStr(true));
2560 result := true;
2561 exit;
2562 end;
2563 if (strEquCI1251(prname, 'textalign')) or (strEquCI1251(prname, 'text-align')) then
2564 begin
2565 parseTextAlign(par, mHAlign, mVAlign);
2566 result := true;
2567 exit;
2568 end;
2569 if (strEquCI1251(prname, 'header')) then
2570 begin
2571 mHeader := true;
2572 result := true;
2573 exit;
2574 end;
2575 if (strEquCI1251(prname, 'line')) then
2576 begin
2577 mLine := true;
2578 result := true;
2579 exit;
2580 end;
2581 result := inherited parseProperty(prname, par);
2582 end;
2585 procedure TUIStaticText.drawControl (gx, gy: Integer);
2586 var
2587 xpos, ypos: Integer;
2588 cidx: Integer;
2589 clr: TGxRGBA;
2590 begin
2591 cidx := getColorIndex;
2592 fillRect(gx, gy, mWidth, mHeight, mBackColor[cidx]);
2594 if (mHAlign < 0) then xpos := 0
2595 else if (mHAlign > 0) then xpos := mWidth-Length(mText)*8
2596 else xpos := (mWidth-Length(mText)*8) div 2;
2598 if (Length(mText) > 0) then
2599 begin
2600 if (mHeader) then clr := mFrameTextColor[cidx] else clr := mTextColor[cidx];
2602 if (mVAlign < 0) then ypos := 0
2603 else if (mVAlign > 0) then ypos := mHeight-8
2604 else ypos := (mHeight-8) div 2;
2606 drawText8(gx+xpos, gy+ypos, mText, clr);
2607 end;
2609 if (mLine) then
2610 begin
2611 if (mHeader) then clr := mFrameColor[cidx] else clr := mTextColor[cidx];
2613 if (mVAlign < 0) then ypos := 0
2614 else if (mVAlign > 0) then ypos := mHeight-1
2615 else ypos := (mHeight div 2);
2616 ypos += gy;
2618 if (Length(mText) = 0) then
2619 begin
2620 drawHLine(gx, ypos, mWidth, clr);
2621 end
2622 else
2623 begin
2624 drawHLine(gx, ypos, xpos-1, clr);
2625 drawHLine(gx+xpos+Length(mText)*8, ypos, mWidth, clr);
2626 end;
2627 end;
2628 end;
2631 // ////////////////////////////////////////////////////////////////////////// //
2632 procedure TUITextLabel.AfterConstruction ();
2633 begin
2634 inherited;
2635 mHAlign := -1;
2636 mVAlign := 0;
2637 mCanFocus := false;
2638 mDefSize := TLaySize.Create(Length(mText)*8, 8);
2639 mCtl4Style := 'label';
2640 mLinkId := '';
2641 end;
2644 procedure TUITextLabel.cacheStyle (root: TUIStyle);
2645 begin
2646 inherited cacheStyle(root);
2647 // active
2648 mHotColor[ClrIdxActive] := root.get('hot-color', 'active', mCtl4Style).asRGBADef(TGxRGBA.Create(0, 128, 0));
2649 // disabled
2650 mHotColor[ClrIdxDisabled] := root.get('hot-color', 'disabled', mCtl4Style).asRGBADef(TGxRGBA.Create(0, 64, 0));
2651 // inactive
2652 mHotColor[ClrIdxInactive] := root.get('hot-color', 'inactive', mCtl4Style).asRGBADef(TGxRGBA.Create(0, 64, 0));
2653 end;
2656 procedure TUITextLabel.setText (const s: AnsiString);
2657 var
2658 f: Integer;
2659 begin
2660 mText := '';
2661 mHotChar := #0;
2662 mHotOfs := 0;
2663 f := 1;
2664 while (f <= Length(s)) do
2665 begin
2666 if (s[f] = '\\') then
2667 begin
2668 Inc(f);
2669 if (f <= Length(s)) then mText += s[f];
2670 Inc(f);
2671 end
2672 else if (s[f] = '~') then
2673 begin
2674 Inc(f);
2675 if (f <= Length(s)) then
2676 begin
2677 if (mHotChar = #0) then
2678 begin
2679 mHotChar := s[f];
2680 mHotOfs := Length(mText)*8;
2681 end;
2682 mText += s[f];
2683 end;
2684 Inc(f);
2685 end
2686 else
2687 begin
2688 mText += s[f];
2689 Inc(f);
2690 end;
2691 end;
2692 mDefSize := TLaySize.Create(Length(mText)*8, 8);
2693 end;
2696 function TUITextLabel.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2697 begin
2698 if (strEquCI1251(prname, 'title')) or (strEquCI1251(prname, 'caption')) or (strEquCI1251(prname, 'text')) then
2699 begin
2700 setText(par.expectIdOrStr(true));
2701 result := true;
2702 exit;
2703 end;
2704 if (strEquCI1251(prname, 'link')) then
2705 begin
2706 mLinkId := par.expectIdOrStr(true);
2707 result := true;
2708 exit;
2709 end;
2710 if (strEquCI1251(prname, 'textalign')) or (strEquCI1251(prname, 'text-align')) then
2711 begin
2712 parseTextAlign(par, mHAlign, mVAlign);
2713 result := true;
2714 exit;
2715 end;
2716 result := inherited parseProperty(prname, par);
2717 end;
2720 procedure TUITextLabel.drawControl (gx, gy: Integer);
2721 var
2722 xpos, ypos: Integer;
2723 cidx: Integer;
2724 begin
2725 cidx := getColorIndex;
2726 fillRect(gx, gy, mWidth, mHeight, mBackColor[cidx]);
2727 if (Length(mText) > 0) then
2728 begin
2729 if (mHAlign < 0) then xpos := 0
2730 else if (mHAlign > 0) then xpos := mWidth-Length(mText)*8
2731 else xpos := (mWidth-Length(mText)*8) div 2;
2733 if (mVAlign < 0) then ypos := 0
2734 else if (mVAlign > 0) then ypos := mHeight-8
2735 else ypos := (mHeight-8) div 2;
2737 drawText8(gx+xpos, gy+ypos, mText, mTextColor[cidx]);
2739 if (Length(mLinkId) > 0) and (mHotChar <> #0) and (mHotChar <> ' ') then
2740 begin
2741 drawText8(gx+xpos+8+mHotOfs, gy+ypos, mHotChar, mHotColor[cidx]);
2742 end;
2743 end;
2744 end;
2747 procedure TUITextLabel.mouseEvent (var ev: THMouseEvent);
2748 var
2749 lx, ly: Integer;
2750 begin
2751 inherited mouseEvent(ev);
2752 if (not ev.eaten) and (not ev.cancelled) and (enabled) and toLocal(ev.x, ev.y, lx, ly) then
2753 begin
2754 ev.eat();
2755 end;
2756 end;
2759 procedure TUITextLabel.keyEventPost (var ev: THKeyEvent);
2760 var
2761 ctl: TUIControl;
2762 begin
2763 if (not enabled) then exit;
2764 if (mHotChar = #0) or (Length(mLinkId) = 0) then exit;
2765 if (ev.eaten) or (ev.cancelled) or (not ev.press) then exit;
2766 if (not ev.isHot(mHotChar)) then exit;
2767 ctl := topLevel[mLinkId];
2768 if (ctl <> nil) then
2769 begin
2770 ev.eat();
2771 if (ctl.canFocus) then ctl.focused := true;
2772 end;
2773 end;
2776 // ////////////////////////////////////////////////////////////////////////// //
2777 procedure TUIButton.AfterConstruction ();
2778 begin
2779 inherited;
2780 mHAlign := -1;
2781 mVAlign := 0;
2782 mCanFocus := true;
2783 mDefSize := TLaySize.Create(Length(mText)*8+8, 10);
2784 mCtl4Style := 'button';
2785 end;
2788 procedure TUIButton.setText (const s: AnsiString);
2789 begin
2790 inherited setText(s);
2791 mDefSize := TLaySize.Create(Length(mText)*8+8*2, 10);
2792 end;
2795 procedure TUIButton.drawControl (gx, gy: Integer);
2796 var
2797 xpos, ypos: Integer;
2798 cidx: Integer;
2799 begin
2800 cidx := getColorIndex;
2802 fillRect(gx+1, gy, mWidth-2, mHeight, mBackColor[cidx]);
2803 fillRect(gx, gy+1, 1, mHeight-2, mBackColor[cidx]);
2804 fillRect(gx+mWidth-1, gy+1, 1, mHeight-2, mBackColor[cidx]);
2806 if (Length(mText) > 0) then
2807 begin
2808 if (mHAlign < 0) then xpos := 0
2809 else if (mHAlign > 0) then xpos := mWidth-Length(mText)*8
2810 else xpos := (mWidth-Length(mText)*8) div 2;
2812 if (mVAlign < 0) then ypos := 0
2813 else if (mVAlign > 0) then ypos := mHeight-8
2814 else ypos := (mHeight-8) div 2;
2816 setScissor(8, 0, mWidth-16, mHeight);
2817 drawText8(gx+xpos+8, gy+ypos, mText, mTextColor[cidx]);
2819 if (mHotChar <> #0) and (mHotChar <> ' ') then drawText8(gx+xpos+8+mHotOfs, gy+ypos, mHotChar, mHotColor[cidx]);
2820 end;
2821 end;
2824 procedure TUIButton.mouseEvent (var ev: THMouseEvent);
2825 var
2826 lx, ly: Integer;
2827 begin
2828 inherited mouseEvent(ev);
2829 if (uiGrabCtl = self) then
2830 begin
2831 ev.eat();
2832 if (ev = '-lmb') and focused and toLocal(ev.x, ev.y, lx, ly) then
2833 begin
2834 doAction();
2835 end;
2836 exit;
2837 end;
2838 if (ev.eaten) or (ev.cancelled) or (not enabled) or not focused then exit;
2839 ev.eat();
2840 end;
2843 procedure TUIButton.keyEvent (var ev: THKeyEvent);
2844 begin
2845 inherited keyEvent(ev);
2846 if (not ev.eaten) and (not ev.cancelled) and (enabled) then
2847 begin
2848 if (ev = 'Enter') or (ev = 'Space') then
2849 begin
2850 ev.eat();
2851 doAction();
2852 exit;
2853 end;
2854 end;
2855 end;
2858 procedure TUIButton.keyEventPost (var ev: THKeyEvent);
2859 begin
2860 if (not enabled) then exit;
2861 if (mHotChar = #0) then exit;
2862 if (ev.eaten) or (ev.cancelled) or (not ev.press) then exit;
2863 if (not ev.isHot(mHotChar)) then exit;
2864 if (not canFocus) then exit;
2865 ev.eat();
2866 focused := true;
2867 doAction();
2868 end;
2871 // ////////////////////////////////////////////////////////////////////////// //
2872 procedure TUISwitchBox.AfterConstruction ();
2873 begin
2874 inherited;
2875 mHAlign := -1;
2876 mVAlign := 0;
2877 mCanFocus := true;
2878 mDefSize := TLaySize.Create(Length(mText)*8+8*3, 8);
2879 mCtl4Style := 'switchbox';
2880 mChecked := false;
2881 mBoolVar := @mChecked;
2882 end;
2885 procedure TUISwitchBox.cacheStyle (root: TUIStyle);
2886 begin
2887 inherited cacheStyle(root);
2888 // active
2889 mSwitchColor[ClrIdxActive] := root.get('switch-color', 'active', mCtl4Style).asRGBADef(TGxRGBA.Create(255, 255, 255));
2890 // disabled
2891 mSwitchColor[ClrIdxDisabled] := root.get('switch-color', 'disabled', mCtl4Style).asRGBADef(TGxRGBA.Create(255, 255, 255));
2892 // inactive
2893 mSwitchColor[ClrIdxInactive] := root.get('switch-color', 'inactive', mCtl4Style).asRGBADef(TGxRGBA.Create(255, 255, 255));
2894 end;
2897 procedure TUISwitchBox.setText (const s: AnsiString);
2898 begin
2899 inherited setText(s);
2900 mDefSize := TLaySize.Create(Length(mText)*8+8*3, 8);
2901 end;
2904 function TUISwitchBox.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2905 begin
2906 if (strEquCI1251(prname, 'checked')) then
2907 begin
2908 result := true;
2909 setChecked(true);
2910 exit;
2911 end;
2912 result := inherited parseProperty(prname, par);
2913 end;
2916 function TUISwitchBox.getChecked (): Boolean;
2917 begin
2918 if (mBoolVar <> nil) then result := mBoolVar^ else result := false;
2919 end;
2922 procedure TUISwitchBox.setVar (pvar: PBoolean);
2923 begin
2924 if (pvar = nil) then pvar := @mChecked;
2925 if (pvar <> mBoolVar) then
2926 begin
2927 mBoolVar := pvar;
2928 setChecked(mBoolVar^);
2929 end;
2930 end;
2933 procedure TUISwitchBox.drawControl (gx, gy: Integer);
2934 var
2935 xpos, ypos: Integer;
2936 cidx: Integer;
2937 begin
2938 cidx := getColorIndex;
2940 if (mHAlign < 0) then xpos := 0
2941 else if (mHAlign > 0) then xpos := mWidth-(Length(mText)+4)*8
2942 else xpos := (mWidth-(Length(mText)+4)*8) div 2;
2944 if (mVAlign < 0) then ypos := 0
2945 else if (mVAlign > 0) then ypos := mHeight-8
2946 else ypos := (mHeight-8) div 2;
2949 fillRect(gx, gy, mWidth, mHeight, mBackColor[cidx]);
2951 if (checked) then
2952 begin
2953 if (Length(mCheckedStr) <> 3) or (mCheckedStr[2] <> '*') then
2954 begin
2955 drawText8(gx+xpos, gy+ypos, mCheckedStr, mSwitchColor[cidx]);
2956 end
2957 else
2958 begin
2959 drawText8(gx+xpos, gy+ypos, mCheckedStr[1], mSwitchColor[cidx]);
2960 drawText8(gx+xpos+2*8, gy+ypos, mCheckedStr[3], mSwitchColor[cidx]);
2961 drawText8(gx+xpos+7, gy+ypos, '*', mSwitchColor[cidx]);
2962 end;
2963 end
2964 else
2965 begin
2966 drawText8(gx+xpos, gy+ypos, mUncheckedStr, mSwitchColor[cidx]);
2967 end;
2969 drawText8(gx+xpos+8*3, gy+ypos, mText, mTextColor[cidx]);
2971 if (mHotChar <> #0) and (mHotChar <> ' ') then drawText8(gx+xpos+8*3+mHotOfs, gy+ypos, mHotChar, mHotColor[cidx]);
2972 end;
2975 procedure TUISwitchBox.mouseEvent (var ev: THMouseEvent);
2976 var
2977 lx, ly: Integer;
2978 begin
2979 inherited mouseEvent(ev);
2980 if (uiGrabCtl = self) then
2981 begin
2982 ev.eat();
2983 if (ev = '-lmb') and focused and toLocal(ev.x, ev.y, lx, ly) then
2984 begin
2985 doAction();
2986 end;
2987 exit;
2988 end;
2989 if (ev.eaten) or (ev.cancelled) or (not enabled) or not focused then exit;
2990 ev.eat();
2991 end;
2994 procedure TUISwitchBox.keyEvent (var ev: THKeyEvent);
2995 begin
2996 inherited keyEvent(ev);
2997 if (not ev.eaten) and (not ev.cancelled) and (enabled) then
2998 begin
2999 if (ev = 'Space') then
3000 begin
3001 ev.eat();
3002 doAction();
3003 exit;
3004 end;
3005 end;
3006 end;
3009 procedure TUISwitchBox.keyEventPost (var ev: THKeyEvent);
3010 begin
3011 if (not enabled) then exit;
3012 if (mHotChar = #0) then exit;
3013 if (ev.eaten) or (ev.cancelled) or (not ev.press) then exit;
3014 if (not ev.isHot(mHotChar)) then exit;
3015 if (not canFocus) then exit;
3016 ev.eat();
3017 focused := true;
3018 doAction();
3019 end;
3022 // ////////////////////////////////////////////////////////////////////////// //
3023 procedure TUICheckBox.AfterConstruction ();
3024 begin
3025 inherited;
3026 mChecked := false;
3027 mBoolVar := @mChecked;
3028 mCheckedStr := '[x]';
3029 mUncheckedStr := '[ ]';
3030 end;
3033 procedure TUICheckBox.setChecked (v: Boolean);
3034 begin
3035 mBoolVar^ := v;
3036 end;
3039 procedure TUICheckBox.doAction ();
3040 begin
3041 if (assigned(actionCB)) then
3042 begin
3043 actionCB(self);
3044 end
3045 else
3046 begin
3047 setChecked(not getChecked);
3048 end;
3049 end;
3052 // ////////////////////////////////////////////////////////////////////////// //
3053 procedure TUIRadioBox.AfterConstruction ();
3054 begin
3055 inherited;
3056 mChecked := false;
3057 mBoolVar := @mChecked;
3058 mCheckedStr := '(*)';
3059 mUncheckedStr := '( )';
3060 mRadioGroup := '';
3061 end;
3064 function TUIRadioBox.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
3065 begin
3066 if (strEquCI1251(prname, 'group')) then
3067 begin
3068 mRadioGroup := par.expectIdOrStr(true);
3069 if (getChecked) then setChecked(true);
3070 result := true;
3071 exit;
3072 end;
3073 if (strEquCI1251(prname, 'checked')) then
3074 begin
3075 result := true;
3076 setChecked(true);
3077 exit;
3078 end;
3079 result := inherited parseProperty(prname, par);
3080 end;
3083 procedure TUIRadioBox.setChecked (v: Boolean);
3085 function resetGroup (ctl: TUIControl): Boolean;
3086 begin
3087 result := false;
3088 if (ctl <> self) and (ctl is TUIRadioBox) and (TUIRadioBox(ctl).mRadioGroup = mRadioGroup) then
3089 begin
3090 TUIRadioBox(ctl).mBoolVar^ := false;
3091 end;
3092 end;
3094 begin
3095 mBoolVar^ := v;
3096 if v then topLevel.forEachControl(resetGroup);
3097 end;
3100 procedure TUIRadioBox.doAction ();
3101 begin
3102 if (assigned(actionCB)) then
3103 begin
3104 actionCB(self);
3105 end
3106 else
3107 begin
3108 setChecked(true);
3109 end;
3110 end;
3113 // ////////////////////////////////////////////////////////////////////////// //
3114 initialization
3115 registerCtlClass(TUIHBox, 'hbox');
3116 registerCtlClass(TUIVBox, 'vbox');
3117 registerCtlClass(TUISpan, 'span');
3118 registerCtlClass(TUIHLine, 'hline');
3119 registerCtlClass(TUIVLine, 'vline');
3120 registerCtlClass(TUITextLabel, 'label');
3121 registerCtlClass(TUIStaticText, 'static');
3122 registerCtlClass(TUIButton, 'button');
3123 registerCtlClass(TUICheckBox, 'checkbox');
3124 registerCtlClass(TUIRadioBox, 'radiobox');
3125 end.