Procházet zdrojové kódy

layouter: absolute items default position behind or below last flow element

mattias před 11 měsíci
rodič
revize
4174e3a8ed

+ 1 - 0
demo/Combobox/.gitignore

@@ -0,0 +1 @@
+ComboboxNative

+ 79 - 0
demo/Combobox/ComboboxNative.lpi

@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="ComboboxNative"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <RequiredPackages>
+      <Item>
+        <PackageName Value="Fresnel"/>
+        <DefaultFilename Value="../../src/fresnel.lpk" Prefer="True"/>
+      </Item>
+    </RequiredPackages>
+    <Units>
+      <Unit>
+        <Filename Value="ComboboxNative.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+      <Unit>
+        <Filename Value="MainUnit.pas"/>
+        <IsPartOfProject Value="True"/>
+        <ComponentName Value="MainForm"/>
+        <HasResources Value="True"/>
+        <ResourceBaseClass Value="Other"/>
+        <ResourceBaseClassname Value="TFresnelForm"/>
+      </Unit>
+      <Unit>
+        <Filename Value="DemoCombobox.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="ComboboxNative"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+    <Linking>
+      <Debugging>
+        <DebugInfoType Value="dsDwarf2"/>
+      </Debugging>
+    </Linking>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 14 - 0
demo/Combobox/ComboboxNative.lpr

@@ -0,0 +1,14 @@
+program ComboboxNative;
+
+uses
+  dl,
+  Fresnel, // initializes the widgetset
+  Fresnel.App, MainUnit, DemoCombobox;
+
+begin
+  FresnelApplication.HookFresnelLog:=true;
+  FresnelApplication.Initialize;
+  FresnelApplication.CreateForm(TMainForm,MainForm);
+  FresnelApplication.Run;
+end.
+

+ 251 - 0
demo/Combobox/DemoCombobox.pas

@@ -0,0 +1,251 @@
+unit DemoCombobox;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, System.UITypes, Fresnel.DOM, Fresnel.Controls,
+  Fresnel.Classes, FCL.Events, Fresnel.Events, fpCSSTree;
+
+type
+
+  { TDemoCombobox }
+
+  TDemoCombobox = class(TDiv,IFPObserver)
+  private
+    FItemIndex: integer;
+    FItems: TStrings;
+    FOnChange: TNotifyEvent;
+    function GetCaption: string;
+    procedure SetCaption(const AValue: string);
+    procedure SetItemIndex(const AValue: integer);
+    procedure SetItems(const AValue: TStrings);
+  protected
+    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
+    Procedure FPOObservedChanged(ASender: TObject; {%H-}Operation: TFPObservedOperation; {%H-}Data: Pointer); override;
+  public
+    // default styles
+    const
+      cStyle = ''
+        +'.Combobox {'+LineEnding
+        +'  position: relative;'+LineEnding
+        +'}'+LineEnding
+        +'.ComboboxCaption {'+LineEnding
+        +'  padding: 6px;'+LineEnding
+        +'  border: 1px solid #5080e0;'+LineEnding
+        +'  border-radius: 5px;'+LineEnding
+        +'  background-color: #b6d6f0;'+LineEnding
+        +'  display: flex;'+LineEnding
+        +'  justify-content: center;'+LineEnding
+        +'}'+LineEnding
+        +'.ComboboxCaption:hover {'+LineEnding
+        +'  background-color: #a6e6ff;'+LineEnding
+        +'}'+LineEnding
+        +'.ComboboxCaptionLabel {'+LineEnding
+        +'}'+LineEnding
+        +'.ComboboxCaptionIcon {'+LineEnding
+        +'}'+LineEnding
+        +'.ComboboxMenu {'+LineEnding
+        +'  position: absolute;'+LineEnding
+        +'  padding-top: 5px;'+LineEnding
+        +'  border-radius: 5px;'+LineEnding
+        +'  border: 1px solid #5080e0;'+LineEnding
+        +'  background-color: #b6d6f0;'+LineEnding
+        +'}'+LineEnding
+        +'.ComboboxContent {'+LineEnding
+        +'  padding: 5px 0;'+LineEnding
+        +'}'+LineEnding
+        +'.ComboboxItem {'+LineEnding
+        +'  padding: 5px 20px;'+LineEnding
+        +'}'+LineEnding
+        +'.ComboboxItem:hover {'+LineEnding
+        +'  background-color: #a6e6ff;'+LineEnding
+        +'}'+LineEnding
+        +'.ComboboxSelected {'+LineEnding
+        +'  background-color: #a6d6ff;'+LineEnding
+        +'}'+LineEnding;
+  public
+    CaptionDiv: TDiv;
+    CaptionLabel: TLabel;
+    CaptionIcon: TLabel;
+    Menu: TDiv;
+    Content: TDiv;
+    constructor Create(AOwner: TComponent); override;
+    destructor Destroy; override;
+    procedure Clear; virtual;
+    class function GetCSSTypeStyle: TCSSString; override;
+    procedure UpdateItems; virtual;
+    property OnChange: TNotifyEvent read FOnChange write FOnChange;
+    property Items: TStrings read FItems write SetItems;
+    property ItemIndex: integer read FItemIndex write SetItemIndex;
+    property Caption: string read GetCaption write SetCaption;
+  end;
+
+implementation
+
+{ TDemoCombobox }
+
+procedure TDemoCombobox.SetItems(const AValue: TStrings);
+begin
+  if (FItems=AValue) or FItems.Equals(AValue) then Exit;
+  FItems:=AValue;
+  UpdateItems;
+end;
+
+procedure TDemoCombobox.Notification(AComponent: TComponent; Operation: TOperation);
+begin
+  inherited Notification(AComponent, Operation);
+  if Operation=opRemove then
+  begin
+    if AComponent=CaptionDiv then
+      CaptionDiv:=nil;
+    if AComponent=CaptionLabel then
+      CaptionLabel:=nil;
+    if AComponent=CaptionIcon then
+      CaptionIcon:=nil;
+    if AComponent=Content then
+      Content:=nil;
+    if AComponent=Menu then
+      Menu:=nil;
+  end;
+end;
+
+procedure TDemoCombobox.FPOObservedChanged(ASender: TObject; Operation: TFPObservedOperation;
+  Data: Pointer);
+begin
+  if ASender=FItems then
+  begin
+    UpdateItems;
+  end;
+end;
+
+procedure TDemoCombobox.SetItemIndex(const AValue: integer);
+begin
+  if FItemIndex=AValue then Exit;
+  if (csDestroying in ComponentState) or (Content=nil) then exit;
+  if (AValue>=Content.NodeCount) and not (csLoading in ComponentState) then
+    exit;
+  if (FItemIndex>=0) and (FItemIndex<Content.NodeCount) then
+    Content.Nodes[FItemIndex].RemoveCSSClass('ComboboxSelected');
+  FItemIndex:=AValue;
+  if (FItemIndex>=0) and (FItemIndex<Content.NodeCount) then
+    Content.Nodes[FItemIndex].AddCSSClass('ComboboxSelected');
+end;
+
+function TDemoCombobox.GetCaption: string;
+begin
+  if CaptionLabel<>nil then
+    Result:=CaptionLabel.Caption
+  else
+    Result:='';
+end;
+
+procedure TDemoCombobox.SetCaption(const AValue: string);
+begin
+  if CaptionLabel<>nil then
+    CaptionLabel.Caption:=AValue;
+end;
+
+constructor TDemoCombobox.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FItemIndex:=-1;
+  FItems:=TStringList.Create(false);
+  FItems.FPOAttachObserver(Self);
+
+  CSSClasses.Add('Combobox');
+
+  CaptionDiv:=TDiv.Create(Self);
+  with CaptionDiv do begin
+    Name:='CaptionDiv';
+    CSSClasses.Add('ComboboxCaption');
+    Parent:=Self;
+  end;
+
+  CaptionLabel:=TLabel.Create(Self);
+  with CaptionLabel do begin
+    Name:='CaptionLabel';
+    CSSClasses.Add('ComboboxCaptionLabel');
+    Parent:=CaptionDiv;
+  end;
+
+  CaptionIcon:=TLabel.Create(Self);
+  with CaptionIcon do begin
+    Name:='CaptionIcon';
+    Caption:='↧';
+    CSSClasses.Add('ComboboxCaptionIcon');
+    Parent:=CaptionDiv;
+  end;
+
+  Menu:=TDiv.Create(Self);
+  with Menu do begin
+    Name:='Menu';
+    CSSClasses.Add('ComboboxMenu');
+    Parent:=Self;
+  end;
+
+  Content:=TDiv.Create(Self);
+  with Content do begin
+    Name:='Content';
+    CSSClasses.Add('ComboboxContent');
+    Parent:=Menu;
+  end;
+end;
+
+destructor TDemoCombobox.Destroy;
+begin
+  FItemIndex:=-1;
+  FreeAndNil(FItems);
+  inherited Destroy;
+end;
+
+procedure TDemoCombobox.Clear;
+begin
+  FItemIndex:=-1;
+  FItems.Clear;
+  while Content.NodeCount>FItems.Count do
+    Content.Nodes[Content.NodeCount-1].Free;
+end;
+
+class function TDemoCombobox.GetCSSTypeStyle: TCSSString;
+begin
+  Result:=cStyle;
+end;
+
+procedure TDemoCombobox.UpdateItems;
+var
+  i: Integer;
+  ItemCaption: String;
+  ItemDiv: TDiv;
+  aLabel: TLabel;
+begin
+  for i:=0 to FItems.Count-1 do
+  begin
+    ItemCaption:=FItems[i];
+    ItemDiv:=FItems.Objects[i] as TDiv;
+    if ItemDiv=nil then
+    begin
+      ItemDiv:=TDiv.Create(Self);
+      ItemDiv.Name:='ItemDiv'+IntToStr(i);
+      ItemDiv.CSSClasses.Add('ComboboxItem');
+      aLabel:=TLabel.Create(Self);
+      aLabel.Name:='ItemLabel'+IntToStr(i);
+      aLabel.Caption:=ItemCaption;
+      aLabel.Parent:=ItemDiv;
+      ItemDiv.Parent:=Content;
+    end else begin
+      aLabel:=ItemDiv.Nodes[0] as TLabel;
+      aLabel.Caption:=ItemCaption;
+    end;
+    if i=ItemIndex then
+      ItemDiv.AddCSSClass('ComboboxSelected')
+    else
+      ItemDiv.RemoveCSSClass('ComboboxSelected');
+  end;
+  while Content.NodeCount>FItems.Count do
+    Content.Nodes[Content.NodeCount-1].Free;
+end;
+
+end.
+

