Browse Source

laytout: flow: position absolute item default position after a relative item

mattias 11 tháng trước cách đây
mục cha
commit
5a84f8c33b
2 tập tin đã thay đổi với 145 bổ sung45 xóa
  1. 84 43
      src/base/fresnel.layouter.pas
  2. 61 2
      tests/base/TCFlowLayout.pas

+ 84 - 43
src/base/fresnel.layouter.pas

@@ -120,12 +120,15 @@ type
 
   protected
     FLineItems: TFPList; // list of TLineItem
+    FAbsoluteItems: TFPList; // list of TLineItem
     procedure PlaceLineItems; virtual;
     function PlaceAbsoluteItem(ChildNode: TUsedLayoutNode; aMode: TFresnelLayoutMode;
                   aMaxWidth, aMaxHeight: TFresnelLength; const DefaultPos: TFresnelPoint;
                   Commit: boolean): TFresnelPoint; virtual;
-    procedure ClearLineItems; virtual;
+    function AddAbsoluteItem(ChildNode: TUsedLayoutNode; NodeClass: TLineItemClass): TLineItem; virtual;
     function AddLineItem(ChildNode: TUsedLayoutNode; NodeClass: TLineItemClass): TLineItem; virtual;
+    procedure ClearAbsoluteItems; virtual;
+    procedure ClearLineItems; virtual;
     procedure ComputeChildAttributes(Item: TLineItem; El: TFresnelElement); virtual;
   public
     destructor Destroy; override;
@@ -146,14 +149,15 @@ type
   protected
     // current line
     FLineBorderBoxHeight: TFresnelLength;
-    FLineBorderBoxLeft: TFresnelLength; // border box of last element relative to parent ContentBox
+    FLineBorderBoxLeft: TFresnelLength; // border box position of last element relative to parent ContentBox
     FLineBorderBoxRight: TFresnelLength;
     FLineBorderBoxTop: TFresnelLength;
     FLineBorderBoxBottom: TFresnelLength;
-    FLineMarginLeft: TFresnelLength;
+    FLineMarginLeft: TFresnelLength; // margin width
     FLineMarginRight: TFresnelLength;
     FLineMarginTop: TFresnelLength;
     FLineMarginBottom: TFresnelLength;
+    FLineFirstAbsoluteIndex: integer;
     FLastLineBorderBoxBottom: TFresnelLength;
     FLastLineMarginBottom: TFresnelLength;
     procedure StartLine; virtual;
@@ -668,6 +672,10 @@ begin
 end;
 
 procedure TFLFlowLayouter.EndLine(Commit: boolean);
+var
+  Item: TLineItem;
+  El: TFresnelElement;
+  IsInline: Boolean;
 begin
   if FLineItems.Count=0 then
     exit;
@@ -676,7 +684,22 @@ begin
   FLineBorderBoxBottom:=FLineBorderBoxTop+FLineBorderBoxHeight;
 
   if Commit then
+  begin
     PlaceLineItems;
+    if FAbsoluteItems<>nil then
+    begin
+      // set default top position of absolute blocks below line
+      while FLineFirstAbsoluteIndex<FAbsoluteItems.Count do
+      begin
+        Item:=TLineItem(FAbsoluteItems[FLineFirstAbsoluteIndex]);
+        El:=Item.Node.Element;
+        IsInline:=El.ComputedDisplayOutside=CSSRegistry.kwInline;
+        if not IsInline then
+          Item.StaticTop:=FLineBorderBoxBottom+FLineMarginBottom;
+        inc(FLineFirstAbsoluteIndex);
+      end;
+    end;
+  end;
   ClearLineItems;
 
   FLastLineBorderBoxBottom:=FLineBorderBoxBottom;
@@ -746,8 +769,8 @@ function TFLFlowLayouter.ComputeLayoutContent(aMode: TFresnelLayoutMode; aMaxWid
   aMaxHeight: TFresnelLength; Commit: boolean): TFresnelPoint;
 var
   ChildIndex: Integer;
