浏览代码

layout: compute scroll size and gutter

mattias 4 月之前
父节点
当前提交
75543d2b41
共有 5 个文件被更改,包括 436 次插入195 次删除
  1. 2 2
      src/base/fresnel.controls.pas
  2. 63 32
      src/base/fresnel.dom.pas
  3. 2 4
      src/base/fresnel.edit.pp
  4. 343 149
      src/base/fresnel.layouter.pas
  5. 26 8
      src/base/fresnel.renderer.pas

+ 2 - 2
src/base/fresnel.controls.pas

@@ -369,7 +369,7 @@ begin
   case aMode of
   flmMinWidth,flmMinHeight:
     exit(Default(TFresnelPoint));
-  flmMaxWidth,flmMaxHeight:
+  flmMax:
     begin
       Result.X:=FImage.Width;
       Result.Y:=FImage.Height;
@@ -530,7 +530,7 @@ begin
       end;
       Result:=FMinWidthSize;
     end;
-  flmMaxHeight,flmMaxWidth,flmMinHeight:
+  flmMax,flmMinHeight:
     begin
       if not (flsMaxWidthValid in FLabelStates) then
       begin

+ 63 - 32
src/base/fresnel.dom.pas

@@ -1060,8 +1060,7 @@ type
   TFresnelLayoutMode = (
     flmMinWidth,
     flmMinHeight,
-    flmMaxWidth,
-    flmMaxHeight
+    flmMax
     );
   TFresnelLayoutModes = set of TFresnelLayoutMode;
 
@@ -1081,6 +1080,19 @@ type
     FContainerContentHeightValid: boolean;
     FContainerContentWidth: TFresnelLength;
     FContainerContentWidthValid: boolean;
+  public type
+    TGutterMode = (
+      gmNone, // no gutter
+      gmAlways, // gutter always present
+      gmAuto // gutter allocated if needed
+      );
+    TContentSize = record
+      Width: TFresnelLength;
+      Height: TFresnelLength;
+      ScrollWidth: TFresnelLength;
+      ScrollHeight: TFresnelLength;
+      NeedGutterHorizontal, NeedGutterVertical: boolean;
+    end;
   public
     Container: TFresnelElement;
     SkipLayout: boolean; // e.g. element or ancestor display:none or visibility=collapse
@@ -1096,35 +1108,41 @@ type
     BorderRight: TFresnelLength;
     BorderTop: TFresnelLength;
     BorderBottom: TFresnelLength;
-    ScrollGutterLeft: TFresnelLength;
-    ScrollGutterRight: TFresnelLength;
-    ScrollGutterTop: TFresnelLength;
-    ScrollGutterBottom: TFresnelLength;
     PaddingLeft: TFresnelLength;
     PaddingRight: TFresnelLength;
     PaddingTop: TFresnelLength;
     PaddingBottom: TFresnelLength;
     LineHeight: TFresnelLength;
-    ScrollGutterBothEdges: boolean; // when scrollbar needs gutter space, add space it on both edges
-    ScrollGutterStable: boolean; // classic(not overlay) scrollbars need gutter if overflow is auto, scroll, or hidden, even if the box is not overflowing
-    ScrollGutterWidth: TCSSNumericalID; // 0, none, thin, auto, see attribute 'scrollbar-width'
     CanScrollX: boolean; // overflow-x in hidden, scroll or auto
     CanScrollY: boolean; // overflow-y in hidden, scroll or auto
+    GutterVertical: TGutterMode;
+    GutterHorizontal: TGutterMode;
+    ScrollGutterLeft: TFresnelLength;
+    ScrollGutterRight: TFresnelLength;
+    ScrollGutterTop: TFresnelLength;
+    ScrollGutterBottom: TFresnelLength;
+
+    // scroll values below are only valid if CanScrollX or CanScrollY
+    ScrollGutterBothEdges: boolean; // when scrollbar needs gutter space, add space on both edges
+    ScrollGutterStable: boolean; // true: classic(not overlay) scrollbars need gutter if overflow is auto, scroll, or hidden, even if the box is not overflowing
+    ScrollGutterWidth: TCSSNumericalID; // 0, none, thin, auto, see attribute 'scrollbar-width'
 
+    // Left, Top, Right, Bottom are initialized with the CSS values, then applied depending on position
+    // the result is the MarginBox
     // attributes depending on position:
     //  static: left, top, right, bottom are ignored
     //  relative: relative to layout position. left 10px moves 10px to the right, right 10px moves 10px to the left
     //  absolute: relative to container's contentbox. left 10px moves 10px to the right, right 10px moves 10px to the left
     //  fixed: as absolute, except relative to viewport
     //  sticky: as relative, except to container's non-scrolled contentbox
-    Left: TFresnelLength;
-    Top: TFresnelLength;
-    Right: TFresnelLength;
-    Bottom: TFresnelLength;
+    Left: TFresnelLength; // after computation: margin-boxed
+    Top: TFresnelLength; // after computation: margin-boxed
+    Right: TFresnelLength; // after computation: margin-boxed
+    Bottom: TFresnelLength; // after computation: margin-boxed
 
     // content-boxed (independent of box-sizing):
-    Width: TFresnelLength;
-    Height: TFresnelLength;
+    Width: TFresnelLength; // after computation: content-boxed
+    Height: TFresnelLength; // after computation: content-boxed
     MinWidth: TFresnelLength;
     MinHeight: TFresnelLength;
     MaxWidth: TFresnelLength;
@@ -1134,7 +1152,8 @@ type
 
     constructor Create(AOwner: TComponent); override;
     destructor Destroy; override;
-    function GetIntrinsicContentSize(aMode: TFresnelLayoutMode; aMaxWidth: TFresnelLength = NaN; aMaxHeight: TFresnelLength = NaN): TFresnelPoint; virtual; // ignoring min|max-height
+    function GetIntrinsicContentSize(aMode: TFresnelLayoutMode;
+      aMaxWidth: TFresnelLength = NaN; aMaxHeight: TFresnelLength = NaN): TContentSize; virtual; // ignoring min|max-height
     function GetRoot: TFresnelLayoutNode;
     function FitWidth(aWidth: TFresnelLength): TFresnelLength;
     function FitHeight(aHeight: TFresnelLength): TFresnelLength;
@@ -1583,19 +1602,19 @@ type
   TFresnelViewport = class(TFresnelElement)
   private
     FDomModified: boolean;
+    FDPI: array[boolean] of TFresnelLength;
+    FFocusedElement : TFresnelElement;
     FFontEngine: TFresnelFontEngine;
+    FHeight: TFresnelLength;
     FLayouter: TFresnelLayouter;
     FOnDomChanged: TNotifyEvent;