+ 23 - 0
demo/Combobox/MainUnit.lfm

@@ -0,0 +1,23 @@
+object MainForm: TMainForm
+  Caption = 'MainForm'
+  FormLeft = 450
+  FormTop = 300
+  FormWidth = 350
+  FormHeight = 255
+  OnCreate = MainFormCreate
+  object Body1: TBody
+    Style = 'border: 2px solid blue;'#10
+    object Div1: TDiv
+      OnMouseDown = Div1MouseDown
+      OnMouseMove = Div1MouseMove
+      object Label1: TLabel
+        Style = 'color: red;'
+        OnClick = Label1Click
+        OnMouseDown = Label1MouseDown
+        OnMouseMove = Label1MouseMove
+        OnMouseUp = Label1MouseUp
+        Caption = 'Label1'
+      end
+    end
+  end
+end

+ 97 - 0
demo/Combobox/MainUnit.pas

@@ -0,0 +1,97 @@
+unit MainUnit;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, Fresnel.Forms, Fresnel.Controls, Fresnel.Events,
+  FCL.Events, DemoCombobox;
+
+type
+
+  { TMainForm }
+
+  TMainForm = class(TFresnelForm)
+    Body1: TBody;
+    Div1: TDiv;
+    Label1: TLabel;
+    procedure Div1MouseDown(Event: TFresnelMouseEvent);
+    procedure Div1MouseMove(Event: TFresnelMouseEvent);
+    procedure Label1Click(Event: TAbstractEvent);
+    procedure Label1MouseDown(Event: TFresnelMouseEvent);
+    procedure Label1MouseMove(Event: TFresnelMouseEvent);
+    procedure Label1MouseUp(Event: TFresnelMouseEvent);
+    procedure MainFormCreate(Sender: TObject);
+  private
+  public
+    Combobox1: TDemoCombobox;
+  end;
+
+var
+  MainForm: TMainForm;
+
+implementation
+
+{$R *.lfm}
+
+{ TMainForm }
+
+procedure TMainForm.Label1Click(Event: TAbstractEvent);
+begin
+  writeln('TMainForm.Label1Click ',Event.EventID);
+end;
+
+procedure TMainForm.Div1MouseDown(Event: TFresnelMouseEvent);
+begin
+  writeln('TMainForm.Div1MouseDown ',Event.ControlX,',',Event.ControlY);
+end;
+
+procedure TMainForm.Div1MouseMove(Event: TFresnelMouseEvent);
+begin
+  writeln('TMainForm.Div1MouseMove ',Event.ControlX,',',Event.ControlY);
+end;
+
+procedure TMainForm.Label1MouseDown(Event: TFresnelMouseEvent);
+begin
+  writeln('TMainForm.Label1MouseDown ',Event.ControlX,',',Event.ControlY);
+end;
+
+procedure TMainForm.Label1MouseMove(Event: TFresnelMouseEvent);
+begin
+  writeln('TMainForm.Label1MouseMove ',Event.ControlX,',',Event.ControlY);
+end;
+
+procedure TMainForm.Label1MouseUp(Event: TFresnelMouseEvent);
+begin
+  writeln('TMainForm.Label1MouseUp ',Event.ControlX,',',Event.ControlY);
+end;
+
+procedure TMainForm.MainFormCreate(Sender: TObject);
+begin
+  Stylesheet.Add(''
+    +'body {'
+    +'  font-size: 15px;'
+    +'}'
+    +'#Div1 {'
+    //+'  background:#44cc66;'
+    +'  background:linear-gradient(#ededed, #bab1ba);'
+    +'  border:7px solid #18ab29;'
+    +'  padding:16px 31px;'
+    +'  font-size:15px; font-family:Arial; font-weight:bold;'
+    +'  text-shadow: 0 1 1 #333;'
+    +'  color:#fff;'
+    +'}'
+    +'#Div1:hover {'
+    +'  background:#88bb22;'
+    +'};');
+  Div1.Style:='';
+
+  Combobox1:=TDemoCombobox.Create(Self);
+  Combobox1.Name:='Combobox1';
+  Combobox1.Parent:=Body1;
+  Combobox1.Items.Add('First');
+end;
+
+end.
+