-  ChildNode, LastFlowNode: TUsedLayoutNode;
-  ChildEl, LastFlowEl, El: TFresnelElement;
+  ChildNode: TUsedLayoutNode;
+  ChildEl, El: TFresnelElement;
   IsInline: Boolean;
   NewChildRight, CurSpace, OldMarginBoxBottom,
     ChildMBoxLeft, ChildMBoxRight { MarginBox relative to parent ContentBox left },
@@ -757,6 +780,7 @@ var
   ChildMarginLeft, ChildMarginRight, ChildMarginTop, ChildMarginBottom,
   ChildPadBorderX, ChildPadBorderY: TFresnelLength;
   ChildPrefSize, ChildDefPos: TFresnelPoint;
+  AbsItem: TLineItem;
 
   procedure AddLineNodeCache;
   var
@@ -786,6 +810,8 @@ begin
 
   FLastLineBorderBoxBottom:=0;
   FLastLineMarginBottom:=0;
+  FLineFirstAbsoluteIndex:=0;
+  ClearAbsoluteItems;
   El:=Node.Element;
 
   // add elements to the line until full
@@ -797,11 +823,23 @@ begin
 
     if ChildNode.SkipLayout then continue;
 
+    IsInline:=ChildEl.ComputedDisplayOutside=CSSRegistry.kwInline;
+
     // skip position: absolute and fixed
     if ChildEl.ComputedPosition in [CSSRegistry.kwAbsolute,CSSRegistry.kwFixed] then
+    begin
+      if Commit then
+      begin
+        AbsItem:=AddAbsoluteItem(ChildNode,TLineItem);
+        if IsInline then
+          AbsItem.StaticLeft:=FLineBorderBoxRight+FLineMarginRight
+        else begin
+          AbsItem.StaticTop:=FLineBorderBoxTop-FLineMarginTop;
+          AbsItem.StaticLeft:=0;
+        end;
+      end;
       continue;
-
-    IsInline:=ChildEl.ComputedDisplayOutside=CSSRegistry.kwInline;
+    end;
 
     // display-outside: inline or block
     if (not IsInline) or (aMode in [flmMinWidth,flmMaxHeight]) then
@@ -967,43 +1005,20 @@ begin
   end;
   EndLine(Commit);
 
-  if Commit then
+  if Commit and (FAbsoluteItems<>nil) then
   begin
     // place absolute items
-    LastFlowNode:=nil;
-    for ChildIndex:=0 to El.NodeCount-1 do
+    for ChildIndex:=0 to FAbsoluteItems.Count-1 do
     begin
-      ChildEl:=El.Nodes[ChildIndex];
-      ChildNode:=TUsedLayoutNode(ChildEl.LayoutNode);
+      AbsItem:=TLineItem(FAbsoluteItems[ChildIndex]);
+      ChildNode:=AbsItem.Node;
 
-      if ChildNode.SkipLayout then continue;
+      ChildDefPos.X:=AbsItem.StaticLeft;
+      ChildDefPos.Y:=AbsItem.StaticTop;
 
-      case ChildEl.ComputedPosition of
-      CSSRegistry.kwStatic,CSSRegistry.kwRelative,CSSRegistry.kwSticky:
-        LastFlowNode:=ChildNode;
-      else
-        // position is absolute or fixed
-        IsInline:=ChildEl.ComputedDisplayOutside=CSSRegistry.kwInline;
-        if LastFlowNode=nil then
-        begin
-          ChildDefPos.X:=0;
-          ChildDefPos.Y:=0;
-        end else if IsInline then
-        begin
-          // next inline position
-          LastFlowEl:=LastFlowNode.Element;
-          ChildDefPos.X:=LastFlowEl.UsedBorderBox.Right+LastFlowNode.MarginRight;
-          ChildDefPos.Y:=LastFlowNode.Top;
-        end else begin
-          // next block position
-          LastFlowEl:=LastFlowNode.Element;
-          ChildDefPos.X:=0;
-          ChildDefPos.Y:=LastFlowEl.UsedBorderBox.Bottom+LastFlowNode.MarginBottom; // todo: use actual line height
-        end;
-
-        PlaceAbsoluteItem(ChildNode,aMode,aMaxWidth,aMaxHeight,ChildDefPos,Commit);
-      end;
+      PlaceAbsoluteItem(ChildNode,aMode,aMaxWidth,aMaxHeight,ChildDefPos,Commit);
     end;