-    FStylesheetStamp: TCSSNumericalID;
-    FStylesheetResolverStamp: TCSSNumericalID;
-    FStylesheet: TStrings;
-    FDPI: array[boolean] of TFresnelLength;
+    FScrollbarOverlay: boolean;
     FScrollbarThinWidth: array[boolean] of TFresnelLength;
     FScrollbarWidth: array[boolean] of TFresnelLength;
-    FScrollbarOverlay: boolean;
-    FHeight: TFresnelLength;
+    FStylesheet: TStrings;
+    FStylesheetResolverStamp: TCSSNumericalID;
+    FStylesheetStamp: TCSSNumericalID;
     FWidth: TFresnelLength;
-    FFocusedElement : TFresnelElement;
     procedure CSSResolverLog(Sender: TObject; Entry: TCSSResolverLogEntry);
     function GetFocusedElement: TFresnelElement;
     procedure SetFocusedElement(const aValue: TFresnelElement);
@@ -6408,11 +6427,10 @@ begin
 end;
 
 function TFresnelLayoutNode.GetIntrinsicContentSize(aMode: TFresnelLayoutMode;
-  aMaxWidth: TFresnelLength; aMaxHeight: TFresnelLength): TFresnelPoint;
+  aMaxWidth: TFresnelLength; aMaxHeight: TFresnelLength): TContentSize;
 begin
-  Result.X:=0;
-  Result.Y:=0;
-  if aMode=flmMaxHeight then ;
+  Result:=Default(TContentSize);
+  if aMode=flmMax then ;
   if IsNan(aMaxWidth) then ;
   if IsNan(aMaxHeight) then ;
 end;
@@ -6454,15 +6472,20 @@ begin
   BorderRight:=0;
   BorderTop:=0;
   BorderBottom:=0;
-  ScrollGutterLeft:=0;
-  ScrollGutterRight:=0;
-  ScrollGutterTop:=0;
-  ScrollGutterBottom:=0;
   PaddingLeft:=0;
   PaddingRight:=0;
   PaddingTop:=0;
   PaddingBottom:=0;
   LineHeight:=0;
+
+  CanScrollX:=false;
+  CanScrollY:=false;
+  GutterVertical:=gmNone;
+  GutterHorizontal:=gmNone;
+  ScrollGutterLeft:=0;
+  ScrollGutterRight:=0;
+  ScrollGutterTop:=0;
+  ScrollGutterBottom:=0;
   ScrollGutterBothEdges:=false;
   ScrollGutterStable:=false;
   ScrollGutterWidth:=0;
@@ -6478,6 +6501,8 @@ begin
   MinHeight:=NaN;
   MaxWidth:=NaN;
   MaxHeight:=NaN;
+  ScrollWidth:=NaN;
+  ScrollHeight:=NaN;
 
   FContainerContentHeightValid:=false;
   FContainerContentHeight:=NaN;
@@ -7274,6 +7299,8 @@ begin
   FDPI[true]:=FresnelDefaultDPI;
   FScrollbarWidth[False]:=12;
   FScrollbarWidth[true]:=12;
+  FScrollbarThinWidth[False]:=6;
+  FScrollbarThinWidth[true]:=6;
 
   FResolver:=TCSSResolver.Create(nil);
   FResolver.OnLog:=@CSSResolverLog;
@@ -8149,8 +8176,12 @@ end;
 
 function TFresnelElement.GetIntrinsicContentSize(aMode: TFresnelLayoutMode;
   aMaxWidth: TFresnelLength; aMaxHeight: TFresnelLength): TFresnelPoint;
+var
+  aSize: TFresnelLayoutNode.TContentSize;
 begin
-  Result:=LayoutNode.GetIntrinsicContentSize(aMode,aMaxWidth,aMaxHeight);
+  aSize:=LayoutNode.GetIntrinsicContentSize(aMode,aMaxWidth,aMaxHeight);
+  Result.X:=aSize.Width;
+  Result.Y:=aSize.Height;
 end;
 
 function TFresnelElement.GetContainerContentWidth(UseNaNOnFail: boolean): TFresnelLength;

+ 2 - 4
src/base/fresnel.edit.pp

@@ -140,14 +140,12 @@ begin
   lVertPadding:=GetComputedLength(fcaPaddingTop)+GetComputedLength(fcaPaddingBottom);
   Lsize:=LFont.TextSize('W');
   case aMode of
-    flmMaxHeight,
-    flmMinHeight:
+    flmMinHeight,flmMax:
       begin
       LSize.Offset(lHorzPadding,lVertPadding);
       Result:=LSize;
       end;
-    flmMinWidth,
-    flmMaxWidth:
+    flmMinWidth:
       begin
       if MaxLength<>0 then
         lSize.X:=lSize.X*MaxLength

+ 343 - 149
src/base/fresnel.layouter.pas

@@ -35,8 +35,9 @@ type
     type
       TLayoutSize = record
         Mode: TFresnelLayoutMode;
+        WithScroll: boolean;
         MaxWidth, MaxHeight: TFresnelLength;
-        ContentSize: TFresnelPoint;
+        ContentSize: TFresnelLayoutNode.TContentSize;
       end;
       TLayoutSizeArray = array of TLayoutSize;
     var
@@ -47,11 +48,14 @@ type
     procedure Init; virtual; // called after layout node and parent was updated, CSS string values were computed, no used values yet
     procedure DeductUsedLengths(NoChildren: boolean); virtual;
     procedure ClearCachedLayoutSizes; virtual;
-    function GetCachedLayoutSize(aMode: TFresnelLayoutMode; aMaxWidth, aMaxHeight: TFresnelLength; out aSize: TFresnelPoint): boolean; virtual;
-    procedure AddCachedLayoutSize(aMode: TFresnelLayoutMode; aMaxWidth, aMaxHeight: TFresnelLength; const aSize: TFresnelPoint); virtual;
-    function ComputeLayoutContent(aMode: TFresnelLayoutMode; aMaxWidth, aMaxHeight: TFresnelLength; Commit: boolean): TFresnelPoint; virtual; abstract;
+    function GetCachedLayoutSize(aMode: TFresnelLayoutMode; aMaxWidth, aMaxHeight: TFresnelLength;
+      aWithScroll: boolean; out aSize: TFresnelLayoutNode.TContentSize): boolean; virtual;
+    procedure AddCachedLayoutSize(aMode: TFresnelLayoutMode; aMaxWidth, aMaxHeight: TFresnelLength;
+      aWithScroll: boolean; const aSize: TFresnelLayoutNode.TContentSize); virtual;
+    function ComputeLayoutContent(aMode: TFresnelLayoutMode; aMaxWidth, aMaxHeight: TFresnelLength;
+      Commit: boolean): TFresnelPoint; virtual; abstract;
     function GetIntrinsicContentSize(aMode: TFresnelLayoutMode; aMaxWidth: TFresnelLength=NaN;
-      aMaxHeight: TFresnelLength=NaN): TFresnelPoint; virtual;
+      aMaxHeight: TFresnelLength=NaN; aWithScroll: boolean = true): TFresnelLayoutNode.TContentSize; virtual;
     function GetViewport: TFresnelViewport;
   end;
   TFLNodeLayouterClass = class of TFLNodeLayouter;
