Преглед изворни кода

flow layout: include absolute positioned child nodes in overflow size

mattias пре 2 месеци
родитељ
комит
693122b632
3 измењених фајлова са 142 додато и 44 уклоњено
  1. 44 6
      src/base/fresnel.dom.pas
  2. 48 38
      src/base/fresnel.layouter.pas
  3. 50 0
      tests/base/TCFlowLayout.pas

+ 44 - 6
src/base/fresnel.dom.pas

@@ -1077,8 +1077,8 @@ type
   TFreIntrinsicContentSize = record
     Width: TFresnelLength;
     Height: TFresnelLength;
-    ScrollWidth: TFresnelLength;
-    ScrollHeight: TFresnelLength;
+    OverflowWidth: TFresnelLength;
+    OverflowHeight: TFresnelLength;
     NeedGutterHorizontal, NeedGutterVertical: boolean;
     function ToString: string;
     procedure SetSize(const p: TFresnelPoint);
@@ -1511,6 +1511,8 @@ type
     function GetIntrinsicContentSize(aMode: TFresnelLayoutMode; aMaxWidth: TFresnelLength = NaN; aMaxHeight: TFresnelLength = NaN): TFreIntrinsicContentSize; virtual; // ignoring min|max-height
     function GetContainerContentWidth(UseNaNOnFail: boolean): TFresnelLength; virtual; // used value
     function GetContainerContentHeight(UseNaNOnFail: boolean): TFresnelLength; virtual; // used value
+    function GetContainerPaddingWidth(UseNaNOnFail: boolean): TFresnelLength; virtual; // used value
+    function GetContainerPaddingHeight(UseNaNOnFail: boolean): TFresnelLength; virtual; // used value
     function GetContainerOffset: TFresnelPoint;
     function IsBlockFormattingContext: boolean; virtual;
     function GetUsedPaddingBox: TFresnelRect;
@@ -6630,8 +6632,8 @@ function TFreIntrinsicContentSize.ToString: string;
 begin
   Result:='Size='+FloatToCSSStr(Width)
     +'x'+FloatToCSSStr(Height)
-    +' ScrollSize='+FloatToCSSStr(ScrollWidth)
-    +'x'+FloatToCSSStr(ScrollHeight);
+    +' OverflSize='+FloatToCSSStr(OverflowWidth)
+    +'x'+FloatToCSSStr(OverflowHeight);
   if NeedGutterHorizontal then
     Result+=' NeedGutterHorz';
   if NeedGutterVertical then
@@ -6647,8 +6649,8 @@ procedure TFreIntrinsicContentSize.SetSize(W, H: TFresnelLength);
 begin
   Width:=W;
   Height:=H;
-  ScrollWidth:=W;
-  ScrollHeight:=H;
+  OverflowWidth:=W;
+  OverflowHeight:=H;
   NeedGutterHorizontal:=false;
   NeedGutterVertical:=false;
 end;
@@ -8680,6 +8682,42 @@ begin
     Result:=0;
 end;
 
+function TFresnelElement.GetContainerPaddingWidth(UseNaNOnFail: boolean): TFresnelLength;
+var
+  El: TFresnelElement;
+  LNode: TFresnelLayoutNode;
+begin
+  El:=LayoutNode.Container;
+  if El<>nil then
+  begin
+    LNode:=El.LayoutNode;
+    Result:=LNode.Width;
+    if not UseNaNOnFail and IsNan(Result) then Result:=0;
+    Result:=Result+LNode.PaddingLeft+LNode.PaddingRight;
+  end else if UseNaNOnFail then
+    Result:=NaN
+  else
+    Result:=0;
+end;
+
+function TFresnelElement.GetContainerPaddingHeight(UseNaNOnFail: boolean): TFresnelLength;
+var
+  El: TFresnelElement;
+  LNode: TFresnelLayoutNode;
+begin
+  El:=LayoutNode.Container;
+  if El<>nil then
+  begin
+    LNode:=El.LayoutNode;
+    Result:=LNode.Height;
+    if not UseNaNOnFail and IsNan(Result) then Result:=0;
+    Result:=Result+LNode.PaddingTop+LNode.PaddingBottom;
+  end else if UseNaNOnFail then
+    Result:=NaN
+  else
+    Result:=0;
+end;
+
 function TFresnelElement.GetContainerOffset: TFresnelPoint;
 var
   Container, El: TFresnelElement;

+ 48 - 38
src/base/fresnel.layouter.pas

@@ -325,8 +325,8 @@ begin
       if Node.Height>AvailHeight then
         Node.Height:=AvailHeight;
     end;