+ 55 - 49
src/base/fresnel.dom.pas

@@ -882,7 +882,7 @@ type
     SubParent: boolean; // a node in between: has children but no Layouter, e.g. span
 
     // used values
-    ZIndex: TFresnelLength;
+    ZIndex: TFresnelLength; // position=static has 0, non static have z-index+0.5
     BorderLeft: TFresnelLength;
     BorderRight: TFresnelLength;
     BorderTop: TFresnelLength;
@@ -923,13 +923,14 @@ type
     function FitWidth(aWidth: TFresnelLength): TFresnelLength;
     function FitHeight(aHeight: TFresnelLength): TFresnelLength;
     procedure ResetUsedLengths; virtual;
+    property Element: TFresnelElement read FElement write SetElement;
+    // rendering order (z-index layout)
     {$IF FPC_FULLVERSION>=30301}
     procedure SortNodes(const Compare: TListSortComparer_Context; Context: Pointer = nil); virtual;
     {$ELSE}
     procedure SortNodes(const Compare: TListSortCompare); virtual;
     {$ENDIF}
     property Parent: TFresnelLayoutNode read FParent write SetParent;
-    property Element: TFresnelElement read FElement write SetElement;
     property NodeCount: integer read GetNodeCount;
     property Nodes[Index: integer]: TFresnelLayoutNode read GetNodes;
   end;
@@ -1036,7 +1037,6 @@ type
     FComputedDirection: TCSSNumericalID;
     FComputedVisibility: TCSSNumericalID;
     FComputedWritingMode: TCSSNumericalID;
-    FDOMIndex: integer;
     FFont: IFresnelFont;
     FLayoutNode: TFresnelLayoutNode;
     FFontDesc: TFresnelFontDesc;
@@ -1148,6 +1148,9 @@ type
     function GetStyleAttr(const AttrName: string): string; virtual; overload;
     function SetStyleAttr(const AttrName, aValue: string): boolean; virtual; overload;
     property StyleElement: TCSSRuleElement read FStyleElement; // computed from Style
+    // CSS classes
+    procedure AddCSSClass(const aName: string); virtual;
+    procedure RemoveCSSClass(const aName: string); virtual;
     // CSS computed attributes
     procedure ComputeStyleElement; virtual; // parse inline style
     procedure ComputeCSSValues; virtual; // call resolver to collect CSS values and resolve shorthands
@@ -1177,6 +1180,7 @@ type
     function GetComputedTextShadow(out aOffsetX, aOffsetY, aRadius: TFresnelLength; out aColor: TFPColor): boolean; virtual; // on fail returns 0
     function GetComputedImage(Attr: TFresnelCSSAttribute): TFresnelCSSBackgroundInfo; virtual; // on fail returns nil
     function GetComputedLinearGradient(const LGParams: string): TFresnelCSSLinearGradient; virtual; // on fail returns nil
+    function GetComputedZIndex: integer; virtual;
     function GetUnsetCSSString(Attr: TFresnelCSSAttribute): string; virtual;
     function GetPixPerUnit(anUnit: TCSSUnit; IsHorizontal: boolean): TFresnelLength; virtual;
     procedure SetComputedCSSString(Attr: TFresnelCSSAttribute; const Value: string); virtual;
@@ -1193,11 +1197,10 @@ type
     property CSSPseudoClass[Pseudo: TFresnelCSSPseudoClass]: boolean read GetCSSPseudoClass write SetCSSPseudoClass;
     // layouter
     function GetIntrinsicContentSize(aMode: TFresnelLayoutMode; aMaxWidth: TFresnelLength = NaN; aMaxHeight: TFresnelLength = NaN): TFresnelPoint; virtual; // ignoring min|max-height
-    function GetContainer: TFresnelElement; virtual;
     function GetContainerContentWidth(UseNaNOnFail: boolean): TFresnelLength; virtual; // used value
     function GetContainerContentHeight(UseNaNOnFail: boolean): TFresnelLength; virtual; // used value
+    function GetContainerOffset: TFresnelPoint; virtual;
     function IsBlockFormattingContext: boolean; virtual;
-    property DOMIndex: integer read FDOMIndex write FDOMIndex;
     property LayoutNode: TFresnelLayoutNode read FLayoutNode write FLayoutNode;
     property UsedBorderBox: TFresnelRect read FUsedBorderBox write FUsedBorderBox; // relative to layout parent's used contentbox
     property UsedContentBox: TFresnelRect read FUsedContentBox write FUsedContentBox; // relative to layout parent's used contentbox
@@ -1212,7 +1215,7 @@ type
     property EventDispatcher : TFresnelEventDispatcher Read FEventDispatcher;
     // font
     property Font: IFresnelFont read GetFont write FFont;
-    //
+    // viewport
     property Resolver: TCSSResolver read FResolver;
     property ViewportConnected: boolean read GetViewportConnected write SetViewportConnected; // true for example if using a Font of Viewport
     property Viewport: TFresnelViewport read FViewPort;
@@ -5590,16 +5593,12 @@ begin
 end;
 
 procedure TFresnelViewport.ApplyCSS;
-var
-  CurDomIndex: integer;
 
   procedure TraverseCSS(El: TFresnelElement);
   var
     i: Integer;
   begin
     //writeln('Traverse ',El.ClassName,' CSSTypeName=',El.CSSTypeName);
-    El.DOMIndex:=CurDomIndex;
-    inc(CurDomIndex);
     if El<>Self then
     begin
       El.FResolver:=Resolver;
@@ -5621,7 +5620,6 @@ begin
   InitResolver;
   //writeln('TFresnelViewport.ApplyCSS ',StylesheetElements.ClassName);
   // compute CSS
-  CurDomIndex:=0;
   TraverseCSS(Self);
   // layout
   Layouter.Apply;
@@ -6294,6 +6292,21 @@ begin
   Result:=true;
 end;
 
+procedure TFresnelElement.AddCSSClass(const aName: string);
+begin
+  if CSSClasses.IndexOf(aName)>=0 then exit;
+  CSSClasses.Add(aName);
+end;
+
+procedure TFresnelElement.RemoveCSSClass(const aName: string);
+var
+  i: Integer;
+begin
+  i:=CSSClasses.IndexOf(aName);
+  if i>=0 then
+    CSSClasses.Delete(i);
+end;
+
 function TFresnelElement.GetDPI(IsHorizontal: boolean): TFresnelLength;
 begin
   if Parent<>nil then
@@ -6568,44 +6581,6 @@ begin
   Result:=LayoutNode.GetIntrinsicContentSize(aMode,aMaxWidth,aMaxHeight);
 end;
 