+    ClearAbsoluteItems;
   end;
 
   Result.Y:=Max(Result.Y,FLastLineBorderBoxBottom+FLastLineMarginBottom);
@@ -1484,7 +1499,7 @@ begin
 
       if ChildNode.SkipLayout then continue;
 
-      // skip position: absolute and fixed
+      // absolute and fixed: todo: place after items have their final static box
       if ChildEl.ComputedPosition in [CSSRegistry.kwAbsolute,CSSRegistry.kwFixed] then
       begin
         ChildDefPos.X:=0;
@@ -1551,6 +1566,7 @@ begin
 
     NewLeft:=Item.StaticLeft;
     NewTop:=Item.StaticTop;
+
     if ChildEl.ComputedPosition in [CSSRegistry.kwRelative,CSSRegistry.kwSticky] then
     begin
       // relative left
@@ -1725,6 +1741,27 @@ begin
   end;
 end;
 
+function TFLLineLayouter.AddLineItem(ChildNode: TUsedLayoutNode; NodeClass: TLineItemClass
+  ): TLineItem;
+begin
+  Result:=NodeClass.Create;
+  if FLineItems=nil then
+    FLineItems:=TFPList.Create;
+  FLineItems.Add(Result);
+  Result.Node:=ChildNode;
+end;
+
+procedure TFLLineLayouter.ClearAbsoluteItems;
+var
+  i: Integer;
+begin
+  if (FAbsoluteItems=nil) or (FAbsoluteItems.Count=0) then
+    exit;
+  for i:=0 to FAbsoluteItems.Count-1 do
+     TObject(FAbsoluteItems[i]).Free;
+  FAbsoluteItems.Clear;
+end;
+
 procedure TFLLineLayouter.ClearLineItems;
 var
   i: Integer;
@@ -1736,14 +1773,16 @@ begin
   FLineItems.Clear;
 end;
 