-    Node.ScrollWidth:=Max(Size.ScrollWidth,AvailWidth);
-    Node.ScrollHeight:=Max(Size.ScrollHeight,AvailHeight);
+    Node.ScrollWidth:=Max(Size.OverflowWidth,AvailWidth);
+    Node.ScrollHeight:=Max(Size.OverflowHeight,AvailHeight);
     {$IFDEF VerboseFresnelScrolling}
     writeln('TFLNodeLayouter.Apply "',Node.Element.Name,'" ScrollSize: ',FloatToCSSStr(Node.ScrollWidth),'x',FloatToCSSStr(Node.ScrollHeight),' Width=',FloatToCSSStr(Node.Width),' Height=',FloatToCSSStr(Node.Height));
     {$ENDIF}
@@ -351,8 +351,8 @@ begin
       Node.Width:=Node.FitWidth(Size.Width);
     if IsNan(Node.Height) then
       Node.Height:=Node.FitHeight(Size.Height);
-    Node.ScrollWidth:=Max(Size.ScrollWidth,Node.Width);
-    Node.ScrollHeight:=Max(Size.ScrollHeight,Node.Height);
+    Node.ScrollWidth:=Max(Size.OverflowWidth,Node.Width);
+    Node.ScrollHeight:=Max(Size.OverflowHeight,Node.Height);
   end;
 
   // clipping
@@ -870,8 +870,8 @@ end;
 
 procedure TUsedLayoutNode.ApplyScrollSize(const aSize: TFreIntrinsicContentSize);
 begin
-  ScrollWidth:=aSize.ScrollWidth;
-  ScrollHeight:=aSize.ScrollHeight;
+  ScrollWidth:=aSize.OverflowWidth;
+  ScrollHeight:=aSize.OverflowHeight;
   if (GutterHorizontal=gmAuto) and aSize.NeedGutterHorizontal then
     AddScrollGutter(true);
   if (GutterVertical=gmAuto) and aSize.NeedGutterVertical then
@@ -910,7 +910,7 @@ begin
       if (not IsNan(Left)) and (not IsNan(Right)) then
       begin
         // left & right -> width
-        aContainerWidth:=Element.GetContainerContentWidth(true);
+        aContainerWidth:=Element.GetContainerPaddingWidth(true);
         if not IsNan(aContainerWidth) then
         begin
           Width:=aContainerWidth-Left-MarginLeft-BorderLeft-PaddingLeft
@@ -925,14 +925,14 @@ begin
         if not IsNan(Right) then
         begin
           // width & right -> left
-          aContainerWidth:=Element.GetContainerContentWidth(true);
+          aContainerWidth:=Element.GetContainerPaddingWidth(true);
           if not IsNan(aContainerWidth) then
             Left:=aContainerWidth-Width-MarginLeft-BorderLeft-PaddingLeft
                                   -Right-MarginRight-BorderRight-PaddingRight;
         end;
       end else if IsNan(Right) then begin
         // width & left -> right
-        aContainerWidth:=Element.GetContainerContentWidth(true);
+        aContainerWidth:=Element.GetContainerPaddingWidth(true);
         if not IsNan(aContainerWidth) then
           Right:=aContainerWidth-Width-Left-MarginLeft-BorderLeft-PaddingLeft
                                 -MarginRight-BorderRight-PaddingRight;
@@ -944,7 +944,7 @@ begin
       if (not IsNan(Top)) and (not IsNan(Bottom)) then
       begin
         // Top & Bottom -> Height
-        aContainerHeight:=Element.GetContainerContentHeight(true);
+        aContainerHeight:=Element.GetContainerPaddingHeight(true);
         if not IsNan(aContainerHeight) then
         begin
           Height:=aContainerHeight-Top-MarginTop-BorderTop-PaddingTop
@@ -959,14 +959,14 @@ begin
         if not IsNan(Bottom) then
         begin
           // Height & Bottom -> Top
-          aContainerHeight:=Element.GetContainerContentHeight(true);
+          aContainerHeight:=Element.GetContainerPaddingHeight(true);
           if not IsNan(aContainerHeight) then
             Top:=aContainerHeight-Height-MarginTop-BorderTop-PaddingTop
                                  -Bottom-MarginBottom-BorderBottom-PaddingBottom;
         end;
       end else if IsNan(Bottom) then begin
         // Height & Top -> Bottom
-        aContainerHeight:=Element.GetContainerContentHeight(true);
+        aContainerHeight:=Element.GetContainerPaddingHeight(true);
         if not IsNan(aContainerHeight) then
           Bottom:=aContainerHeight-Height-Top-MarginTop-BorderTop-PaddingTop
                                   -MarginBottom-BorderBottom-PaddingBottom;
@@ -1142,18 +1142,18 @@ var
     N.StaticLeft:=ChildMBoxLeft;
     N.ContentBoxWidth:=ChildWidth;
     N.ContentBoxHeight:=ChildHeight;
