DEADSOFTWARE

Holmes is using new FlexUI renderer now
[d2df-sdl.git] / src / flexui / fui_ctls.pas
index 8c3834e37bc3f6f79c3624a99cfd7a49e661fe9f..7969a2617ef52edbaeb113583ae4888d72052413 100644 (file)
@@ -70,11 +70,7 @@ type
     mFrameColor: array[0..ClrIdxMax] of TGxRGBA;
     mFrameTextColor: array[0..ClrIdxMax] of TGxRGBA;
     mFrameIconColor: array[0..ClrIdxMax] of TGxRGBA;
-    mDarken: array[0..ClrIdxMax] of Integer; // -1: none
-
-  private
-    scis: TScissorSave;
-    scallowed: Boolean;
+    mDarken: array[0..ClrIdxMax] of Integer; // >255: none
 
   protected
     procedure updateStyle (); virtual;
@@ -110,15 +106,11 @@ type
 
     procedure calcFullClientSize ();
 
+  protected
+    var savedClip: TGxRect; // valid only in `draw*()` calls
     //WARNING! do not call scissor functions outside `.draw*()` API!
     // set scissor to this rect (in local coords)
-    procedure setScissor (lx, ly, lw, lh: Integer);
-    // reset scissor to whole control
-    procedure resetScissor (fullArea: Boolean); inline; // "full area" means "with frame"
-
-    // DO NOT USE!
-    // set scissor to this rect (in global coords)
-    procedure setScissorGLInternal (x, y, w, h: Integer);
+    procedure setScissor (lx, ly, lw, lh: Integer); // valid only in `draw*()` calls
 
   public
     actionCB: TActionCB;
@@ -138,29 +130,26 @@ type
     mLayDefSize: TLaySize;
     mLayMaxSize: TLaySize;
     mFullSize: TLaySize;
+    mNoPad: Boolean;
+    mPadding: TLaySize;
 
   public
     // layouter interface
     function getDefSize (): TLaySize; inline; // default size; <0: use max size
     //procedure setDefSize (const sz: TLaySize); inline; // default size; <0: use max size
     function getMargins (): TLayMargins; inline;
+    function getPadding (): TLaySize; inline; // children padding (each non-first child will get this on left/top)
     function getMaxSize (): TLaySize; inline; // max size; <0: set to some huge value
     //procedure setMaxSize (const sz: TLaySize); inline; // max size; <0: set to some huge value
     function getFlex (): Integer; inline; // <=0: not flexible
     function isHorizBox (): Boolean; inline; // horizontal layout for children?
-    procedure setHorizBox (v: Boolean); inline; // horizontal layout for children?
     function canWrap (): Boolean; inline; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
-    procedure setCanWrap (v: Boolean); inline; // for horizontal boxes: can wrap children? for child: `false` means 'nonbreakable at *next* ctl'
+    function noPad (): Boolean; inline; // ignore padding in box direction for this control
     function isLineStart (): Boolean; inline; // `true` if this ctl should start a new line; ignored for vertical boxes
-    procedure setLineStart (v: Boolean); inline; // `true` if this ctl should start a new line; ignored for vertical boxes
     function getAlign (): Integer; inline; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
-    procedure setAlign (v: Integer); inline; // aligning in non-main direction: <0: left/up; 0: center; >0: right/down
     function getExpand (): Boolean; inline; // expanding in non-main direction: `true` will ignore align and eat all available space
-    procedure setExpand (v: Boolean); inline; // expanding in non-main direction: `true` will ignore align and eat all available space
     function getHGroup (): AnsiString; inline; // empty: not grouped
-    procedure setHGroup (const v: AnsiString); inline; // empty: not grouped
     function getVGroup (): AnsiString; inline; // empty: not grouped
-    procedure setVGroup (const v: AnsiString); inline; // empty: not grouped
 
     procedure setActualSizePos (constref apos: TLayPos; constref asize: TLaySize); inline;
 
@@ -170,18 +159,23 @@ type
     property flex: Integer read mFlex write mFlex;
     property flDefaultSize: TLaySize read mDefSize write mDefSize;
     property flMaxSize: TLaySize read mMaxSize write mMaxSize;
-    property flHoriz: Boolean read isHorizBox write setHorizBox;
-    property flCanWrap: Boolean read canWrap write setCanWrap;
-    property flLineStart: Boolean read isLineStart write setLineStart;
-    property flAlign: Integer read getAlign write setAlign;
-    property flExpand: Boolean read getExpand write setExpand;
-    property flHGroup: AnsiString read getHGroup write setHGroup;
-    property flVGroup: AnsiString read getVGroup write setVGroup;
+    property flPadding: TLaySize read mPadding write mPadding;
+    property flHoriz: Boolean read mHoriz write mHoriz;
+    property flCanWrap: Boolean read mCanWrap write mCanWrap;
+    property flLineStart: Boolean read mLineStart write mLineStart;
+    property flAlign: Integer read mAlign write mAlign;
+    property flExpand: Boolean read mExpand write mExpand;
+    property flHGroup: AnsiString read mHGroup write mHGroup;
+    property flVGroup: AnsiString read mVGroup write mVGroup;
+    property flNoPad: Boolean read mNoPad write mNoPad;
     property fullSize: TLaySize read mFullSize;
 
   protected
     function parsePos (par: TTextParser): TLayPos;
     function parseSize (par: TTextParser): TLaySize;
+    function parsePadding (par: TTextParser): TLaySize;
+    function parseHPadding (par: TTextParser; def: Integer): TLaySize;
+    function parseVPadding (par: TTextParser; def: Integer): TLaySize;
     function parseBool (par: TTextParser): Boolean;
     function parseAnyAlign (par: TTextParser): Integer;
     function parseHAlign (par: TTextParser): Integer;