-function TFresnelElement.GetContainer: TFresnelElement;
-begin
-  case ComputedPosition of
-  CSSRegistry.kwAbsolute:
-    begin
-      // the containing block is nearest ancestor element that has a position value other than static
-      Result:=Parent;
-      while (Result<>nil) do
-      begin
-        if not (Result.ComputedPosition in [CSSRegistry.kwStatic,CSSIDNone]) then
-          exit;
-        // Note:check for transform<>'none'
-        Result:=Result.Parent;
-      end;
-      Result:=Viewport;
-    end;
-  CSSRegistry.kwFixed:
-    begin
-      // viewport
-      Result:=Viewport;
-    end;
-  else
-    // static, relative, sticky
-    // the containing block is the nearest ancestor element that is either a
-    // block container (such as an inline-block, block, or list-item element)
-    // or establishes a formatting context (such as a table container,
-    // flex container, grid container, or the block container itself)
-    Result:=Parent;
-    while (Result<>nil) do
-    begin
-      if Result.IsBlockFormattingContext then
-        exit;
-      Result:=Result.Parent;
-    end;
-    Result:=Viewport;
-  end;
-end;
-
 function TFresnelElement.GetContainerContentWidth(UseNaNOnFail: boolean): TFresnelLength;
 var
   El: TFresnelElement;
@@ -6636,6 +6611,21 @@ begin
     Result:=0;
 end;
 
+function TFresnelElement.GetContainerOffset: TFresnelPoint;
+var
+  Container, El: TFresnelElement;
+begin
+  Result:=Default(TFresnelPoint);
+  Container:=LayoutNode.Container;
+  El:=Parent;
+  while El<>Container do
+  begin
+    Result.X:=Result.X+El.UsedContentBox.Left;
+    Result.Y:=Result.Y+El.UsedContentBox.Top;
+    El:=El.Parent;
+  end;
+end;
+
 function TFresnelElement.IsBlockFormattingContext: boolean;
 // BFC
 // contain internal and external floats
@@ -7083,6 +7073,22 @@ begin
   //  writeln('TFresnelElement.GetRenderedCSSLinearGradient ',GetPath,' ',i,' ',dbgs(Result.Colors[i].Color),' ',Result.Colors[i].Percentage);
 end;
 
+function TFresnelElement.GetComputedZIndex: integer;
+var
+  Complete: boolean;
+  s: String;
+  Code: integer;
+begin
+  Result:=0;
+  s:=GetCSSString(CSSRegistry.FresnelAttrs[fcaZIndex].Index,false,Complete);
+  case s of
+  '','auto': exit;
+  else
+    val(s,Result,Code);
+    if Code=0 then ;
+  end;
+end;
+
 function TFresnelElement.GetUnsetCSSString(Attr: TFresnelCSSAttribute): string;
 var
   AttrDesc: TFresnelCSSAttrDesc;

+ 164 - 151
src/base/fresnel.layouter.pas

@@ -223,16 +223,16 @@ type
     procedure SetViewport(const AValue: TFresnelViewport);
   protected
     procedure ErrorLayout(const ID: int64; const Msg: string); virtual;
-    procedure Layout(Node: TUsedLayoutNode); virtual;
+    procedure Layout(El: TFresnelElement); virtual;
     function CreateLayoutNode(El: TFresnelElement): TUsedLayoutNode; virtual;
   public
     constructor Create(AOwner: TComponent); override;
     procedure Apply; override;
     procedure UpdateLayouter(El: TFresnelElement; LNode: TUsedLayoutNode); virtual;
-    procedure UpdateLayoutParent(El: TFresnelElement; LNode: TUsedLayoutNode); virtual;
-    procedure ComputeCSSLayoutNode(El: TFresnelElement); virtual; // called after basic CSS properties were computed, create the layout nodes
+    function GetContainer(El: TFresnelElement): TFresnelElement; virtual;
+    procedure UpdateLayoutNode(El: TFresnelElement); virtual; // called after basic CSS properties were computed, create the layout nodes
     procedure SortStackingContext(LNode: TUsedLayoutNode); virtual; // apply z-index
-    procedure WriteLayoutTree; // for debugging: write tree information to log
+    procedure WriteRenderingTree; // for debugging: write tree information to log
     property Viewport: TFresnelViewport read FViewport write SetViewport;
     // needs:
     // viewport width, height, DPI, font size default, font size min
@@ -241,37 +241,28 @@ type
     // measure text width
 
     // produces:
-    // layout tree
+    // layout rendering tree
     // boxes
     // margin, border, padding
     // left, top, width, height
     // clipping: left, top, right, bottom
   end;
 
-function CompareLayoutNodesZIndexDomIndex(Item1, Item2{$IF FPC_FULLVERSION>=30301}, {%H-}Context{$ENDIF}: Pointer): integer;
+function CompareLayoutNodesZIndex(Item1, Item2{$IF FPC_FULLVERSION>=30301}, {%H-}Context{$ENDIF}: Pointer): integer;
 
 implementation
 
-function CompareLayoutNodesZIndexDomIndex(Item1, Item2{$IF FPC_FULLVERSION>=30301}, {%H-}Context{$ENDIF}: Pointer): integer;
+function CompareLayoutNodesZIndex(Item1, Item2{$IF FPC_FULLVERSION>=30301}, {%H-}Context{$ENDIF}: Pointer): integer;
 var
   Node1: TUsedLayoutNode absolute Item1;
   Node2: TUsedLayoutNode absolute Item2;
-  DomIndex1, DomIndex2: Integer;
 begin
   if Node1.ZIndex>Node2.ZIndex then
     Result:=1
   else if Node1.ZIndex<Node2.ZIndex then
     Result:=-1
-  else begin
-    DomIndex1:=Node1.Element.DOMIndex;
-    DomIndex2:=Node2.Element.DOMIndex;
-    if DomIndex1>DomIndex2 then
-      Result:=1
-    else if DomIndex1<DomIndex2 then
-      Result:=-1
-    else
-      Result:=0;
-  end;
+  else
+    Result:=0;
 end;
 
 { TFLNodeLayouter }
@@ -439,9 +430,9 @@ end;
 procedure TUsedLayoutNode.ApplyMinMaxWidth;
 begin
   if IsNan(Width) then exit;
-  if Width<0 then Width:=0;
   if (not IsNan(MaxWidth)) and (Width>MaxWidth) then
     Width:=MaxWidth;
+  if Width<0 then Width:=0;
   if (not IsNan(MinWidth)) and (Width<MinWidth) then
     Width:=MinWidth;
 end;
@@ -564,9 +555,6 @@ begin
   begin
     // left, top, right, bottom are used
 
-    // either Left or Right must be set
-    if IsNan(Left) and IsNan(Right) then
-      Left:=0;
     if IsNan(Width) then
     begin
       if (not IsNan(Left)) and (not IsNan(Right)) then
@@ -584,11 +572,14 @@ begin
       // width is set
       if IsNan(Left) then
       begin
-        // width & right -> left
-        aContainerWidth:=Element.GetContainerContentWidth(true);
-        if not IsNan(aContainerWidth) then
-          Left:=aContainerWidth-Width-MarginLeft-BorderLeft-PaddingLeft
-                                -Right-MarginRight-BorderRight-PaddingRight;
+        if not IsNan(Right) then
+        begin
+          // width & right -> left
+          aContainerWidth:=Element.GetContainerContentWidth(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);
@@ -598,9 +589,6 @@ begin
       end;
     end;
 