-    N.IntrinsicWidth:=Max(ChildWidth,ChildPrefSize.ScrollWidth);
-    N.IntrinsicHeight:=Max(ChildHeight,ChildPrefSize.ScrollHeight);
+    N.IntrinsicWidth:=Max(ChildWidth,ChildPrefSize.OverflowWidth);
+    N.IntrinsicHeight:=Max(ChildHeight,ChildPrefSize.OverflowHeight);
     Result.Width:=Max(Result.Width,ChildMBoxRight);
 
-    if Result.ScrollWidth<Result.Width then
-      Result.ScrollWidth:=Result.Width;
-    r:=ChildPrefSize.ScrollWidth-ChildWidth;
+    if Result.OverflowWidth<Result.Width then
+      Result.OverflowWidth:=Result.Width;
+    r:=ChildPrefSize.OverflowWidth-ChildWidth;
     if r>0 then
     begin
       r:=ChildMBoxRight+r;
-      if Result.ScrollWidth<r then
-        Result.ScrollWidth:=r;
+      if Result.OverflowWidth<r then
+        Result.OverflowWidth:=r;
     end;
   end;
 
@@ -1390,10 +1390,14 @@ begin
       ChildDefPos.Y:=AbsItem.StaticTop;
 
       ChildPrefSize:=PlaceAbsoluteItem(ChildNode,aMode,aMaxWidth,aMaxHeight,ChildDefPos,Commit);
-      if Result.ScrollWidth<ChildPrefSize.ScrollWidth then
-        Result.ScrollWidth:=ChildPrefSize.ScrollWidth;
-      if Result.ScrollHeight<ChildPrefSize.ScrollHeight then
-        Result.ScrollHeight:=ChildPrefSize.ScrollHeight;
+      // an absolute item is excluded from the auto-content size,
+      // and included in the overflow size
+      //writeln('TFLFlowLayouter.ComputeLayoutContent ',ChildEl.Name,' ChildPrefSize=',ChildPrefSize.ToString,' Result=',Result.ToString);
+      if Result.OverflowWidth<ChildPrefSize.OverflowWidth then
+        Result.OverflowWidth:=ChildPrefSize.OverflowWidth;
+      if Result.OverflowHeight<ChildPrefSize.OverflowHeight then
+        Result.OverflowHeight:=ChildPrefSize.OverflowHeight;
+      //writeln('TFLFlowLayouter.ComputeLayoutContent ',ChildEl.Name,' ChildPrefSize=',ChildPrefSize.ToString,' Result=',Result.ToString);
     end;
   end;
   if FAbsoluteItems<>nil then
@@ -2129,39 +2133,45 @@ begin
 
   // compute right, bottom of marginbox
   Result.Width:=NewLeft+FrameLeft+NewWidth+FrameRight;
-  if not IsNan(NewRight) then
+  //writeln('TFLLineLayouter.PlaceAbsoluteItem ',ChildEl.Name,' NewLeft=',FloatToCSSStr(NewLeft),' FrameLeft=',FloatToCSSStr(FrameLeft),' NewWidth=',FloatToCSSStr(NewWidth),' FrameRight=',FloatToCSSStr(FrameRight),' NewRight=',FloatToCSSStr(NewRight));
+  if (not IsNan(NewRight)) and (NewRight>0) then
     Result.Width:=Result.Width+NewRight;
-  Result.ScrollWidth:=Result.Width;
-  if Size.ScrollWidth>NewWidth then
+  Result.OverflowWidth:=Result.Width;
+  if Size.OverflowWidth>NewWidth then
   begin
-    Result.ScrollWidth:=FrameLeft+Result.ScrollWidth;
+    Result.OverflowWidth:=FrameLeft+Result.OverflowWidth;
     if not IsNan(NewLeft) then
-      Result.ScrollWidth:=Result.ScrollWidth+NewLeft;
-    if Result.ScrollWidth<Result.Width then
-      Result.ScrollWidth:=Result.Width;
+      Result.OverflowWidth:=Result.OverflowWidth+NewLeft;
+    if Result.OverflowWidth<Result.Width then
+      Result.OverflowWidth:=Result.Width;
   end;
 
-  Result.Height:=FrameTop+NewWidth+FrameBottom;
+  Result.Height:=FrameTop+NewHeight+FrameBottom;
+  //writeln('TFLLineLayouter.PlaceAbsoluteItem ',ChildEl.Name,' NewTop=',FloatToCSSStr(NewTop),' FrameTop=',FloatToCSSStr(FrameTop),' NewHeight=',FloatToCSSStr(NewHeight),' FrameBottom=',FloatToCSSStr(FrameBottom),' NewBottom=',FloatToCSSStr(NewBottom));
   if not IsNan(NewTop) then
     Result.Height:=Result.Height+NewTop;