@@ -66,10 +70,12 @@ type
     procedure ComputeUsedLengths(NoChildren: boolean); virtual;
     procedure ComputeScrollbarGutter; virtual; // compute attribute scrollbar-gutter
     procedure ComputeScrollbarsNoChildren; virtual;
+    procedure GetScrollGutterWidths(Horizontal: boolean; out LT, RB: TFresnelLength);
     procedure AddScrollGutter(Horizontal: boolean);
+    procedure ApplyScrollSize(const aSize: TContentSize);
     procedure DeductUsedLengths(NoChildren: boolean); virtual;
     function GetIntrinsicContentSize(aMode: TFresnelLayoutMode; aMaxWidth: TFresnelLength=NaN;
-      aMaxHeight: TFresnelLength=NaN): TFresnelPoint; override;
+      aMaxHeight: TFresnelLength=NaN): TContentSize; override;
     destructor Destroy; override;
   end;
 
@@ -247,70 +253,81 @@ end;
 
 procedure TFLNodeLayouter.Apply;
 var
-  MaxWidth, MaxHeight, Avail: TFresnelLength;
+  MaxWidth, MaxHeight, aClientWidth, aClientHeight: TFresnelLength;
+  ContSize: TFresnelLayoutNode.TContentSize;
   Size: TFresnelPoint;
-  VP: TFresnelViewport;
-  NeedScrollX, NeedScrollY: Boolean;
+  HasMaxWidth, HasMaxHeight: Boolean;
+begin
+  MaxWidth:=Node.Width;
+  if IsNan(MaxWidth) then
+    MaxWidth:=Node.MaxWidth;
+  HasMaxWidth:=not IsNan(MaxWidth);
 
-  procedure CheckScrollY;
+  MaxHeight:=Node.Height;
+  if IsNan(MaxHeight) then
+    MaxHeight:=Node.MaxWidth;
+  HasMaxHeight:=not IsNan(MaxHeight);
+
+  if Node.CanScrollX or Node.CanScrollY then
   begin
-    if not Node.CanScrollY then exit;
-    Avail:=Node.Height+Node.PaddingTop+Node.PaddingBottom-Node.ScrollGutterTop-Node.ScrollGutterBottom;
-    NeedScrollY:=(Avail<Size.Y);
-    if NeedScrollY and (not VP.ScrollbarsOverlay) and (Node.ScrollGutterRight=0) then
-      Node.AddScrollGutter(false);
-  end;
+    // can scroll
+    if IsNan(Node.ScrollWidth) then
+    begin
+      {$IFDEF VerboseFresnelScrolling}
+      writeln('TFLNodeLayouter.Apply "',Node.Element.Name,'" Before Intrinsic: CanScrollX=',Node.CanScrollX,' CanScrollY=',Node.CanScrollY,' MaxSize=',FloatToCSSStr(MaxWidth),'x',FloatToCSSStr(MaxHeight),' GutterHorz=',Node.GutterHorizontal,' GutterVert=',Node.GutterVertical);
+      {$ENDIF}
+      ContSize:=GetIntrinsicContentSize(flmMax,MaxWidth,MaxHeight);
+      Node.ApplyScrollSize(ContSize);
+      {$IFDEF VerboseFresnelScrolling}
+      writeln('TFLNodeLayouter.Apply "',Node.Element.Name,'" IntrinsicContentSize: Size=',FloatToCSSStr(ContSize.Width),'x',FloatToCSSStr(ContSize.Height),' ScrollSize=',FloatToCSSStr(ContSize.ScrollWidth),'x',FloatToCSSStr(ContSize.ScrollHeight),' NeedGutterHorz=',ContSize.NeedGutterHorizontal,' Vert=',ContSize.NeedGutterVertical,' Gutter:L=',FloatToCSSStr(Node.ScrollGutterLeft),',R=',FloatToCSSStr(Node.ScrollGutterRight),',T=',FloatToCSSStr(Node.ScrollGutterTop),',B=',FloatToCSSStr(Node.ScrollGutterBottom));
+      {$ENDIF}
+    end;
 
-begin
-  // get maximum width/height for content
-  if Node.CanScrollX then
-    MaxWidth:=NaN
-  else begin
-    MaxWidth:=Node.Width;
-    if IsNan(MaxWidth) then
-      MaxWidth:=Node.MaxWidth;
-  end;
+    aClientWidth:=MaxWidth;
+    if HasMaxWidth then
+      aClientWidth:=aClientWidth-Node.ScrollGutterLeft-Node.ScrollGutterRight;
+    aClientHeight:=MaxHeight;
+    if HasMaxHeight then
+      aClientHeight:=aClientHeight-Node.ScrollGutterTop-Node.ScrollGutterBottom;
 
-  if Node.CanScrollY then
-    MaxHeight:=NaN
-  else begin
-    MaxHeight:=Node.Height;
-    if IsNan(MaxHeight) then
-      MaxHeight:=Node.MaxWidth;
-  end;
-
-  // layout content and compute its size
-  //writeln('TFLNodeLayouter.Apply "',Node.Element.Name,'" CanScrollX=',Node.CanScrollX,' CanScrollY=',Node.CanScrollY,' MaxWidth=',FloatToCSSStr(MaxWidth),' MaxHeight=',FloatToCSSStr(MaxHeight));
-  Size:=ComputeLayoutContent(flmMaxWidth,MaxWidth,MaxHeight,true);
-  if IsNan(Node.Width) then
-    Node.Width:=Node.FitWidth(Size.X);
-  if IsNan(Node.Height) then
-    Node.Height:=Node.FitHeight(Size.Y);
-  Node.ScrollWidth:=Max(Size.X,Node.Width);
-  Node.ScrollHeight:=Max(Size.Y,Node.Height);
-
-  // check if scrolling is needed and add scrollbar gutter
-  {$IFDEF VerboseFresnelScrolling}
-  writeln('TFLNodeLayouter.Apply CHECK SCROLL NEEDED: "',Node.Element.Name,'" CanScrollX=',Node.CanScrollX,' CanScrollY=',Node.CanScrollY,' Width=',FloatToCSSStr(Node.Width),' Height=',FloatToCSSStr(Node.Height),' SizeX=',FloatToCSSStr(Size.X),' SizeY=',FloatToCSSStr(Size.Y));
-  {$ENDIF}
-  VP:=Node.Element.Viewport;
-  CheckScrollY;
-  if Node.CanScrollX then
-  begin
-    Avail:=Node.Width+Node.PaddingLeft+Node.PaddingRight-Node.ScrollGutterLeft-Node.ScrollGutterRight;
-    NeedScrollX:=(Avail<Size.X);
-    if NeedScrollX and (not VP.ScrollbarsOverlay) and (Node.ScrollGutterBottom=0) then
+    {$IFDEF VerboseFresnelScrolling}
+    writeln('TFLNodeLayouter.Apply "',Node.Element.Name,'" CanScrollX=',Node.CanScrollX,' CanScrollY=',Node.CanScrollY,' MaxSize=',FloatToCSSStr(MaxWidth),'x',FloatToCSSStr(MaxHeight),' Client=',FloatToCSSStr(aClientWidth),'x',FloatToCSSStr(aClientHeight));
+    {$ENDIF}
+    Size:=ComputeLayoutContent(flmMax,aClientWidth,aClientHeight,true);
+    {$IFDEF VerboseFresnelScrolling}
+    writeln('TFLNodeLayouter.Apply "',Node.Element.Name,'" ContentSize: ',FloatToCSSStr(Size.X),'x',FloatToCSSStr(Size.Y));
+    {$ENDIF}
+    if IsNan(Node.Width) then
     begin