-    // either Top or Bottom must be set
-    if IsNan(Top) and IsNan(Bottom) then
-      Top:=0;
     if IsNan(Height) then
     begin
       if (not IsNan(Top)) and (not IsNan(Bottom)) then
@@ -618,11 +606,14 @@ begin
       // Height is set
       if IsNan(Top) then
       begin
-        // Height & Bottom -> Top
-        aContainerHeight:=Element.GetContainerContentHeight(true);
-        if not IsNan(aContainerHeight) then
-          Top:=aContainerHeight-Height-MarginTop-BorderTop-PaddingTop
-                               -Bottom-MarginBottom-BorderBottom-PaddingBottom;
+        if not IsNan(Bottom) then
+        begin
+          // Height & Bottom -> Top
+          aContainerHeight:=Element.GetContainerContentHeight(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);
@@ -754,9 +745,9 @@ end;
 function TFLFlowLayouter.ComputeLayoutContent(aMode: TFresnelLayoutMode; aMaxWidth,
   aMaxHeight: TFresnelLength; Commit: boolean): TFresnelPoint;
 var
-  NodeIndex: Integer;
-  ChildNode: TUsedLayoutNode;
-  ChildEl: TFresnelElement;
+  ChildIndex: Integer;
+  ChildNode, LastFlowNode: TUsedLayoutNode;
+  ChildEl, LastFlowEl, El: TFresnelElement;
   IsInline: Boolean;
   NewChildRight, CurSpace, OldMarginBoxBottom,
     ChildMBoxLeft, ChildMBoxRight { MarginBox relative to parent ContentBox left },
@@ -765,7 +756,7 @@ var
     ChildMaxWidth, ChildMaxHeight { ContentBox, can be NaN },
   ChildMarginLeft, ChildMarginRight, ChildMarginTop, ChildMarginBottom,
   ChildPadBorderX, ChildPadBorderY: TFresnelLength;
-  ChildPrefSize, ChildPrefPos: TFresnelPoint;
+  ChildPrefSize, ChildDefPos: TFresnelPoint;
 
   procedure AddLineNodeCache;
   var
@@ -795,41 +786,22 @@ begin
 
   FLastLineBorderBoxBottom:=0;
   FLastLineMarginBottom:=0;
+  El:=Node.Element;
 
   // add elements to the line until full
   StartLine;
-  for NodeIndex:=0 to Node.NodeCount-1 do
+  for ChildIndex:=0 to El.NodeCount-1 do
   begin
-    ChildNode:=TUsedLayoutNode(Node.Nodes[NodeIndex]);
-    ChildEl:=ChildNode.Element;
-    //writeln('TFLFlowLayouter.ComputeLayoutBorderBox ',ChildEl.GetPath);
-
-    // skip hidden
-    if (ChildEl.ComputedVisibility=CSSRegistry.kwCollapse)
-        or (ChildEl.ComputedDisplayOutside=CSSIDNone) then
-    begin
-      continue;
-    end;
+    ChildEl:=El.Nodes[ChildIndex];
+    ChildNode:=TUsedLayoutNode(ChildEl.LayoutNode);
 
-    IsInline:=ChildEl.ComputedDisplayOutside=CSSRegistry.kwInline;
+    if ChildNode.SkipLayout then continue;
 
     // skip position: absolute and fixed
-    case ChildEl.ComputedPosition of
-    CSSRegistry.kwStatic,CSSRegistry.kwRelative,CSSRegistry.kwSticky: ;
-    else
-      // position is absolute or fixed
-      if IsInline then
-      begin
-        ChildPrefPos.X:=FLineBorderBoxRight+FLineMarginRight;
-        ChildPrefPos.Y:=FLineBorderBoxTop;
-      end else begin
-        ChildPrefPos.X:=0;
-        ChildPrefPos.Y:=FLineMarginBottom;
-      end;
-      PlaceAbsoluteItem(ChildNode,aMode,aMaxWidth,aMaxHeight,ChildPrefPos,Commit);
-
+    if ChildEl.ComputedPosition in [CSSRegistry.kwAbsolute,CSSRegistry.kwFixed] then
       continue;
-    end;
+
+    IsInline:=ChildEl.ComputedDisplayOutside=CSSRegistry.kwInline;
 
     // display-outside: inline or block
     if (not IsInline) or (aMode in [flmMinWidth,flmMaxHeight]) then
@@ -852,7 +824,7 @@ begin
     else
       CurSpace:=Max(0,aMaxWidth-FLineBorderBoxRight-Max(FLineMarginRight,ChildNode.MarginLeft)-ChildNode.MarginRight);
 
-    //writeln('TFLFlowLayouter.ComputeLayoutBorderBox ',ChildEl.GetPath,' Margin=',FloatToStr(ChildMarginLeft),',',FloatToStr(ChildMarginTop),',',FloatToStr(ChildMarginRight),',',FloatToStr(ChildMarginBottom),' Border=',FloatToStr(ChildBorderLeft),',',FloatToStr(ChildBorderTop),',',FloatToStr(ChildBorderRight),',',FloatToStr(ChildBorderBottom),' Padding=',FloatToStr(ChildPaddingLeft),',',FloatToStr(ChildPaddingTop),',',FloatToStr(ChildPaddingRight),',',FloatToStr(ChildPaddingBottom));
+    //writeln('TFLFlowLayouter.ComputeLayoutContent ',ChildEl.GetPath,' Margin=',FloatToStr(ChildMarginLeft),',',FloatToStr(ChildMarginTop),',',FloatToStr(ChildMarginRight),',',FloatToStr(ChildMarginBottom),' Border=',FloatToStr(ChildBorderLeft),',',FloatToStr(ChildBorderTop),',',FloatToStr(ChildBorderRight),',',FloatToStr(ChildBorderBottom),' Padding=',FloatToStr(ChildPaddingLeft),',',FloatToStr(ChildPaddingTop),',',FloatToStr(ChildPaddingRight),',',FloatToStr(ChildPaddingBottom));
 
     ChildMarginLeft:=ChildNode.MarginLeft;
     ChildMarginRight:=ChildNode.MarginRight;
@@ -867,12 +839,12 @@ begin
     ChildMinHeight:=ChildNode.MinHeight; // at least 0
     ChildMaxHeight:=ChildNode.MaxHeight; // can be NaN
 
-    //writeln('TFLFlowLayouter.ComputeLayoutBorderBox ',ChildEl.GetPath,' Commit=',Commit,' ChildWidth=',FloatToStr(ChildWidth),' ChildHeight=',FloatToStr(ChildHeight));
+    //writeln('TFLFlowLayouter.ComputeLayoutContent ',ChildEl.GetPath,' Commit=',Commit,' ChildWidth=',FloatToStr(ChildWidth),' ChildHeight=',FloatToStr(ChildHeight));
 
     if Commit and (not IsInline) and IsNan(ChildWidth) and (not IsNan(CurSpace)) then
     begin
       // a block element expands the full line