@@ -255,7 +249,7 @@ type
     procedure close (); // this closes *top-level* control
 
   public
-    property id: AnsiString read mId;
+    property id: AnsiString read mId write mId;
     property styleId: AnsiString read mStyleId;
     property scrollX: Integer read mScrollX write mScrollX;
     property scrollY: Integer read mScrollY write mScrollY;
@@ -472,8 +466,7 @@ type
   protected
     mBoolVar: PBoolean;
     mChecked: Boolean;
-    mCheckedStr: AnsiString;
-    mUncheckedStr: AnsiString;
+    mIcon: TGxContext.TMarkIcon;
     mSwitchColor: array[0..ClrIdxMax] of TGxRGBA;
 
   protected
@@ -551,6 +544,7 @@ procedure uiLayoutCtl (ctl: TUIControl);
 // ////////////////////////////////////////////////////////////////////////// //
 var
   fuiRenderScale: Single = 1.0;
+  uiContext: TGxContext = nil;
 
 
 implementation
@@ -778,7 +772,9 @@ var
   ctl: TUIControl;
 begin
   processKills();
-  gxBeginUIDraw(fuiRenderScale);
+  //if (uiContext = nil) then uiContext := TGxContext.Create();
+  gxSetContext(uiContext, fuiRenderScale);
+  uiContext.resetClip();
   try
     for f := 0 to High(uiTopList) do
     begin
@@ -787,11 +783,11 @@ begin
       if (f <> High(uiTopList)) then
       begin
         cidx := ctl.getColorIndex;
-        if (ctl.mDarken[cidx] > 0) then darkenRect(ctl.x0, ctl.y0, ctl.width, ctl.height, ctl.mDarken[cidx]);
+        uiContext.darkenRect(ctl.x0, ctl.y0, ctl.width, ctl.height, ctl.mDarken[cidx]);
       end;
     end;
   finally
-    gxEndUIDraw();
+    gxSetContext(nil);
   end;
 end;
 
@@ -882,7 +878,7 @@ begin
   mX := 0;
   mY := 0;
   mWidth := 64;
-  mHeight := 8;
+  mHeight := uiContext.charHeight(' ');
   mFrameWidth := 0;
   mFrameHeight := 0;
   mEnabled := true;
@@ -890,13 +886,14 @@ begin
   mChildren := nil;
   mFocused := nil;
   mEscClose := false;
-  scallowed := false;
   mDrawShadow := false;
   actionCB := nil;
   // layouter interface
-  //mDefSize := TLaySize.Create(64, 8); // default size
+  //mDefSize := TLaySize.Create(64, uiContext.charHeight(' ')); // default size
   mDefSize := TLaySize.Create(0, 0); // default size
   mMaxSize := TLaySize.Create(-1, -1); // maximum size
+  mPadding := TLaySize.Create(0, 0);
+  mNoPad := false;
   mFlex := 0;
   mHoriz := true;
   mCanWrap := false;