-      Node.AddScrollGutter(true);
-      CheckScrollY;
+      Node.Width:=Size.X;
+      if Node.Width>aClientWidth then
+        Node.Width:=aClientWidth;
     end;
+    if IsNan(Node.Height) then
+    begin
+      Node.Height:=Node.FitHeight(Size.Y);
+      if Node.Height>aClientHeight then
+        Node.Height:=aClientHeight;
+    end;
+    Node.ScrollWidth:=Max(Size.X,aClientWidth);
+    Node.ScrollHeight:=Max(Size.Y,aClientHeight);
     {$IFDEF VerboseFresnelScrolling}
-    writeln('TFLNodeLayouter.Apply "',Node.Element.Name,'" NeedScrollX=',NeedScrollX,' NeedScrollY=',NeedScrollY,' Gutter: Left=',FloatToCSSStr(Node.ScrollGutterLeft),' Right=',FloatToCSSStr(Node.ScrollGutterRight),' Top=',FloatToCSSStr(Node.ScrollGutterTop),' Bottom=',FloatToCSSStr(Node.ScrollGutterBottom));
+    writeln('TFLNodeLayouter.Apply "',Node.Element.Name,'" ScrollSize: ',FloatToCSSStr(Node.ScrollWidth),'x',FloatToCSSStr(Node.ScrollHeight),' Width=',FloatToCSSStr(Node.Width),' Height=',FloatToCSSStr(Node.Height));
     {$ENDIF}
-  end;
 
-  // todo: ScrollLeft, ScrollTop
-  // todo: scroll anchor
+    // todo: ScrollLeft, ScrollTop
+    // todo: scroll anchor
+  end else begin
+    // no scrolling
+    //writeln('TFLNodeLayouter.Apply "',Node.Element.Name,'" no scroll MaxWidth=',FloatToCSSStr(MaxWidth),' MaxHeight=',FloatToCSSStr(MaxHeight));
+    Size:=ComputeLayoutContent(flmMax,MaxWidth,MaxHeight,true);
+    if IsNan(Node.Width) then
+      Node.Width:=Node.FitWidth(Size.X);
+    if IsNan(Node.Height) then
+      Node.Height:=Node.FitHeight(Size.Y);
+    Node.ScrollWidth:=Max(Size.X,Node.Width);
+    Node.ScrollHeight:=Max(Size.Y,Node.Height);
+  end;
 end;
 
 procedure TFLNodeLayouter.Init;
@@ -329,7 +346,8 @@ begin
 end;
 
 function TFLNodeLayouter.GetCachedLayoutSize(aMode: TFresnelLayoutMode; aMaxWidth,
-  aMaxHeight: TFresnelLength; out aSize: TFresnelPoint): boolean;
+  aMaxHeight: TFresnelLength; aWithScroll: boolean; out aSize: TFresnelLayoutNode.TContentSize
+  ): boolean;
 
   function CheckMax(OldMax, NewMax, OldResult: TFresnelLength): boolean;
   begin
@@ -390,26 +408,27 @@ begin
   for i:=0 to length(FLastLayoutSizes)-1 do
   with FLastLayoutSizes[i] do begin
     if (aMode<>Mode) then continue;
+    if (aWithScroll<>WithScroll) then continue;
     case aMode of
     flmMinWidth:
-      if not CheckMax(MaxHeight,aMaxHeight,ContentSize.Y) then continue;
+      if not CheckMax(MaxHeight,aMaxHeight,ContentSize.Height) then continue;
     flmMinHeight:
-      if not CheckMax(MaxWidth,aMaxWidth,ContentSize.X) then continue;
-    flmMaxWidth,flmMaxHeight:
+      if not CheckMax(MaxWidth,aMaxWidth,ContentSize.Width) then continue;
+    flmMax:
       begin
-        if not CheckMax(MaxWidth,aMaxWidth,ContentSize.X) then continue;
-        if not CheckMax(MaxHeight,aMaxHeight,ContentSize.Y) then continue;
+        if not CheckMax(MaxWidth,aMaxWidth,ContentSize.Width) then continue;
+        if not CheckMax(MaxHeight,aMaxHeight,ContentSize.Height) then continue;
       end;
     end;
     aSize:=ContentSize;
     exit(true);
   end;
-  aSize:=Default(TFresnelPoint);
+  aSize:=Default(TFresnelLayoutNode.TContentSize);
   Result:=false;
 end;
 
 procedure TFLNodeLayouter.AddCachedLayoutSize(aMode: TFresnelLayoutMode; aMaxWidth,
-  aMaxHeight: TFresnelLength; const aSize: TFresnelPoint);
+  aMaxHeight: TFresnelLength; aWithScroll: boolean; const aSize: TFresnelLayoutNode.TContentSize);
 var
   l: integer;
 begin
@@ -417,6 +436,7 @@ begin
   SetLength(FLastLayoutSizes,l+1);
   with FLastLayoutSizes[l] do begin
     Mode:=aMode;
+    WithScroll:=aWithScroll;
     MaxWidth:=aMaxWidth;
     MaxHeight:=aMaxHeight;
     ContentSize:=aSize;