-      //writeln('TFLFlowLayouter.ComputeLayoutBorderBox BLOCK FULL LINE: ',ChildEl.GetPath,' CurSpace=',FloatToStr(CurSpace),' ChildPadBorderX=',FloatToStr(ChildPadBorderX));
+      //writeln('TFLFlowLayouter.ComputeLayoutContent BLOCK FULL LINE: ',ChildEl.GetPath,' CurSpace=',FloatToStr(CurSpace),' ChildPadBorderX=',FloatToStr(ChildPadBorderX));
       ChildWidth:=CurSpace-ChildPadBorderX;
     end;
     if not IsNan(ChildWidth) then
@@ -898,7 +870,7 @@ begin
       end;
       ChildPrefSize:=ChildEl.GetIntrinsicContentSize(aMode,CurSpace,NaN);
       //if ChildEl.Name='ButtonLabel' then
-      //  writeln('TFLFlowLayouter.ComputeLayoutBorderBox ',ChildEl.GetPath,' MaxWidth=',CurSpace,' Preferred=',ChildPrefSize.ToString,' ChildPadBorderX=',ChildPadBorderX,' ChildPadBorderY=',ChildPadBorderY);
+      //  writeln('TFLFlowLayouter.ComputeLayoutContent ',ChildEl.GetPath,' MaxWidth=',CurSpace,' Preferred=',ChildPrefSize.ToString,' ChildPadBorderX=',ChildPadBorderX,' ChildPadBorderY=',ChildPadBorderY);
       // apply min, max
       if IsNan(ChildWidth) then
       begin
@@ -932,7 +904,7 @@ begin
       end;
     end;
 
-    //debugln(['TFLFlowLayouter.ComputeLayoutBorderBox ',ChildEl.Name,' ',ChildHeight,':=BT ',ChildBorderTop,'+PT ',ChildPaddingTop,'+H ',ChildHeight,'+PB ',ChildPaddingBottom,'+BB ',ChildBorderBottom]);
+    //debugln(['TFLFlowLayouter.ComputeLayoutContent ',ChildEl.Name,' ',ChildHeight,':=BT ',ChildBorderTop,'+PT ',ChildPaddingTop,'+H ',ChildHeight,'+PB ',ChildPaddingBottom,'+BB ',ChildBorderBottom]);
     if FLineItems.Count=0 then
     begin
       // first element in line
@@ -951,7 +923,7 @@ begin
       FLineBorderBoxLeft:=ChildMBoxLeft+ChildMarginLeft;
       FLineBorderBoxRight:=ChildMBoxRight-ChildMarginRight;
       FLineBorderBoxHeight:=ChildHeight+ChildPadBorderY;
-      //writeln('TFLFlowLayouter.ComputeLayoutBorderBox ',ChildEl.Name,' FBorderLeft=',FloatToStr(FBorderLeft),' FPaddingLeft=',FloatToStr(FPaddingLeft),' ChildMarginLeft=',FloatToStr(ChildMarginLeft),' FLineBorderBoxLeft=',FloatToStr(FLineBorderBoxLeft),' ChildLeft=',FloatToStr(ChildMBoxLeft),' ChildRight=',FloatToStr(ChildMBoxRight),
+      //writeln('TFLFlowLayouter.ComputeLayoutContent ',ChildEl.Name,' FBorderLeft=',FloatToStr(FBorderLeft),' FPaddingLeft=',FloatToStr(FPaddingLeft),' ChildMarginLeft=',FloatToStr(ChildMarginLeft),' FLineBorderBoxLeft=',FloatToStr(FLineBorderBoxLeft),' ChildLeft=',FloatToStr(ChildMBoxLeft),' ChildRight=',FloatToStr(ChildMBoxRight),
       //  ' ChildLeft=',FloatToStr(ChildMBoxLeft),
       //  ' ChildMarginLeft=',FloatToStr(ChildMarginLeft),' ChildBorderLeft=',FloatToStr(ChildBorderLeft),' ChildPaddingLeft=',FloatToStr(ChildPaddingLeft),
       //  ' ChildWidth=',FloatToStr(ChildWidth),
@@ -991,10 +963,49 @@ begin
       AddLineNodeCache;
     end;
 
-    //writeln('TFLFlowLayouter.ComputeLayoutBorderBox ',ChildEl.Name,' Commit=',Commit,' ChildLeft=',ChildMBoxLeft,' ChildRight=',ChildMBoxRight,' ChildWidth=',ChildWidth,' ChildHeight=',ChildHeight);
+    //writeln('TFLFlowLayouter.ComputeLayoutContent ',ChildEl.Name,' Commit=',Commit,' ChildLeft=',ChildMBoxLeft,' ChildRight=',ChildMBoxRight,' ChildWidth=',ChildWidth,' ChildHeight=',ChildHeight);
   end;
   EndLine(Commit);
 
+  if Commit then
+  begin
+    // place absolute items
+    LastFlowNode:=nil;
+    for ChildIndex:=0 to El.NodeCount-1 do
+    begin
+      ChildEl:=El.Nodes[ChildIndex];
+      ChildNode:=TUsedLayoutNode(ChildEl.LayoutNode);
+
+      if ChildNode.SkipLayout then continue;
+
+      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;
+    end;
+  end;
+
   Result.Y:=Max(Result.Y,FLastLineBorderBoxBottom+FLastLineMarginBottom);
 end;
 
@@ -1438,9 +1449,9 @@ end;
 function TFLFlexLayouter.ComputeLayoutContent(aMode: TFresnelLayoutMode; aMaxWidth,
   aMaxHeight: TFresnelLength; Commit: boolean): TFresnelPoint;
 var
-  NodeIndex: Integer;
+  ChildIndex: Integer;
   ChildNode: TUsedLayoutNode;
-  ChildEl: TFresnelElement;
+  ChildEl, El: TFresnelElement;
   Item: TFlexItem;
   MaxMainSize: TFresnelLength; // max content size in main direction, can be NaN
   CurMainSize: TFresnelLength; // current line size in main direction
@@ -1464,18 +1475,14 @@ begin
   StartLine;
   Item:=nil;
   ItemAdded:=false;
+  El:=Node.Element;
   try
-    for NodeIndex:=0 to Node.NodeCount-1 do
+    for ChildIndex:=0 to El.NodeCount-1 do
     begin
-      ChildNode:=TUsedLayoutNode(Node.Nodes[NodeIndex]);
-      ChildEl:=ChildNode.Element;
+      ChildEl:=El.Nodes[ChildIndex];
+      ChildNode:=TUsedLayoutNode(ChildEl.LayoutNode);
 
-      // skip collapsed
-      if (ChildEl.ComputedVisibility=CSSRegistry.kwCollapse)
-          or (ChildEl.ComputedDisplayOutside=CSSIDNone) then
-      begin
-        continue;
-      end;
+      if ChildNode.SkipLayout then continue;
 
       // skip position: absolute and fixed
       if ChildEl.ComputedPosition in [CSSRegistry.kwAbsolute,CSSRegistry.kwFixed] then
@@ -1600,15 +1607,23 @@ begin
   ChildEl:=ChildNode.Element;
 
   //if ChildEl.Name='Div1' then