@@ -979,47 +976,38 @@ begin
   mFrameColor[ClrIdxActive] := root.get('frame-color', 'active', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
   mFrameTextColor[ClrIdxActive] := root.get('frame-text-color', 'active', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
   mFrameIconColor[ClrIdxActive] := root.get('frame-icon-color', 'active', cst).asRGBADef(TGxRGBA.Create(0, 255, 0));
-  mDarken[ClrIdxActive] := root.get('darken', 'active', cst).asInt(-1);
+  mDarken[ClrIdxActive] := root.get('darken', 'active', cst).asInt(666);
   // disabled
   mBackColor[ClrIdxDisabled] := root.get('back-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(0, 0, 128));
   mTextColor[ClrIdxDisabled] := root.get('text-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(127, 127, 127));
   mFrameColor[ClrIdxDisabled] := root.get('frame-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(127, 127, 127));
   mFrameTextColor[ClrIdxDisabled] := root.get('frame-text-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(127, 127, 127));
   mFrameIconColor[ClrIdxDisabled] := root.get('frame-icon-color', 'disabled', cst).asRGBADef(TGxRGBA.Create(0, 127, 0));
-  mDarken[ClrIdxDisabled] := root.get('darken', 'disabled', cst).asInt(-1);
+  mDarken[ClrIdxDisabled] := root.get('darken', 'disabled', cst).asInt(666);
   // inactive
   mBackColor[ClrIdxInactive] := root.get('back-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(0, 0, 128));
   mTextColor[ClrIdxInactive] := root.get('text-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
   mFrameColor[ClrIdxInactive] := root.get('frame-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
   mFrameTextColor[ClrIdxInactive] := root.get('frame-text-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(255, 255, 255));
   mFrameIconColor[ClrIdxInactive] := root.get('frame-icon-color', 'inactive', cst).asRGBADef(TGxRGBA.Create(0, 255, 0));
-  mDarken[ClrIdxInactive] := root.get('darken', 'inactive', cst).asInt(-1);
+  mDarken[ClrIdxInactive] := root.get('darken', 'inactive', cst).asInt(666);
 end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
 function TUIControl.getDefSize (): TLaySize; inline; begin result := mLayDefSize; end;
 function TUIControl.getMaxSize (): TLaySize; inline; begin result := mLayMaxSize; end;
+function TUIControl.getPadding (): TLaySize; inline; begin result := mPadding; end;
 function TUIControl.getFlex (): Integer; inline; begin result := mFlex; end;
 function TUIControl.isHorizBox (): Boolean; inline; begin result := mHoriz; end;
-procedure TUIControl.setHorizBox (v: Boolean); inline; begin mHoriz := v; end;
 function TUIControl.canWrap (): Boolean; inline; begin result := mCanWrap; end;
-procedure TUIControl.setCanWrap (v: Boolean); inline; begin mCanWrap := v; end;
+function TUIControl.noPad (): Boolean; inline; begin result := mNoPad; end;
 function TUIControl.isLineStart (): Boolean; inline; begin result := mLineStart; end;
-procedure TUIControl.setLineStart (v: Boolean); inline; begin mLineStart := v; end;
 function TUIControl.getAlign (): Integer; inline; begin result := mAlign; end;
-procedure TUIControl.setAlign (v: Integer); inline; begin mAlign := v; end;
 function TUIControl.getExpand (): Boolean; inline; begin result := mExpand; end;
-procedure TUIControl.setExpand (v: Boolean); inline; begin mExpand := v; end;
 function TUIControl.getHGroup (): AnsiString; inline; begin result := mHGroup; end;
-procedure TUIControl.setHGroup (const v: AnsiString); inline; begin mHGroup := v; end;
 function TUIControl.getVGroup (): AnsiString; inline; begin result := mVGroup; end;
-procedure TUIControl.setVGroup (const v: AnsiString); inline; begin mVGroup := v; end;
-
-function TUIControl.getMargins (): TLayMargins; inline;
-begin
-  result := TLayMargins.Create(mFrameHeight, mFrameWidth, mFrameHeight, mFrameWidth);
-end;
+function TUIControl.getMargins (): TLayMargins; inline; begin result := TLayMargins.Create(mFrameHeight, mFrameWidth, mFrameHeight, mFrameWidth); end;
 
 procedure TUIControl.setActualSizePos (constref apos: TLayPos; constref asize: TLaySize); inline;
 begin
@@ -1067,6 +1055,37 @@ begin
   par.expectDelim(ech);
 end;
 
+function TUIControl.parsePadding (par: TTextParser): TLaySize;
+begin
+  result := parseSize(par);
+end;
+
+function TUIControl.parseHPadding (par: TTextParser; def: Integer): TLaySize;
+begin
+  if (par.isInt) then
+  begin
+    result.h := def;
+    result.w := par.expectInt();
+  end
+  else
+  begin
+    result := parsePadding(par);
+  end;
+end;
+
+function TUIControl.parseVPadding (par: TTextParser; def: Integer): TLaySize;
+begin
+  if (par.isInt) then
+  begin
+    result.w := def;
+    result.h := par.expectInt();
+  end
+  else
+  begin
+    result := parsePadding(par);
+  end;
+end;
+
 function TUIControl.parseBool (par: TTextParser): Boolean;
 begin
   result :=
@@ -1244,6 +1263,9 @@ begin
   if (strEquCI1251(prname, 'defheight')) or (strEquCI1251(prname, 'height')) then begin mDefSize.h := par.expectInt(); exit; end;
   if (strEquCI1251(prname, 'maxwidth')) then begin mMaxSize.w := par.expectInt(); exit; end;
   if (strEquCI1251(prname, 'maxheight')) then begin mMaxSize.h := par.expectInt(); exit; end;
+  // padding
+  if (strEquCI1251(prname, 'padding')) then begin mPadding := parsePadding(par); exit; end;
+  if (strEquCI1251(prname, 'nopad')) then begin mNoPad := true; exit; end;
   // flags
   if (strEquCI1251(prname, 'wrap')) then begin mCanWrap := parseBool(par); exit; end;
   if (strEquCI1251(prname, 'linestart')) then begin mLineStart := parseBool(par); exit; end;
@@ -1809,51 +1831,30 @@ end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
-procedure TUIControl.setScissorGLInternal (x, y, w, h: Integer);
-begin
-  if not scallowed then exit;
-  x := trunc(x*fuiRenderScale);
-  y := trunc(y*fuiRenderScale);
-  w := trunc(w*fuiRenderScale);
-  h := trunc(h*fuiRenderScale);
-  scis.combineRect(x, y, w, h);
-end;
-
 procedure TUIControl.setScissor (lx, ly, lw, lh: Integer);
 var
   gx, gy, wdt, hgt, cgx, cgy: Integer;
 begin
-  if not scallowed then exit;
-
-  if not intersectRect(lx, ly, lw, lh, 0, 0, mWidth, mHeight) then
+  if (not intersectRect(lx, ly, lw, lh, 0, 0, mWidth, mHeight)) then
   begin
-    scis.combineRect(0, 0, 0, 0);
+    uiContext.clip := TGxRect.Create(0, 0, 0, 0);
     exit;
   end;
 
   getDrawRect(gx, gy, wdt, hgt);
+
   toGlobal(lx, ly, cgx, cgy);
-  if not intersectRect(gx, gy, wdt, hgt, cgx, cgy, lw, lh) then
+  if (not intersectRect(gx, gy, wdt, hgt, cgx, cgy, lw, lh)) then
   begin
-    scis.combineRect(0, 0, 0, 0);
+    uiContext.clip := TGxRect.Create(0, 0, 0, 0);
     exit;
   end;
 
-  setScissorGLInternal(gx, gy, wdt, hgt);
+  uiContext.clip := savedClip;
+  uiContext.combineClip(TGxRect.Create(gx, gy, wdt, hgt));
+  //uiContext.clip := TGxRect.Create(gx, gy, wdt, hgt);
 end;
 
-procedure TUIControl.resetScissor (fullArea: Boolean); inline;
-begin
-  if not scallowed then exit;
-  if (fullArea) then
-  begin
-    setScissor(0, 0, mWidth, mHeight);
-  end
-  else
-  begin
-    setScissor(mFrameWidth, mFrameHeight, mWidth-mFrameWidth*2, mHeight-mFrameHeight*2);
-  end;
-end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
@@ -1861,13 +1862,26 @@ procedure TUIControl.draw ();
 var
   f: Integer;
   gx, gy: Integer;
+
+  procedure resetScissor (fullArea: Boolean); inline;
+  begin
+    uiContext.clip := savedClip;
+    if (fullArea) then
+    begin
+      setScissor(0, 0, mWidth, mHeight);
+    end
+    else
+    begin
+      setScissor(mFrameWidth, mFrameHeight, mWidth-mFrameWidth*2, mHeight-mFrameHeight*2);
+    end;
+  end;
+
 begin
-  if (mWidth < 1) or (mHeight < 1) then exit;
+  if (mWidth < 1) or (mHeight < 1) or (uiContext = nil) or (not uiContext.active) then exit;
   toGlobal(0, 0, gx, gy);
 
-  scis.save(true); // scissoring enabled
+  savedClip := uiContext.clip;
   try
-    scallowed := true;
     resetScissor(true); // full area
     drawControl(gx, gy);
     resetScissor(false); // client area
@@ -1875,8 +1889,7 @@ begin
     resetScissor(true); // full area
     drawControlPost(gx, gy);
   finally
-    scis.restore();
-    scallowed := false;
+    uiContext.clip := savedClip;
   end;
 end;
 
@@ -1888,11 +1901,12 @@ end;
 procedure TUIControl.drawControlPost (gx, gy: Integer);
 begin
   // shadow
-  if mDrawShadow and (mWidth > 0) and (mHeight > 0) then
+  if (mParent = nil) and (mDrawShadow) and (mWidth > 0) and (mHeight > 0) then
   begin
-    setScissorGLInternal(gx+8, gy+8, mWidth, mHeight);
-    darkenRect(gx+mWidth, gy+8, 8, mHeight, 128);
-    darkenRect(gx+8, gy+mHeight, mWidth-8, 8, 128);
+    //setScissorGLInternal(gx+8, gy+8, mWidth, mHeight);
+    uiContext.resetClip();
+    uiContext.darkenRect(gx+mWidth, gy+8, 8, mHeight, 128);
+    uiContext.darkenRect(gx+8, gy+mHeight, mWidth-8, 8, 128);
   end;
 end;
 
@@ -2032,11 +2046,14 @@ begin
   mFitToScreen := true;
   mFrameWidth := 8;
   mFrameHeight := 8;
-  if (mWidth < mFrameWidth*2+3*8) then mWidth := mFrameWidth*2+3*8;
+  if (mWidth < mFrameWidth*2+uiContext.iconWinWidth(TGxContext.TWinIcon.Close)) then mWidth := mFrameWidth*2+uiContext.iconWinWidth(TGxContext.TWinIcon.Close);
   if (mHeight < mFrameHeight*2) then mHeight := mFrameHeight*2;
   if (Length(mTitle) > 0) then
   begin
-    if (mWidth < Length(mTitle)*8+mFrameWidth*2+3*8) then mWidth := Length(mTitle)*8+mFrameWidth*2+3*8;
+    if (mWidth < uiContext.textWidth(mTitle)+mFrameWidth*2+uiContext.iconWinWidth(TGxContext.TWinIcon.Close)) then
+    begin
+      mWidth := uiContext.textWidth(mTitle)+mFrameWidth*2+uiContext.iconWinWidth(TGxContext.TWinIcon.Close);
+    end;
   end;
   mCanFocus := false;
   mDragScroll := TXMode.None;
@@ -2097,54 +2114,61 @@ end;
 
 procedure TUITopWindow.drawControl (gx, gy: Integer);
 begin
-  fillRect(gx, gy, mWidth, mHeight, mBackColor[getColorIndex]);
+  uiContext.color := mBackColor[getColorIndex];
+  uiContext.fillRect(gx, gy, mWidth, mHeight);
 end;
 
 
 procedure TUITopWindow.drawControlPost (gx, gy: Integer);
 var
   cidx: Integer;
-  tx, hgt, sbhgt: Integer;
+  tx, hgt, sbhgt, iwdt: Integer;
 begin
   cidx := getColorIndex;
   if (mDragScroll = TXMode.Drag) then
   begin
-    drawRectUI(gx+4, gy+4, mWidth-8, mHeight-8, mFrameColor[cidx]);
+    uiContext.color := mFrameColor[cidx];
+    uiContext.rect(gx+4, gy+4, mWidth-8, mHeight-8);
   end
   else
   begin
-    drawRectUI(gx+3, gy+3, mWidth-6, mHeight-6, mFrameColor[cidx]);
-    drawRectUI(gx+5, gy+5, mWidth-10, mHeight-10, mFrameColor[cidx]);
+    uiContext.color := mFrameColor[cidx];
+    uiContext.rect(gx+3, gy+3, mWidth-6, mHeight-6);
+    uiContext.rect(gx+5, gy+5, mWidth-10, mHeight-10);
     // vertical scroll bar
     hgt := mHeight-mFrameHeight*2;
     if (hgt > 0) and (mFullSize.h > hgt) then
     begin
       //writeln(mTitle, ': height=', mHeight-mFrameHeight*2, '; fullsize=', mFullSize.toString);
       sbhgt := mHeight-mFrameHeight*2+2;
-      fillRect(gx+mWidth-mFrameWidth+1, gy+7, mFrameWidth-3, sbhgt, mFrameColor[cidx]);
+      uiContext.fillRect(gx+mWidth-mFrameWidth+1, gy+7, mFrameWidth-3, sbhgt);
       hgt += mScrollY;
       if (hgt > mFullSize.h) then hgt := mFullSize.h;
       hgt := sbhgt*hgt div mFullSize.h;
       if (hgt > 0) then
       begin
         setScissor(mWidth-mFrameWidth+1, 7, mFrameWidth-3, sbhgt);
-        darkenRect(gx+mWidth-mFrameWidth+1, gy+7+hgt, mFrameWidth-3, sbhgt, 128);
+        uiContext.darkenRect(gx+mWidth-mFrameWidth+1, gy+7+hgt, mFrameWidth-3, sbhgt, 128);
       end;
     end;
     // frame icon
-    setScissor(mFrameWidth, 0, 3*8, 8);
-    fillRect(gx+mFrameWidth, gy, 3*8, 8, mBackColor[cidx]);
-    drawText8(gx+mFrameWidth, gy, '[ ]', mFrameColor[cidx]);
-    if mInClose then drawText8(gx+mFrameWidth+7, gy, '#', mFrameIconColor[cidx])
-    else drawText8(gx+mFrameWidth+7, gy, '*', mFrameIconColor[cidx]);
+    iwdt := uiContext.iconWinWidth(TGxContext.TWinIcon.Close);
+    setScissor(mFrameWidth, 0, iwdt, 8);
+    uiContext.color := mBackColor[cidx];
+    uiContext.fillRect(gx+mFrameWidth, gy, iwdt, 8);
+    uiContext.color := mFrameIconColor[cidx];
+    uiContext.drawIconWin(TGxContext.TWinIcon.Close, gx+mFrameWidth, gy, mInClose);
   end;
   // title
   if (Length(mTitle) > 0) then
   begin
-    setScissor(mFrameWidth+3*8, 0, mWidth-mFrameWidth*2-3*8, 8);
-    tx := (gx+3*8)+((mWidth-3*8)-Length(mTitle)*8) div 2;
-    fillRect(tx-3, gy, Length(mTitle)*8+3+2, 8, mBackColor[cidx]);
-    drawText8(tx, gy, mTitle, mFrameTextColor[cidx]);
+    iwdt := uiContext.iconWinWidth(TGxContext.TWinIcon.Close);
+    setScissor(mFrameWidth+iwdt, 0, mWidth-mFrameWidth*2-iwdt, 8);
+    tx := (gx+iwdt)+((mWidth-iwdt)-uiContext.textWidth(mTitle)) div 2;
+    uiContext.color := mBackColor[cidx];
+    uiContext.fillRect(tx-3, gy, uiContext.textWidth(mTitle)+3+2, 8);
+    uiContext.color := mFrameTextColor[cidx];
+    uiContext.drawText(tx, gy, mTitle);
   end;
   // shadow
   inherited drawControlPost(gx, gy);
@@ -2239,7 +2263,7 @@ begin
       if (ly < 8) then
       begin
         uiGrabCtl := self;
-        if (lx >= mFrameWidth) and (lx < mFrameWidth+3*8) then
+        if (lx >= mFrameWidth) and (lx < mFrameWidth+uiContext.iconWinWidth(TGxContext.TWinIcon.Close)) then
         begin
           //uiRemoveWindow(self);
           mWaitingClose := true;
@@ -2285,7 +2309,7 @@ begin
     begin
       if mWaitingClose then
       begin
-        if (lx >= mFrameWidth) and (lx < mFrameWidth+3*8) then
+        if (lx >= mFrameWidth) and (lx < mFrameWidth+uiContext.iconWinWidth(TGxContext.TWinIcon.Close)) then
         begin
           if (not assigned(closeRequestCB)) or (closeRequestCB(self)) then
           begin
@@ -2303,7 +2327,7 @@ begin
     begin
       if mWaitingClose then
       begin
-        mInClose := (lx >= mFrameWidth) and (lx < mFrameWidth+3*8);
+        mInClose := (lx >= mFrameWidth) and (lx < mFrameWidth+uiContext.iconWinWidth(TGxContext.TWinIcon.Close));
         ev.eat();
         exit;
       end;
@@ -2339,7 +2363,7 @@ end;
 procedure TUIBox.setCaption (const acap: AnsiString);
 begin
   mCaption := acap;
-  mDefSize := TLaySize.Create(Length(mCaption)*8+3, 8);
+  mDefSize := TLaySize.Create(uiContext.textWidth(mCaption)+3, uiContext.textHeight(mCaption));
 end;
 
 
@@ -2347,12 +2371,19 @@ procedure TUIBox.setHasFrame (v: Boolean);
 begin
   mHasFrame := v;
   if (mHasFrame) then begin mFrameWidth := 8; mFrameHeight := 8; end else begin mFrameWidth := 0; mFrameHeight := 0; end;
+  if (mHasFrame) then mNoPad := true;
 end;
 
 
 function TUIBox.parseProperty (const prname: AnsiString; par: TTextParser): Boolean;
 begin
   if (parseOrientation(prname, par)) then begin result := true; exit; end;
+  if (strEquCI1251(prname, 'padding')) then
+  begin
+    if (mHoriz) then mPadding := parseHPadding(par, 0) else mPadding := parseVPadding(par, 0);
+    result := true;
+    exit;
+  end;
   if (strEquCI1251(prname, 'frame')) then
   begin
     setHasFrame(parseBool(par));
@@ -2387,23 +2418,30 @@ var
   xpos: Integer;
 begin
   cidx := getColorIndex;
-  fillRect(gx, gy, mWidth, mHeight, mBackColor[cidx]);
+  uiContext.color := mBackColor[cidx];
+  uiContext.fillRect(gx, gy, mWidth, mHeight);
   if mHasFrame then
   begin
     // draw frame
-    drawRectUI(gx+3, gy+3, mWidth-6, mHeight-6, mFrameColor[cidx]);
+    uiContext.color := mFrameColor[cidx];
+    uiContext.rect(gx+3, gy+3, mWidth-6, mHeight-6);
   end;
   // draw caption
   if (Length(mCaption) > 0) then
   begin
          if (mHAlign < 0) then xpos := 3
-    else if (mHAlign > 0) then xpos := mWidth-mFrameWidth*2-Length(mCaption)*8
-    else xpos := (mWidth-mFrameWidth*2-Length(mCaption)*8) div 2;
+    else if (mHAlign > 0) then xpos := mWidth-mFrameWidth*2-uiContext.textWidth(mCaption)
+    else xpos := (mWidth-mFrameWidth*2-uiContext.textWidth(mCaption)) div 2;
     xpos += gx+mFrameWidth;
 
     setScissor(mFrameWidth+1, 0, mWidth-mFrameWidth-2, 8);
-    if mHasFrame then fillRect(xpos-3, gy, Length(mCaption)*8+4, 8, mBackColor[cidx]);
-    drawText8(xpos, gy, mCaption, mFrameTextColor[cidx]);
+    if mHasFrame then
+    begin
+      uiContext.color := mBackColor[cidx];
+      uiContext.fillRect(xpos-3, gy, uiContext.textWidth(mCaption)+4, 8);
+    end;
+    uiContext.color := mFrameTextColor[cidx];
+    uiContext.drawText(xpos, gy, mCaption);
   end;
 end;
 
@@ -2478,6 +2516,7 @@ begin
   inherited;
   mExpand := true;
   mCanFocus := false;
+  mNoPad := true;
   mCtl4Style := 'span';
 end;
 
@@ -2517,14 +2556,9 @@ var
   cidx: Integer;
 begin
   cidx := getColorIndex;
-  if mHoriz then
-  begin
-    drawHLine(gx, gy+(mHeight div 2), mWidth, mTextColor[cidx]);
-  end
-  else
-  begin
-    drawVLine(gx+(mWidth div 2), gy, mHeight, mTextColor[cidx]);
-  end;
+  uiContext.color := mTextColor[cidx];
+  if mHoriz then uiContext.hline(gx, gy+(mHeight div 2), mWidth)
+  else uiContext.vline(gx+(mWidth div 2), gy, mHeight);
 end;
 
 
@@ -2556,7 +2590,7 @@ begin
   mHoriz := true; // nobody cares
   mHeader := false;
   mLine := false;
-  mDefSize.h := 8;
+  mDefSize.h := uiContext.charHeight(' ');
   mCtl4Style := 'static';
 end;
 
@@ -2564,7 +2598,7 @@ end;
 procedure TUIStaticText.setText (const atext: AnsiString);
 begin
   mText := atext;
-  mDefSize := TLaySize.Create(Length(mText)*8, 8);
+  mDefSize := TLaySize.Create(uiContext.textWidth(mText), uiContext.textHeight(mText));
 end;
 
 
@@ -2602,29 +2636,29 @@ procedure TUIStaticText.drawControl (gx, gy: Integer);
 var
   xpos, ypos: Integer;
   cidx: Integer;
-  clr: TGxRGBA;
 begin
   cidx := getColorIndex;
-  fillRect(gx, gy, mWidth, mHeight, mBackColor[cidx]);
+  uiContext.color := mBackColor[cidx];
+  uiContext.fillRect(gx, gy, mWidth, mHeight);
 
        if (mHAlign < 0) then xpos := 0
-  else if (mHAlign > 0) then xpos := mWidth-Length(mText)*8
-  else xpos := (mWidth-Length(mText)*8) div 2;
+  else if (mHAlign > 0) then xpos := mWidth-uiContext.textWidth(mText)
+  else xpos := (mWidth-uiContext.textWidth(mText)) div 2;
 
   if (Length(mText) > 0) then
   begin
-    if (mHeader) then clr := mFrameTextColor[cidx] else clr := mTextColor[cidx];
+    if (mHeader) then uiContext.color := mFrameTextColor[cidx] else uiContext.color := mTextColor[cidx];
 
          if (mVAlign < 0) then ypos := 0
-    else if (mVAlign > 0) then ypos := mHeight-8
-    else ypos := (mHeight-8) div 2;
+    else if (mVAlign > 0) then ypos := mHeight-uiContext.textHeight(mText)
+    else ypos := (mHeight-uiContext.textHeight(mText)) div 2;
 
-    drawText8(gx+xpos, gy+ypos, mText, clr);
+    uiContext.drawText(gx+xpos, gy+ypos, mText);
   end;
 
   if (mLine) then
   begin
-    if (mHeader) then clr := mFrameColor[cidx] else clr := mTextColor[cidx];
+    if (mHeader) then uiContext.color := mFrameColor[cidx] else uiContext.color := mTextColor[cidx];
 
          if (mVAlign < 0) then ypos := 0
     else if (mVAlign > 0) then ypos := mHeight-1
@@ -2633,12 +2667,12 @@ begin
 
     if (Length(mText) = 0) then
     begin
-      drawHLine(gx, ypos, mWidth, clr);
+      uiContext.hline(gx, ypos, mWidth);
     end
     else
     begin
-      drawHLine(gx, ypos, xpos-1, clr);
-      drawHLine(gx+xpos+Length(mText)*8, ypos, mWidth, clr);
+      uiContext.hline(gx, ypos, xpos-1);
+      uiContext.hline(gx+xpos+uiContext.textWidth(mText), ypos, mWidth);
     end;
   end;
 end;
@@ -2651,7 +2685,7 @@ begin
   mHAlign := -1;
   mVAlign := 0;
   mCanFocus := false;
-  mDefSize := TLaySize.Create(Length(mText)*8, 8);
+  mDefSize := TLaySize.Create(uiContext.textWidth(mText), uiContext.textHeight(mText));
   mCtl4Style := 'label';
   mLinkId := '';
 end;
@@ -2693,7 +2727,7 @@ begin
         if (mHotChar = #0) then
         begin
           mHotChar := s[f];
-          mHotOfs := Length(mText)*8;
+          mHotOfs := Length(mText);
         end;
         mText += s[f];
       end;
@@ -2705,7 +2739,13 @@ begin
       Inc(f);
     end;
   end;
-  mDefSize := TLaySize.Create(Length(mText)*8, 8);
+  // fix hotchar offset
+  if (mHotChar <> #0) and (mHotOfs > 0) then
+  begin
+    mHotOfs := uiContext.textWidth(Copy(mText, 1, mHotOfs+1))-uiContext.charWidth(mText[mHotOfs+1]);
+  end;
+  // fix size
+  mDefSize := TLaySize.Create(uiContext.textWidth(mText), uiContext.textHeight(mText));
 end;
 
 
@@ -2739,22 +2779,25 @@ var
   cidx: Integer;
 begin
   cidx := getColorIndex;
-  fillRect(gx, gy, mWidth, mHeight, mBackColor[cidx]);
+  uiContext.color := mBackColor[cidx];
+  uiContext.fillRect(gx, gy, mWidth, mHeight);
   if (Length(mText) > 0) then
   begin
          if (mHAlign < 0) then xpos := 0
-    else if (mHAlign > 0) then xpos := mWidth-Length(mText)*8
-    else xpos := (mWidth-Length(mText)*8) div 2;
+    else if (mHAlign > 0) then xpos := mWidth-uiContext.textWidth(mText)
+    else xpos := (mWidth-uiContext.textWidth(mText)) div 2;
 
          if (mVAlign < 0) then ypos := 0
-    else if (mVAlign > 0) then ypos := mHeight-8
-    else ypos := (mHeight-8) div 2;
+    else if (mVAlign > 0) then ypos := mHeight-uiContext.textHeight(mText)
+    else ypos := (mHeight-uiContext.textHeight(mText)) div 2;
 
-    drawText8(gx+xpos, gy+ypos, mText, mTextColor[cidx]);
+    uiContext.color := mTextColor[cidx];
+    uiContext.drawText(gx+xpos, gy+ypos, mText);
 
     if (Length(mLinkId) > 0) and (mHotChar <> #0) and (mHotChar <> ' ') then
     begin
-      drawText8(gx+xpos+8+mHotOfs, gy+ypos, mHotChar, mHotColor[cidx]);
+      uiContext.color := mHotColor[cidx];
+      uiContext.drawChar(gx+xpos+mHotOfs, gy+ypos, mHotChar);
     end;
   end;
 end;
@@ -2811,7 +2854,7 @@ begin
   mHAlign := -1;
   mVAlign := 0;
   mCanFocus := true;
-  mDefSize := TLaySize.Create(Length(mText)*8+8, 10);
+  mDefSize := TLaySize.Create(uiContext.textWidth(mText)+8*2, uiContext.textHeight(mText)+2);
   mCtl4Style := 'button';
 end;
 
@@ -2819,7 +2862,7 @@ end;
 procedure TUIButton.setText (const s: AnsiString);
 begin
   inherited setText(s);
-  mDefSize := TLaySize.Create(Length(mText)*8+8*2, 10);
+  mDefSize := TLaySize.Create(uiContext.textWidth(mText)+8*2, uiContext.textHeight(mText)+2);
 end;
 
 
@@ -2830,24 +2873,30 @@ var
 begin
   cidx := getColorIndex;
 
-  fillRect(gx+1, gy, mWidth-2, mHeight, mBackColor[cidx]);
-  fillRect(gx, gy+1, 1, mHeight-2, mBackColor[cidx]);
-  fillRect(gx+mWidth-1, gy+1, 1, mHeight-2, mBackColor[cidx]);
+  uiContext.color := mBackColor[cidx];
+  uiContext.fillRect(gx+1, gy, mWidth-2, mHeight);
+  uiContext.fillRect(gx, gy+1, 1, mHeight-2);
+  uiContext.fillRect(gx+mWidth-1, gy+1, 1, mHeight-2);
 
   if (Length(mText) > 0) then
   begin
          if (mHAlign < 0) then xpos := 0
-    else if (mHAlign > 0) then xpos := mWidth-Length(mText)*8
-    else xpos := (mWidth-Length(mText)*8) div 2;
+    else if (mHAlign > 0) then xpos := mWidth-uiContext.textWidth(mText)
+    else xpos := (mWidth-uiContext.textWidth(mText)) div 2;
 
          if (mVAlign < 0) then ypos := 0
-    else if (mVAlign > 0) then ypos := mHeight-8
-    else ypos := (mHeight-8) div 2;
+    else if (mVAlign > 0) then ypos := mHeight-uiContext.textHeight(mText)
+    else ypos := (mHeight-uiContext.textHeight(mText)) div 2;
 
     setScissor(8, 0, mWidth-16, mHeight);
-    drawText8(gx+xpos+8, gy+ypos, mText, mTextColor[cidx]);
+    uiContext.color := mTextColor[cidx];
+    uiContext.drawText(gx+xpos+8, gy+ypos, mText);
 
-    if (mHotChar <> #0) and (mHotChar <> ' ') then drawText8(gx+xpos+8+mHotOfs, gy+ypos, mHotChar, mHotColor[cidx]);
+    if (mHotChar <> #0) and (mHotChar <> ' ') then
+    begin
+      uiContext.color := mHotColor[cidx];
+      uiContext.drawChar(gx+xpos+8+mHotOfs, gy+ypos, mHotChar);
+    end;
   end;
 end;
 
@@ -2893,7 +2942,8 @@ begin
   mHAlign := -1;
   mVAlign := 0;
   mCanFocus := true;
-  mDefSize := TLaySize.Create(Length(mText)*8+8*3, 8);
+  mIcon := TGxContext.TMarkIcon.Checkbox;
+  mDefSize := TLaySize.Create(uiContext.textWidth(mText)+3+uiContext.iconMarkWidth(mIcon), uiContext.iconMarkHeight(mIcon));
   mCtl4Style := 'switchbox';
   mChecked := false;
   mBoolVar := @mChecked;
@@ -2915,7 +2965,7 @@ end;
 procedure TUISwitchBox.setText (const s: AnsiString);
 begin
   inherited setText(s);
-  mDefSize := TLaySize.Create(Length(mText)*8+8*3, 8);
+  mDefSize := TLaySize.Create(uiContext.textWidth(mText)+3+uiContext.iconMarkWidth(mIcon), uiContext.iconMarkHeight(mIcon));
 end;
 
 
@@ -2956,37 +3006,31 @@ begin
   cidx := getColorIndex;
 
        if (mHAlign < 0) then xpos := 0
-  else if (mHAlign > 0) then xpos := mWidth-(Length(mText)+4)*8
-  else xpos := (mWidth-(Length(mText)+4)*8) div 2;
+  else if (mHAlign > 0) then xpos := mWidth-(uiContext.textWidth(mText)+3+uiContext.iconMarkWidth(mIcon))
+  else xpos := (mWidth-(uiContext.textWidth(mText)+3+uiContext.iconMarkWidth(mIcon))) div 2;
 
        if (mVAlign < 0) then ypos := 0
-  else if (mVAlign > 0) then ypos := mHeight-8
-  else ypos := (mHeight-8) div 2;
+  else if (mVAlign > 0) then ypos := mHeight-uiContext.iconMarkHeight(mIcon)
+  else ypos := (mHeight-uiContext.iconMarkHeight(mIcon)) div 2;
 
+  uiContext.color := mBackColor[cidx];
+  uiContext.fillRect(gx, gy, mWidth, mHeight);
 
-  fillRect(gx, gy, mWidth, mHeight, mBackColor[cidx]);
+  uiContext.color := mSwitchColor[cidx];
+  uiContext.drawIconMark(mIcon, gx, gy, checked);
 
-  if (checked) then
-  begin
-    if (Length(mCheckedStr) <> 3) or (mCheckedStr[2] <> '*') then
-    begin
-      drawText8(gx+xpos, gy+ypos, mCheckedStr, mSwitchColor[cidx]);
-    end
-    else
-    begin
-      drawText8(gx+xpos, gy+ypos, mCheckedStr[1], mSwitchColor[cidx]);
-      drawText8(gx+xpos+2*8, gy+ypos, mCheckedStr[3], mSwitchColor[cidx]);
-      drawText8(gx+xpos+7, gy+ypos, '*', mSwitchColor[cidx]);
-    end;
-  end
-  else
-  begin
-    drawText8(gx+xpos, gy+ypos, mUncheckedStr, mSwitchColor[cidx]);
-  end;
+       if (mVAlign < 0) then ypos := 0
+  else if (mVAlign > 0) then ypos := mHeight-uiContext.textHeight(mText)
+  else ypos := (mHeight-uiContext.textHeight(mText)) div 2;
 
-  drawText8(gx+xpos+8*3, gy+ypos, mText, mTextColor[cidx]);
+  uiContext.color := mTextColor[cidx];
+  uiContext.drawText(gx+xpos+3+uiContext.iconMarkWidth(mIcon), gy+ypos, mText);
 
-  if (mHotChar <> #0) and (mHotChar <> ' ') then drawText8(gx+xpos+8*3+mHotOfs, gy+ypos, mHotChar, mHotColor[cidx]);
+  if (mHotChar <> #0) and (mHotChar <> ' ') then
+  begin
+    uiContext.color := mHotColor[cidx];
+    uiContext.drawChar(gx+xpos+3+uiContext.iconMarkWidth(mIcon)+mHotOfs, gy+ypos, mHotChar);
+  end;
 end;
 
 
@@ -3030,8 +3074,8 @@ begin
   inherited;
   mChecked := false;
   mBoolVar := @mChecked;
-  mCheckedStr := '[x]';
-  mUncheckedStr := '[ ]';
+  mIcon := TGxContext.TMarkIcon.Checkbox;
+  setText('');
 end;
 
 
@@ -3060,9 +3104,9 @@ begin
   inherited;
   mChecked := false;
   mBoolVar := @mChecked;
-  mCheckedStr := '(*)';
-  mUncheckedStr := '( )';
   mRadioGroup := '';
+  mIcon := TGxContext.TMarkIcon.Radiobox;
+  setText('');
 end;
 
 
@@ -3127,4 +3171,6 @@ initialization
   registerCtlClass(TUIButton, 'button');
   registerCtlClass(TUICheckBox, 'checkbox');
   registerCtlClass(TUIRadioBox, 'radiobox');
+
+  uiContext := TGxContext.Create();
 end.