DEADSOFTWARE

Holmes now require "data/flexui.wad" (it is not fatal to not have this file; Holmes...
[d2df-sdl.git] / src / flexui / fui_ctls.pas
index c573de87b2816387c38ccdddea88d5ba297a7262..ef5ab367e43e2bd11e0d8fe558c20dfb2fca4f52 100644 (file)
@@ -64,6 +64,7 @@ type
     mCancel: Boolean;
     mDefault: Boolean;
     // colors
+    mStyleLoaded: Boolean;
     mCtl4Style: AnsiString;
     mBackColor: array[0..ClrIdxMax] of TGxRGBA;
     mTextColor: array[0..ClrIdxMax] of TGxRGBA;
@@ -106,6 +107,8 @@ type
 
     procedure calcFullClientSize ();
 
+    procedure drawFrame (gx, gy, resx, thalign: Integer; const text: AnsiString; dbl: Boolean);
+
   protected
     var savedClip: TGxRect; // valid only in `draw*()` calls
     //WARNING! do not call scissor functions outside `.draw*()` API!
@@ -121,8 +124,6 @@ type
     mMaxSize: TLaySize; // maximum size
     mFlex: Integer;
     mHoriz: Boolean;
-    mCanWrap: Boolean;
-    mLineStart: Boolean;
     mHGroup: AnsiString;
     mVGroup: AnsiString;
     mAlign: Integer;
@@ -143,9 +144,7 @@ type
     //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?
-    function canWrap (): 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
     function getAlign (): 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
     function getHGroup (): AnsiString; inline; // empty: not grouped
@@ -161,8 +160,6 @@ type
     property flMaxSize: TLaySize read mMaxSize write mMaxSize;
     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;
@@ -249,7 +246,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;
@@ -373,17 +370,9 @@ type
 
     function parseProperty (const prname: AnsiString; par: TTextParser): Boolean; override;
 
-    procedure drawControl (gx, gy: Integer); override;
-  end;
-
-  TUIHLine = class(TUILine)
-  public
-    procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
-  end;
+    procedure layPrepare (); override; // called before registering control in layouter
 
-  TUIVLine = class(TUILine)
-  public
-    procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
+    procedure drawControl (gx, gy: Integer); override;
   end;
 
   // ////////////////////////////////////////////////////////////////////// //
@@ -449,18 +438,42 @@ type
 
   // ////////////////////////////////////////////////////////////////////// //
   TUIButton = class(TUITextLabel)
+  protected
+    mSkipLayPrepare: Boolean;
+    mShadowSize: Integer;
+    mAddMarkers: Boolean;
+    mHideMarkers: Boolean;
+    mPushed: Boolean;
+
   protected
     procedure setText (const s: AnsiString); override;
 
+    procedure cacheStyle (root: TUIStyle); override;
+
   public
     procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
 
+    procedure layPrepare (); override; // called before registering control in layouter
+
     procedure drawControl (gx, gy: Integer); override;
 
     procedure mouseEvent (var ev: THMouseEvent); override;
     procedure keyEvent (var ev: THKeyEvent); override;
   end;
 
+  // ////////////////////////////////////////////////////////////////////// //
+  TUIButtonRound = class(TUIButton)
+  protected
+    procedure setText (const s: AnsiString); override;
+
+  public
+    procedure AfterConstruction (); override; // so it will be correctly initialized when created from parser
+
+    procedure layPrepare (); override; // called before registering control in layouter
+
+    procedure drawControl (gx, gy: Integer); override;
+  end;
+
   // ////////////////////////////////////////////////////////////////////// //
   TUISwitchBox = class(TUITextLabel)
   protected
@@ -636,6 +649,7 @@ begin
   if (ctl = nil) then exit;
   lay := TFlexLayouter.Create();
   try
+    if (not ctl.mStyleLoaded) then ctl.updateStyle();
     if (ctl is TUITopWindow) and (TUITopWindow(ctl).fitToScreen) then TUITopWindow(ctl).flFitToScreen();
 
     lay.setup(ctl);
@@ -816,7 +830,7 @@ begin
   if (Length(uiTopList) > 0) then uiTopList[High(uiTopList)].blurred();
   SetLength(uiTopList, Length(uiTopList)+1);
   uiTopList[High(uiTopList)] := ctl;
-  ctl.updateStyle();
+  if (not ctl.mStyleLoaded) then ctl.updateStyle();
   ctl.activated();
 end;
 
@@ -890,20 +904,19 @@ begin
   actionCB := nil;
   // layouter interface
   //mDefSize := TLaySize.Create(64, uiContext.charHeight(' ')); // default size
-  mDefSize := TLaySize.Create(0, 0); // default size
+  mDefSize := TLaySize.Create(0, 0); // default size: hidden control
   mMaxSize := TLaySize.Create(-1, -1); // maximum size
   mPadding := TLaySize.Create(0, 0);
   mNoPad := false;
   mFlex := 0;
   mHoriz := true;
-  mCanWrap := false;
-  mLineStart := false;
   mHGroup := '';
   mVGroup := '';
   mStyleId := '';
   mCtl4Style := '';
   mAlign := -1; // left/top
   mExpand := false;
+  mStyleLoaded := false;
 end;
 
 
@@ -962,6 +975,7 @@ begin
   if (stl = nil) then stl := uiFindStyle(''); // default
   cacheStyle(stl);
   for ctl in mChildren do ctl.updateStyle();
+  mStyleLoaded := true;
 end;
 
 procedure TUIControl.cacheStyle (root: TUIStyle);
@@ -1000,9 +1014,7 @@ function TUIControl.getMaxSize (): TLaySize; inline; begin result := mLayMaxSize
 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;
-function TUIControl.canWrap (): Boolean; inline; begin result := mCanWrap; end;
 function TUIControl.noPad (): Boolean; inline; begin result := mNoPad; end;
-function TUIControl.isLineStart (): Boolean; inline; begin result := mLineStart; end;
 function TUIControl.getAlign (): Integer; inline; begin result := mAlign; end;
 function TUIControl.getExpand (): Boolean; inline; begin result := mExpand; end;
 function TUIControl.getHGroup (): AnsiString; inline; begin result := mHGroup; end;
@@ -1019,14 +1031,23 @@ begin
   end;
   mWidth := asize.w;
   mHeight := asize.h;
+  if (mLayMaxSize.w >= 0) then mWidth := nmin(mWidth, mLayMaxSize.w);
+  if (mLayMaxSize.h >= 0) then mHeight := nmin(mHeight, mLayMaxSize.h);
 end;
 
 procedure TUIControl.layPrepare ();
 begin
   mLayDefSize := mDefSize;
-  mLayMaxSize := mMaxSize;
-  if (mLayMaxSize.w >= 0) then mLayMaxSize.w += mFrameWidth*2;
-  if (mLayMaxSize.h >= 0) then mLayMaxSize.h += mFrameHeight*2;
+  if (mLayDefSize.w <> 0) or (mLayDefSize.h <> 0) then
+  begin
+    mLayMaxSize := mMaxSize;
+    if (mLayMaxSize.w >= 0) then begin mLayDefSize.w += mFrameWidth*2; mLayMaxSize.w += mFrameWidth*2; end;
+    if (mLayMaxSize.h >= 0) then begin mLayDefSize.h += mFrameHeight*2; mLayMaxSize.h += mFrameHeight*2; end;
+  end
+  else
+  begin
+    mLayMaxSize := TLaySize.Create(0, 0);
+  end;
 end;
 
 
@@ -1267,8 +1288,6 @@ begin
   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;
   if (strEquCI1251(prname, 'expand')) then begin mExpand := parseBool(par); exit; end;
   // align
   if (strEquCI1251(prname, 'align')) then begin mAlign := parseAnyAlign(par); exit; end;
@@ -1490,7 +1509,7 @@ begin
     mParent.getDrawRect(gx, gy, wdt, hgt);
     if (wdt > 0) and (hgt > 0) then
     begin
-      if not intersectRect(gx, gy, wdt, hgt, cgx, cgy, mWidth, mHeight) then
+      if (not intersectRect(gx, gy, wdt, hgt, cgx, cgy, mWidth, mHeight)) then
       begin
         wdt := 0;
         hgt := 0;
@@ -1858,6 +1877,79 @@ end;
 
 
 // ////////////////////////////////////////////////////////////////////////// //
+procedure TUIControl.drawFrame (gx, gy, resx, thalign: Integer; const text: AnsiString; dbl: Boolean);
+var
+  cidx, tx, tw: Integer;
+begin
+  if (mFrameWidth < 1) or (mFrameHeight < 1) then exit;
+  cidx := getColorIndex;
+  uiContext.color := mFrameColor[cidx];
+  case mFrameHeight of
+    8:
+      begin
+        if dbl then
+        begin
+          uiContext.rect(gx+3, gy+3, mWidth-6, mHeight-6);
+          uiContext.rect(gx+5, gy+5, mWidth-10, mHeight-10);
+        end
+        else
+        begin
+          uiContext.rect(gx+4, gy+4, mWidth-8, mHeight-8);
+        end;
+      end;
+    14:
+      begin
+        if dbl then
+        begin
+          uiContext.rect(gx+3, gy+3+3, mWidth-6, mHeight-6-6);
+          uiContext.rect(gx+5, gy+5+3, mWidth-10, mHeight-10-6);
+        end
+        else
+        begin
+          uiContext.rect(gx+4, gy+4+3, mWidth-8, mHeight-8-6);
+        end;
+      end;
+    16:
+      begin
+        if dbl then
+        begin
+          uiContext.rect(gx+3, gy+3+4, mWidth-6, mHeight-6-8);
+          uiContext.rect(gx+5, gy+5+4, mWidth-10, mHeight-10-8);
+        end
+        else
+        begin
+          uiContext.rect(gx+4, gy+4+4, mWidth-8, mHeight-8-8);
+        end;
+      end;
+    else
+      begin
+        //TODO!
+        if dbl then
+        begin
+        end
+        else
+        begin
+        end;
+      end;
+  end;
+
+  // title
+  if (Length(text) > 0) then
+  begin
+    if (resx < 0) then resx := 0;
+    tw := uiContext.textWidth(text);
+    setScissor(mFrameWidth+resx, 0, mWidth-mFrameWidth*2-resx, mFrameHeight);
+         if (thalign < 0) then tx := gx+resx+mFrameWidth+2
+    else if (thalign > 0) then tx := gx+mWidth-mFrameWidth-1-tw
+    else tx := (gx+resx+mFrameWidth)+(mWidth-mFrameWidth*2-resx-tw) div 2;
+    uiContext.color := mBackColor[cidx];
+    uiContext.fillRect(tx-2, gy, tw+4, mFrameHeight);
+    uiContext.color := mFrameTextColor[cidx];
+    uiContext.drawText(tx, gy, text);
+  end;
+end;
+
+
 procedure TUIControl.draw ();
 var
   f: Integer;
@@ -1866,12 +1958,13 @@ var
   procedure resetScissor (fullArea: Boolean); inline;
   begin
     uiContext.clip := savedClip;
-    if (fullArea) then
+    if (fullArea) or ((mFrameWidth = 0) and (mFrameHeight = 0)) then
     begin
       setScissor(0, 0, mWidth, mHeight);
     end
     else
     begin
+      //writeln('frm: (', mFrameWidth, 'x', mFrameHeight, ')');
       setScissor(mFrameWidth, mFrameHeight, mWidth-mFrameWidth*2, mHeight-mFrameHeight*2);
     end;
   end;
@@ -1887,6 +1980,28 @@ begin
     resetScissor(false); // client area
     for f := 0 to High(mChildren) do mChildren[f].draw();
     resetScissor(true); // full area
+    if (self is TUISwitchBox) then
+    begin
+      uiContext.color := TGxRGBA.Create(255, 0, 0, 255);
+      //uiContext.fillRect(gx, gy, mWidth, mHeight);
+      //writeln('frm: (', mFrameWidth, 'x', mFrameHeight, '); sz=(', mWidth, 'x', mHeight, '); clip=', uiContext.clip.toString);
+    end;
+    if false and (mId = 'cbtest') then
+    begin
+      uiContext.color := TGxRGBA.Create(255, 127, 0, 96);
+      uiContext.fillRect(gx, gy, mWidth, mHeight);
+      if (mFrameWidth > 0) and (mFrameHeight > 0) then
+      begin
+        uiContext.color := TGxRGBA.Create(255, 255, 0, 96);
+        uiContext.fillRect(gx+mFrameWidth, gy+mFrameHeight, mWidth-mFrameWidth*2, mHeight-mFrameHeight*2);
+      end;
+    end
+    else if false and (self is TUISwitchBox) then
+    begin
+      uiContext.color := TGxRGBA.Create(255, 0, 0, 255);
+      uiContext.fillRect(gx, gy, mWidth, mHeight);
+      //writeln('frm: (', mFrameWidth, 'x', mFrameHeight, ')');
+    end;
     drawControlPost(gx, gy);
   finally
     uiContext.clip := savedClip;
@@ -2045,7 +2160,7 @@ begin
   inherited;
   mFitToScreen := true;
   mFrameWidth := 8;
-  mFrameHeight := 8;
+  mFrameHeight := uiContext.charHeight(#184);
   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
@@ -2062,6 +2177,8 @@ begin
   mInClose := false;
   closeCB := nil;
   mCtl4Style := 'window';
+  mDefSize.w := nmax(1, mDefSize.w);
+  mDefSize.h := nmax(1, mDefSize.h);
 end;
 
 
@@ -2118,58 +2235,46 @@ begin
   uiContext.fillRect(gx, gy, mWidth, mHeight);
 end;
 
-
 procedure TUITopWindow.drawControlPost (gx, gy: Integer);
 var
   cidx: Integer;
-  tx, hgt, sbhgt, iwdt: Integer;
+  hgt, sbhgt, iwdt, ihgt: Integer;
 begin
   cidx := getColorIndex;
+  iwdt := uiContext.iconWinWidth(TGxContext.TWinIcon.Close);
   if (mDragScroll = TXMode.Drag) then
   begin
-    uiContext.color := mFrameColor[cidx];
-    uiContext.rect(gx+4, gy+4, mWidth-8, mHeight-8);
+    //uiContext.color := mFrameColor[cidx];
+    drawFrame(gx, gy, iwdt, 0, mTitle, false);
   end
   else
   begin
-    uiContext.color := mFrameColor[cidx];
-    uiContext.rect(gx+3, gy+3, mWidth-6, mHeight-6);
-    uiContext.rect(gx+5, gy+5, mWidth-10, mHeight-10);
+    ihgt := uiContext.iconWinHeight(TGxContext.TWinIcon.Close);
+    //uiContext.color := mFrameColor[cidx];
+    drawFrame(gx, gy, iwdt, 0, mTitle, true);
     // 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;
-      uiContext.fillRect(gx+mWidth-mFrameWidth+1, gy+7, mFrameWidth-3, sbhgt);
+      uiContext.fillRect(gx+mWidth-mFrameWidth+1, gy+mFrameHeight-1, 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);
-        uiContext.darkenRect(gx+mWidth-mFrameWidth+1, gy+7+hgt, mFrameWidth-3, sbhgt, 128);
+        setScissor(mWidth-mFrameWidth+1, mFrameHeight-1, mFrameWidth-3, sbhgt);
+        uiContext.darkenRect(gx+mWidth-mFrameWidth+1, gy+mFrameHeight-1+hgt, mFrameWidth-3, sbhgt, 128);
       end;
     end;
     // frame icon
-    iwdt := uiContext.iconWinWidth(TGxContext.TWinIcon.Close);
-    setScissor(mFrameWidth, 0, iwdt, 8);
+    setScissor(mFrameWidth, 0, iwdt, ihgt);
     uiContext.color := mBackColor[cidx];
-    uiContext.fillRect(gx+mFrameWidth, gy, iwdt, 8);
+    uiContext.fillRect(gx+mFrameWidth, gy, iwdt, ihgt);
     uiContext.color := mFrameIconColor[cidx];
     uiContext.drawIconWin(TGxContext.TWinIcon.Close, gx+mFrameWidth, gy, mInClose);
   end;
-  // title
-  if (Length(mTitle) > 0) then
-  begin
-    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);
 end;
@@ -2260,7 +2365,7 @@ begin
   begin
     if (ev.press) then
     begin
-      if (ly < 8) then
+      if (ly < mFrameHeight) then
       begin
         uiGrabCtl := self;
         if (lx >= mFrameWidth) and (lx < mFrameWidth+uiContext.iconWinWidth(TGxContext.TWinIcon.Close)) then
@@ -2357,6 +2462,7 @@ begin
   mCanFocus := false;
   mHAlign := -1; // left
   mCtl4Style := 'box';
+  mDefSize := TLaySize.Create(-1, -1);
 end;
 
 
@@ -2370,7 +2476,7 @@ end;
 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 begin mFrameWidth := 8; mFrameHeight := uiContext.charHeight(#184); end else begin mFrameWidth := 0; mFrameHeight := 0; end;
   if (mHasFrame) then mNoPad := true;
 end;
 
@@ -2420,26 +2526,29 @@ begin
   cidx := getColorIndex;
   uiContext.color := mBackColor[cidx];
   uiContext.fillRect(gx, gy, mWidth, mHeight);
-  if mHasFrame then
+  if (mHasFrame) then
   begin
     // draw frame
-    uiContext.color := mFrameColor[cidx];
-    uiContext.rect(gx+3, gy+3, mWidth-6, mHeight-6);
-  end;
-  // draw caption
-  if (Length(mCaption) > 0) then
+    drawFrame(gx, gy, 0, -1, mCaption, false);
+    //uiContext.color := mFrameColor[cidx];
+    //uiContext.rect(gx+3, gy+3, mWidth-6, mHeight-6);
+  end
+  else if (Length(mCaption) > 0) then
   begin
+    // draw caption
          if (mHAlign < 0) then xpos := 3
     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
+    setScissor(mFrameWidth+1, 0, mWidth-mFrameWidth-2, uiContext.textHeight(mCaption));
+    {
+    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;
@@ -2518,6 +2627,7 @@ begin
   mCanFocus := false;
   mNoPad := true;
   mCtl4Style := 'span';
+  mDefSize := TLaySize.Create(-1, -1);
 end;
 
 
@@ -2541,6 +2651,7 @@ begin
   mExpand := true;
   mCanFocus := false;
   mCtl4Style := 'line';
+  mDefSize := TLaySize.Create(-1, -1);
 end;
 
 
@@ -2551,6 +2662,23 @@ begin
 end;
 
 
+procedure TUILine.layPrepare ();
+begin
+  inherited layPrepare();
+  if (mParent <> nil) then mHoriz := not mParent.mHoriz;
+  if (mHoriz) then
+  begin
+    if (mLayDefSize.w < 0) then mLayDefSize.w := 1;
+    if (mLayDefSize.h < 0) then mLayDefSize.h := 7;
+  end
+  else
+  begin
+    if (mLayDefSize.w < 0) then mLayDefSize.w := 7;
+    if (mLayDefSize.h < 0) then mLayDefSize.h := 1;
+  end;
+end;
+
+
 procedure TUILine.drawControl (gx, gy: Integer);
 var
   cidx: Integer;
@@ -2562,24 +2690,6 @@ begin
 end;
 
 
-// ////////////////////////////////////////////////////////////////////////// //
-procedure TUIHLine.AfterConstruction ();
-begin
-  inherited;
-  mHoriz := true;
-  mDefSize.h := 7;
-end;
-
-
-// ////////////////////////////////////////////////////////////////////////// //
-procedure TUIVLine.AfterConstruction ();
-begin
-  inherited;
-  mHoriz := false;
-  mDefSize.w := 7;
-end;
-
-
 // ////////////////////////////////////////////////////////////////////////// //
 procedure TUIStaticText.AfterConstruction ();
 begin
@@ -2590,7 +2700,6 @@ begin
   mHoriz := true; // nobody cares
   mHeader := false;
   mLine := false;
-  mDefSize.h := uiContext.charHeight(' ');
   mCtl4Style := 'static';
 end;
 
@@ -2685,7 +2794,6 @@ begin
   mHAlign := -1;
   mVAlign := 0;
   mCanFocus := false;
-  mDefSize := TLaySize.Create(uiContext.textWidth(mText), uiContext.textHeight(mText));
   mCtl4Style := 'label';
   mLinkId := '';
 end;
@@ -2851,51 +2959,182 @@ end;
 procedure TUIButton.AfterConstruction ();
 begin
   inherited;
-  mHAlign := -1;
+  mHAlign := 0;
   mVAlign := 0;
+  mShadowSize := 0;
   mCanFocus := true;
-  mDefSize := TLaySize.Create(uiContext.textWidth(mText)+8*2, uiContext.textHeight(mText)+2);
+  mDefSize := TLaySize.Create(uiContext.textWidth(mText)+uiContext.textWidth('[  ]'), uiContext.textHeight(mText));
   mCtl4Style := 'button';
+  mSkipLayPrepare := false;
+  mAddMarkers := false;
+  mHideMarkers := false;
+end;
+
+
+procedure TUIButton.cacheStyle (root: TUIStyle);
+var
+  sz: Integer = 0;
+begin
+  inherited cacheStyle(root);
+  // shadow size
+  sz := nmax(0, root.get('shadow-size', 'active', mCtl4Style).asInt(0));
+  sz := nmax(sz, root.get('shadow-size', 'disabled', mCtl4Style).asInt(0));
+  sz := nmax(sz, root.get('shadow-size', 'inactive', mCtl4Style).asInt(0));
+  mShadowSize := sz;
+  // markers mode
+  mAddMarkers := root.get('add-markers', 'active', mCtl4Style).asBool(false);
+  mAddMarkers := mAddMarkers or root.get('add-markers', 'disabled', mCtl4Style).asBool(false);
+  mAddMarkers := mAddMarkers or root.get('add-markers', 'inactive', mCtl4Style).asBool(false);
+  // hide markers?
+  mHideMarkers := root.get('hide-markers', 'active', mCtl4Style).asBool(false);
+  mHideMarkers := mHideMarkers or root.get('hide-markers', 'disabled', mCtl4Style).asBool(false);
+  mHideMarkers := mHideMarkers or root.get('hide-markers', 'inactive', mCtl4Style).asBool(false);
 end;
 
 
 procedure TUIButton.setText (const s: AnsiString);
 begin
   inherited setText(s);
-  mDefSize := TLaySize.Create(uiContext.textWidth(mText)+8*2, uiContext.textHeight(mText)+2);
+  if (mHideMarkers) then
+  begin
+    mDefSize := TLaySize.Create(uiContext.textWidth(mText)+10, uiContext.textHeight(mText));
+  end
+  else if (mAddMarkers) then
+  begin
+    mDefSize := TLaySize.Create(uiContext.textWidth(mText)+uiContext.textWidth('[<>]'), uiContext.textHeight(mText));
+  end
+  else
+  begin
+    mDefSize := TLaySize.Create(uiContext.textWidth(mText)+uiContext.textWidth('<>'), uiContext.textHeight(mText));
+  end;
+end;
+
+
+procedure TUIButton.layPrepare ();
+var
+  ods: TLaySize;
+  ww: Integer;
+begin
+  if (not mSkipLayPrepare) then
+  begin
+    ods := mDefSize;
+    if (ods.w <> 0) or (ods.h <> 0) then
+    begin
+      mDefSize := TLaySize.Create(uiContext.textWidth(mText), uiContext.textHeight(mText));
+      if (mHideMarkers) then
+      begin
+        ww := 10;
+      end
+      else if (mAddMarkers) then
+      begin
+             if (mDefault) then ww := uiContext.textWidth('[<  >]')
+        else if (mCancel) then ww := uiContext.textWidth('[{  }]')
+        else ww := uiContext.textWidth('[  ]');
+      end
+      else
+      begin
+        ww := nmax(0, uiContext.textWidth('<  >'));
+        ww := nmax(ww, uiContext.textWidth('{  }'));
+        ww := nmax(ww, uiContext.textWidth('[  ]'));
+      end;
+      mDefSize.w += ww+mShadowSize;
+      mDefSize.h += mShadowSize;
+    end;
+  end
+  else
+  begin
+    ods := TLaySize.Create(0, 0); // fpc is dumb!
+  end;
+  inherited layPrepare();
+  if (not mSkipLayPrepare) then mDefSize := ods;
 end;
 
 
 procedure TUIButton.drawControl (gx, gy: Integer);
 var
-  xpos, ypos: Integer;
+  wdt, hgt: Integer;
+  xpos, ypos, xofsl, xofsr{, sofs}: Integer;
   cidx: Integer;
+  lch, rch: AnsiChar;
+  lstr, rstr: AnsiString;
 begin
   cidx := getColorIndex;
 
+  wdt := mWidth-mShadowSize;
+  hgt := mHeight-mShadowSize;
+  if (mPushed) {or (cidx = ClrIdxActive)} then
+  begin
+    //sofs := mShadowSize;
+    gx += mShadowSize;
+    gy += mShadowSize;
+  end
+  else
+  begin
+    //sofs := 0;
+    if (mShadowSize > 0) then
+    begin
+      uiContext.darkenRect(gx+mShadowSize, gy+hgt, wdt, mShadowSize, 96);
+      uiContext.darkenRect(gx+wdt, gy+mShadowSize, mShadowSize, hgt-mShadowSize, 96);
+    end;
+  end;
+
   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);
+  //setScissor(sofs, sofs, wdt, hgt);
+  uiContext.fillRect(gx, gy, wdt, hgt);
 
-  if (Length(mText) > 0) then
+       if (mVAlign < 0) then ypos := 0
+  else if (mVAlign > 0) then ypos := hgt-uiContext.textHeight(mText)
+  else ypos := (hgt-uiContext.textHeight(mText)) div 2;
+  ypos += gy;
+
+  uiContext.color := mTextColor[cidx];
+
+  if (mHideMarkers) then
   begin
-         if (mHAlign < 0) then xpos := 0
-    else if (mHAlign > 0) then xpos := mWidth-uiContext.textWidth(mText)
-    else xpos := (mWidth-uiContext.textWidth(mText)) div 2;
+    xofsl := 5;
+    xofsr := 5;
+  end
+  else
+  begin
+    if (mAddMarkers) then
+    begin
+           if (mDefault) then begin lstr := '[< '; rstr := ' >]'; end
+      else if (mCancel) then begin lstr := '[{ '; rstr := ' }]'; end
+      else begin lstr := '[ '; rstr := ' ]'; end;
+      xofsl := uiContext.textWidth(lstr);
+      xofsr := uiContext.textWidth(rstr);
+      uiContext.drawText(gx, ypos, lstr);
+      uiContext.drawText(gx+wdt-uiContext.textWidth(rstr), ypos, rstr);
+    end
+    else
+    begin
+      xofsl := nmax(0, uiContext.textWidth('< '));
+      xofsl := nmax(xofsl, uiContext.textWidth('{ '));
+      xofsl := nmax(xofsl, uiContext.textWidth('[ '));
+      xofsr := nmax(0, uiContext.textWidth(' >'));
+      xofsr := nmax(xofsr, uiContext.textWidth(' }'));
+      xofsr := nmax(xofsr, uiContext.textWidth(' ]'));
+           if (mDefault) then begin lch := '<'; rch := '>'; end
+      else if (mCancel) then begin lch := '{'; rch := '}'; end
+      else begin lch := '['; rch := ']'; end;
+      uiContext.drawChar(gx, ypos, lch);
+      uiContext.drawChar(gx+wdt-uiContext.charWidth(rch), ypos, rch);
+    end;
+  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;
+  if (Length(mText) > 0) then
+  begin
+    if (mHAlign < 0) then xpos := 0
+    else begin xpos := wdt-xofsl-xofsr-uiContext.textWidth(mText); if (mHAlign = 0) then xpos := xpos div 2; end;
+    xpos += xofsl;
 
-    setScissor(8, 0, mWidth-16, mHeight);
-    uiContext.color := mTextColor[cidx];
-    uiContext.drawText(gx+xpos+8, gy+ypos, mText);
+    //setScissor(xofsl+sofs, sofs, wdt-xofsl-xofsr, hgt);
+    uiContext.drawText(gx+xpos, ypos, mText);
 
     if (mHotChar <> #0) and (mHotChar <> ' ') then
     begin
       uiContext.color := mHotColor[cidx];
-      uiContext.drawChar(gx+xpos+8+mHotOfs, gy+ypos, mHotChar);
+      uiContext.drawChar(gx+xpos+mHotOfs, ypos, mHotChar);
     end;
   end;
 end;
@@ -2909,13 +3148,16 @@ begin
   if (uiGrabCtl = self) then
   begin
     ev.eat();
-    if (ev = '-lmb') and focused and toLocal(ev.x, ev.y, lx, ly) then
+    mPushed := toLocal(ev.x, ev.y, lx, ly);
+    if (ev = '-lmb') and focused and mPushed then
     begin
+      mPushed := false;
       doAction();
     end;
     exit;
   end;
   if (ev.eaten) or (ev.cancelled) or (not enabled) or not focused then exit;
+  mPushed := true;
   ev.eat();
 end;
 
@@ -2935,6 +3177,75 @@ begin
 end;
 
 
+// ////////////////////////////////////////////////////////////////////////// //
+procedure TUIButtonRound.AfterConstruction ();
+begin
+  inherited;
+  mHAlign := -1;
+  mVAlign := 0;
+  mCanFocus := true;
+  mDefSize := TLaySize.Create(uiContext.textWidth(mText)+8*2, uiContext.textHeight(mText)+2);
+  mCtl4Style := 'button-round';
+  mSkipLayPrepare := true;
+end;
+
+
+procedure TUIButtonRound.setText (const s: AnsiString);
+begin
+  inherited setText(s);
+  mDefSize := TLaySize.Create(uiContext.textWidth(mText)+8*2, uiContext.textHeight(mText)+2);
+end;
+
+
+procedure TUIButtonRound.layPrepare ();
+var
+  ods: TLaySize;
+begin
+  ods := mDefSize;
+  if (ods.w <> 0) or (ods.h <> 0) then
+  begin
+    mDefSize := TLaySize.Create(uiContext.textWidth(mText)+8*2, uiContext.textHeight(mText)+2);
+  end;
+  inherited layPrepare();
+  mDefSize := ods;
+end;
+
+
+procedure TUIButtonRound.drawControl (gx, gy: Integer);
+var
+  xpos, ypos: Integer;
+  cidx: Integer;
+begin
+  cidx := getColorIndex;
+
+  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-uiContext.textWidth(mText)
+    else xpos := (mWidth-uiContext.textWidth(mText)) div 2;
+
+         if (mVAlign < 0) then ypos := 0
+    else if (mVAlign > 0) then ypos := mHeight-uiContext.textHeight(mText)
+    else ypos := (mHeight-uiContext.textHeight(mText)) div 2;
+
+    setScissor(8, 0, mWidth-16, mHeight);
+    uiContext.color := mTextColor[cidx];
+    uiContext.drawText(gx+xpos+8, gy+ypos, mText);
+
+    if (mHotChar <> #0) and (mHotChar <> ' ') then
+    begin
+      uiContext.color := mHotColor[cidx];
+      uiContext.drawChar(gx+xpos+8+mHotOfs, gy+ypos, mHotChar);
+    end;
+  end;
+end;
+
+
 // ////////////////////////////////////////////////////////////////////////// //
 procedure TUISwitchBox.AfterConstruction ();
 begin
@@ -2943,7 +3254,7 @@ begin
   mVAlign := 0;
   mCanFocus := true;
   mIcon := TGxContext.TMarkIcon.Checkbox;
-  mDefSize := TLaySize.Create(uiContext.textWidth(mText)+3+uiContext.iconMarkWidth(mIcon), uiContext.iconMarkHeight(mIcon));
+  mDefSize := TLaySize.Create(uiContext.textWidth(mText)+3+uiContext.iconMarkWidth(mIcon), nmax(uiContext.iconMarkHeight(mIcon), uiContext.textHeight(mText)));
   mCtl4Style := 'switchbox';
   mChecked := false;
   mBoolVar := @mChecked;
@@ -2965,7 +3276,7 @@ end;
 procedure TUISwitchBox.setText (const s: AnsiString);
 begin
   inherited setText(s);
-  mDefSize := TLaySize.Create(uiContext.textWidth(mText)+3+uiContext.iconMarkWidth(mIcon), uiContext.iconMarkHeight(mIcon));
+  mDefSize := TLaySize.Create(uiContext.textWidth(mText)+3+uiContext.iconMarkWidth(mIcon), nmax(uiContext.iconMarkHeight(mIcon), uiContext.textHeight(mText)));
 end;
 
 
@@ -3000,36 +3311,45 @@ end;
 
 procedure TUISwitchBox.drawControl (gx, gy: Integer);
 var
-  xpos, ypos: Integer;
+  xpos, ypos, iwdt, dy: Integer;
   cidx: Integer;
 begin
   cidx := getColorIndex;
 
+  iwdt := uiContext.iconMarkWidth(mIcon);
        if (mHAlign < 0) then xpos := 0
-  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;
+  else if (mHAlign > 0) then xpos := mWidth-(uiContext.textWidth(mText)+3+iwdt)
+  else xpos := (mWidth-(uiContext.textWidth(mText)+3+iwdt)) div 2;
 
        if (mVAlign < 0) then ypos := 0
-  else if (mVAlign > 0) then ypos := mHeight-uiContext.iconMarkHeight(mIcon)
-  else ypos := (mHeight-uiContext.iconMarkHeight(mIcon)) div 2;
+  else if (mVAlign > 0) then ypos := mHeight-uiContext.textHeight(mText)
+  else ypos := (mHeight-uiContext.textHeight(mText)) div 2;
 
   uiContext.color := mBackColor[cidx];
   uiContext.fillRect(gx, gy, mWidth, mHeight);
 
   uiContext.color := mSwitchColor[cidx];
-  uiContext.drawIconMark(mIcon, gx, gy, checked);
-
-       if (mVAlign < 0) then ypos := 0
-  else if (mVAlign > 0) then ypos := mHeight-uiContext.textHeight(mText)
-  else ypos := (mHeight-uiContext.textHeight(mText)) div 2;
+  if (uiContext.iconMarkHeight(mIcon) < uiContext.textHeight(mText)) then
+  begin
+    case uiContext.textHeight(mText) of
+      14: dy := 2;
+      16: dy := 3;
+      else dy := 1;
+    end;
+    uiContext.drawIconMark(mIcon, gx, gy+ypos+uiContext.textHeight(mText)-uiContext.iconMarkHeight(mIcon)-dy, checked);
+  end
+  else
+  begin
+    uiContext.drawIconMark(mIcon, gx, gy, checked);
+  end;
 
   uiContext.color := mTextColor[cidx];
-  uiContext.drawText(gx+xpos+3+uiContext.iconMarkWidth(mIcon), gy+ypos, mText);
+  uiContext.drawText(gx+xpos+3+iwdt, gy+ypos, mText);
 
   if (mHotChar <> #0) and (mHotChar <> ' ') then
   begin
     uiContext.color := mHotColor[cidx];
-    uiContext.drawChar(gx+xpos+3+uiContext.iconMarkWidth(mIcon)+mHotOfs, gy+ypos, mHotChar);
+    uiContext.drawChar(gx+xpos+3+iwdt+mHotOfs, gy+ypos, mHotChar);
   end;
 end;
 
@@ -3164,10 +3484,10 @@ initialization
   registerCtlClass(TUIHBox, 'hbox');
   registerCtlClass(TUIVBox, 'vbox');
   registerCtlClass(TUISpan, 'span');
-  registerCtlClass(TUIHLine, 'hline');
-  registerCtlClass(TUIVLine, 'vline');
+  registerCtlClass(TUILine, 'line');
   registerCtlClass(TUITextLabel, 'label');
   registerCtlClass(TUIStaticText, 'static');
+  registerCtlClass(TUIButtonRound, 'round-button');
   registerCtlClass(TUIButton, 'button');
   registerCtlClass(TUICheckBox, 'checkbox');
   registerCtlClass(TUIRadioBox, 'radiobox');