@@ -424,18 +444,155 @@ begin
 end;
 
 function TFLNodeLayouter.GetIntrinsicContentSize(aMode: TFresnelLayoutMode;
-  aMaxWidth: TFresnelLength; aMaxHeight: TFresnelLength): TFresnelPoint;
+  aMaxWidth: TFresnelLength; aMaxHeight: TFresnelLength; aWithScroll: boolean): TFresnelLayoutNode.
+  TContentSize;
+var
+  GutterLeft, GutterRight, GutterTop, GutterBottom: TFresnelLength;
+
+  procedure Compute(CurMode: TFresnelLayoutMode; CurMaxWidth, CurMaxHeight: TFresnelLength;
+    AddGutter: boolean);
+  var
+    aSize: TFresnelPoint;
+  begin
+    if not GetCachedLayoutSize(CurMode,CurMaxWidth,CurMaxHeight,false,Result) then
+    begin
+      aSize:=ComputeLayoutContent(CurMode,CurMaxWidth,CurMaxHeight,false);
+      Result:=Default(TFresnelLayoutNode.TContentSize);
+      Result.Width:=aSize.X;
+      Result.Height:=aSize.Y;
+      Result.ScrollWidth:=aSize.X;
+      Result.ScrollHeight:=aSize.Y;
+      AddCachedLayoutSize(CurMode,CurMaxWidth,CurMaxHeight,false,Result);
+    end;
+    if AddGutter then begin
+      Result.Width:=Result.Width+GutterLeft+GutterRight;
+      Result.Height:=Result.Height+GutterTop+GutterBottom;
+    end;
+  end;
+
+var
+  GutterVertical, GutterHorizontal: TFresnelLayoutNode.TGutterMode;
+  HasMaxWidth, HasMaxHeight, Done: Boolean;
+  aClientHeight, aClientWidth: TFresnelLength;
+
+  procedure CheckOverflowX;
+  begin
+    if Node.CanScrollX and HasMaxWidth and (Result.Width>aMaxWidth) then
+    begin
+      // width does not fit and scrolling allowed
+      Result.Width:=aMaxWidth;
+      if GutterHorizontal=gmAuto then
+      begin
+        // auto horizontal gutter needed
+        Result.NeedGutterHorizontal:=true;
+        GutterHorizontal:=gmAlways;
+        Node.GetScrollGutterWidths(true,GutterTop,GutterBottom);
+        aClientHeight:=aClientHeight-GutterTop-GutterBottom;
+        if aClientHeight<0 then aClientHeight:=0;
+        Result.Height:=Result.Height+GutterTop+GutterBottom;
+      end;
+    end;
+  end;
+
+  procedure CheckOverflowY;
+  begin
+    if Node.CanScrollY and HasMaxHeight and (Result.Height>aMaxHeight) then
+    begin
+      // height does not fit and scrolling allowed
+      Result.Height:=aMaxHeight;
+      if GutterVertical=gmAuto then
+      begin
+        // auto vertical gutter needed
+        Result.NeedGutterVertical:=true;
+        GutterVertical:=gmAlways;
+        Node.GetScrollGutterWidths(false,GutterLeft,GutterRight);
+        aClientWidth:=aClientWidth-GutterLeft-GutterRight;
+        if aClientWidth<0 then aClientWidth:=0;
+        Result.Width:=Result.Width+GutterLeft+GutterRight;
+      end;
+    end;
+  end;
+
+var
+  aSize: TFresnelPoint;
 begin
   case aMode of
+    flmMinWidth:  aMaxWidth:=NaN;
+    flmMinHeight: aMaxHeight:=NaN;
+  end;
+
+  GutterVertical:=Node.GutterVertical;
+  GutterHorizontal:=Node.GutterHorizontal;
+  if aWithScroll and (GutterVertical=gmNone) and (GutterHorizontal=gmNone) then
+  begin
+    // no gutters -> simply compute content
+    // Note: Even with scrolling short lines are broken at aMaxWidth, not at ScrollWidth.
+    //       So the layout is the same.
+    aWithScroll:=false;
+  end;
+
+  if GetCachedLayoutSize(aMode,aMaxWidth,aMaxHeight,aWithScroll,Result) then
+    exit;
+
+  Done:=false;
+  if aWithScroll then
+  begin
+    // consider gutters
+    GutterLeft:=Node.ScrollGutterLeft;
+    GutterRight:=Node.ScrollGutterRight;
+    GutterTop:=Node.ScrollGutterTop;
+    GutterBottom:=Node.ScrollGutterBottom;
+
+    HasMaxWidth:=not IsNan(aMaxWidth);
+    aClientWidth:=aMaxWidth;
+    if HasMaxWidth then
+    begin
+      if aMaxWidth<0 then aMaxWidth:=0;
+      aClientWidth:=Max(0,aClientWidth-GutterLeft-GutterRight);
+    end;
+
+    HasMaxHeight:=not IsNan(aMaxHeight);
+    aClientHeight:=aMaxHeight;
+    if HasMaxHeight then
+    begin
+      if aMaxHeight<0 then aMaxHeight:=0;
+      aClientHeight:=Max(0,aClientHeight-GutterTop-GutterBottom);
+    end;
+
+    Compute(aMode,aClientWidth,aClientHeight,true);
+    case aMode of
     flmMinWidth:
-      aMaxWidth:=NaN;
+      CheckOverflowY;
     flmMinHeight:
-      aMaxHeight:=NaN;
+      CheckOverflowX;
+    flmMax:
+      begin
+        // maximize width
+        CheckOverflowX;
+        CheckOverflowY;
+        if HasMaxWidth and (Result.Width>aMaxWidth) and Node.CanScrollX then
+        begin
+          // width no longer fits -> compute again
+          Compute(flmMax,aClientWidth,aClientHeight,true);
+          Result.NeedGutterHorizontal:=true;
+          CheckOverflowX;
+          CheckOverflowY;
+        end;
+      end;
+    end;
+    Done:=true;
   end;
-  if GetCachedLayoutSize(aMode,aMaxWidth,aMaxHeight,Result) then
-    exit;
-  Result:=ComputeLayoutContent(aMode,aMaxWidth,aMaxHeight,false);
-  AddCachedLayoutSize(aMode,aMaxWidth,aMaxHeight,Result);
+
+  if not Done then
+  begin
+    aSize:=ComputeLayoutContent(aMode,aMaxWidth,aMaxHeight,false);
+    Result:=Default(TFresnelLayoutNode.TContentSize);
+    Result.Width:=aSize.X;
+    Result.Height:=aSize.Y;
+    Result.ScrollWidth:=aSize.X;
+    Result.ScrollHeight:=aSize.Y;
+  end;
+  AddCachedLayoutSize(aMode,aMaxWidth,aMaxHeight,aWithScroll,Result);
 end;
 
 function TFLNodeLayouter.GetViewport: TFresnelViewport;
@@ -589,16 +746,26 @@ procedure TUsedLayoutNode.ComputeScrollbarsNoChildren;
 
   procedure CalcGutter(ComputedOverflow: TCSSNumericalID; Horizontal: boolean);
   var