-function TFLLineLayouter.AddLineItem(ChildNode: TUsedLayoutNode; NodeClass: TLineItemClass
+function TFLLineLayouter.AddAbsoluteItem(ChildNode: TUsedLayoutNode; NodeClass: TLineItemClass
   ): TLineItem;
 begin
   Result:=NodeClass.Create;
-  if FLineItems=nil then
-    FLineItems:=TFPList.Create;
-  FLineItems.Add(Result);
+  if FAbsoluteItems=nil then
+    FAbsoluteItems:=TFPList.Create;
+  FAbsoluteItems.Add(Result);
   Result.Node:=ChildNode;
+  Result.StaticLeft:=NaN;
+  Result.StaticTop:=NaN;
 end;
 
 procedure TFLLineLayouter.ComputeChildAttributes(Item: TLineItem;
@@ -1755,6 +1794,8 @@ end;
 
 destructor TFLLineLayouter.Destroy;
 begin
+  ClearAbsoluteItems;
+  FreeAndNil(FAbsoluteItems);
   ClearLineItems;
   FreeAndNil(FLineItems);
   inherited Destroy;

+ 61 - 2
tests/base/TCFlowLayout.pas

@@ -20,7 +20,8 @@ type
     procedure TestMarginPercentage;
     procedure TestPaddingPercentage;
     procedure TestPositionAbsolute_Right_WidthAuto;
-    procedure TestPositionAbsolute_DivDefaultPos;
+    procedure TestPositionAbsolute_DivDefaultPosBehindStatic;
+    procedure TestPositionAbsolute_DivDefaultPosBehindRelative;
     // todo procedure TestPositionAbsolute_DivTop100Percent;
     // todo: test break line
   end;
@@ -405,7 +406,7 @@ begin
   AssertEquals('Div2.LayoutNode.Height',52,Div2.LayoutNode.Height);
 end;
 
-procedure TTestFlowLayout.TestPositionAbsolute_DivDefaultPos;
+procedure TTestFlowLayout.TestPositionAbsolute_DivDefaultPosBehindStatic;
 var
   Body: TBody;
   Div1: TDiv;
@@ -470,7 +471,65 @@ begin
   AssertEquals('Div1.RenderedBorderBox.Height',20,Div1.RenderedBorderBox.Height);
   AssertEquals('Div1.RenderedBorderBox.Left',0,Div1.RenderedBorderBox.Left);
   AssertEquals('Div1.RenderedBorderBox.Top',23,Div1.RenderedBorderBox.Top);
+end;
+
+procedure TTestFlowLayout.TestPositionAbsolute_DivDefaultPosBehindRelative;
+var
+  Body: TBody;
+  Div1: TDiv;
+  Label1: TLabel;
+begin
+  Body:=TBody.Create(Viewport);
+  Body.Name:='Body';
+  Body.Parent:=Viewport;
+
+  Label1:=TLabel.Create(Viewport);
+  Label1.Name:='Label1';
+  Label1.Caption:='Label1';
+  Label1.Parent:=Body;
+
+  Div1:=TDiv.Create(Viewport);
+  Div1.Name:='Div1';
+  Div1.Parent:=Body;
+
+  Viewport.Stylesheet.Text:=LinesToStr([
+    'body {',
+    '  margin: 0;', // content width = 800
+    '  font-size: 20px;',
+    '}',
+    '#Label1 {',
+    '  position: relative; top:10px;',
+    '}',
+    '#Div1 {',
+    '  position: absolute;',
+    '  width: 30px; height: 20px;',
+    '}']);
+
+  // Div1 is absolute to the Viewport, because Body position is static
+  // Div1 default position is below Label1 static position, because Div1 display is block
+  // Body width/height includes Label1 static bounds, and not Div1
+
+  Viewport.Draw;
+  AssertEquals('Body.RenderedContentBox.Width',800,Body.RenderedContentBox.Width);
+
+  AssertEquals('Div1.Rendered',true,Div1.Rendered);
 
+  //writeln('TTestFlowLayout.TestPositionAbsolute_DivDefaultPos Label1.RenderedBorderBox=',Label1.RenderedBorderBox.ToString);
+  AssertEquals('Label1.GetComputedString(fcaPosition)','relative',Label1.GetComputedString(fcaPosition));
+  AssertEquals('Label1.GetComputedString(fcaDisplay)','inline flow',Label1.GetComputedString(fcaDisplay));
+  AssertEquals('Label1.GetComputedString(fcaWidth)','auto',Label1.GetComputedString(fcaWidth));
+  AssertEquals('Label1.GetComputedString(fcaTop)','10px',Label1.GetComputedString(fcaTop));
+  AssertEquals('Label1.RenderedBorderBox.Top',10,Label1.RenderedBorderBox.Top);
+  AssertEquals('Label1.RenderedBorderBox.Left',0,Label1.RenderedBorderBox.Left);
+  AssertEquals('Label1.RenderedBorderBox.Height',23,Label1.RenderedBorderBox.Height);
+
+  AssertEquals('Body.RenderedBorderBox.Height',23,Body.RenderedBorderBox.Height);
+
+  //writeln('TTestFlowLayout.TestPositionAbsolute_DivDefaultPos Div1.RenderedBorderBox=',Div1.RenderedBorderBox.ToString);
+  AssertEquals('Div1.RenderedBorderBox.Width',30,Div1.RenderedBorderBox.Width);
+  AssertEquals('Div1.RenderedBorderBox.Height',20,Div1.RenderedBorderBox.Height);
+  AssertEquals('Div1.RenderedBorderBox.Left',0,Div1.RenderedBorderBox.Left);
+  AssertEquals('Div1.RenderedBorderBox.Top',23,Div1.RenderedBorderBox.Top);
 end;
 
 Initialization