-  //  writeln('TFLFlowLayouter.PlaceAbsoluteItem ',ChildEl.GetPath,' ',aMode,' Commit=',Commit,' Left=',FloatToCSSStr(ChildNode.Left),',Top=',FloatToCSSStr(ChildNode.Top),',Right=',FloatToCSSStr(ChildNode.Right),',Bottom=',FloatToCSSStr(ChildNode.Bottom),' Width=',FloatToCSSStr(ChildNode.Width),' Height=',FloatToCSSStr(ChildNode.Height));
+    writeln('TFLFlowLayouter.PlaceAbsoluteItem ',ChildEl.GetPath,' ',aMode,' Commit=',Commit,' Left=',FloatToCSSStr(ChildNode.Left),',Top=',FloatToCSSStr(ChildNode.Top),',Right=',FloatToCSSStr(ChildNode.Right),',Bottom=',FloatToCSSStr(ChildNode.Bottom),' Width=',FloatToCSSStr(ChildNode.Width),' Height=',FloatToCSSStr(ChildNode.Height),' Default=',FloatToCSSStr(DefaultPos.X),',',FloatToCSSStr(DefaultPos.Y));
   NewLeft:=ChildNode.Left;
   NewTop:=ChildNode.Top;
   NewRight:=ChildNode.Right;
   NewBottom:=ChildNode.Bottom;
+  p.X:=NaN;
   if IsNan(NewLeft) and IsNan(NewRight) then
-    NewLeft:=DefaultPos.X;
+  begin
+    p:=ChildEl.GetContainerOffset;
+    NewLeft:=p.X+DefaultPos.X;
+  end;
   if IsNan(NewTop) and IsNan(NewBottom) then
-    NewTop:=DefaultPos.Y;
+  begin
+    if IsNan(p.X) then
+      p:=ChildEl.GetContainerOffset;
+    NewTop:=p.y+DefaultPos.Y;
+  end;
 
   if Commit then
   begin
@@ -1777,27 +1792,32 @@ begin
   raise EFresnelLayout.Create(s);
 end;
 
-procedure TViewportLayouter.Layout(Node: TUsedLayoutNode);
+procedure TViewportLayouter.Layout(El: TFresnelElement);
 var
   i: Integer;
+  Node: TUsedLayoutNode;
+  ChildEl: TFresnelElement;
 begin
+  Node:=TUsedLayoutNode(El.LayoutNode);
   if Node.SkipLayout then exit;
 
   //writeln('TViewportLayouter.Layout ',Node.Element.GetPath,' Width=',FloatToCSSStr(Node.Width),' Height=',FloatToCSSStr(Node.Height),' Layouter=',DbgSName(Node.Layouter));
 
+  // sort for z-index
+  SortStackingContext(Node);
+
   // compute used layout lengths of children, with access to used grand child lengths
-  for i:=0 to Node.NodeCount-1 do
-    TUsedLayoutNode(Node.Nodes[i]).ComputeUsedLengths(false);
-  //writeln('TViewportLayouter.Layout AFTER child ComputeUsedLengths');
+  for i:=0 to El.NodeCount-1 do
+  begin
+    ChildEl:=El.Nodes[i];
+    TUsedLayoutNode(ChildEl.LayoutNode).ComputeUsedLengths(false);
+  end;
 
   if Node.Layouter<>nil then
     Node.Layouter.Apply;
 
