DEADSOFTWARE

72008e0233a0cfe8d88b1360e95de059bf0229f7
[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 end;
346 TUIHBox = class(TUIBox)
347 public
348 constructor Create ();
350 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
351 end;
353 TUIVBox = class(TUIBox)
354 public
355 constructor Create ();
357 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
358 end;
360 // ////////////////////////////////////////////////////////////////////// //
361 TUISpan = class(TUIControl)
362 public
363 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
365 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
367 procedure drawControl (gx, gy: Integer); override;
368 end;
370 // ////////////////////////////////////////////////////////////////////// //
371 TUILine = class(TUIControl)
372 public
373 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
375 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
377 procedure drawControl (gx, gy: Integer); override;
378 end;
380 TUIHLine = class(TUILine)
381 public
382 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
383 end;
385 TUIVLine = class(TUILine)
386 public
387 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
388 end;
390 // ////////////////////////////////////////////////////////////////////// //
391 TUIStaticText = class(TUIControl)
392 private
393 mText: AnsiString;
394 mHAlign: Integer; // -1: left; 0: center; 1: right; default: left
395 mVAlign: Integer; // -1: top; 0: center; 1: bottom; default: center
396 mHeader: Boolean; // true: draw with frame text color
397 mLine: Boolean; // true: draw horizontal line
399 private
400 procedure setText (const atext: AnsiString);
402 public
403 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
405 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
407 procedure drawControl (gx, gy: Integer); override;
409 public
410 property text: AnsiString read mText write setText;
411 property halign: Integer read mHAlign write mHAlign;
412 property valign: Integer read mVAlign write mVAlign;
413 property header: Boolean read mHeader write mHeader;
414 property line: Boolean read mLine write mLine;
415 end;
417 // ////////////////////////////////////////////////////////////////////// //
418 TUITextLabel = class(TUIControl)
419 private
420 mText: AnsiString;
421 mHAlign: Integer; // -1: left; 0: center; 1: right; default: left
422 mVAlign: Integer; // -1: top; 0: center; 1: bottom; default: center
423 mHotChar: AnsiChar;
424 mHotOfs: Integer; // from text start, in pixels
425 mHotColor: array[0..ClrIdxMax] of TGxRGBA;
426 mLinkId: AnsiString; // linked control
428 protected
429 procedure cacheStyle (root: TUIStyle); override;
431 procedure setText (const s: AnsiString); virtual;
433 public
434 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
436 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
438 procedure drawControl (gx, gy: Integer); override;
440 procedure mouseEvent (var ev: THMouseEvent); override;
441 procedure keyEventPost (var ev: THKeyEvent); override;
443 public
444 property text: AnsiString read mText write setText;
445 property halign: Integer read mHAlign write mHAlign;
446 property valign: Integer read mVAlign write mVAlign;
447 end;
449 // ////////////////////////////////////////////////////////////////////// //
450 TUIButton = class(TUITextLabel)
451 protected
452 procedure setText (const s: AnsiString); override;
454 public
455 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
457 procedure drawControl (gx, gy: Integer); override;
459 procedure mouseEvent (var ev: THMouseEvent); override;
460 procedure keyEvent (var ev: THKeyEvent); override;
461 procedure keyEventPost (var ev: THKeyEvent); override;
462 end;
464 // ////////////////////////////////////////////////////////////////////// //
465 TUISwitchBox = class(TUITextLabel)
466 protected
467 mBoolVar: PBoolean;
468 mChecked: Boolean;
469 mCheckedStr: AnsiString;
470 mUncheckedStr: AnsiString;
471 mSwitchColor: array[0..ClrIdxMax] of TGxRGBA;
473 protected
474 procedure cacheStyle (root: TUIStyle); override;
476 procedure setText (const s: AnsiString); override;
478 function getChecked (): Boolean; virtual;
479 procedure setChecked (v: Boolean); virtual; abstract;
481 public
482 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
484 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
486 procedure drawControl (gx, gy: Integer); override;
488 procedure mouseEvent (var ev: THMouseEvent); override;
489 procedure keyEvent (var ev: THKeyEvent); override;
490 procedure keyEventPost (var ev: THKeyEvent); override;
492 procedure setVar (pvar: PBoolean);
494 public
495 property checked: Boolean read getChecked write setChecked;
496 end;
498 TUICheckBox = class(TUISwitchBox)
499 protected
500 procedure setChecked (v: Boolean); override;
502 public
503 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
505 procedure doAction (); override;
506 end;
508 TUIRadioBox = class(TUISwitchBox)
509 private
510 mRadioGroup: AnsiString;
512 protected
513 procedure setChecked (v: Boolean); override;
515 public
516 procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
518 function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
520 procedure doAction (); override;
522 public
523 property radioGroup: AnsiString read mRadioGroup write mRadioGroup; //FIXME
524 end;
527 // ////////////////////////////////////////////////////////////////////////// //
528 procedure uiMouseEvent (var evt: THMouseEvent);
529 procedure uiKeyEvent (var evt: THKeyEvent);
530 procedure uiDraw ();
533 // ////////////////////////////////////////////////////////////////////////// //
534 procedure uiAddWindow (ctl: TUIControl);
535 procedure uiRemoveWindow (ctl: TUIControl); // will free window if `mFreeOnClose` is `true`
536 function uiVisibleWindow (ctl: TUIControl): Boolean;
538 procedure uiUpdateStyles ();
541 // ////////////////////////////////////////////////////////////////////////// //
542 // do layouting
543 procedure uiLayoutCtl (ctl: TUIControl);
546 // ////////////////////////////////////////////////////////////////////////// //
547 var
548 gh_ui_scale: Single = 1.0;
551 implementation
553 uses
554 gh_flexlay,
555 utils;
558 // ////////////////////////////////////////////////////////////////////////// //
559 var
560 ctlsToKill: array of TUIControl = nil;
563 procedure scheduleKill (ctl: TUIControl);
564 var
565 f: Integer;
566 begin
567 if (ctl = nil) then exit;
568 ctl := ctl.topLevel;
569 for f := 0 to High(ctlsToKill) do
570 begin
571 if (ctlsToKill[f] = ctl) then exit;
572 if (ctlsToKill[f] = nil) then begin ctlsToKill[f] := ctl; exit; end;
573 end;
574 SetLength(ctlsToKill, Length(ctlsToKill)+1);
575 ctlsToKill[High(ctlsToKill)] := ctl;
576 end;
579 procedure processKills ();
580 var
581 f: Integer;
582 ctl: TUIControl;
583 begin
584 for f := 0 to High(ctlsToKill) do
585 begin
586 ctl := ctlsToKill[f];
587 if (ctl = nil) then break;
588 ctlsToKill[f] := nil;
589 FreeAndNil(ctl);
590 end;
591 if (Length(ctlsToKill) > 0) then ctlsToKill[0] := nil; // just in case
592 end;
595 // ////////////////////////////////////////////////////////////////////////// //
596 var
597 knownCtlClasses: array of record
598 klass: TUIControlClass;
599 name: AnsiString;
600 end = nil;
603 procedure registerCtlClass (aklass: TUIControlClass; const aname: AnsiString);
604 begin
605 assert(aklass <> nil);
606 assert(Length(aname) > 0);
607 SetLength(knownCtlClasses, Length(knownCtlClasses)+1);
608 knownCtlClasses[High(knownCtlClasses)].klass := aklass;
609 knownCtlClasses[High(knownCtlClasses)].name := aname;
610 end;
613 function findCtlClass (const aname: AnsiString): TUIControlClass;
614 var
615 f: Integer;
616 begin
617 for f := 0 to High(knownCtlClasses) do
618 begin
619 if (strEquCI1251(aname, knownCtlClasses[f].name)) then
620 begin
621 result := knownCtlClasses[f].klass;
622 exit;
623 end;
624 end;
625 result := nil;
626 end;
629 // ////////////////////////////////////////////////////////////////////////// //
630 type
631 TFlexLayouter = specialize TFlexLayouterBase<TUIControl>;
633 procedure uiLayoutCtl (ctl: TUIControl);
634 var
635 lay: TFlexLayouter;
636 begin
637 if (ctl = nil) then exit;
638 lay := TFlexLayouter.Create();
639 try
640 lay.setup(ctl);
641 //lay.layout();
643 //writeln('============================'); lay.dumpFlat();
645 //writeln('=== initial ==='); lay.dump();
647 //lay.calcMaxSizeInternal(0);
649 lay.firstPass();
650 writeln('=== after first pass ===');
651 lay.dump();
653 lay.secondPass();
654 writeln('=== after second pass ===');
655 lay.dump();
658 lay.layout();
659 //writeln('=== final ==='); lay.dump();
661 if (ctl.mParent = nil) and (ctl is TUITopWindow) and (TUITopWindow(ctl).mDoCenter) then
662 begin
663 TUITopWindow(ctl).centerInScreen();
664 end;
666 // calculate full size
667 ctl.calcFullClientSize();
669 // fix focus
670 if (ctl.mParent = nil) then
671 begin
672 if (ctl.mFocused = nil) or (ctl.mFocused = ctl) or (not ctl.mFocused.enabled) then
673 begin
674 ctl.mFocused := ctl.findFirstFocus();
675 end;
676 end;
678 finally
679 FreeAndNil(lay);
680 end;
681 end;
684 // ////////////////////////////////////////////////////////////////////////// //
685 var
686 uiTopList: array of TUIControl = nil;
687 uiGrabCtl: TUIControl = nil;
690 procedure uiUpdateStyles ();
691 var
692 ctl: TUIControl;
693 begin
694 for ctl in uiTopList do ctl.updateStyle();
695 end;
698 procedure uiMouseEvent (var evt: THMouseEvent);
699 var
700 ev: THMouseEvent;
701 f, c: Integer;
702 lx, ly: Integer;
703 ctmp: TUIControl;
704 begin
705 processKills();
706 if (evt.eaten) or (evt.cancelled) then exit;
707 ev := evt;
708 ev.x := trunc(ev.x/gh_ui_scale);
709 ev.y := trunc(ev.y/gh_ui_scale);
710 ev.dx := trunc(ev.dx/gh_ui_scale); //FIXME
711 ev.dy := trunc(ev.dy/gh_ui_scale); //FIXME
712 try
713 if (uiGrabCtl <> nil) then
714 begin
715 uiGrabCtl.mouseEvent(ev);
716 if (ev.release) and ((ev.bstate and (not ev.but)) = 0) then uiGrabCtl := nil;
717 ev.eat();
718 exit;
719 end;
720 if (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)].enabled) then uiTopList[High(uiTopList)].mouseEvent(ev);
721 if (not ev.eaten) and (not ev.cancelled) and (ev.press) then
722 begin
723 for f := High(uiTopList) downto 0 do
724 begin
725 if uiTopList[f].toLocal(ev.x, ev.y, lx, ly) then
726 begin
727 if (uiTopList[f].enabled) and (f <> High(uiTopList)) then
728 begin
729 uiTopList[High(uiTopList)].blurred();
730 ctmp := uiTopList[f];
731 uiGrabCtl := nil;
732 for c := f+1 to High(uiTopList) do uiTopList[c-1] := uiTopList[c];
733 uiTopList[High(uiTopList)] := ctmp;
734 ctmp.activated();
735 ctmp.mouseEvent(ev);
736 end;
737 ev.eat();
738 exit;
739 end;
740 end;
741 end;
742 finally
743 if (ev.eaten) then evt.eat();
744 if (ev.cancelled) then evt.cancel();
745 end;
746 end;
749 procedure uiKeyEvent (var evt: THKeyEvent);
750 var
751 ev: THKeyEvent;
752 begin
753 processKills();
754 if (evt.eaten) or (evt.cancelled) then exit;
755 ev := evt;
756 ev.x := trunc(ev.x/gh_ui_scale);
757 ev.y := trunc(ev.y/gh_ui_scale);
758 try
759 if (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)].enabled) then uiTopList[High(uiTopList)].keyEvent(ev);
760 //if (ev.release) then begin ev.eat(); exit; end;
761 finally
762 if (ev.eaten) then evt.eat();
763 if (ev.cancelled) then evt.cancel();
764 end;
765 end;
768 procedure uiDraw ();
769 var
770 f, cidx: Integer;
771 ctl: TUIControl;
772 begin
773 processKills();
774 gxBeginUIDraw(gh_ui_scale);
775 try
776 for f := 0 to High(uiTopList) do
777 begin
778 ctl := uiTopList[f];
779 ctl.draw();
780 if (f <> High(uiTopList)) then
781 begin
782 cidx := ctl.getColorIndex;
783 if (ctl.mDarken[cidx] > 0) then darkenRect(ctl.x0, ctl.y0, ctl.width, ctl.height, ctl.mDarken[cidx]);
784 end;
785 end;
786 finally
787 gxEndUIDraw();
788 end;
789 end;
792 procedure uiAddWindow (ctl: TUIControl);
793 var
794 f, c: Integer;
795 begin
796 if (ctl = nil) then exit;
797 ctl := ctl.topLevel;
798 if not (ctl is TUITopWindow) then exit; // alas
799 for f := 0 to High(uiTopList) do
800 begin
801 if (uiTopList[f] = ctl) then
802 begin
803 if (f <> High(uiTopList)) then
804 begin
805 uiTopList[High(uiTopList)].blurred();
806 for c := f+1 to High(uiTopList) do uiTopList[c-1] := uiTopList[c];
807 uiTopList[High(uiTopList)] := ctl;
808 ctl.activated();
809 end;
810 exit;
811 end;
812 end;
813 if (Length(uiTopList) > 0) then uiTopList[High(uiTopList)].blurred();
814 SetLength(uiTopList, Length(uiTopList)+1);
815 uiTopList[High(uiTopList)] := ctl;
816 ctl.updateStyle();
817 ctl.activated();
818 end;
821 procedure uiRemoveWindow (ctl: TUIControl);
822 var
823 f, c: Integer;
824 begin
825 if (ctl = nil) then exit;
826 ctl := ctl.topLevel;
827 if not (ctl is TUITopWindow) then exit; // alas
828 for f := 0 to High(uiTopList) do
829 begin
830 if (uiTopList[f] = ctl) then
831 begin
832 ctl.blurred();
833 for c := f+1 to High(uiTopList) do uiTopList[c-1] := uiTopList[c];
834 SetLength(uiTopList, Length(uiTopList)-1);
835 if (ctl is TUITopWindow) then
836 begin
837 try
838 if assigned(TUITopWindow(ctl).closeCB) then TUITopWindow(ctl).closeCB(ctl);
839 finally
840 if (TUITopWindow(ctl).mFreeOnClose) then scheduleKill(ctl);
841 end;
842 end;
843 exit;
844 end;
845 end;
846 end;
849 function uiVisibleWindow (ctl: TUIControl): Boolean;
850 var
851 f: Integer;
852 begin
853 result := false;
854 if (ctl = nil) then exit;
855 ctl := ctl.topLevel;
856 if not (ctl is TUITopWindow) then exit; // alas
857 for f := 0 to High(uiTopList) do
858 begin
859 if (uiTopList[f] = ctl) then begin result := true; exit; end;
860 end;
861 end;
864 // ////////////////////////////////////////////////////////////////////////// //
865 constructor TUIControl.Create ();
866 begin
867 end;
870 procedure TUIControl.AfterConstruction ();
871 begin
872 inherited;
873 mParent := nil;
874 mId := '';
875 mX := 0;
876 mY := 0;
877 mWidth := 64;
878 mHeight := 8;
879 mFrameWidth := 0;
880 mFrameHeight := 0;
881 mEnabled := true;
882 mCanFocus := true;
883 mChildren := nil;
884 mFocused := nil;
885 mEscClose := false;
886 scallowed := false;
887 mDrawShadow := false;
888 actionCB := nil;
889 // layouter interface
890 //mDefSize := TLaySize.Create(64, 8); // default size
891 mDefSize := TLaySize.Create(0, 0); // default size
892 mMaxSize := TLaySize.Create(-1, -1); // maximum size
893 mFlex := 0;
894 mHoriz := true;
895 mCanWrap := false;
896 mLineStart := false;
897 mHGroup := '';
898 mVGroup := '';
899 mStyleId := '';
900 mCtl4Style := '';
901 mAlign := -1; // left/top
902 mExpand := false;
903 end;
906 destructor TUIControl.Destroy ();
907 var
908 f, c: Integer;
909 begin
910 if (mParent <> nil) then
911 begin
912 setFocused(false);
913 for f := 0 to High(mParent.mChildren) do
914 begin
915 if (mParent.mChildren[f] = self) then
916 begin
917 for c := f+1 to High(mParent.mChildren) do mParent.mChildren[c-1] := mParent.mChildren[c];
918 SetLength(mParent.mChildren, Length(mParent.mChildren)-1);
919 end;
920 end;
921 end;
922 for f := 0 to High(mChildren) do
923 begin
924 mChildren[f].mParent := nil;
925 mChildren[f].Free();
926 end;
927 mChildren := nil;
928 end;
931 function TUIControl.getColorIndex (): Integer; inline;
932 begin
933 if (not enabled) then begin result := ClrIdxDisabled; exit; end;
934 // top windows: no focus hack
935 if (self is TUITopWindow) then
936 begin
937 if (getActive) then begin result := ClrIdxActive; exit; end;
938 end
939 else
940 begin
941 // if control cannot be focused, take "active" color scheme for it (it is easier this way)
942 if (not canFocus) or (getActive) then begin result := ClrIdxActive; exit; end;
943 end;
944 result := ClrIdxInactive;
945 end;
947 procedure TUIControl.updateStyle ();
948 var
949 stl: TUIStyle = nil;
950 ctl: TUIControl;
951 begin
952 ctl := self;
953 while (ctl <> nil) do
954 begin
955 if (Length(ctl.mStyleId) <> 0) then begin stl := uiFindStyle(ctl.mStyleId); break; end;
956 ctl := ctl.mParent;
957 end;
958 if (stl = nil) then stl := uiFindStyle(''); // default
959 cacheStyle(stl);
960 for ctl in mChildren do ctl.updateStyle();
961 end;
963 procedure TUIControl.cacheStyle (root: TUIStyle);
964 var
965 cst: AnsiString;
966 begin
967 //writeln('caching style for <', className, '> (', mCtl4Style, ')...');
968 cst := mCtl4Style;
969 // active
970 mBackColor[ClrIdxActive] := root.get('back-color', 'active', cst).asRGBADef(TGxRGBA.Create(0, 0, 128));
971 mTextColor[ClrIdxActive] := root.get('text-color', 'active', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
972 mFrameColor[ClrIdxActive] := root.get('frame-color', 'active', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
973 mFrameTextColor[ClrIdxActive] := root.get('frame-text-color', 'active', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
974 mFrameIconColor[ClrIdxActive] := root.get('frame-icon-color', 'active', cst).asRGBADef(TGxRGBA.Create(0, 255, 0));
975 mDarken[ClrIdxActive] := root.get('darken', 'active', cst).asInt(-1);
976 // disabled
977 mBackColor[ClrIdxDisabled] := root.get('back-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(0, 0, 128));
978 mTextColor[ClrIdxDisabled] := root.get('text-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(127, 127, 127));
979 mFrameColor[ClrIdxDisabled] := root.get('frame-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(127, 127, 127));
980 mFrameTextColor[ClrIdxDisabled] := root.get('frame-text-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(127, 127, 127));
981 mFrameIconColor[ClrIdxDisabled] := root.get('frame-icon-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(0, 127, 0));
982 mDarken[ClrIdxDisabled] := root.get('darken', 'disabled', cst).asInt(-1);
983 // inactive
984 mBackColor[ClrIdxInactive] := root.get('back-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(0, 0, 128));
985 mTextColor[ClrIdxInactive] := root.get('text-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
986 mFrameColor[ClrIdxInactive] := root.get('frame-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
987 mFrameTextColor[ClrIdxInactive] := root.get('frame-text-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
988 mFrameIconColor[ClrIdxInactive] := root.get('frame-icon-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(0, 255, 0));
989 mDarken[ClrIdxInactive] := root.get('darken', 'inactive', cst).asInt(-1);
990 end;
993 // ////////////////////////////////////////////////////////////////////////// //
994 function TUIControl.getDefSize (): TLaySize; inline; begin result := mLayDefSize; end;
995 function TUIControl.getMaxSize (): TLaySize; inline; begin result := mLayMaxSize; end;
996 function TUIControl.getFlex (): Integer; inline; begin result := mFlex; end;
997 function TUIControl.isHorizBox (): Boolean; inline; begin result := mHoriz; end;
998 procedure TUIControl.setHorizBox (v: Boolean); inline; begin mHoriz := v; end;
999 function TUIControl.canWrap (): Boolean; inline; begin result := mCanWrap; end;
1000 procedure TUIControl.setCanWrap (v: Boolean); inline; begin mCanWrap := v; end;
1001 function TUIControl.isLineStart (): Boolean; inline; begin result := mLineStart; end;
1002 procedure TUIControl.setLineStart (v: Boolean); inline; begin mLineStart := v; end;
1003 function TUIControl.getAlign (): Integer; inline; begin result := mAlign; end;
1004 procedure TUIControl.setAlign (v: Integer); inline; begin mAlign := v; end;
1005 function TUIControl.getExpand (): Boolean; inline; begin result := mExpand; end;
1006 procedure TUIControl.setExpand (v: Boolean); inline; begin mExpand := v; end;
1007 function TUIControl.getHGroup (): AnsiString; inline; begin result := mHGroup; end;
1008 procedure TUIControl.setHGroup (const v: AnsiString); inline; begin mHGroup := v; end;
1009 function TUIControl.getVGroup (): AnsiString; inline; begin result := mVGroup; end;
1010 procedure TUIControl.setVGroup (const v: AnsiString); inline; begin mVGroup := v; end;
1012 function TUIControl.getMargins (): TLayMargins; inline;
1013 begin
1014 result := TLayMargins.Create(mFrameHeight, mFrameWidth, mFrameHeight, mFrameWidth);
1015 end;
1017 procedure TUIControl.setActualSizePos (constref apos: TLayPos; constref asize: TLaySize); inline;
1018 begin
1019 //writeln(self.className, '; pos=', apos.toString, '; size=', asize.toString);
1020 if (mParent <> nil) then
1021 begin
1022 mX := apos.x;
1023 mY := apos.y;
1024 end;
1025 mWidth := asize.w;
1026 mHeight := asize.h;
1027 end;
1029 procedure TUIControl.layPrepare ();
1030 begin
1031 mLayDefSize := mDefSize;
1032 mLayMaxSize := mMaxSize;
1033 if (mLayMaxSize.w >= 0) then mLayMaxSize.w += mFrameWidth*2;
1034 if (mLayMaxSize.h >= 0) then mLayMaxSize.h += mFrameHeight*2;
1035 end;
1038 // ////////////////////////////////////////////////////////////////////////// //
1039 function TUIControl.parsePos (par: TTextParser): TLayPos;
1040 var
1041 ech: AnsiChar = ')';
1042 begin
1043 if (par.eatDelim('[')) then ech := ']' else par.expectDelim('(');
1044 result.x := par.expectInt();
1045 par.eatDelim(','); // optional comma
1046 result.y := par.expectInt();
1047 par.eatDelim(','); // optional comma
1048 par.expectDelim(ech);
1049 end;
1051 function TUIControl.parseSize (par: TTextParser): TLaySize;
1052 var
1053 ech: AnsiChar = ')';
1054 begin
1055 if (par.eatDelim('[')) then ech := ']' else par.expectDelim('(');
1056 result.w := par.expectInt();
1057 par.eatDelim(','); // optional comma
1058 result.h := par.expectInt();
1059 par.eatDelim(','); // optional comma
1060 par.expectDelim(ech);
1061 end;
1063 function TUIControl.parseBool (par: TTextParser): Boolean;
1064 begin
1065 result :=
1066 par.eatIdOrStrCI('true') or
1067 par.eatIdOrStrCI('yes') or
1068 par.eatIdOrStrCI('tan');
1069 if not result then
1070 begin
1071 if (not par.eatIdOrStrCI('false')) and (not par.eatIdOrStrCI('no')) and (not par.eatIdOrStrCI('ona')) then
1072 begin
1073 par.error('boolean value expected');
1074 end;
1075 end;
1076 end;
1078 function TUIControl.parseAnyAlign (par: TTextParser): Integer;
1079 begin
1080 if (par.eatIdOrStrCI('left')) or (par.eatIdOrStrCI('top')) then result := -1
1081 else if (par.eatIdOrStrCI('right')) or (par.eatIdOrStrCI('bottom')) then result := 1
1082 else if (par.eatIdOrStrCI('center')) then result := 0
1083 else par.error('invalid align value');
1084 end;
1086 function TUIControl.parseHAlign (par: TTextParser): Integer;
1087 begin
1088 if (par.eatIdOrStrCI('left')) then result := -1
1089 else if (par.eatIdOrStrCI('right')) then result := 1
1090 else if (par.eatIdOrStrCI('center')) then result := 0
1091 else par.error('invalid horizontal align value');
1092 end;
1094 function TUIControl.parseVAlign (par: TTextParser): Integer;
1095 begin
1096 if (par.eatIdOrStrCI('top')) then result := -1
1097 else if (par.eatIdOrStrCI('bottom')) then result := 1
1098 else if (par.eatIdOrStrCI('center')) then result := 0
1099 else par.error('invalid vertical align value');
1100 end;
1102 procedure TUIControl.parseTextAlign (par: TTextParser; var h, v: Integer);
1103 var
1104 wasH: Boolean = false;
1105 wasV: Boolean = false;
1106 begin
1107 while true do
1108 begin
1109 if (par.eatIdOrStrCI('left')) then
1110 begin
1111 if wasH then par.error('too many align directives');
1112 wasH := true;
1113 h := -1;
1114 continue;
1115 end;
1116 if (par.eatIdOrStrCI('right')) then
1117 begin
1118 if wasH then par.error('too many align directives');
1119 wasH := true;
1120 h := 1;
1121 continue;
1122 end;
1123 if (par.eatIdOrStrCI('hcenter')) then
1124 begin
1125 if wasH then par.error('too many align directives');
1126 wasH := true;
1127 h := 0;
1128 continue;
1129 end;
1130 if (par.eatIdOrStrCI('top')) then
1131 begin
1132 if wasV then par.error('too many align directives');
1133 wasV := true;
1134 v := -1;
1135 continue;
1136 end;
1137 if (par.eatIdOrStrCI('bottom')) then
1138 begin
1139 if wasV then par.error('too many align directives');
1140 wasV := true;
1141 v := 1;
1142 continue;
1143 end;
1144 if (par.eatIdOrStrCI('vcenter')) then
1145 begin
1146 if wasV then par.error('too many align directives');
1147 wasV := true;
1148 v := 0;
1149 continue;
1150 end;
1151 if (par.eatIdOrStrCI('center')) then
1152 begin
1153 if wasV or wasH then par.error('too many align directives');
1154 wasV := true;
1155 wasH := true;
1156 h := 0;
1157 v := 0;
1158 continue;
1159 end;
1160 break;
1161 end;
1162 if not wasV and not wasH then par.error('invalid align value');
1163 end;
1165 function TUIControl.parseOrientation (const prname: AnsiString; par: TTextParser): Boolean;
1166 begin
1167 if (strEquCI1251(prname, 'orientation')) or (strEquCI1251(prname, 'orient')) then
1168 begin
1169 if (par.eatIdOrStrCI('horizontal')) or (par.eatIdOrStrCI('horiz')) then mHoriz := true
1170 else if (par.eatIdOrStrCI('vertical')) or (par.eatIdOrStrCI('vert')) then mHoriz := false
1171 else par.error('`horizontal` or `vertical` expected');
1172 result := true;
1173 end
1174 else
1175 begin
1176 result := false;
1177 end;
1178 end;
1180 // par should be on '{'; final '}' is eaten
1181 procedure TUIControl.parseProperties (par: TTextParser);
1182 var
1183 pn: AnsiString;
1184 begin
1185 if (not par.eatDelim('{')) then exit;
1186 while (not par.eatDelim('}')) do
1187 begin
1188 if (not par.isIdOrStr) then par.error('property name expected');
1189 pn := par.tokStr;
1190 par.skipToken();
1191 par.eatDelim(':'); // optional
1192 if not parseProperty(pn, par) then par.errorfmt('invalid property name ''%s''', [pn]);
1193 par.eatDelim(','); // optional
1194 end;
1195 end;
1197 // par should be on '{'
1198 procedure TUIControl.parseChildren (par: TTextParser);
1199 var
1200 cc: TUIControlClass;
1201 ctl: TUIControl;
1202 begin
1203 par.expectDelim('{');
1204 while (not par.eatDelim('}')) do
1205 begin
1206 if (not par.isIdOrStr) then par.error('control name expected');
1207 cc := findCtlClass(par.tokStr);
1208 if (cc = nil) then par.errorfmt('unknown control name: ''%s''', [par.tokStr]);
1209 //writeln('children for <', par.tokStr, '>: <', cc.className, '>');
1210 par.skipToken();
1211 par.eatDelim(':'); // optional
1212 ctl := cc.Create();
1213 //writeln(' mHoriz=', ctl.mHoriz);
1214 try
1215 ctl.parseProperties(par);
1216 except
1217 FreeAndNil(ctl);
1218 raise;
1219 end;
1220 //writeln(': ', ctl.mDefSize.toString);
1221 appendChild(ctl);
1222 par.eatDelim(','); // optional
1223 end;
1224 end;
1227 function TUIControl.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
1228 begin
1229 result := true;
1230 if (strEquCI1251(prname, 'id')) then begin mId := par.expectIdOrStr(true); exit; end; // allow empty strings
1231 if (strEquCI1251(prname, 'style')) then begin mStyleId := par.expectIdOrStr(); exit; end; // no empty strings
1232 if (strEquCI1251(prname, 'flex')) then begin flex := par.expectInt(); exit; end;
1233 // sizes
1234 if (strEquCI1251(prname, 'defsize')) or (strEquCI1251(prname, 'size')) then begin mDefSize := parseSize(par); exit; end;
1235 if (strEquCI1251(prname, 'maxsize')) then begin mMaxSize := parseSize(par); exit; end;
1236 if (strEquCI1251(prname, 'defwidth')) or (strEquCI1251(prname, 'width')) then begin mDefSize.w := par.expectInt(); exit; end;
1237 if (strEquCI1251(prname, 'defheight')) or (strEquCI1251(prname, 'height')) then begin mDefSize.h := par.expectInt(); exit; end;
1238 if (strEquCI1251(prname, 'maxwidth')) then begin mMaxSize.w := par.expectInt(); exit; end;
1239 if (strEquCI1251(prname, 'maxheight')) then begin mMaxSize.h := par.expectInt(); exit; end;
1240 // flags
1241 if (strEquCI1251(prname, 'wrap')) then begin mCanWrap := parseBool(par); exit; end;
1242 if (strEquCI1251(prname, 'linestart')) then begin mLineStart := parseBool(par); exit; end;
1243 if (strEquCI1251(prname, 'expand')) then begin mExpand := parseBool(par); exit; end;
1244 // align
1245 if (strEquCI1251(prname, 'align')) then begin mAlign := parseAnyAlign(par); exit; end;
1246 if (strEquCI1251(prname, 'hgroup')) then begin mHGroup := par.expectIdOrStr(true); exit; end; // allow empty strings
1247 if (strEquCI1251(prname, 'vgroup')) then begin mVGroup := par.expectIdOrStr(true); exit; end; // allow empty strings
1248 // other
1249 if (strEquCI1251(prname, 'canfocus')) then begin mCanFocus := true; exit; end;
1250 if (strEquCI1251(prname, 'nofocus')) then begin mCanFocus := false; exit; end;
1251 if (strEquCI1251(prname, 'disabled')) then begin mEnabled := false; exit; end;
1252 if (strEquCI1251(prname, 'enabled')) then begin mEnabled := true; exit; end;
1253 if (strEquCI1251(prname, 'escclose')) then begin mEscClose := not parseBool(par); exit; end;
1254 if (strEquCI1251(prname, 'default')) then begin mDefault := true; exit; end;
1255 if (strEquCI1251(prname, 'cancel')) then begin mCancel := true; exit; end;
1256 result := false;
1257 end;
1260 // ////////////////////////////////////////////////////////////////////////// //
1261 procedure TUIControl.activated ();
1262 begin
1263 makeVisibleInParent();
1264 end;
1267 procedure TUIControl.blurred ();
1268 begin
1269 if (uiGrabCtl = self) then uiGrabCtl := nil;
1270 end;
1273 procedure TUIControl.calcFullClientSize ();
1274 var
1275 ctl: TUIControl;
1276 begin
1277 mFullSize := TLaySize.Create(0, 0);
1278 if (mWidth < 1) or (mHeight < 1) then exit;
1279 for ctl in mChildren do
1280 begin
1281 ctl.calcFullClientSize();
1282 mFullSize.w := nmax(mFullSize.w, ctl.mX-mFrameWidth+ctl.mFullSize.w);
1283 mFullSize.h := nmax(mFullSize.h, ctl.mY-mFrameHeight+ctl.mFullSize.h);
1284 end;
1285 mFullSize.w := nmax(mFullSize.w, mWidth-mFrameWidth*2);
1286 mFullSize.h := nmax(mFullSize.h, mHeight-mFrameHeight*2);
1287 end;
1290 function TUIControl.topLevel (): TUIControl; inline;
1291 begin
1292 result := self;
1293 while (result.mParent <> nil) do result := result.mParent;
1294 end;
1297 function TUIControl.getEnabled (): Boolean;
1298 var
1299 ctl: TUIControl;
1300 begin
1301 result := false;
1302 if (not mEnabled) then exit;
1303 ctl := mParent;
1304 while (ctl <> nil) do
1305 begin
1306 if (not ctl.mEnabled) then exit;
1307 ctl := ctl.mParent;
1308 end;
1309 result := true;
1310 end;
1313 procedure TUIControl.setEnabled (v: Boolean); inline;
1314 begin
1315 if (mEnabled = v) then exit;
1316 mEnabled := v;
1317 if (not v) and focused then setFocused(false);
1318 end;
1321 function TUIControl.getFocused (): Boolean; inline;
1322 begin
1323 if (mParent = nil) then
1324 begin
1325 result := (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)] = self);
1326 end
1327 else
1328 begin
1329 result := (topLevel.mFocused = self);
1330 if (result) then result := (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)] = topLevel);
1331 end;
1332 end;
1335 function TUIControl.getActive (): Boolean; inline;
1336 var
1337 ctl: TUIControl;
1338 begin
1339 if (mParent = nil) then
1340 begin
1341 result := (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)] = self);
1342 end
1343 else
1344 begin
1345 ctl := topLevel.mFocused;
1346 while (ctl <> nil) and (ctl <> self) do ctl := ctl.mParent;
1347 result := (ctl = self);
1348 if (result) then result := (Length(uiTopList) > 0) and (uiTopList[High(uiTopList)] = topLevel);
1349 end;
1350 end;
1353 procedure TUIControl.setFocused (v: Boolean); inline;
1354 var
1355 tl: TUIControl;
1356 begin
1357 tl := topLevel;
1358 if (not v) then
1359 begin
1360 if (tl.mFocused = self) then
1361 begin
1362 blurred(); // this will reset grab, but still...
1363 if (uiGrabCtl = self) then uiGrabCtl := nil;
1364 tl.mFocused := tl.findNextFocus(self, true);
1365 if (tl.mFocused = self) then tl.mFocused := nil;
1366 if (tl.mFocused <> nil) then tl.mFocused.activated();
1367 end;
1368 exit;
1369 end;
1370 if (not canFocus) then exit;
1371 if (tl.mFocused <> self) then
1372 begin
1373 if (tl.mFocused <> nil) then tl.mFocused.blurred();
1374 tl.mFocused := self;
1375 if (uiGrabCtl <> self) then uiGrabCtl := nil;
1376 activated();
1377 end;
1378 end;
1381 function TUIControl.getCanFocus (): Boolean; inline;
1382 begin
1383 result := (getEnabled) and (mCanFocus) and (mWidth > 0) and (mHeight > 0);
1384 end;
1387 function TUIControl.isMyChild (ctl: TUIControl): Boolean;
1388 begin
1389 result := true;
1390 while (ctl <> nil) do
1391 begin
1392 if (ctl.mParent = self) then exit;
1393 ctl := ctl.mParent;
1394 end;
1395 result := false;
1396 end;
1399 // returns `true` if global coords are inside this control
1400 function TUIControl.toLocal (var x, y: Integer): Boolean;
1401 begin
1402 if (mParent = nil) then
1403 begin
1404 Dec(x, mX);
1405 Dec(y, mY);
1406 result := true; // hack
1407 end
1408 else
1409 begin
1410 result := mParent.toLocal(x, y);
1411 Inc(x, mParent.mScrollX);
1412 Inc(y, mParent.mScrollY);
1413 Dec(x, mX);
1414 Dec(y, mY);
1415 if result then result := (x >= 0) and (y >= 0) and (x < mParent.mWidth) and (y < mParent.mHeight);
1416 end;
1417 if result then result := (x >= 0) and (y >= 0) and (x < mWidth) and (y < mHeight);
1418 end;
1420 function TUIControl.toLocal (gx, gy: Integer; out x, y: Integer): Boolean; inline;
1421 begin
1422 x := gx;
1423 y := gy;
1424 result := toLocal(x, y);
1425 end;
1428 procedure TUIControl.toGlobal (var x, y: Integer);
1429 begin
1430 Inc(x, mX);
1431 Inc(y, mY);
1432 if (mParent <> nil) then
1433 begin
1434 Dec(x, mParent.mScrollX);
1435 Dec(y, mParent.mScrollY);
1436 mParent.toGlobal(x, y);
1437 end;
1438 end;
1440 procedure TUIControl.toGlobal (lx, ly: Integer; out x, y: Integer); inline;
1441 begin
1442 x := lx;
1443 y := ly;
1444 toGlobal(x, y);
1445 end;
1447 procedure TUIControl.getDrawRect (out gx, gy, wdt, hgt: Integer);
1448 var
1449 cgx, cgy: Integer;
1450 begin
1451 if (mParent = nil) then
1452 begin
1453 gx := mX;
1454 gy := mY;
1455 wdt := mWidth;
1456 hgt := mHeight;
1457 end
1458 else
1459 begin
1460 toGlobal(0, 0, cgx, cgy);
1461 mParent.getDrawRect(gx, gy, wdt, hgt);
1462 if (wdt > 0) and (hgt > 0) then
1463 begin
1464 if not intersectRect(gx, gy, wdt, hgt, cgx, cgy, mWidth, mHeight) then
1465 begin
1466 wdt := 0;
1467 hgt := 0;
1468 end;
1469 end;
1470 end;
1471 end;
1474 // x and y are global coords
1475 function TUIControl.controlAtXY (x, y: Integer; allowDisabled: Boolean=false): TUIControl;
1476 var
1477 lx, ly: Integer;
1478 f: Integer;
1479 begin
1480 result := nil;
1481 if (not allowDisabled) and (not enabled) then exit;
1482 if (mWidth < 1) or (mHeight < 1) then exit;
1483 if not toLocal(x, y, lx, ly) then exit;
1484 for f := High(mChildren) downto 0 do
1485 begin
1486 result := mChildren[f].controlAtXY(x, y, allowDisabled);
1487 if (result <> nil) then exit;
1488 end;
1489 result := self;
1490 end;
1493 function TUIControl.parentScrollX (): Integer; inline; begin if (mParent <> nil) then result := mParent.mScrollX else result := 0; end;
1494 function TUIControl.parentScrollY (): Integer; inline; begin if (mParent <> nil) then result := mParent.mScrollY else result := 0; end;
1497 procedure TUIControl.makeVisibleInParent ();
1498 var
1499 sy, ey, cy: Integer;
1500 p: TUIControl;
1501 begin
1502 if (mWidth < 1) or (mHeight < 1) then exit;
1503 p := mParent;
1504 if (p = nil) then exit;
1505 if (p.mFullSize.w < 1) or (p.mFullSize.h < 1) then
1506 begin
1507 p.mScrollX := 0;
1508 p.mScrollY := 0;
1509 exit;
1510 end;
1511 p.makeVisibleInParent();
1512 cy := mY-p.mFrameHeight;
1513 sy := p.mScrollY;
1514 ey := sy+(p.mHeight-p.mFrameHeight*2);
1515 if (cy < sy) then
1516 begin
1517 p.mScrollY := nmax(0, cy);
1518 end
1519 else if (cy+mHeight > ey) then
1520 begin
1521 p.mScrollY := nmax(0, cy+mHeight-(p.mHeight-p.mFrameHeight*2));
1522 end;
1523 end;
1526 // ////////////////////////////////////////////////////////////////////////// //
1527 function TUIControl.prevSibling (): TUIControl;
1528 var
1529 f: Integer;
1530 begin
1531 if (mParent <> nil) then
1532 begin
1533 for f := 1 to High(mParent.mChildren) do
1534 begin
1535 if (mParent.mChildren[f] = self) then begin result := mParent.mChildren[f-1]; exit; end;
1536 end;
1537 end;
1538 result := nil;
1539 end;
1541 function TUIControl.nextSibling (): TUIControl;
1542 var
1543 f: Integer;
1544 begin
1545 if (mParent <> nil) then
1546 begin
1547 for f := 0 to High(mParent.mChildren)-1 do
1548 begin
1549 if (mParent.mChildren[f] = self) then begin result := mParent.mChildren[f+1]; exit; end;
1550 end;
1551 end;
1552 result := nil;
1553 end;
1555 function TUIControl.firstChild (): TUIControl; inline;
1556 begin
1557 if (Length(mChildren) <> 0) then result := mChildren[0] else result := nil;
1558 end;
1560 function TUIControl.lastChild (): TUIControl; inline;
1561 begin
1562 if (Length(mChildren) <> 0) then result := mChildren[High(mChildren)] else result := nil;
1563 end;
1566 function TUIControl.findFirstFocus (): TUIControl;
1567 var
1568 f: Integer;
1569 begin
1570 result := nil;
1571 if enabled then
1572 begin
1573 for f := 0 to High(mChildren) do
1574 begin
1575 result := mChildren[f].findFirstFocus();
1576 if (result <> nil) then exit;
1577 end;
1578 if (canFocus) then result := self;
1579 end;
1580 end;
1583 function TUIControl.findLastFocus (): TUIControl;
1584 var
1585 f: Integer;
1586 begin
1587 result := nil;
1588 if enabled then
1589 begin
1590 for f := High(mChildren) downto 0 do
1591 begin
1592 result := mChildren[f].findLastFocus();
1593 if (result <> nil) then exit;
1594 end;
1595 if (canFocus) then result := self;
1596 end;
1597 end;
1600 function TUIControl.findNextFocus (cur: TUIControl; wrap: Boolean): TUIControl;
1601 var
1602 curHit: Boolean = false;
1604 function checkFocus (ctl: TUIControl): Boolean;
1605 begin
1606 if curHit then
1607 begin
1608 result := (ctl.canFocus);
1609 end
1610 else
1611 begin
1612 curHit := (ctl = cur);
1613 result := false; // don't stop
1614 end;
1615 end;
1617 begin
1618 result := nil;
1619 if enabled then
1620 begin
1621 if not isMyChild(cur) then
1622 begin
1623 result := findFirstFocus();
1624 end
1625 else
1626 begin
1627 result := forEachControl(checkFocus);
1628 if (result = nil) and (wrap) then result := findFirstFocus();
1629 end;
1630 end;
1631 end;
1634 function TUIControl.findPrevFocus (cur: TUIControl; wrap: Boolean): TUIControl;
1635 var
1636 lastCtl: TUIControl = nil;
1638 function checkFocus (ctl: TUIControl): Boolean;
1639 begin
1640 if (ctl = cur) then
1641 begin
1642 result := true;
1643 end
1644 else
1645 begin
1646 result := false;
1647 if (ctl.canFocus) then lastCtl := ctl;
1648 end;
1649 end;
1651 begin
1652 result := nil;
1653 if enabled then
1654 begin
1655 if not isMyChild(cur) then
1656 begin
1657 result := findLastFocus();
1658 end
1659 else
1660 begin
1661 forEachControl(checkFocus);
1662 if (lastCtl = nil) and (wrap) then lastCtl := findLastFocus();
1663 result := lastCtl;
1664 //if (lastCtl <> nil) then writeln('ctl<', lastCtl.className, '>: {', lastCtl.id, '}');
1665 end;
1666 end;
1667 end;
1670 function TUIControl.findDefaulControl (): TUIControl;
1671 var
1672 ctl: TUIControl;
1673 begin
1674 if (enabled) then
1675 begin
1676 if (mDefault) then begin result := self; exit; end;
1677 for ctl in mChildren do
1678 begin
1679 result := ctl.findDefaulControl();
1680 if (result <> nil) then exit;
1681 end;
1682 end;
1683 result := nil;
1684 end;
1686 function TUIControl.findCancelControl (): TUIControl;
1687 var
1688 ctl: TUIControl;
1689 begin
1690 if (enabled) then
1691 begin
1692 if (mCancel) then begin result := self; exit; end;
1693 for ctl in mChildren do
1694 begin
1695 result := ctl.findCancelControl();
1696 if (result <> nil) then exit;
1697 end;
1698 end;
1699 result := nil;
1700 end;
1703 function TUIControl.findControlById (const aid: AnsiString): TUIControl;
1704 var
1705 ctl: TUIControl;
1706 begin
1707 if (strEquCI1251(aid, mId)) then begin result := self; exit; end;
1708 for ctl in mChildren do
1709 begin
1710 result := ctl.findControlById(aid);
1711 if (result <> nil) then exit;
1712 end;
1713 result := nil;
1714 end;
1717 procedure TUIControl.appendChild (ctl: TUIControl);
1718 begin
1719 if (ctl = nil) then exit;
1720 if (ctl.mParent <> nil) then exit;
1721 SetLength(mChildren, Length(mChildren)+1);
1722 mChildren[High(mChildren)] := ctl;
1723 ctl.mParent := self;
1724 Inc(ctl.mX, mFrameWidth);
1725 Inc(ctl.mY, mFrameHeight);
1726 if (ctl.mWidth > 0) and (ctl.mHeight > 0) and
1727 (ctl.mX+ctl.mWidth > mFrameWidth) and (ctl.mY+ctl.mHeight > mFrameHeight) then
1728 begin
1729 if (mWidth+mFrameWidth < ctl.mX+ctl.mWidth) then mWidth := ctl.mX+ctl.mWidth+mFrameWidth;
1730 if (mHeight+mFrameHeight < ctl.mY+ctl.mHeight) then mHeight := ctl.mY+ctl.mHeight+mFrameHeight;
1731 end;
1732 end;
1735 function TUIControl.setActionCBFor (const aid: AnsiString; cb: TActionCB): TActionCB;
1736 var
1737 ctl: TUIControl;
1738 begin
1739 ctl := self[aid];
1740 if (ctl <> nil) then
1741 begin
1742 result := ctl.actionCB;
1743 ctl.actionCB := cb;
1744 end
1745 else
1746 begin
1747 result := nil;
1748 end;
1749 end;
1752 function TUIControl.forEachChildren (cb: TCtlEnumCB): TUIControl;
1753 var
1754 ctl: TUIControl;
1755 begin
1756 result := nil;
1757 if (not assigned(cb)) then exit;
1758 for ctl in mChildren do
1759 begin
1760 if cb(ctl) then begin result := ctl; exit; end;
1761 end;
1762 end;
1765 function TUIControl.forEachControl (cb: TCtlEnumCB; includeSelf: Boolean=true): TUIControl;
1767 function forChildren (p: TUIControl; incSelf: Boolean): TUIControl;
1768 var
1769 ctl: TUIControl;
1770 begin
1771 result := nil;
1772 if (p = nil) then exit;
1773 if (incSelf) and (cb(p)) then begin result := p; exit; end;
1774 for ctl in p.mChildren do
1775 begin
1776 result := forChildren(ctl, true);
1777 if (result <> nil) then break;
1778 end;
1779 end;
1781 begin
1782 result := nil;
1783 if (not assigned(cb)) then exit;
1784 result := forChildren(self, includeSelf);
1785 end;
1788 procedure TUIControl.close (); // this closes *top-level* control
1789 var
1790 ctl: TUIControl;
1791 begin
1792 ctl := topLevel;
1793 uiRemoveWindow(ctl);
1794 if (ctl is TUITopWindow) and (TUITopWindow(ctl).mFreeOnClose) then scheduleKill(ctl); // just in case
1795 end;
1798 procedure TUIControl.doAction ();
1799 begin
1800 if assigned(actionCB) then actionCB(self);
1801 end;
1804 // ////////////////////////////////////////////////////////////////////////// //
1805 procedure TUIControl.setScissorGLInternal (x, y, w, h: Integer);
1806 begin
1807 if not scallowed then exit;
1808 x := trunc(x*gh_ui_scale);
1809 y := trunc(y*gh_ui_scale);
1810 w := trunc(w*gh_ui_scale);
1811 h := trunc(h*gh_ui_scale);
1812 scis.combineRect(x, y, w, h);
1813 end;
1815 procedure TUIControl.setScissor (lx, ly, lw, lh: Integer);
1816 var
1817 gx, gy, wdt, hgt, cgx, cgy: Integer;
1818 begin
1819 if not scallowed then exit;
1821 if not intersectRect(lx, ly, lw, lh, 0, 0, mWidth, mHeight) then
1822 begin
1823 scis.combineRect(0, 0, 0, 0);
1824 exit;
1825 end;
1827 getDrawRect(gx, gy, wdt, hgt);
1828 toGlobal(lx, ly, cgx, cgy);
1829 if not intersectRect(gx, gy, wdt, hgt, cgx, cgy, lw, lh) then
1830 begin
1831 scis.combineRect(0, 0, 0, 0);
1832 exit;
1833 end;
1835 setScissorGLInternal(gx, gy, wdt, hgt);
1836 end;
1838 procedure TUIControl.resetScissor (fullArea: Boolean); inline;
1839 begin
1840 if not scallowed then exit;
1841 if (fullArea) then
1842 begin
1843 setScissor(0, 0, mWidth, mHeight);
1844 end
1845 else
1846 begin
1847 setScissor(mFrameWidth, mFrameHeight, mWidth-mFrameWidth*2, mHeight-mFrameHeight*2);
1848 end;
1849 end;
1852 // ////////////////////////////////////////////////////////////////////////// //
1853 procedure TUIControl.draw ();
1854 var
1855 f: Integer;
1856 gx, gy: Integer;
1857 begin
1858 if (mWidth < 1) or (mHeight < 1) then exit;
1859 toGlobal(0, 0, gx, gy);
1861 scis.save(true); // scissoring enabled
1862 try
1863 scallowed := true;
1864 resetScissor(true); // full area
1865 drawControl(gx, gy);
1866 resetScissor(false); // client area
1867 for f := 0 to High(mChildren) do mChildren[f].draw();
1868 resetScissor(true); // full area
1869 drawControlPost(gx, gy);
1870 finally
1871 scis.restore();
1872 scallowed := false;
1873 end;
1874 end;
1876 procedure TUIControl.drawControl (gx, gy: Integer);
1877 begin
1878 //if (mParent = nil) then darkenRect(gx, gy, mWidth, mHeight, 64);
1879 end;
1881 procedure TUIControl.drawControlPost (gx, gy: Integer);
1882 begin
1883 // shadow
1884 if mDrawShadow and (mWidth > 0) and (mHeight > 0) then
1885 begin
1886 setScissorGLInternal(gx+8, gy+8, mWidth, mHeight);
1887 darkenRect(gx+mWidth, gy+8, 8, mHeight, 128);
1888 darkenRect(gx+8, gy+mHeight, mWidth-8, 8, 128);
1889 end;
1890 end;
1893 // ////////////////////////////////////////////////////////////////////////// //
1894 procedure TUIControl.mouseEvent (var ev: THMouseEvent);
1895 var
1896 ctl: TUIControl;
1897 begin
1898 if (not enabled) then exit;
1899 if (mWidth < 1) or (mHeight < 1) then exit;
1900 ctl := controlAtXY(ev.x, ev.y);
1901 if (ctl = nil) then exit;
1902 if (ctl.canFocus) and (ev.press) then
1903 begin
1904 if (ctl <> topLevel.mFocused) then ctl.setFocused(true);
1905 uiGrabCtl := ctl;
1906 end;
1907 if (ctl <> self) then ctl.mouseEvent(ev);
1908 //ev.eat();
1909 end;
1912 procedure TUIControl.keyEvent (var ev: THKeyEvent);
1914 function doPreKey (ctl: TUIControl): Boolean;
1915 begin
1916 if (not ctl.enabled) then begin result := false; exit; end;
1917 ctl.keyEventPre(ev);
1918 result := (ev.eaten) or (ev.cancelled); // stop if event was consumed
1919 end;
1921 function doPostKey (ctl: TUIControl): Boolean;
1922 begin
1923 if (not ctl.enabled) then begin result := false; exit; end;
1924 ctl.keyEventPost(ev);
1925 result := (ev.eaten) or (ev.cancelled); // stop if event was consumed
1926 end;
1928 var
1929 ctl: TUIControl;
1930 begin
1931 if (not enabled) then exit;
1932 if (ev.eaten) or (ev.cancelled) then exit;
1933 // call pre-key
1934 if (mParent = nil) then
1935 begin
1936 forEachControl(doPreKey);
1937 if (ev.eaten) or (ev.cancelled) then exit;
1938 end;
1939 // focused control should process keyboard first
1940 if (topLevel.mFocused <> self) and isMyChild(topLevel.mFocused) and (topLevel.mFocused.enabled) then
1941 begin
1942 // bubble keyboard event
1943 ctl := topLevel.mFocused;
1944 while (ctl <> nil) and (ctl <> self) do
1945 begin
1946 ctl.keyEvent(ev);
1947 if (ev.eaten) or (ev.cancelled) then exit;
1948 ctl := ctl.mParent;
1949 end;
1950 end;
1951 // for top-level controls
1952 if (mParent = nil) then
1953 begin
1954 if (ev = 'S-Tab') then
1955 begin
1956 ctl := findPrevFocus(mFocused, true);
1957 if (ctl <> nil) and (ctl <> mFocused) then ctl.setFocused(true);
1958 ev.eat();
1959 exit;
1960 end;
1961 if (ev = 'Tab') then
1962 begin
1963 ctl := findNextFocus(mFocused, true);
1964 if (ctl <> nil) and (ctl <> mFocused) then ctl.setFocused(true);
1965 ev.eat();
1966 exit;
1967 end;
1968 if (ev = 'Enter') or (ev = 'C-Enter') then
1969 begin
1970 ctl := findDefaulControl();
1971 if (ctl <> nil) then
1972 begin
1973 ev.eat();
1974 ctl.doAction();
1975 exit;
1976 end;
1977 end;
1978 if (ev = 'Escape') then
1979 begin
1980 ctl := findCancelControl();
1981 if (ctl <> nil) then
1982 begin
1983 ev.eat();
1984 ctl.doAction();
1985 exit;
1986 end;
1987 end;
1988 if mEscClose and (ev = 'Escape') then
1989 begin
1990 if (not assigned(closeRequestCB)) or (closeRequestCB(self)) then
1991 begin
1992 uiRemoveWindow(self);
1993 end;
1994 ev.eat();
1995 exit;
1996 end;
1997 // call post-keys
1998 if (ev.eaten) or (ev.cancelled) then exit;
1999 forEachControl(doPostKey);
2000 end;
2001 end;
2004 procedure TUIControl.keyEventPre (var ev: THKeyEvent);
2005 begin
2006 end;
2009 procedure TUIControl.keyEventPost (var ev: THKeyEvent);
2010 begin
2011 end;
2014 // ////////////////////////////////////////////////////////////////////////// //
2015 constructor TUITopWindow.Create (const atitle: AnsiString);
2016 begin
2017 inherited Create();
2018 mTitle := atitle;
2019 end;
2022 procedure TUITopWindow.AfterConstruction ();
2023 begin
2024 inherited;
2025 mFrameWidth := 8;
2026 mFrameHeight := 8;
2027 if (mWidth < mFrameWidth*2+3*8) then mWidth := mFrameWidth*2+3*8;
2028 if (mHeight < mFrameHeight*2) then mHeight := mFrameHeight*2;
2029 if (Length(mTitle) > 0) then
2030 begin
2031 if (mWidth < Length(mTitle)*8+mFrameWidth*2+3*8) then mWidth := Length(mTitle)*8+mFrameWidth*2+3*8;
2032 end;
2033 mCanFocus := false;
2034 mDragScroll := TXMode.None;
2035 mDrawShadow := true;
2036 mWaitingClose := false;
2037 mInClose := false;
2038 closeCB := nil;
2039 mCtl4Style := 'window';
2040 end;
2043 function TUITopWindow.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2044 begin
2045 if (strEquCI1251(prname, 'title')) or (strEquCI1251(prname, 'caption')) then
2046 begin
2047 mTitle := par.expectIdOrStr(true);
2048 result := true;
2049 exit;
2050 end;
2051 if (strEquCI1251(prname, 'children')) then
2052 begin
2053 parseChildren(par);
2054 result := true;
2055 exit;
2056 end;
2057 if (strEquCI1251(prname, 'position')) then
2058 begin
2059 if (par.eatIdOrStrCI('default')) then mDoCenter := false
2060 else if (par.eatIdOrStrCI('center')) then mDoCenter := true
2061 else par.error('`center` or `default` expected');
2062 result := true;
2063 exit;
2064 end;
2065 if (parseOrientation(prname, par)) then begin result := true; exit; end;
2066 result := inherited parseProperty(prname, par);
2067 end;
2070 procedure TUITopWindow.centerInScreen ();
2071 begin
2072 if (mWidth > 0) and (mHeight > 0) then
2073 begin
2074 mX := trunc((gScrWidth/gh_ui_scale-mWidth)/2);
2075 mY := trunc((gScrHeight/gh_ui_scale-mHeight)/2);
2076 end;
2077 end;
2080 procedure TUITopWindow.drawControl (gx, gy: Integer);
2081 begin
2082 fillRect(gx, gy, mWidth, mHeight, mBackColor[getColorIndex]);
2083 end;
2086 procedure TUITopWindow.drawControlPost (gx, gy: Integer);
2087 var
2088 cidx: Integer;
2089 tx, hgt, sbhgt: Integer;
2090 begin
2091 cidx := getColorIndex;
2092 if (mDragScroll = TXMode.Drag) then
2093 begin
2094 drawRectUI(gx+4, gy+4, mWidth-8, mHeight-8, mFrameColor[cidx]);
2095 end
2096 else
2097 begin
2098 drawRectUI(gx+3, gy+3, mWidth-6, mHeight-6, mFrameColor[cidx]);
2099 drawRectUI(gx+5, gy+5, mWidth-10, mHeight-10, mFrameColor[cidx]);
2100 // vertical scroll bar
2101 hgt := mHeight-mFrameHeight*2;
2102 if (hgt > 0) and (mFullSize.h > hgt) then
2103 begin
2104 //writeln(mTitle, ': height=', mHeight-mFrameHeight*2, '; fullsize=', mFullSize.toString);
2105 sbhgt := mHeight-mFrameHeight*2+2;
2106 fillRect(gx+mWidth-mFrameWidth+1, gy+7, mFrameWidth-3, sbhgt, mFrameColor[cidx]);
2107 hgt += mScrollY;
2108 if (hgt > mFullSize.h) then hgt := mFullSize.h;
2109 hgt := sbhgt*hgt div mFullSize.h;
2110 if (hgt > 0) then
2111 begin
2112 setScissor(mWidth-mFrameWidth+1, 7, mFrameWidth-3, sbhgt);
2113 darkenRect(gx+mWidth-mFrameWidth+1, gy+7+hgt, mFrameWidth-3, sbhgt, 128);
2114 end;
2115 end;
2116 // frame icon
2117 setScissor(mFrameWidth, 0, 3*8, 8);
2118 fillRect(gx+mFrameWidth, gy, 3*8, 8, mBackColor[cidx]);
2119 drawText8(gx+mFrameWidth, gy, '[ ]', mFrameColor[cidx]);
2120 if mInClose then drawText8(gx+mFrameWidth+7, gy, '#', mFrameIconColor[cidx])
2121 else drawText8(gx+mFrameWidth+7, gy, '*', mFrameIconColor[cidx]);
2122 end;
2123 // title
2124 if (Length(mTitle) > 0) then
2125 begin
2126 setScissor(mFrameWidth+3*8, 0, mWidth-mFrameWidth*2-3*8, 8);
2127 tx := (gx+3*8)+((mWidth-3*8)-Length(mTitle)*8) div 2;
2128 fillRect(tx-3, gy, Length(mTitle)*8+3+2, 8, mBackColor[cidx]);
2129 drawText8(tx, gy, mTitle, mFrameTextColor[cidx]);
2130 end;
2131 // shadow
2132 inherited drawControlPost(gx, gy);
2133 end;
2136 procedure TUITopWindow.activated ();
2137 begin
2138 if (mFocused = nil) or (mFocused = self) then
2139 begin
2140 mFocused := findFirstFocus();
2141 end;
2142 if (mFocused <> nil) and (mFocused <> self) then mFocused.activated();
2143 inherited;
2144 end;
2147 procedure TUITopWindow.blurred ();
2148 begin
2149 mDragScroll := TXMode.None;
2150 mWaitingClose := false;
2151 mInClose := false;
2152 if (mFocused <> nil) and (mFocused <> self) then mFocused.blurred();
2153 inherited;
2154 end;
2157 procedure TUITopWindow.keyEvent (var ev: THKeyEvent);
2158 begin
2159 inherited keyEvent(ev);
2160 if (ev.eaten) or (ev.cancelled) or (not enabled) {or (not getFocused)} then exit;
2161 if (ev = 'M-F3') then
2162 begin
2163 if (not assigned(closeRequestCB)) or (closeRequestCB(self)) then
2164 begin
2165 uiRemoveWindow(self);
2166 end;
2167 ev.eat();
2168 exit;
2169 end;
2170 end;
2173 procedure TUITopWindow.mouseEvent (var ev: THMouseEvent);
2174 var
2175 lx, ly: Integer;
2176 hgt, sbhgt: Integer;
2177 begin
2178 if (not enabled) then exit;
2179 if (mWidth < 1) or (mHeight < 1) then exit;
2181 if (mDragScroll = TXMode.Drag) then
2182 begin
2183 mX += ev.x-mDragStartX;
2184 mY += ev.y-mDragStartY;
2185 mDragStartX := ev.x;
2186 mDragStartY := ev.y;
2187 if (ev.release) and (ev.but = ev.Left) then mDragScroll := TXMode.None;
2188 ev.eat();
2189 exit;
2190 end;
2192 if (mDragScroll = TXMode.Scroll) then
2193 begin
2194 // check for vertical scrollbar
2195 ly := ev.y-mY;
2196 if (ly < 7) then
2197 begin
2198 mScrollY := 0;
2199 end
2200 else
2201 begin
2202 sbhgt := mHeight-mFrameHeight*2+2;
2203 hgt := mHeight-mFrameHeight*2;
2204 if (hgt > 0) and (mFullSize.h > hgt) then
2205 begin
2206 hgt := (mFullSize.h*(ly-7) div (sbhgt-1))-(mHeight-mFrameHeight*2);
2207 mScrollY := nmax(0, hgt);
2208 hgt := mHeight-mFrameHeight*2;
2209 if (mScrollY+hgt > mFullSize.h) then mScrollY := nmax(0, mFullSize.h-hgt);
2210 end;
2211 end;
2212 if (ev.release) and (ev.but = ev.Left) then mDragScroll := TXMode.None;
2213 ev.eat();
2214 exit;
2215 end;
2217 if toLocal(ev.x, ev.y, lx, ly) then
2218 begin
2219 if (ev.press) then
2220 begin
2221 if (ly < 8) then
2222 begin
2223 uiGrabCtl := self;
2224 if (lx >= mFrameWidth) and (lx < mFrameWidth+3*8) then
2225 begin
2226 //uiRemoveWindow(self);
2227 mWaitingClose := true;
2228 mInClose := true;
2229 end
2230 else
2231 begin
2232 mDragScroll := TXMode.Drag;
2233 mDragStartX := ev.x;
2234 mDragStartY := ev.y;
2235 end;
2236 ev.eat();
2237 exit;
2238 end;
2239 // check for vertical scrollbar
2240 if (lx >= mWidth-mFrameWidth+1) and (ly >= 7) and (ly < mHeight-mFrameHeight+1) then
2241 begin
2242 sbhgt := mHeight-mFrameHeight*2+2;
2243 hgt := mHeight-mFrameHeight*2;
2244 if (hgt > 0) and (mFullSize.h > hgt) then
2245 begin
2246 hgt := (mFullSize.h*(ly-7) div (sbhgt-1))-(mHeight-mFrameHeight*2);
2247 mScrollY := nmax(0, hgt);
2248 uiGrabCtl := self;
2249 mDragScroll := TXMode.Scroll;
2250 ev.eat();
2251 exit;
2252 end;
2253 end;
2254 // drag
2255 if (lx < mFrameWidth) or (lx >= mWidth-mFrameWidth) or (ly >= mHeight-mFrameHeight) then
2256 begin
2257 uiGrabCtl := self;
2258 mDragScroll := TXMode.Drag;
2259 mDragStartX := ev.x;
2260 mDragStartY := ev.y;
2261 ev.eat();
2262 exit;
2263 end;
2264 end;
2266 if (ev.release) then
2267 begin
2268 if mWaitingClose then
2269 begin
2270 if (lx >= mFrameWidth) and (lx < mFrameWidth+3*8) then
2271 begin
2272 if (not assigned(closeRequestCB)) or (closeRequestCB(self)) then
2273 begin
2274 uiRemoveWindow(self);
2275 end;
2276 end;
2277 mWaitingClose := false;
2278 mInClose := false;
2279 ev.eat();
2280 exit;
2281 end;
2282 end;
2284 if (ev.motion) then
2285 begin
2286 if mWaitingClose then
2287 begin
2288 mInClose := (lx >= mFrameWidth) and (lx < mFrameWidth+3*8);
2289 ev.eat();
2290 exit;
2291 end;
2292 end;
2294 inherited mouseEvent(ev);
2295 end
2296 else
2297 begin
2298 mInClose := false;
2299 if (not ev.motion) and (mWaitingClose) then begin ev.eat(); mWaitingClose := false; exit; end;
2300 end;
2301 end;
2304 // ////////////////////////////////////////////////////////////////////////// //
2305 constructor TUIBox.Create (ahoriz: Boolean);
2306 begin
2307 inherited Create();
2308 mHoriz := ahoriz;
2309 end;
2312 procedure TUIBox.AfterConstruction ();
2313 begin
2314 inherited;
2315 mCanFocus := false;
2316 mHAlign := -1; // left
2317 mCtl4Style := 'box';
2318 end;
2321 procedure TUIBox.setCaption (const acap: AnsiString);
2322 begin
2323 mCaption := acap;
2324 mDefSize := TLaySize.Create(Length(mCaption)*8+3, 8);
2325 end;
2328 procedure TUIBox.setHasFrame (v: Boolean);
2329 begin
2330 mHasFrame := v;
2331 if (mHasFrame) then begin mFrameWidth := 8; mFrameHeight := 8; end else begin mFrameWidth := 0; mFrameHeight := 0; end;
2332 end;
2335 function TUIBox.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2336 begin
2337 if (parseOrientation(prname, par)) then begin result := true; exit; end;
2338 if (strEquCI1251(prname, 'frame')) then
2339 begin
2340 setHasFrame(parseBool(par));
2341 result := true;
2342 exit;
2343 end;
2344 if (strEquCI1251(prname, 'title')) or (strEquCI1251(prname, 'caption')) then
2345 begin
2346 setCaption(par.expectIdOrStr(true));
2347 result := true;
2348 exit;
2349 end;
2350 if (strEquCI1251(prname, 'textalign')) or (strEquCI1251(prname, 'text-align')) then
2351 begin
2352 mHAlign := parseHAlign(par);
2353 result := true;
2354 exit;
2355 end;
2356 if (strEquCI1251(prname, 'children')) then
2357 begin
2358 parseChildren(par);
2359 result := true;
2360 exit;
2361 end;
2362 result := inherited parseProperty(prname, par);
2363 end;
2366 procedure TUIBox.drawControl (gx, gy: Integer);
2367 var
2368 cidx: Integer;
2369 xpos: Integer;
2370 begin
2371 cidx := getColorIndex;
2372 fillRect(gx, gy, mWidth, mHeight, mBackColor[cidx]);
2373 if mHasFrame then
2374 begin
2375 // draw frame
2376 drawRectUI(gx+3, gy+3, mWidth-6, mHeight-6, mFrameColor[cidx]);
2377 end;
2378 // draw caption
2379 if (Length(mCaption) > 0) then
2380 begin
2381 if (mHAlign < 0) then xpos := 3
2382 else if (mHAlign > 0) then xpos := mWidth-mFrameWidth*2-Length(mCaption)*8
2383 else xpos := (mWidth-mFrameWidth*2-Length(mCaption)*8) div 2;
2384 xpos += gx+mFrameWidth;
2386 setScissor(mFrameWidth+1, 0, mWidth-mFrameWidth-2, 8);
2387 if mHasFrame then fillRect(xpos-3, gy, Length(mCaption)*8+4, 8, mBackColor[cidx]);
2388 drawText8(xpos, gy, mCaption, mFrameTextColor[cidx]);
2389 end;
2390 end;
2393 procedure TUIBox.mouseEvent (var ev: THMouseEvent);
2394 var
2395 lx, ly: Integer;
2396 begin
2397 inherited mouseEvent(ev);
2398 if (not ev.eaten) and (not ev.cancelled) and (enabled) and toLocal(ev.x, ev.y, lx, ly) then
2399 begin
2400 ev.eat();
2401 end;
2402 end;
2405 procedure TUIBox.keyEvent (var ev: THKeyEvent);
2406 var
2407 dir: Integer = 0;
2408 cur, ctl: TUIControl;
2409 begin
2410 inherited keyEvent(ev);
2411 if (ev.eaten) or (ev.cancelled) or (not ev.press) or (not enabled) or (not getActive) then exit;
2412 if (Length(mChildren) = 0) then exit;
2413 if (mHoriz) and (ev = 'Left') then dir := -1
2414 else if (mHoriz) and (ev = 'Right') then dir := 1
2415 else if (not mHoriz) and (ev = 'Up') then dir := -1
2416 else if (not mHoriz) and (ev = 'Down') then dir := 1;
2417 if (dir = 0) then exit;
2418 ev.eat();
2419 cur := topLevel.mFocused;
2420 while (cur <> nil) and (cur.mParent <> self) do cur := cur.mParent;
2421 //if (cur = nil) then writeln('CUR: nil') else writeln('CUR: ', cur.className, '#', cur.id);
2422 if (dir < 0) then ctl := findPrevFocus(cur, true) else ctl := findNextFocus(cur, true);
2423 //if (ctl = nil) then writeln('CTL: nil') else writeln('CTL: ', ctl.className, '#', ctl.id);
2424 if (ctl <> nil) and (ctl <> self) then
2425 begin
2426 ctl.focused := true;
2427 end;
2428 end;
2431 // ////////////////////////////////////////////////////////////////////////// //
2432 constructor TUIHBox.Create ();
2433 begin
2434 end;
2437 procedure TUIHBox.AfterConstruction ();
2438 begin
2439 inherited;
2440 mHoriz := true;
2441 end;
2444 // ////////////////////////////////////////////////////////////////////////// //
2445 constructor TUIVBox.Create ();
2446 begin
2447 end;
2450 procedure TUIVBox.AfterConstruction ();
2451 begin
2452 inherited;
2453 mHoriz := false;
2454 writeln('VBOX: ', canFocus, ':', enabled);
2455 end;
2458 // ////////////////////////////////////////////////////////////////////////// //
2459 procedure TUISpan.AfterConstruction ();
2460 begin
2461 inherited;
2462 mExpand := true;
2463 mCanFocus := false;
2464 mCtl4Style := 'span';
2465 end;
2468 function TUISpan.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2469 begin
2470 if (parseOrientation(prname, par)) then begin result := true; exit; end;
2471 result := inherited parseProperty(prname, par);
2472 end;
2475 procedure TUISpan.drawControl (gx, gy: Integer);
2476 begin
2477 end;
2480 // ////////////////////////////////////////////////////////////////////// //
2481 procedure TUILine.AfterConstruction ();
2482 begin
2483 inherited;
2484 mCanFocus := false;
2485 mExpand := true;
2486 mCanFocus := false;
2487 mCtl4Style := 'line';
2488 end;
2491 function TUILine.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2492 begin
2493 if (parseOrientation(prname, par)) then begin result := true; exit; end;
2494 result := inherited parseProperty(prname, par);
2495 end;
2498 procedure TUILine.drawControl (gx, gy: Integer);
2499 var
2500 cidx: Integer;
2501 begin
2502 cidx := getColorIndex;
2503 if mHoriz then
2504 begin
2505 drawHLine(gx, gy+(mHeight div 2), mWidth, mTextColor[cidx]);
2506 end
2507 else
2508 begin
2509 drawVLine(gx+(mWidth div 2), gy, mHeight, mTextColor[cidx]);
2510 end;
2511 end;
2514 // ////////////////////////////////////////////////////////////////////////// //
2515 procedure TUIHLine.AfterConstruction ();
2516 begin
2517 inherited;
2518 mHoriz := true;
2519 mDefSize.h := 7;
2520 end;
2523 // ////////////////////////////////////////////////////////////////////////// //
2524 procedure TUIVLine.AfterConstruction ();
2525 begin
2526 inherited;
2527 mHoriz := false;
2528 mDefSize.w := 7;
2529 end;
2532 // ////////////////////////////////////////////////////////////////////////// //
2533 procedure TUIStaticText.AfterConstruction ();
2534 begin
2535 inherited;
2536 mCanFocus := false;
2537 mHAlign := -1;
2538 mVAlign := 0;
2539 mHoriz := true; // nobody cares
2540 mHeader := false;
2541 mLine := false;
2542 mDefSize.h := 8;
2543 mCtl4Style := 'static';
2544 end;
2547 procedure TUIStaticText.setText (const atext: AnsiString);
2548 begin
2549 mText := atext;
2550 mDefSize := TLaySize.Create(Length(mText)*8, 8);
2551 end;
2554 function TUIStaticText.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2555 begin
2556 if (strEquCI1251(prname, 'title')) or (strEquCI1251(prname, 'caption')) or (strEquCI1251(prname, 'text')) then
2557 begin
2558 setText(par.expectIdOrStr(true));
2559 result := true;
2560 exit;
2561 end;
2562 if (strEquCI1251(prname, 'textalign')) or (strEquCI1251(prname, 'text-align')) then
2563 begin
2564 parseTextAlign(par, mHAlign, mVAlign);
2565 result := true;
2566 exit;
2567 end;
2568 if (strEquCI1251(prname, 'header')) then
2569 begin
2570 mHeader := true;
2571 result := true;
2572 exit;
2573 end;
2574 if (strEquCI1251(prname, 'line')) then
2575 begin
2576 mLine := true;
2577 result := true;
2578 exit;
2579 end;
2580 result := inherited parseProperty(prname, par);
2581 end;
2584 procedure TUIStaticText.drawControl (gx, gy: Integer);
2585 var
2586 xpos, ypos: Integer;
2587 cidx: Integer;
2588 clr: TGxRGBA;
2589 begin
2590 cidx := getColorIndex;
2591 fillRect(gx, gy, mWidth, mHeight, mBackColor[cidx]);
2593 if (mHAlign < 0) then xpos := 0
2594 else if (mHAlign > 0) then xpos := mWidth-Length(mText)*8
2595 else xpos := (mWidth-Length(mText)*8) div 2;
2597 if (Length(mText) > 0) then
2598 begin
2599 if (mHeader) then clr := mFrameTextColor[cidx] else clr := mTextColor[cidx];
2601 if (mVAlign < 0) then ypos := 0
2602 else if (mVAlign > 0) then ypos := mHeight-8
2603 else ypos := (mHeight-8) div 2;
2605 drawText8(gx+xpos, gy+ypos, mText, clr);
2606 end;
2608 if (mLine) then
2609 begin
2610 if (mHeader) then clr := mFrameColor[cidx] else clr := mTextColor[cidx];
2612 if (mVAlign < 0) then ypos := 0
2613 else if (mVAlign > 0) then ypos := mHeight-1
2614 else ypos := (mHeight div 2);
2615 ypos += gy;
2617 if (Length(mText) = 0) then
2618 begin
2619 drawHLine(gx, ypos, mWidth, clr);
2620 end
2621 else
2622 begin
2623 drawHLine(gx, ypos, xpos-1, clr);
2624 drawHLine(gx+xpos+Length(mText)*8, ypos, mWidth, clr);
2625 end;
2626 end;
2627 end;
2630 // ////////////////////////////////////////////////////////////////////////// //
2631 procedure TUITextLabel.AfterConstruction ();
2632 begin
2633 inherited;
2634 mHAlign := -1;
2635 mVAlign := 0;
2636 mCanFocus := false;
2637 mDefSize := TLaySize.Create(Length(mText)*8, 8);
2638 mCtl4Style := 'label';
2639 mLinkId := '';
2640 end;
2643 procedure TUITextLabel.cacheStyle (root: TUIStyle);
2644 begin
2645 inherited cacheStyle(root);
2646 // active
2647 mHotColor[ClrIdxActive] := root.get('hot-color', 'active', mCtl4Style).asRGBADef(TGxRGBA.Create(0, 128, 0));
2648 // disabled
2649 mHotColor[ClrIdxDisabled] := root.get('hot-color', 'disabled', mCtl4Style).asRGBADef(TGxRGBA.Create(0, 64, 0));
2650 // inactive
2651 mHotColor[ClrIdxInactive] := root.get('hot-color', 'inactive', mCtl4Style).asRGBADef(TGxRGBA.Create(0, 64, 0));
2652 end;
2655 procedure TUITextLabel.setText (const s: AnsiString);
2656 var
2657 f: Integer;
2658 begin
2659 mText := '';
2660 mHotChar := #0;
2661 mHotOfs := 0;
2662 f := 1;
2663 while (f <= Length(s)) do
2664 begin
2665 if (s[f] = '\\') then
2666 begin
2667 Inc(f);
2668 if (f <= Length(s)) then mText += s[f];
2669 Inc(f);
2670 end
2671 else if (s[f] = '~') then
2672 begin
2673 Inc(f);
2674 if (f <= Length(s)) then
2675 begin
2676 if (mHotChar = #0) then
2677 begin
2678 mHotChar := s[f];
2679 mHotOfs := Length(mText)*8;
2680 end;
2681 mText += s[f];
2682 end;
2683 Inc(f);
2684 end
2685 else
2686 begin
2687 mText += s[f];
2688 Inc(f);
2689 end;
2690 end;
2691 mDefSize := TLaySize.Create(Length(mText)*8, 8);
2692 end;
2695 function TUITextLabel.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2696 begin
2697 if (strEquCI1251(prname, 'title')) or (strEquCI1251(prname, 'caption')) or (strEquCI1251(prname, 'text')) then
2698 begin
2699 setText(par.expectIdOrStr(true));
2700 result := true;
2701 exit;
2702 end;
2703 if (strEquCI1251(prname, 'link')) then
2704 begin
2705 mLinkId := par.expectIdOrStr(true);
2706 result := true;
2707 exit;
2708 end;
2709 if (strEquCI1251(prname, 'textalign')) or (strEquCI1251(prname, 'text-align')) then
2710 begin
2711 parseTextAlign(par, mHAlign, mVAlign);
2712 result := true;
2713 exit;
2714 end;
2715 result := inherited parseProperty(prname, par);
2716 end;
2719 procedure TUITextLabel.drawControl (gx, gy: Integer);
2720 var
2721 xpos, ypos: Integer;
2722 cidx: Integer;
2723 begin
2724 cidx := getColorIndex;
2725 fillRect(gx, gy, mWidth, mHeight, mBackColor[cidx]);
2726 if (Length(mText) > 0) then
2727 begin
2728 if (mHAlign < 0) then xpos := 0
2729 else if (mHAlign > 0) then xpos := mWidth-Length(mText)*8
2730 else xpos := (mWidth-Length(mText)*8) div 2;
2732 if (mVAlign < 0) then ypos := 0
2733 else if (mVAlign > 0) then ypos := mHeight-8
2734 else ypos := (mHeight-8) div 2;
2736 drawText8(gx+xpos, gy+ypos, mText, mTextColor[cidx]);
2738 if (Length(mLinkId) > 0) and (mHotChar <> #0) and (mHotChar <> ' ') then
2739 begin
2740 drawText8(gx+xpos+8+mHotOfs, gy+ypos, mHotChar, mHotColor[cidx]);
2741 end;
2742 end;
2743 end;
2746 procedure TUITextLabel.mouseEvent (var ev: THMouseEvent);
2747 var
2748 lx, ly: Integer;
2749 begin
2750 inherited mouseEvent(ev);
2751 if (not ev.eaten) and (not ev.cancelled) and (enabled) and toLocal(ev.x, ev.y, lx, ly) then
2752 begin
2753 ev.eat();
2754 end;
2755 end;
2758 procedure TUITextLabel.keyEventPost (var ev: THKeyEvent);
2759 var
2760 ctl: TUIControl;
2761 begin
2762 if (not enabled) then exit;
2763 if (mHotChar = #0) or (Length(mLinkId) = 0) then exit;
2764 if (ev.eaten) or (ev.cancelled) or (not ev.press) then exit;
2765 if (not ev.isHot(mHotChar)) then exit;
2766 ctl := topLevel[mLinkId];
2767 if (ctl <> nil) then
2768 begin
2769 ev.eat();
2770 if (ctl.canFocus) then ctl.focused := true;
2771 end;
2772 end;
2775 // ////////////////////////////////////////////////////////////////////////// //
2776 procedure TUIButton.AfterConstruction ();
2777 begin
2778 inherited;
2779 mHAlign := -1;
2780 mVAlign := 0;
2781 mCanFocus := true;
2782 mDefSize := TLaySize.Create(Length(mText)*8+8, 10);
2783 mCtl4Style := 'button';
2784 end;
2787 procedure TUIButton.setText (const s: AnsiString);
2788 begin
2789 inherited setText(s);
2790 mDefSize := TLaySize.Create(Length(mText)*8+8*2, 10);
2791 end;
2794 procedure TUIButton.drawControl (gx, gy: Integer);
2795 var
2796 xpos, ypos: Integer;
2797 cidx: Integer;
2798 begin
2799 cidx := getColorIndex;
2801 fillRect(gx+1, gy, mWidth-2, mHeight, mBackColor[cidx]);
2802 fillRect(gx, gy+1, 1, mHeight-2, mBackColor[cidx]);
2803 fillRect(gx+mWidth-1, gy+1, 1, mHeight-2, mBackColor[cidx]);
2805 if (Length(mText) > 0) then
2806 begin
2807 if (mHAlign < 0) then xpos := 0
2808 else if (mHAlign > 0) then xpos := mWidth-Length(mText)*8
2809 else xpos := (mWidth-Length(mText)*8) div 2;
2811 if (mVAlign < 0) then ypos := 0
2812 else if (mVAlign > 0) then ypos := mHeight-8
2813 else ypos := (mHeight-8) div 2;
2815 setScissor(8, 0, mWidth-16, mHeight);
2816 drawText8(gx+xpos+8, gy+ypos, mText, mTextColor[cidx]);
2818 if (mHotChar <> #0) and (mHotChar <> ' ') then drawText8(gx+xpos+8+mHotOfs, gy+ypos, mHotChar, mHotColor[cidx]);
2819 end;
2820 end;
2823 procedure TUIButton.mouseEvent (var ev: THMouseEvent);
2824 var
2825 lx, ly: Integer;
2826 begin
2827 inherited mouseEvent(ev);
2828 if (uiGrabCtl = self) then
2829 begin
2830 ev.eat();
2831 if (ev = '-lmb') and focused and toLocal(ev.x, ev.y, lx, ly) then
2832 begin
2833 doAction();
2834 end;
2835 exit;
2836 end;
2837 if (ev.eaten) or (ev.cancelled) or (not enabled) or not focused then exit;
2838 ev.eat();
2839 end;
2842 procedure TUIButton.keyEvent (var ev: THKeyEvent);
2843 begin
2844 inherited keyEvent(ev);
2845 if (not ev.eaten) and (not ev.cancelled) and (enabled) then
2846 begin
2847 if (ev = 'Enter') or (ev = 'Space') then
2848 begin
2849 ev.eat();
2850 doAction();
2851 exit;
2852 end;
2853 end;
2854 end;
2857 procedure TUIButton.keyEventPost (var ev: THKeyEvent);
2858 begin
2859 if (not enabled) then exit;
2860 if (mHotChar = #0) then exit;
2861 if (ev.eaten) or (ev.cancelled) or (not ev.press) then exit;
2862 if (not ev.isHot(mHotChar)) then exit;
2863 if (not canFocus) then exit;
2864 ev.eat();
2865 focused := true;
2866 doAction();
2867 end;
2870 // ////////////////////////////////////////////////////////////////////////// //
2871 procedure TUISwitchBox.AfterConstruction ();
2872 begin
2873 inherited;
2874 mHAlign := -1;
2875 mVAlign := 0;
2876 mCanFocus := true;
2877 mDefSize := TLaySize.Create(Length(mText)*8+8*3, 8);
2878 mCtl4Style := 'switchbox';
2879 mChecked := false;
2880 mBoolVar := @mChecked;
2881 end;
2884 procedure TUISwitchBox.cacheStyle (root: TUIStyle);
2885 begin
2886 inherited cacheStyle(root);
2887 // active
2888 mSwitchColor[ClrIdxActive] := root.get('switch-color', 'active', mCtl4Style).asRGBADef(TGxRGBA.Create(255, 255, 255));
2889 // disabled
2890 mSwitchColor[ClrIdxDisabled] := root.get('switch-color', 'disabled', mCtl4Style).asRGBADef(TGxRGBA.Create(255, 255, 255));
2891 // inactive
2892 mSwitchColor[ClrIdxInactive] := root.get('switch-color', 'inactive', mCtl4Style).asRGBADef(TGxRGBA.Create(255, 255, 255));
2893 end;
2896 procedure TUISwitchBox.setText (const s: AnsiString);
2897 begin
2898 inherited setText(s);
2899 mDefSize := TLaySize.Create(Length(mText)*8+8*3, 8);
2900 end;
2903 function TUISwitchBox.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
2904 begin
2905 if (strEquCI1251(prname, 'checked')) then
2906 begin
2907 result := true;
2908 setChecked(true);
2909 exit;
2910 end;
2911 result := inherited parseProperty(prname, par);
2912 end;
2915 function TUISwitchBox.getChecked (): Boolean;
2916 begin
2917 if (mBoolVar <> nil) then result := mBoolVar^ else result := false;
2918 end;
2921 procedure TUISwitchBox.setVar (pvar: PBoolean);
2922 begin
2923 if (pvar = nil) then pvar := @mChecked;
2924 if (pvar <> mBoolVar) then
2925 begin
2926 mBoolVar := pvar;
2927 setChecked(mBoolVar^);
2928 end;
2929 end;
2932 procedure TUISwitchBox.drawControl (gx, gy: Integer);
2933 var
2934 xpos, ypos: Integer;
2935 cidx: Integer;
2936 begin
2937 cidx := getColorIndex;
2939 if (mHAlign < 0) then xpos := 0
2940 else if (mHAlign > 0) then xpos := mWidth-(Length(mText)+4)*8
2941 else xpos := (mWidth-(Length(mText)+4)*8) div 2;
2943 if (mVAlign < 0) then ypos := 0
2944 else if (mVAlign > 0) then ypos := mHeight-8
2945 else ypos := (mHeight-8) div 2;
2948 fillRect(gx, gy, mWidth, mHeight, mBackColor[cidx]);
2950 if (checked) then
2951 begin
2952 if (Length(mCheckedStr) <> 3) or (mCheckedStr[2] <> '*') then
2953 begin
2954 drawText8(gx+xpos, gy+ypos, mCheckedStr, mSwitchColor[cidx]);
2955 end
2956 else
2957 begin
2958 drawText8(gx+xpos, gy+ypos, mCheckedStr[1], mSwitchColor[cidx]);
2959 drawText8(gx+xpos+2*8, gy+ypos, mCheckedStr[3], mSwitchColor[cidx]);
2960 drawText8(gx+xpos+7, gy+ypos, '*', mSwitchColor[cidx]);
2961 end;
2962 end
2963 else
2964 begin
2965 drawText8(gx+xpos, gy+ypos, mUncheckedStr, mSwitchColor[cidx]);
2966 end;
2968 drawText8(gx+xpos+8*3, gy+ypos, mText, mTextColor[cidx]);
2970 if (mHotChar <> #0) and (mHotChar <> ' ') then drawText8(gx+xpos+8*3+mHotOfs, gy+ypos, mHotChar, mHotColor[cidx]);
2971 end;
2974 procedure TUISwitchBox.mouseEvent (var ev: THMouseEvent);
2975 var
2976 lx, ly: Integer;
2977 begin
2978 inherited mouseEvent(ev);
2979 if (uiGrabCtl = self) then
2980 begin
2981 ev.eat();
2982 if (ev = '-lmb') and focused and toLocal(ev.x, ev.y, lx, ly) then
2983 begin
2984 doAction();
2985 end;
2986 exit;
2987 end;
2988 if (ev.eaten) or (ev.cancelled) or (not enabled) or not focused then exit;
2989 ev.eat();
2990 end;
2993 procedure TUISwitchBox.keyEvent (var ev: THKeyEvent);
2994 begin
2995 inherited keyEvent(ev);
2996 if (not ev.eaten) and (not ev.cancelled) and (enabled) then
2997 begin
2998 if (ev = 'Space') then
2999 begin
3000 ev.eat();
3001 doAction();
3002 exit;
3003 end;
3004 end;
3005 end;
3008 procedure TUISwitchBox.keyEventPost (var ev: THKeyEvent);
3009 begin
3010 if (not enabled) then exit;
3011 if (mHotChar = #0) then exit;
3012 if (ev.eaten) or (ev.cancelled) or (not ev.press) then exit;
3013 if (not ev.isHot(mHotChar)) then exit;
3014 if (not canFocus) then exit;
3015 ev.eat();
3016 focused := true;
3017 doAction();
3018 end;
3021 // ////////////////////////////////////////////////////////////////////////// //
3022 procedure TUICheckBox.AfterConstruction ();
3023 begin
3024 inherited;
3025 mChecked := false;
3026 mBoolVar := @mChecked;
3027 mCheckedStr := '[x]';
3028 mUncheckedStr := '[ ]';
3029 end;
3032 procedure TUICheckBox.setChecked (v: Boolean);
3033 begin
3034 mBoolVar^ := v;
3035 end;
3038 procedure TUICheckBox.doAction ();
3039 begin
3040 if (assigned(actionCB)) then
3041 begin
3042 actionCB(self);
3043 end
3044 else
3045 begin
3046 setChecked(not getChecked);
3047 end;
3048 end;
3051 // ////////////////////////////////////////////////////////////////////////// //
3052 procedure TUIRadioBox.AfterConstruction ();
3053 begin
3054 inherited;
3055 mChecked := false;
3056 mBoolVar := @mChecked;
3057 mCheckedStr := '(*)';
3058 mUncheckedStr := '( )';
3059 mRadioGroup := '';
3060 end;
3063 function TUIRadioBox.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
3064 begin
3065 if (strEquCI1251(prname, 'group')) then
3066 begin
3067 mRadioGroup := par.expectIdOrStr(true);
3068 if (getChecked) then setChecked(true);
3069 result := true;
3070 exit;
3071 end;
3072 if (strEquCI1251(prname, 'checked')) then
3073 begin
3074 result := true;
3075 setChecked(true);
3076 exit;
3077 end;
3078 result := inherited parseProperty(prname, par);
3079 end;
3082 procedure TUIRadioBox.setChecked (v: Boolean);
3084 function resetGroup (ctl: TUIControl): Boolean;
3085 begin
3086 result := false;
3087 if (ctl <> self) and (ctl is TUIRadioBox) and (TUIRadioBox(ctl).mRadioGroup = mRadioGroup) then
3088 begin
3089 TUIRadioBox(ctl).mBoolVar^ := false;
3090 end;
3091 end;
3093 begin
3094 mBoolVar^ := v;
3095 if v then topLevel.forEachControl(resetGroup);
3096 end;
3099 procedure TUIRadioBox.doAction ();
3100 begin
3101 if (assigned(actionCB)) then
3102 begin
3103 actionCB(self);
3104 end
3105 else
3106 begin
3107 setChecked(true);
3108 end;
3109 end;
3112 // ////////////////////////////////////////////////////////////////////////// //
3113 initialization
3114 registerCtlClass(TUIHBox, 'hbox');
3115 registerCtlClass(TUIVBox, 'vbox');
3116 registerCtlClass(TUISpan, 'span');
3117 registerCtlClass(TUIHLine, 'hline');
3118 registerCtlClass(TUIVLine, 'vline');
3119 registerCtlClass(TUITextLabel, 'label');
3120 registerCtlClass(TUIStaticText, 'static');
3121 registerCtlClass(TUIButton, 'button');
3122 registerCtlClass(TUICheckBox, 'checkbox');
3123 registerCtlClass(TUIRadioBox, 'radiobox');
3124 end.