Fresnel CSS Algorithm

From Free Pascal wiki
Jump to navigationJump to search

Overview

This page describes the how Fresnel parses and applies CSS, layouts and renders.

Delayed computation

Whenever an element or CSS is added, removed, altered TFresnelElement.DomChanged is called. This does not compute anything immediately in contrary to LCL or VCL. Instead a change:

  • calls TFresnelCustomForm.DomChanged to LayoutQueued to true,
  • the first change queues a call of TFresnelCustomForm.OnQueuedLayout.
  • which eventually calls ApplyCSS and Invalidate,
  • which queues a WSDraw,
  • which eventually calls Renderer.Draw.

Each form has its own queued call.

If you need to compute a change immediately call Form.ApplyCSS.

ApplyCSS

The article gives an overview of the order and how the CSS is parsed and computed in Fresnel. The central method is TFresnelViewport.ApplyCSS.

Collecting type stylesheets

The Viewport gathers all used type stylesheets via GetCSSTypeStyle and adds them to resolver. These have origin user-agent.

  • E.g. if there is a TDiv and a TButton on the form, it will use the stylesheets returned by TDiv.GetCSSTypeStyle and TButton.GetCSSTypeStyle.
  • If there is a TMyButton, with TMyButton = class(TButton), it will contain both stylesheets. This allows to create a 'button' descendant, adding the 'button' stylesheet only once.

Parsing stylesheets

Each form has one Resolver: TCSSResolver. The resolver parses all stylesheets:

  • Starts with the stylesheets from the used element types, e.g. all used TFresnelElement classes. These have origin user-agent.
  • Next it parses the application stylesheets. These have origin user.
  • Finally it parses the form stylesheet. This has origin author.
  • Checks every attribute:
    • The resolver searches all attribute names and stores the numerical ID for later faster lookup.
    • Every attribute is registered in the CSSRegistry and has a OnCheck method. E.g. the 'display' attribute calls TFresnelCSSRegistry.CheckDisplay.
    • Every value is checked for syntax. If the value contains a var function, its syntax cannot be checked at this point.
    • If the value has a syntax error, then it is marked invalid and will be skipped. A warning can be logged.

Collect CSS attributes for each element

The Viewport traverses all nodes (TFresnelElement) to compute basic attributes:

  • It calls ComputeInlineStyle, which parses the Style property as inline style, similar to the above.
  • It calls ComputeCSSValues:
    • Calls Resolver.Compute
      • Collects all matching rules for this element
      • Merges the attributes of these rules and the inline style to a list of attributes. Considering origin, rule-specifity, short-hands, all and !important.
      • Substitutes var() calls using the custom-attributes of the element, or the inherited values.
      • Applies shorthands. E.g. padding is split into padding-top, padding-right, padding-bottom and padding-left.
      • Returns a list of attributes.
    • Substitute the base keywords initial, inherit, unset. Note: revert and revert-layer are currently treated as unset.
    • Compute base attributes: visibility, display, position, box-sizing, direction, writing-mode, overflow-x, overflow-y, float
      • if display:none then ComputedVisibility becomes CSSRegistry.kwCollapse

Layout

The Viewport calls Layouter.Apply, which is TViewportLayouter.Apply.

Layout Top-Down

First all elements are traversed top-down.

  • Every visible, non collapsed element (TFresnelElement) gets an instance of TUsedLayoutNode in LayoutNode.
  • Every non static element gets a z-index.
  • Every block element gets a layouter: TFLFlowLayouter, TFLFlexLayouter or TFLGridLayouter.
  • Set LayoutNode.Container
  • All used lengths are reset:
    • border-, padding-, margin-top/right/bottom/left, and line-height are set 0.
    • left, top, right, bottom, width, height, min-width, max-width, min-height, max-height are set to NaN.
  • TUsedLayoutNode.ComputeUsedLengths is called with NoChildren=true (top-down run).
    • It computes each length, using only the element's and its parents' attributes.
    • Some values can be computed immediately e.g. 'width:100px'
    • Some values are relative:
      • font-size:150% means 150% of parent's font-size, that means it can be computed top-down.
      • line-height:150% means 150% of element's font-size, it can be computed top-down
      • line-height:1.2 must be inherited as '1.2', because it can mean different sizes in descendants, that means distinguish Computed and Used
    • Some values can be deducted. E.g. given left, right, it can deduct width. Well, it is more complicated due to min-width, max-width, display, position, and box-sizing, but you get the idea.

Layout Final

After the top-down traversal, all fixed or relative to fixed lengths are computed.

The elements are again traversed top-down, but now the layouters can use child lengths and compute intrinsic sizes recursively. Intrinsic sizes are the width and/or height of the content ignoring the element's min/max-width/height. The intrinsic sizes are cached, so the recursion runs in linear time.

After the layout was computed, each TFresnelElement.ComputeCSSLayoutFinished is called.

Flow Layouter

Flex Layouter

Grid Layouter