-  for i:=0 to Node.NodeCount-1 do
-    Layout(TUsedLayoutNode(Node.Nodes[i]));
-
-  // sort for z-index
-  SortStackingContext(Node);
+  for i:=0 to El.NodeCount-1 do
+    Layout(El.Nodes[i]);
 end;
 
 function TViewportLayouter.CreateLayoutNode(El: TFresnelElement
@@ -1825,7 +1845,7 @@ procedure TViewportLayouter.Apply;
     Node: TUsedLayoutNode;
   begin
     // create or free layout nodes
-    ComputeCSSLayoutNode(El);
+    UpdateLayoutNode(El);
     El.ComputeCSSAfterLayoutNode(Self);
 
     // compute used layout lengths like padding and width *without*
@@ -1873,7 +1893,7 @@ begin
 
   // layout
   VPNode.ComputeUsedLengths(false);
-  Layout(VPNode);
+  Layout(Viewport);
 end;
 
 procedure TViewportLayouter.UpdateLayouter(El: TFresnelElement;
@@ -1909,58 +1929,49 @@ begin
   end;
 end;
 
-procedure TViewportLayouter.UpdateLayoutParent(El: TFresnelElement;
-  LNode: TUsedLayoutNode);
-var
-  ZIndexStr: String;
-  ZIndexInt, {%H-}Code: Integer;
-  NewParent: TFresnelElement;
-  ParentLNode: TUsedLayoutNode;
+function TViewportLayouter.GetContainer(El: TFresnelElement): TFresnelElement;
 begin
-  //FLLog(etDebug,['TSimpleFresnelLayouter.UpdateLayoutParent ',El.GetPath]);
-  if LNode.SkipLayout or (El.Parent=nil) then
-    LNode.Parent:=nil
-  else begin
-    case El.ComputedPosition of
-    CSSRegistry.kwAbsolute,
-    CSSRegistry.kwFixed,
-    CSSRegistry.kwRelative,
-    CSSRegistry.kwSticky:
+  case El.ComputedPosition of
+  CSSRegistry.kwAbsolute:
+    begin
+      // the containing block is nearest ancestor element that has a position value other than static
+      Result:=El.Parent;
+      while (Result<>nil) do
       begin
-        ZIndexStr:=El.ComputedAttribute[fcaZIndex];
-        if (ZIndexStr='') or (ZIndexStr='auto') then
-          LNode.ZIndex:=0.0
-        else begin
-          ZIndexInt:=0;
-          val(ZIndexStr,ZIndexInt,Code);
-          LNode.ZIndex:=ZIndexInt;
-        end;
-        LNode.ZIndex:=LNode.ZIndex+0.1; // slightly higher than no position
+        if not (Result.ComputedPosition in [CSSRegistry.kwStatic,CSSIDNone]) then
+          exit;
+        // Note:check for transform<>'none'
+        Result:=Result.Parent;
       end;
-    else
-      LNode.ZIndex:=0;
     end;
-
-    NewParent:=LNode.Container;
-    while (NewParent<>nil) and (TUsedLayoutNode(NewParent.LayoutNode).Layouter=nil) do
-      NewParent:=NewParent.Parent;
-    if NewParent=nil then
-      LNode.Parent:=nil
-    else begin
-      ParentLNode:=TUsedLayoutNode(NewParent.LayoutNode);
-      LNode.Parent:=ParentLNode;
-      if ParentLNode.Layouter=nil then
-        ParentLNode.SubParent:=true;
+  CSSRegistry.kwFixed:
+    // viewport
+  else
+    // static, relative, sticky
+    // the containing block is the nearest ancestor element that is either a
+    // block container (such as an inline-block, block, or list-item element)
+    // or establishes a formatting context (such as a table container,
+    // flex container, grid container, or the block container itself)
+    Result:=El.Parent;
+    while (Result<>nil) do
+    begin
+      if Result.IsBlockFormattingContext then
+        exit;
+      Result:=Result.Parent;
     end;
   end;
+
+  // default viewport
+  if El.Parent=nil then
+    Result:=nil
+  else
+    Result:=Viewport;
 end;
 
-procedure TViewportLayouter.ComputeCSSLayoutNode(El: TFresnelElement);
+procedure TViewportLayouter.UpdateLayoutNode(El: TFresnelElement);
 var
   LNode, ParentLNode: TUsedLayoutNode;
-  {%H-}Code: integer;
 begin
-  //FLLog(etDebug,['TSimpleFresnelLayouter.ComputeCSS ',El.GetPath]);
   // every node gets a layout node
   LNode:=CreateLayoutNode(El);
 
@@ -1994,15 +2005,21 @@ begin
     end;
   end;
 
+  // rendering hierarchy
+  if (El.Parent=nil) or LNode.SkipRendering then
+    LNode.Parent:=nil
+  else
+    LNode.Parent:=El.Parent.LayoutNode;
+  if El.ComputedPosition<>CSSRegistry.kwStatic then
+    LNode.ZIndex:=TFresnelLength(El.GetComputedZIndex)+0.5
+  else
+    LNode.ZIndex:=0;
+
   // layouter
   UpdateLayouter(El,LNode);
 
   // block container
-  LNode.Container:=El.GetContainer;
-  //FLLog(etDebug,['TSimpleFresnelLayouter.ComputeCSS ',El.Name,' BlockContainer=',LNode.Container.ToString]);
-
-  // LayoutParent
-  UpdateLayoutParent(El,LNode);
+  LNode.Container:=GetContainer(El);
 
   // fetch basic CSS layout values
   if LNode.Layouter<>nil then
@@ -2025,11 +2042,7 @@ procedure TViewportLayouter.SortStackingContext(LNode: TUsedLayoutNode
     begin
       dec(i);
       Node:=TUsedLayoutNode(LNode.Nodes[i]);
-      if (Node.ZIndex<NextNode.ZIndex) then
-        // ok
-      else if (Node.ZIndex>NextNode.ZIndex) then
-        exit(false) // need sorting
-      else if Node.Element.DOMIndex>NextNode.Element.DOMIndex then
+      if (Node.ZIndex>NextNode.ZIndex) then
         exit(false); // need sorting
       NextNode:=Node;
     end;
@@ -2038,14 +2051,14 @@ procedure TViewportLayouter.SortStackingContext(LNode: TUsedLayoutNode
 
 begin
   if IsSorted then exit;
-  LNode.SortNodes(@CompareLayoutNodesZIndexDomIndex);
+  LNode.SortNodes(@CompareLayoutNodesZIndex);
   {$IFDEF VerboseFresnelLayouter}
   if not IsSorted then
     raise Exception.Create('20221031180116 TSimpleFresnelLayouter.SortLayoutNodes');
   {$ENDIF}
 end;
 
-procedure TViewportLayouter.WriteLayoutTree;
+procedure TViewportLayouter.WriteRenderingTree;
 
   procedure WriteNode(const Prefix: string; Node: TUsedLayoutNode);
   var

+ 70 - 0
tests/base/TCFlowLayout.pas

@@ -20,6 +20,8 @@ type
     procedure TestMarginPercentage;
     procedure TestPaddingPercentage;
     procedure TestPositionAbsolute_Right_WidthAuto;
+    procedure TestPositionAbsolute_DivDefaultPos;
+    // todo procedure TestPositionAbsolute_DivTop100Percent;
     // todo: test break line
   end;
 
@@ -403,6 +405,74 @@ begin
   AssertEquals('Div2.LayoutNode.Height',52,Div2.LayoutNode.Height);
 end;
 
+procedure TTestFlowLayout.TestPositionAbsolute_DivDefaultPos;
+var
+  Body: TBody;
+  Div1: TDiv;
+  Label1, Label2: 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;
+
+  Label2:=TLabel.Create(Viewport);
+  Label2.Name:='Label2';
+  Label2.Caption:='Label2';
+  Label2.Parent:=Body;
+
+  Viewport.Stylesheet.Text:=LinesToStr([
+    'body {',
+    '  margin: 0;', // content width = 800
+    '  font-size: 20px;',
+    '}',
+    '#Div1 {',
+    '  position: absolute;',
+    '  width: 30px; height: 20px;',
+    '}']);
+
+  // Div1 is absolute to the Viewport, because Body position is static
+  // Div1 default position is below Label1, because Div1 display is block
+  // Label2 is right behind Label1
+  // Body width/height includes Label1 and Label2, but 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)','static',Label1.GetComputedString(fcaPosition));
+  AssertEquals('Label1.GetComputedString(fcaDisplay)','inline flow',Label1.GetComputedString(fcaDisplay));
+  AssertEquals('Label1.GetComputedString(fcaWidth)','auto',Label1.GetComputedString(fcaWidth));
+  AssertEquals('Label1.RenderedBorderBox.Top',0,Label1.RenderedBorderBox.Left);
+  AssertEquals('Label1.RenderedBorderBox.Left',0,Label1.RenderedBorderBox.Left);
+  AssertEquals('Label1.RenderedBorderBox.Height',23,Label1.RenderedBorderBox.Height);
+
+  //writeln('TTestFlowLayout.TestPositionAbsolute_DivDefaultPos Label2.RenderedBorderBox=',Label2.RenderedBorderBox.ToString);
+  AssertEquals('Label2.GetComputedString(fcaPosition)','static',Label2.GetComputedString(fcaPosition));
+  AssertEquals('Label2.GetComputedString(fcaDisplay)','inline flow',Label2.GetComputedString(fcaDisplay));
+  AssertEquals('Label2.GetComputedString(fcaWidth)','auto',Label2.GetComputedString(fcaWidth));
+  AssertEquals('Label2.RenderedBorderBox.Height',23,Label2.RenderedBorderBox.Height);
+  AssertEquals('Label2.RenderedBorderBox.Top',0,Label2.RenderedBorderBox.Top);
+  AssertEquals('Label2.RenderedBorderBox.Left',Label1.RenderedBorderBox.Right,Label2.RenderedBorderBox.Left);
+
+  //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
   RegisterTests([TTestFlowLayout]);
 end.

+ 3 - 3
tests/base/TCFresnelCSS.pas

@@ -152,7 +152,7 @@ function LinesToStr(const Args: array of const): string;
 implementation
 
 const
-  // char sizes for a font size of 100
+  // char sizes for a font size of 1000
   CharHeight = 115;
   CharWidths: array[32..126] of word = (
     278, // space
@@ -388,7 +388,7 @@ begin
       end;
     32..126:
       begin
-        AddChar(aSize*CharWidths[CodePoint]/100);
+        AddChar(aSize*CharWidths[CodePoint]/1000);
         inc(p);
       end
     else
@@ -397,7 +397,7 @@ begin
       {$ELSE}
       CodePoint:=0;
       {$ENDIF}
-      AddChar(aSize*CharWidths[65]/100);
+      AddChar(aSize*CharWidths[65]/1000);
       inc(p,CodepointLen);
     end;
   end;