-  if not IsNan(NewBottom) then
+  if (not IsNan(NewBottom)) and (NewBottom>0) then
     Result.Height:=Result.Height+NewBottom;
-  Result.ScrollHeight:=Result.Height;
-  if Size.ScrollHeight>NewHeight then
+  Result.OverflowHeight:=Result.Height;
+  if Size.OverflowHeight>NewHeight then
   begin
-    Result.ScrollHeight:=FrameTop+Result.ScrollHeight;
+    Result.OverflowHeight:=FrameTop+Result.OverflowHeight;
     if not IsNan(NewTop) then
-      Result.ScrollHeight:=Result.ScrollHeight+NewTop;
-    if Result.ScrollHeight<Result.Height then
-      Result.ScrollHeight:=Result.Height;
+      Result.OverflowHeight:=Result.OverflowHeight+NewTop;
+    if Result.OverflowHeight<Result.Height then
+      Result.OverflowHeight:=Result.Height;
   end;
 
+  writeln('TFLLineLayouter.PlaceAbsoluteItem ',ChildEl.GetPath,' Result=',Result.ToString);
+
   if Commit then
   begin
+    // marginbox
     ChildNode.Left:=NewLeft;
     ChildNode.Top:=NewTop;
     ChildNode.Right:=NewRight;
     ChildNode.Bottom:=NewBottom;
+    // contentbox
     ChildNode.Width:=NewWidth;
     ChildNode.Height:=NewHeight;
 

+ 50 - 0
tests/base/TCFlowLayout.pas

@@ -22,6 +22,7 @@ type
     procedure TestPositionAbsolute_Right_WidthAuto;
     procedure TestPositionAbsolute_DivDefaultPosBehindStatic;
     procedure TestPositionAbsolute_DivDefaultPosBehindRelative;
+    procedure TestPositionAbsolute_Left_ScrollWidth;
     // todo procedure TestPositionAbsolute_DivTop100Percent;
     // todo: test break line
   end;
@@ -532,6 +533,55 @@ begin
   AssertEquals('Div1.UsedBorderBox.Top',23,Div1.UsedBorderBox.Top);
 end;
 
+procedure TTestFlowLayout.TestPositionAbsolute_Left_ScrollWidth;
+var
+  Body: TBody;
+  Div1: TDiv;
+begin
+  Body:=TBody.Create(Viewport);
+  Body.Name:='Body';
+  Body.Parent:=Viewport;
+
+  Div1:=TDiv.Create(Viewport);
+  Div1.Name:='Div1';
+  Div1.Parent:=Body;
+
+  Viewport.Stylesheet.Text:=LinesToStr([
+    'body {',
+    '  position: relative;', // make body a container
+    '  width: 100px;',
+    '  padding: 23px;', // padding is irrelevant for child positioned absolute
+    '  overflow: auto;',
+    '}',
+    '#Div1 {',
+    '  position: absolute;',
+    '  margin: 2px;',
+    '  border: 1px;',
+    '  padding: 4px;',
+    '  left: 140px;', // marginbox left, relative to container's paddingbox
+    '  width: 30px;',
+    '  height: 20px;',
+    '}']);
+
+  Viewport.Draw;
+  AssertEquals('Body.GetComputedString(fcaOverflowX)','auto',Body.GetComputedString(fcaOverflowX));
+  AssertEquals('Body.GetComputedString(fcaOverflowY)','auto',Body.GetComputedString(fcaOverflowY));
+  AssertEquals('Body.GetComputedString(fcaOverflow)','auto',Body.GetComputedString(fcaOverflow));
+  AssertEquals('Body.UsedContentBox.Width',100,Body.UsedContentBox.Width);
+  AssertEquals('Body.UsedBorderBox.Width',146,Body.UsedBorderBox.Width); // width+padding
+  AssertEquals('Body.UsedBorderBox.Height',46,Body.UsedBorderBox.Height); // padding is bigger than Div1
+
+  if Div1.LayoutNode.Container<>Body then
+    Fail('Div1.LayoutNode.Container<>Body');
+  AssertEquals('Div1.UsedBorderBox.Left',142,Div1.UsedBorderBox.Left);
+  AssertEquals('Div1.UsedBorderBox.Top',2,Div1.UsedBorderBox.Top);
+  AssertEquals('Div1.UsedBorderBox.Width',40,Div1.UsedBorderBox.Width);
+  AssertEquals('Div1.UsedBorderBox.Height',30,Div1.UsedBorderBox.Height);
+
+  AssertEquals('Body.LayoutNode.ScrollWidth',184,Body.LayoutNode.ScrollWidth);
+  AssertEquals('Body.LayoutNode.ScrollHeight',34,Body.LayoutNode.ScrollHeight);
+end;
+
 Initialization
   RegisterTests([TTestFlowLayout]);
 end.