Difference between revisions of "The Pipeline"

From PowerUI
Jump to: navigation, search
(Update CSS)
 
Line 110: Line 110:
 
This, in short, builds the complete set of boxes (as there's often more than one) and positions them relative to the elements render parent (usually its parent in the DOM).  
 
This, in short, builds the complete set of boxes (as there's often more than one) and positions them relative to the elements render parent (usually its parent in the DOM).  
  
=== Render and global reflow ==
+
=== Render and global reflow ===
  
 
The final phase - the method called 'Render' in RenderableData - positions the boxes relative to the top left corner of the screen. This happens here such that scrolling only needs to call this second phase (as elements aren't moving relative to their parent).
 
The final phase - the method called 'Render' in RenderableData - positions the boxes relative to the top left corner of the screen. This happens here such that scrolling only needs to call this second phase (as elements aren't moving relative to their parent).

Latest revision as of 19:19, 25 January 2017

There's a lot of steps between your HTML file and a UI being displayed. This series of steps - the pipeline - is designed to be extremely flexible at every stage to be able to give you as much freedom as possible. In order to really maximize that, you'll need to have a good idea of how the pipeline works (and which internal classes are actually being used).

How the pipeline works

Content Collection

First, the content - typically HTML files and images - needs to be collected. That's done by creating one of a few types of ContentPackage, depending on the type of content that is wanted:

  • Bytes/ text data (DataPackage).

An XMLHttpRequest object is also a DataPackage, so these two have the same outcome:

// Create the package:
XMLHttpRequest package=new XMLHttpRequest();

// Open:
package.open("get","http://..");

// All packages receive events that are the same as XMLHttpRequest:
package.addEventListener("load",delegate(UIEvent e){
  
  // Do something with package.responseBytes
  
});

// Send the request:
package.send();
// Create the package:
DataPackage package=new DataPackage("https://..");

// All packages receive events that are the same as XMLHttpRequest:
package.addEventListener("load",delegate(UIEvent e){
  
  // Do something with package.responseBytes
  
});

// Send the request:
package.send();


  • Images (ImagePackage)
// Create the package:
ImagePackage package=new ImagePackage("https://..");

// All packages receive events that are the same as XMLHttpRequest:
package.addEventListener("load",delegate(UIEvent e){
  
  // Do something with package.Contents
  
});

// Send the request:
package.send();

Internally this figures out which FileProtocol you're using - https above - and submits the request using that protocol. Note that you can add your own protocols (schema's).

When loading images, the ImagePackage will attempt to figure out which type of image you have and then find the right ImageFormat to work with it. You can add custom image formats too.

DOM Construction

Once the content has been retrieved, the DOM is built by parsing it with a Dom.HtmlLexer (this lexer is always used; e.g. for SVG files too). This uses the Dom.TagHandlers class to lookup the available elements within the current xmlns namespace. See how to add custom elements.

Usually that's the HTML namespace, but it can also be e.g. the MathML or SVG namespaces. If an SVG is inline in the HTML, the lexer changes namespace accordingly.

Loading CSS

Style attributes are simply set directly to anElement.style.cssText. Link tags (once the content has been received) and style tags create a Css.StyleSheet object which then uses a Css.CssLexer to parse the CSS content. This lexer is also very flexible; you can define custom CSS properties, functions, units etc.

Loading Scripts

As the last phase of DOM construction, scripts are grouped together by type and compiled in one go. This is non-standard behaviour but generally makes no difference. It's done this way because it provides a guarantee that a compiler will only be invoked once per document; that's important for platforms that don't allow compilers at runtime such as iOS (i.e. it runs that single compile ahead of time).

Each script type results in a ScriptEngine on the document. You can also define your own ScriptEngine to add support for e.g. Lua if you'd like.

Reflow (Computing positions)

Reflow is the name given to the process where a web user agent figures out where your elements are positioned on the screen. If you make a change to an element that requires a reflow, one is scheduled and then triggered during the update loop. There's only a few exceptions to this - using some web API's may force a reflow to happen immediately if one was scheduled.

The core of the reflow process is the Css.Renderman object. It manages the following reflow phases and keeps track of reflow state. There's one Renderman per document (Available as UI.document.Renderer) and reflow itself happens when the Renderman.Layout method is called.

Each renderable node (SVG elements, HTML elements etc) has a Css.RenderableData object which tracks the computed boxes and any virtual elements. It's available from element.RenderData. The rest of the computed style is available as element.ComputedStyle.

Update CSS

You'll see a method called UpdateCss in the RenderableData object. It gets called by Renderman.Layout and it's the first line that really does something!

Css.ComputedStyle objects contain the raw CSS values. Note that they're actually kept accurate at all times - this CSS update doesn't need to happen in order for the values in ComputedStyle to be correct.

So, what it does is take those CSS values and starts building the basic box (a Css.LayoutBox) getting values of e.g. the font size. This phase exists at all so the following phase has the ability to compute the shrink to fit width without needing to convert those raw CSS values into usable floats repeatedly.

Local Reflow

This is the most important part of reflow - it's the method simply called 'Reflow' in RenderableData, and is called immediately after UpdateCss in Renderman.Layout.

This, in short, builds the complete set of boxes (as there's often more than one) and positions them relative to the elements render parent (usually its parent in the DOM).

Render and global reflow

The final phase - the method called 'Render' in RenderableData - positions the boxes relative to the top left corner of the screen. This happens here such that scrolling only needs to call this second phase (as elements aren't moving relative to their parent).

Then, for any box which is visible on the screen, the contents of each box are rendered using one or more Css.DisplayableProperty objects on the element. These displayable properties are always drawn in the same order and a visible element will always have at least one. For example, the background colour and background image are two displayable properties (and they draw in that order). The order of the built in properties is as follows:


Draw Order Property Name Class Name Used By
90 Inverse round corners RoundBorderInverseProperty border-radius
100 Border and normal round corners BorderProperty border
200 Background colour BackgroundColour background-color
300 Background image BackgroundImage background-image
380 Text shadow TextShadowProperty text-shadow
385 Text selection SelectionRenderingProperty -moz-user-select
390 Text stroke TextStrokeProperty text-stroke
400 Text TextRenderingProperty TextNode (All characters)

You can add custom displayable properties and insert them at any point by setting the DisplayableProperty.DrawOrder property. They receive this render phase as the DisplayableProperty.Layout method.

Each displayable property generates one or more MeshBlocks. Note that the MeshBlock object is only instanced once; it just acts as a convenient interface for creating these blocks. Each one is simply a pair of triangles with vertices that are typically specified by the box, but can be whatever the displayable property requires.

Special note about text

Text rendering properties are designed to be inherited from if you'd like to either completely replace or customise the text rendering in some way. For example, the built in text-extrude CSS property does this. See the TextRenderingProperty3D class.

Paint

Some CSS properties such as color or background-color are often animated, yet when changed they don't affect the layout. So, this is where the paint phase steps in - it's an alternative reflow. Essentially each displayable property that requested a paint simply updates the MeshBlocks that it generated, then the mesh is flushed out.


How the meshes work

PowerUI's final output is a collection of meshes (usually just one). Each mesh is a UIBatch. As displayable properties generate mesh blocks, they're written into a linked list of buffers which are then flushed out into one of these batches.

Isolation

Sometimes a displayable property needs to specify a custom material. It's usually background image that does this; for example, when it's displaying a video then it needs to use a special material. They achieve this by "isolating" themselves - that forces PowerUI to stop building the current mesh, then begin a new one with the specified material.