-    GutterNeeded: Boolean;
+    GutterMode: TGutterMode;
   begin
-    GutterNeeded:=false;
+    GutterMode:=gmNone;
     case ComputedOverflow of
     CSSRegistry.kwScroll:
-      GutterNeeded:=true;
-    CSSRegistry.kwAuto,CSSRegistry.kwHidden:
-      GutterNeeded:=ScrollGutterStable;
+      GutterMode:=gmAlways;
+    CSSRegistry.kwAuto:
+      if ScrollGutterStable then
+        GutterMode:=gmAlways
+      else
+        GutterMode:=gmAuto;
+    CSSRegistry.kwHidden:
+      if ScrollGutterStable then
+        GutterMode:=gmAlways;
     end;
-    if GutterNeeded then
+    if Horizontal then
+      GutterHorizontal:=GutterMode
+    else
+      GutterVertical:=GutterMode;
+    if GutterMode=gmAlways then
       AddScrollGutter(Horizontal);
   end;
 
@@ -615,46 +782,72 @@ begin
     ComputeScrollbarGutter;
     if ScrollGutterWidth=CSSIDNone then
       ScrollGutterWidth:=Element.GetComputedKeyword(fcaScrollbarWidth,CSSRegistry.Chk_ScrollbarWidth_KeywordIDs);
-    if ScrollGutterWidth=CSSIDNone then
+    case ScrollGutterWidth of
+    CSSIDNone:
       ScrollGutterWidth:=CSSRegistry.kwAuto;
+    CSSRegistry.kwNone:
+      exit;
+    end;
   end else
     exit;
   {$IFDEF VerboseFresnelScrolling}
-  writeln('TUsedLayoutNode.ComputeScrollbarsNoChildren ',Element.Name,' ComputedOverflowX=',CSSRegistry.Keywords[Element.ComputedOverflowX],' ComputedOverflowY=',CSSRegistry.Keywords[Element.ComputedOverflowY],' BothEdges=',ScrollGutterBothEdges,' Stable=',ScrollGutterStable,' ScrollGutterWidth=',ScrollGutterWidth);
+  writeln('TUsedLayoutNode.ComputeScrollbarsNoChildren ',Element.Name,' ComputedOverflowX=',CSSRegistry.Keywords[Element.ComputedOverflowX],' ComputedOverflowY=',CSSRegistry.Keywords[Element.ComputedOverflowY],' BothEdges=',ScrollGutterBothEdges,' Stable=',ScrollGutterStable,' ScrollGutterWidth=',CSSRegistry.Keywords[ScrollGutterWidth]);
   {$ENDIF}
 
   CalcGutter(Element.ComputedOverflowX,true);
   CalcGutter(Element.ComputedOverflowY,false);
 end;
 
-procedure TUsedLayoutNode.AddScrollGutter(Horizontal: boolean);
+procedure TUsedLayoutNode.GetScrollGutterWidths(Horizontal: boolean; out LT, RB: TFresnelLength);
 var
   VP: TFresnelViewport;
-  GutterW: TFresnelLength;
+  W: TFresnelLength;
 begin
+  LT:=0;
+  RB:=0;
   VP:=Element.Viewport;
+  if VP.ScrollbarsOverlay then exit;
   case ScrollGutterWidth of
-  CSSRegistry.kwThin:
-    GutterW:=VP.ScrollbarsThinWidth[Horizontal];
-  CSSRegistry.kwAuto:
-    GutterW:=VP.ScrollbarsWidth[Horizontal];
+  CSSRegistry.kwNone: exit;
+  CSSRegistry.kwThin: W:=VP.ScrollbarsThinWidth[Horizontal];
   else
-    exit; // none
+    // auto
+    W:=VP.ScrollbarsWidth[Horizontal];
   end;
 
-  if Horizontal then
-    ScrollGutterBottom:=GutterW
-  else
-    ScrollGutterRight:=GutterW;
   if ScrollGutterBothEdges then
   begin
-    if Horizontal then
-      ScrollGutterTop:=GutterW
-    else
-      ScrollGutterLeft:=GutterW;
+    LT:=W;
+    RB:=W;
+  end
+  else if (not Horizontal) and (Element.ComputedDirection=CSSRegistry.kwRTL) then
+    LT:=W
+  else
+    RB:=W;
+end;
+
+procedure TUsedLayoutNode.AddScrollGutter(Horizontal: boolean);
+begin
+  if Horizontal then
+  begin
+    GetScrollGutterWidths(Horizontal,ScrollGutterTop,ScrollGutterBottom);
+    GutterHorizontal:=gmAlways;
+  end else begin
+    GetScrollGutterWidths(Horizontal,ScrollGutterLeft,ScrollGutterRight);
+    GutterVertical:=gmAlways;
   end;
 end;
 
+procedure TUsedLayoutNode.ApplyScrollSize(const aSize: TContentSize);
+begin
+  ScrollWidth:=aSize.ScrollWidth;
+  ScrollHeight:=aSize.ScrollHeight;
+  if (GutterHorizontal=gmAuto) and aSize.NeedGutterHorizontal then
+    AddScrollGutter(true);
+  if (GutterVertical=gmAuto) and aSize.NeedGutterVertical then
+    AddScrollGutter(false);
+end;
+
 procedure TUsedLayoutNode.DeductUsedLengths(NoChildren: boolean);
 var
   aContainerWidth, aContainerHeight: TFresnelLength;
@@ -760,14 +953,12 @@ begin
 end;
 
 function TUsedLayoutNode.GetIntrinsicContentSize(aMode: TFresnelLayoutMode;
-  aMaxWidth: TFresnelLength; aMaxHeight: TFresnelLength): TFresnelPoint;
+  aMaxWidth: TFresnelLength; aMaxHeight: TFresnelLength): TContentSize;
 begin
   if Layouter<>nil then
     Result:=Layouter.GetIntrinsicContentSize(aMode,aMaxWidth,aMaxHeight)
-  else begin
-    Result.X:=0;
-    Result.Y:=0;
-  end;
+  else
+    Result:=Default(TContentSize);
 end;
 
 destructor TUsedLayoutNode.Destroy;
@@ -981,7 +1172,7 @@ begin
     end;
 
     // display-outside: inline or block
-    if (not IsInline) or (aMode in [flmMinWidth,flmMaxHeight]) then
+    if (not IsInline) or (aMode=flmMinWidth) then
     begin
       // start new line
       if FLineItems.Count>0 then
@@ -994,7 +1185,7 @@ begin
     ChildPadBorderX:=ChildNode.BorderLeft+ChildNode.PaddingLeft+ChildNode.PaddingRight+ChildNode.BorderRight;
     ChildPadBorderY:=ChildNode.BorderTop+ChildNode.PaddingTop+ChildNode.PaddingBottom+ChildNode.BorderBottom;
 
-    if aMode in [flmMinWidth,flmMaxHeight] then
+    if aMode=flmMinWidth then
       CurSpace:=0
     else if IsNan(aMaxWidth) then
       CurSpace:=NaN
@@ -1171,7 +1362,7 @@ begin
   Result:=Default(TFresnelPoint);
 
   if Commit then ;
-  if aMode=flmMaxWidth then ;
+  if aMode=flmMax then ;
   if IsNan(aMaxWidth) then ;
   if IsNan(aMaxHeight) then ;
 end;
@@ -1508,7 +1699,7 @@ procedure TFLFlexLayouter.ComputeChildAttributes(Item: TLineItem;
 var
   FlexItem: TFlexItem;
   ItemNode: TUsedLayoutNode;
-  p: TFresnelPoint;
+  Size: TFresnelLayoutNode.TContentSize;
 begin
   inherited ComputeChildAttributes(Item, El);
 
@@ -1534,13 +1725,13 @@ begin
       FlexItem.Basis:=ItemNode.Width;
       if IsNan(FlexItem.Basis) then
       begin
-        p:=El.GetIntrinsicContentSize(flmMaxWidth,ItemNode.MaxWidth,ItemNode.MaxHeight);
-        FlexItem.Basis:=ItemNode.FitWidth(p.X);
+        Size:=ItemNode.GetIntrinsicContentSize(flmMax,ItemNode.MaxWidth,ItemNode.MaxHeight);
+        FlexItem.Basis:=ItemNode.FitWidth(Size.Width);
         if IsNan(FlexItem.CrossContentSize) then
-          FlexItem.CrossContentSize:=ItemNode.FitHeight(p.Y);
+          FlexItem.CrossContentSize:=ItemNode.FitHeight(Size.Height);
       end else if IsNan(FlexItem.CrossContentSize) then begin
-        p:=El.GetIntrinsicContentSize(flmMaxWidth,FlexItem.Basis,ItemNode.MaxHeight);
-        FlexItem.CrossContentSize:=ItemNode.FitHeight(p.Y);
+        Size:=ItemNode.GetIntrinsicContentSize(flmMax,FlexItem.Basis,ItemNode.MaxHeight);
+        FlexItem.CrossContentSize:=ItemNode.FitHeight(Size.Height);
       end;
 
     end else begin
@@ -1553,13 +1744,13 @@ begin
       FlexItem.Basis:=ItemNode.Height;
       if IsNan(FlexItem.Basis) then
       begin
-        p:=El.GetIntrinsicContentSize(flmMaxHeight,ItemNode.MaxWidth,ItemNode.MaxHeight);
-        FlexItem.Basis:=ItemNode.FitWidth(p.Y);
+        Size:=ItemNode.GetIntrinsicContentSize(flmMax,ItemNode.MaxWidth,ItemNode.MaxHeight);
+        FlexItem.Basis:=ItemNode.FitWidth(Size.Height);
         if IsNan(FlexItem.CrossContentSize) then
-          FlexItem.CrossContentSize:=ItemNode.FitWidth(p.X);
+          FlexItem.CrossContentSize:=ItemNode.FitWidth(Size.Width);
       end else if IsNan(FlexItem.CrossContentSize) then begin
-        p:=El.GetIntrinsicContentSize(flmMaxHeight,ItemNode.MaxWidth,FlexItem.Basis);
-        FlexItem.CrossContentSize:=ItemNode.FitWidth(p.X);
+        Size:=ItemNode.GetIntrinsicContentSize(flmMax,ItemNode.MaxWidth,FlexItem.Basis);
+        FlexItem.CrossContentSize:=ItemNode.FitWidth(Size.Width);
       end;
     end;
   end;
@@ -1698,8 +1889,8 @@ var
   Item: TLineItem;
   ChildNode: TUsedLayoutNode;
   ChildEl: TFresnelElement;
-  NewLeft, NewTop: TFresnelLength;
-  r: TFresnelRect;
+  NewLeft, NewTop, FrameLeft, FrameRight, FrameTop, FrameBottom: TFresnelLength;
+  aBorderBox, aContentBox: TFresnelRect;
 begin
   for i:=0 to FLineItems.Count-1 do
   begin
@@ -1733,27 +1924,29 @@ begin
         NewTop:=NewTop-ChildNode.Bottom;
     end;
 
-    // used borderbox
-    r.Left:=NewLeft+ChildNode.MarginLeft;
-    r.Top:=NewTop+ChildNode.MarginTop;
-    r.Right:=r.Left+ChildNode.BorderLeft+ChildNode.PaddingLeft
-             +Item.ContentBoxWidth
-             +ChildNode.PaddingRight+ChildNode.BorderRight;
-    r.Bottom:=r.Top+ChildNode.BorderTop+ChildNode.PaddingTop
-             +Item.ContentBoxHeight
-             +ChildNode.PaddingBottom+ChildNode.BorderBottom;
-    ChildEl.UsedBorderBox:=r;
+    FrameLeft:=ChildNode.BorderLeft+ChildNode.PaddingLeft;
+    FrameRight:=ChildNode.BorderRight+ChildNode.PaddingRight;
+    FrameTop:=ChildNode.BorderTop+ChildNode.PaddingTop;
+    FrameBottom:=ChildNode.BorderBottom+ChildNode.PaddingBottom;
+
+    // UsedBorderBox, UsedContentBox
+    aBorderBox.Left:=NewLeft+ChildNode.MarginLeft;
+    aBorderBox.Top:=NewTop+ChildNode.MarginTop;
+    aContentBox.Left:=aBorderBox.Left+FrameLeft;
+    aContentBox.Top:=aBorderBox.Top+FrameTop;
+    aContentBox.Right:=aContentBox.Left+Item.ContentBoxWidth;
+    aContentBox.Bottom:=aContentBox.Top+Item.ContentBoxHeight;
+    aBorderBox.Right:=aContentBox.Right+FrameRight;
+    aBorderBox.Bottom:=aContentBox.Bottom+FrameBottom;
+
+    ChildEl.UsedBorderBox:=aBorderBox;
+    ChildEl.UsedContentBox:=aContentBox;
     ChildNode.Left:=NewLeft;
     ChildNode.Top:=NewTop;
-
-    // used contentbox
-    r.Left:=r.Left+ChildNode.BorderLeft+ChildNode.PaddingLeft;
-    r.Top:=r.Top+ChildNode.BorderTop+ChildNode.PaddingTop;
-    r.Right:=r.Left+Item.ContentBoxWidth;
-    r.Bottom:=r.Top+Item.ContentBoxHeight;
-    ChildEl.UsedContentBox:=r;
-    ChildNode.Width:=r.Width;
-    ChildNode.Height:=r.Height;
+    ChildNode.Right:=aBorderBox.Right+ChildNode.MarginRight;
+    ChildNode.Bottom:=aBorderBox.Bottom+ChildNode.MarginBottom;
+    ChildNode.Width:=aContentBox.Width;
+    ChildNode.Height:=aContentBox.Height;
 
     {$IFDEF VerboseFresnelPlacing}
     writeln('TFLFlowLayouter.PlaceLineItems '+ChildEl.GetPath+' BorderBox='+ChildEl.UsedBorderBox.ToString);
@@ -1767,10 +1960,12 @@ function TFLLineLayouter.PlaceAbsoluteItem(ChildNode: TUsedLayoutNode; aMode: TF
 // returns right, bottom marginbox, so the parent can compute its needed content size
 var
   ChildEl: TFresnelElement;
-  NewLeft, NewTop, NewRight, NewBottom, NewWidth, NewHeight, FrameLeft, FrameRight,
+  NewLeft, NewTop, NewRight, NewBottom, NewWidth, NewHeight,
+    FrameLeft, FrameRight,
     FrameTop, FrameBottom: TFresnelLength;
-  p: TFresnelPoint;
+  Size: TFresnelLayoutNode.TContentSize;
   r: TFresnelRect;
+  p: TFresnelPoint;
 begin
   Result.X:=0;
   Result.Y:=0;
@@ -1816,18 +2011,18 @@ begin
     if IsNan(NewHeight) then
     begin
       // auto width and height
-      p:=ChildEl.GetIntrinsicContentSize(aMode,ChildNode.MaxWidth,ChildNode.MaxHeight);
-      NewWidth:=ChildNode.FitWidth(p.X);
-      NewHeight:=ChildNode.FitHeight(p.Y);
+      Size:=ChildNode.GetIntrinsicContentSize(aMode,ChildNode.MaxWidth,ChildNode.MaxHeight);
+      NewWidth:=ChildNode.FitWidth(Size.Width);
+      NewHeight:=ChildNode.FitHeight(Size.Height);
     end else begin
       // height set, width auto
-      p:=ChildEl.GetIntrinsicContentSize(aMode,ChildNode.MaxWidth,NewHeight);
-      NewWidth:=ChildNode.FitWidth(p.X);
+      Size:=ChildNode.GetIntrinsicContentSize(aMode,ChildNode.MaxWidth,NewHeight);
+      NewWidth:=ChildNode.FitWidth(Size.Width);
     end;
   end else if IsNan(NewHeight) then begin
     // width set, height auto
-    p:=ChildEl.GetIntrinsicContentSize(aMode,NewWidth,ChildNode.MaxHeight);
-    NewHeight:=ChildNode.FitHeight(p.Y);
+    Size:=ChildNode.GetIntrinsicContentSize(aMode,NewWidth,ChildNode.MaxHeight);
+    NewHeight:=ChildNode.FitHeight(Size.Height);
   end;
 
   FrameLeft:=ChildNode.MarginLeft+ChildNode.BorderLeft+ChildNode.PaddingLeft;
@@ -1874,7 +2069,6 @@ begin
     ChildNode.Width:=NewWidth;
     ChildNode.Height:=NewHeight;
 
-    // used borderbox
     r.Left:=NewLeft+ChildNode.MarginLeft;
     r.Top:=NewTop+ChildNode.MarginTop;
     r.Right:=NewLeft+FrameLeft+NewWidth+FrameRight-ChildNode.MarginRight;
@@ -1999,7 +2193,7 @@ begin
 
   //writeln('TViewportLayouter.Layout ',Node.Element.GetPath,' Width=',FloatToCSSStr(Node.Width),' Height=',FloatToCSSStr(Node.Height),' Layouter=',DbgSName(Node.Layouter));
 
-  // sort for z-index
+  // sort children for z-index
   SortStackingContext(Node);
 
   // compute used layout lengths of children, with access to used grand child lengths

+ 26 - 8
src/base/fresnel.renderer.pas

@@ -511,6 +511,7 @@ begin
 
   //writeln('TFresnelRenderer.DrawElement ',El.Name,' BorderBox=',El.RenderedBorderBox.ToString,' ContentBox=',El.RenderedContentBox.ToString);
 
+  // background and border
   BorderParams:=CreateBorderAndBackground;
   try
     BorderParams.BoundingBox.Box:=aBorderBox;
@@ -548,7 +549,10 @@ begin
     BorderParams.Free;
   end;
 
-  // Give element a chance to draw itself
+  // draw scrollbars
+
+
+  // Give element a chance to draw itself (on top of background and border)
   aRenderable.Render(Self as IFresnelRenderer);
 
   DrawChildren(El);
@@ -579,23 +583,37 @@ end;
 
 procedure TFresnelRenderer.Draw(Viewport: TFresnelViewport);
 var
-  aContentBox: TFresnelRect;
+  aBorderBox, aContentBox: TFresnelRect;
   BackgroundColorFP: TFPColor;
   aRenderable: IFresnelRenderable;
+  LNode: TFresnelLayoutNode;
 begin
   //debugln(['TFresnelRenderer.Draw Origin=',dbgs(Origin)]);
-  aContentBox.Left:=0;
-  aContentBox.Top:=0;
-  aContentBox.Right:=Viewport.Width;
-  aContentBox.Bottom:=Viewport.Height;
-  Viewport.UsedBorderBox:=aContentBox;
+  aBorderBox.Left:=0;
+  aBorderBox.Top:=0;
+  aBorderBox.Right:=Viewport.Width;
+  aBorderBox.Bottom:=Viewport.Height;
+  Viewport.UsedBorderBox:=aBorderBox;
+
+  aContentBox:=aBorderBox;
+  LNode:=Viewport.LayoutNode;
+  if LNode<>nil then
+  begin
+    aContentBox.Left:=LNode.ScrollGutterLeft;
+    aContentBox.Top:=LNode.ScrollGutterTop;
+    aContentBox.Right:=Max(aContentBox.Left,aContentBox.Right-LNode.ScrollGutterRight);
+    aContentBox.Bottom:=Max(aContentBox.Top,aContentBox.Bottom-LNode.ScrollGutterBottom);
+  end;
   Viewport.UsedContentBox:=aContentBox;
+
   aRenderable:=Viewport as IFresnelRenderable;
   aRenderable.BeforeRender;
   Viewport.Rendered:=true;
 
   BackgroundColorFP:=Viewport.GetComputedColor(fcaBackgroundColor,colWhite);
-  FillRect(BackgroundColorFP,aContentBox);
+  FillRect(BackgroundColorFP,aBorderBox);
+
+  // todo: draw scrollbars
 
   aRenderable.Render(Self as IFresnelRenderer);
   DrawChildren(Viewport);