| Index: sky/specs/style.md
|
| diff --git a/sky/specs/style.md b/sky/specs/style.md
|
| deleted file mode 100644
|
| index 2f878bb844f066a5487097de760d50d9e605c8bd..0000000000000000000000000000000000000000
|
| --- a/sky/specs/style.md
|
| +++ /dev/null
|
| @@ -1,1082 +0,0 @@
|
| -Sky Style Language
|
| -==================
|
| -
|
| -THIS IS NOT UP TO DATE
|
| -
|
| -DO NOT IMPLEMENT THIS YET
|
| -
|
| -It has not been Dartified.
|
| -
|
| -It has not been converted to have a sane element.style API.
|
| -
|
| -It has not been converted to have layout be interruptible using Futures.
|
| -
|
| -
|
| -Planed changes
|
| ---------------
|
| -
|
| -Add //-to-end-of-line comments to be consistent with the script
|
| -language.
|
| -
|
| -
|
| -Style Parser
|
| -------------
|
| -
|
| -(this section is incomplete)
|
| -
|
| -### Tokenisation
|
| -
|
| -
|
| -#### Value parser
|
| -
|
| -
|
| -##### **Value** state
|
| -
|
| -If the current character is...
|
| -
|
| -* '``;``': Consume the character and exit the value parser
|
| - successfully.
|
| -
|
| -* '``@``': Consume the character and switch to the **at**
|
| - state.
|
| -
|
| -* '``#``': Consume the character and switch to the **hash**
|
| - state.
|
| -
|
| -* '``$``': Consume the character and switch to the **dollar**
|
| - state.
|
| -
|
| -* '``%``': Consume the character and switch to the **percent**
|
| - state.
|
| -
|
| -* '``&``': Consume the character and switch to the **ampersand**
|
| - state.
|
| -
|
| -* '``'``': Set _value_ to the empty string, consume the character, and
|
| - switch to the **single-quoted string** state.
|
| -
|
| -* '``"``': Set _value_ to the empty string, consume the character, and
|
| - switch to the **double-quoted string** state.
|
| -
|
| -* '``-``': Consume the character, and switch to the **negative
|
| - integer** state.
|
| -
|
| -* '``0``'-'``9``': Set _value_ to the decimal value of the current
|
| - character, consume the character, and switch to the **integer**
|
| - state.
|
| -
|
| -* '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to the current
|
| - character, consume the character, and switch to the **identifier**
|
| - state.
|
| -
|
| -* '``*``', '``^``', '``!``', '``?``', '``,``', '``/``', '``<``',
|
| - '``[``', '``)``', '``>``', '``]``', '``+``': Emit a symbol token
|
| - with the current character as the symbol, consume the character, and
|
| - stay in this state.
|
| -
|
| -* Anything else: Consume the character and switch to the **error**
|
| - state.
|
| -
|
| -
|
| -##### **At** state
|
| -
|
| -* '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to
|
| - the current character, create a literal token with the unit set to
|
| - ``@``, consume the character, and switch to the **literal** state.
|
| -
|
| -* Anything else: Emit a symbol token with ``@`` as the symbol, and
|
| - switch to the **value** state without consuming the character.
|
| -
|
| -
|
| -##### **Hash** state
|
| -
|
| -* '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to
|
| - the current character, create a literal token with the unit set to
|
| - ``@``, consume the character, and switch to the **literal** state.
|
| -
|
| -* Anything else: Emit a symbol token with ``#`` as the symbol, and
|
| - switch to the **value** state without consuming the character.
|
| -
|
| -
|
| -##### **Dollar** state
|
| -
|
| -* '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to
|
| - the current character, create a literal token with the unit set to
|
| - ``@``, consume the character, and switch to the **literal** state.
|
| -
|
| -* Anything else: Emit a symbol token with ``$`` as the symbol, and
|
| - switch to the **value** state without consuming the character.
|
| -
|
| -
|
| -##### **Percent** state
|
| -
|
| -* '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to
|
| - the current character, create a literal token with the unit set to
|
| - ``@``, consume the character, and switch to the **literal** state.
|
| -
|
| -* Anything else: Emit a symbol token with ``%`` as the symbol, and
|
| - switch to the **value** state without consuming the character.
|
| -
|
| -
|
| -##### **Ampersand** state
|
| -
|
| -* '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to
|
| - the current character, create a literal token with the unit set to
|
| - ``@``, consume the character, and switch to the **literal** state.
|
| -
|
| -* Anything else: Emit a symbol token with ``&`` as the symbol, and
|
| - switch to the **value** state without consuming the character.
|
| -
|
| -
|
| -##### TODO(ianh): more states...
|
| -
|
| -
|
| -##### **Error** state
|
| -
|
| -If the current character is...
|
| -
|
| -* '``;``': Consume the character and exit the value parser in failure.
|
| -
|
| -* Anything else: Consume the character and stay in this state.
|
| -
|
| -
|
| -
|
| -Selectors
|
| ----------
|
| -
|
| -Sky Style uses whatever SelectorQuery. Maybe one day we'll make
|
| -SelectorQuery support being extended to support arbitrary selectors,
|
| -but for now, it supports:
|
| -
|
| -```css
|
| -tagname
|
| -#id
|
| -.class
|
| -[attrname]
|
| -[attrname=value]
|
| -:host ("host" string is fixed)
|
| -::pseudo-element
|
| -```
|
| -
|
| -These can be combined (without whitespace), with at most one tagname
|
| -(must be first) and at most one pseudo-element (must be last) as in:
|
| -
|
| -```css
|
| -tagname[attrname]#id:host.class.class[attrname=value]::foo
|
| -```
|
| -
|
| -In debug mode, giving two IDs, or the same selector twice (e.g. the
|
| -same classname), or specifying other redundant or conflicting
|
| -selectors (e.g. [foo][foo=bar], or [foo=bar][foo=baz]) will be
|
| -flagged.
|
| -
|
| -Alternatively, a selector can be the special value "@root",
|
| -optionally followed by a pseudo-element, as in:
|
| -
|
| -```css
|
| -@root::bar
|
| -```
|
| -
|
| -
|
| -Value Parser
|
| -------------
|
| -
|
| -```javascript
|
| -class StyleToken {
|
| - constructor (String king, String value);
|
| - readonly attribute String kind;
|
| - // string
|
| - // identifier
|
| - // function (identifier + '(')
|
| - // number
|
| - // symbol (one of @#$%& if not immediately following numeric or preceding alphanumeric, or one of *^!?,/<[)>]+ or, if not followed by a digit, -)
|
| - // dimension (number + identifier or number + one of @#$%&)
|
| - // literal (one of @#$%& + alphanumeric)
|
| - readonly attribute String value;
|
| - readonly attribute String unit; // for 'dimension' type, this is the punctuation or identifier that follows the number, for 'literal' type, this is the punctuation that precedes it
|
| -}
|
| -
|
| -class TokenSource {
|
| - constructor (Array<StyleToken> tokens);
|
| - IteratorResult next();
|
| - TokenSourceBookmark getBookmark();
|
| - void rewind(TokenSourceBookmark bookmark);
|
| -}
|
| -
|
| -class TokenSourceBookmark {
|
| - constructor ();
|
| - // TokenSource stores unforgeable state on this object using symbols or a weakmap or some such
|
| -}
|
| -
|
| -callback ParserCallback = AbstractStyleValue (TokenSource tokens); // return if successful, throw if not
|
| -
|
| -class StyleGrammar {
|
| - constructor ();
|
| - void addParser(ParserCallback parser);
|
| - AbstractStyleValue parse(TokenSource tokens, Boolean root = false);
|
| - // for each parser callback that was registered, in reverse
|
| - // order (most recently registered first), run these steps:
|
| - // let bookmark = tokens.getBookmark();
|
| - // try {
|
| - // let result = parser(tokens);
|
| - // if (root) {
|
| - // if (!tokens.next().done)
|
| - // throw new Error();
|
| - // }
|
| - // } except {
|
| - // tokens.rewind(bookmark);
|
| - // }
|
| - // (root is set when you need to parse the entire token stream to be valid)
|
| -}
|
| -
|
| -/*
|
| -StyleNode
|
| - |
|
| - +-- Property
|
| - |
|
| - +-- AbstractStyleValue
|
| - |
|
| - +-- NumericStyleValue
|
| - | |
|
| - | +-- AnimatableNumericStyleValue*
|
| - |
|
| - +-- LengthStyleValue
|
| - | |
|
| - | +-- AnimatableLengthStyleValue*
|
| - | |
|
| - | +-- TransitionLengthStyleValue*
|
| - | |
|
| - | +-- PixelLengthStyleValue
|
| - | |
|
| - | +-- EmLengthStyleValue*
|
| - | |
|
| - | +-- VHLengthStyleValue*
|
| - | |
|
| - | +-- CalcLengthStyleValue*
|
| - |
|
| - +-- ColorStyleValue
|
| - | |
|
| - | +-- RGBColorStyleValue
|
| - | |
|
| - | +-- AnimatableColorStyleValue*
|
| - |
|
| - +-- AbstractOpaqueStyleValue
|
| - | |
|
| - | +-- IdentifierStyleValue
|
| - | | |
|
| - | | +-- AnimatableIdentifierStyleValue*
|
| - | |
|
| - | +-- URLStyleValue*
|
| - | | |
|
| - | | +-- AnimatableURLStyleValue*
|
| - | |
|
| - | +-- StringStyleValue*
|
| - | | |
|
| - | | +-- AnimatableStringStyleValue*
|
| - | |
|
| - | +-- ObjectStyleValue
|
| - |
|
| - +-- PrimitiveValuesListStyleValue*
|
| -*/
|
| -```
|
| -
|
| -The types marked with * in the list above are not part of dart:sky,
|
| -and are only shown here to illustrate what kinds of extensions are
|
| -possible and where they would fit.
|
| -
|
| -TODO(ianh): consider removing 'StyleValue' from these class names
|
| -
|
| -```javascript
|
| -abstract class StyleNode {
|
| - abstract void markDirty();
|
| -}
|
| -
|
| -dictionary StyleValueResolverSettingsSettings {
|
| - Boolean firstTime = false;
|
| - any state = null;
|
| -}
|
| -
|
| -class StyleValueResolverSettings {
|
| - // this is used as an "out" parameter for 'resolve()' below
|
| - constructor(StyleValueResolverSettingsSettings initial);
|
| - void reset(StyleValueResolverSettingsSettings initial);
|
| - // sets firstTime and state to given values
|
| - // sets layoutDependent to false
|
| - // sets dependencies to empty set
|
| - // sets lifetime to Infinity
|
| -
|
| - readonly attribute Boolean firstTime;
|
| - // true if this is the first time this property is being resolved for this element,
|
| - // or if the last time it was resolved, the value was a different object
|
| -
|
| - // attribute Boolean layoutDependent
|
| - void setLayoutDependent();
|
| - // call this if the value should be recomputed each time the ownerLayoutManager's dimensions change, rather than being cached
|
| - Boolean getLayoutDependent();
|
| - // returns true if setLayoutDependent has been called since the last reset()
|
| -
|
| - // attribute "BitField" dependencies; // defaults to no bits set
|
| - void dependsOn(PropertyHandle property);
|
| - // if the given property doesn't have a dependency bit assigned:
|
| - // - assign the next bit to the property
|
| - // - if there's no bits left, throw
|
| - // set the bit on this StyleValueResolverSettings's dependencies bitfield
|
| - Array<PropertyHandle> getDependencies();
|
| - // returns an array of the PropertyHandle values for the bits that are set in dependencies
|
| -
|
| - // attribute (Float or Infinity) lifetime;
|
| - void setLifetime(Float age);
|
| - // if the new value is less than the current value of lifetime, update the current value
|
| - (Float or Infinity) getLifetime();
|
| - // return current value of lfietime
|
| -
|
| - attribute any state; // initially null, can be set to store value for this RenderNode/property pair
|
| - // for example, TransitioningColorStyleValue would store
|
| - // {
|
| - // initial: /* color at time of transition */,
|
| - // target: /* color at end of transition */,
|
| - // start: /* time at start of transition */,
|
| - // }
|
| - // ...which would enable it to update appropriately, and would also
|
| - // let other transitions that come later know that you were half-way
|
| - // through a transition so they can shorten their time accordingly
|
| - //
|
| - // best practices: if you're storing values on the state object,
|
| - // then remove the values once they are no longer needed. For
|
| - // example, when your transition ends, set the object to null.
|
| - //
|
| - // best practices: if you're a style value that contains multiple
|
| - // style values, then before you call their resolve you should
|
| - // replace the state with a state that is specific to them, and
|
| - // when you get it back you should insert that value into your
|
| - // state somehow. For example, in a resolve()r with two child
|
| - // style values a and b:
|
| - // let ourState;
|
| - // if (settings.firstTime)
|
| - // ourState = { a: null, b: null };
|
| - // else
|
| - // ourState = settings.state;
|
| - // settings.state = ourState.a;
|
| - // let aResult = a.resolve(node, settings);
|
| - // ourState.a = settings.state;
|
| - // settings.state = ourState.b;
|
| - // let aResult = b.resolve(node, settings);
|
| - // ourState.b = settings.state;
|
| - // settings.state = ourState;
|
| - // return a + b; // or whatever
|
| - //
|
| - // best practices: if you're a style value that contains multiple
|
| - // style values, and all those style values are storing null, then
|
| - // store null yourself, instead of storing many nulls of your own.
|
| -
|
| - // attribute Boolean wasStateSet;
|
| - Boolean getShouldSaveState();
|
| - // returns true if state is not null, and either state was set
|
| - // since the last reset, or firstTime is false.
|
| -
|
| -}
|
| -
|
| -class Property : StyleNode {
|
| - constructor (AbstractStyleDeclaration parentNode, PropertyHandle property, AbstractStyleValue? initialValue = null);
|
| - readonly attribute AbstractStyleDeclaration parentNode;
|
| - readonly attribute PropertyHandle property;
|
| - readonly attribute AbstractStyleValue value;
|
| -
|
| - void setValue(AbstractStyleValue? newValue);
|
| - // updates value and calls markDirty()
|
| -
|
| - void markDirty();
|
| - // call parentNode.markDirty(property);
|
| -
|
| - abstract any resolve(RenderNode node, StyleValueResolverSettings? settings = null);
|
| - // if value is null, returns null
|
| - // otherwise, returns value.resolve(property, node, settings)
|
| -}
|
| -
|
| -abstract class AbstractStyleValue : StyleNode {
|
| - abstract constructor(StyleNode? parentNode = null);
|
| - attribute StyleNode? parentNode;
|
| -
|
| - void markDirty();
|
| - // call this.parentNode.markDirty()
|
| -
|
| - abstract any resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettings? settings = null);
|
| -}
|
| -
|
| -abstract class LengthStyleValue : AbstractStyleValue {
|
| - abstract Float resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettings? settings = null);
|
| -}
|
| -
|
| -class PixelLengthStyleValue : LengthStyleValue {
|
| - constructor(Float number, StyleNode? parentNode = null);
|
| - attribute Float value;
|
| - // on setting, calls markDirty();
|
| - Float resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettings? settings = null);
|
| - // return value
|
| -}
|
| -
|
| -typedef RawColor Float; // TODO(ianh): figure out what Color should be
|
| -class ColorStyleValue : LengthStyleValue {
|
| - constructor(Float red, Float green, Float blue, Float alpha, StyleNode? parentNode = null);
|
| - // ... color API ...
|
| - RawColor resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettings? settings = null);
|
| -}
|
| -
|
| -class AbstractOpaqueStyleValue : AbstractStyleValue {
|
| - abstract constructor(any value, StyleNode? parentNode = null);
|
| - attribute any value;
|
| - // on setting, calls markDirty();
|
| - any resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettings? settings = null);
|
| - // returns value
|
| -}
|
| -
|
| -class IdentifierStyleValue : AbstractOpaqueStyleValue {
|
| - constructor(String value, StyleNode? parentNode = null);
|
| - // calls superclass constructor
|
| -}
|
| -
|
| -/*
|
| -class AnimatableIdentifierStyleValue : AbstractOpaqueStyleValue {
|
| - constructor(String value, String newValue, AnimationFunction player, StyleNode? parentNode = null);
|
| - readonly attribute String newValue;
|
| - readonly attribute AnimationFunction player;
|
| - any resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettings? settings = null);
|
| -}
|
| -*/
|
| -
|
| -class ObjectStyleValue : AbstractOpaqueStyleValue {
|
| - constructor(any value, StyleNode? parentNode = null);
|
| - // calls superclass constructor
|
| -}
|
| -
|
| -dictionary PropertySettings {
|
| - String? name = null; // null if the property can't be set from a <style> block
|
| - StyleGrammar? grammar = null; // must be non-null if name is non-null; must be null otherwise
|
| - Boolean inherited = false;
|
| - any initialValue = null;
|
| - Boolean needsManager = false;
|
| - Boolean needsLayout = false;
|
| - Boolean needsPaint = false;
|
| - // PropertyHandle propertyHandle; // assigned by registerProperty
|
| - // Integer dependencyBit; // assigned by StyleValueResolverSettings.dependsOn()
|
| -}
|
| -typedef PropertyHandle Integer;
|
| -PropertyHandle registerProperty(PropertySettings propertySettings);
|
| - // registers a property with the given settings, and returns an integer >= 0
|
| - // that can be used to refer to this property
|
| -
|
| -// dart:sky exports a bunch of style grammars so that people can extend them
|
| -attribute StyleGrammar PositiveLengthOrInfinityStyleGrammar; // resolves to LengthStyleValue
|
| -attribute StyleGrammar PositiveLengthOrAutoStyleGrammar; // resolves to LengthStyleValue or IdentifierStyleValue (with value 'auto')
|
| -attribute StyleGrammar PositiveLengthStyleGrammar; // resolves to LengthStyleValue
|
| -attribute StyleGrammar NumberGrammar; // resolves to NumericStyleValue
|
| -attribute StyleGrammar ColorGrammar; // resolves to ColorStyleValue
|
| -attribute StyleGrammar DisplayStyleGrammar; // resolves to ObjectStyleValue
|
| -```
|
| -
|
| -Inline Styles
|
| --------------
|
| -
|
| -```javascript
|
| -abstract class AbstractStyleDeclarationList {
|
| - void addStyles(StyleDeclaration styles, String pseudoElement = ''); // O(1)
|
| - void removeStyles(StyleDeclaration styles, String pseudoElement = ''); // O(N) in number of declarations
|
| - Array<StyleDeclaration> getDeclarations(String pseudoElement = ''); // O(N) in number of declarations
|
| -}
|
| -
|
| -class ElementStyleDeclarationList : AbstractStyleDeclarationList {
|
| - constructor (Element? element);
|
| - readonly attribute Element? element;
|
| -
|
| - // there are two batches of styles in an ElementStyleDeclarationList.
|
| -
|
| - // the first batch is the per-frame styles; these get (conceptually)
|
| - // cleared each frame, after which all the matching rules in relevant
|
| - // <style> blocks get added back in, followed by all the animation-
|
| - // derived rules; scripts can also add styles themselves, but they are
|
| - // dropped after the next frame
|
| - void addFrameStyles(StyleDeclaration styles, String pseudoElement = ''); // O(1)
|
| - void clearFrameStyles();
|
| -
|
| - // the second batch is the persistent styles, which remain until removed;
|
| - // they are accessed via the AbstractStyleDeclarationList accessors
|
| -
|
| - // as StyleDeclarations are added and removed, the ElementStyleDeclarationList
|
| - // calls register(element) and unregister(element) respectively on those
|
| - // StyleDeclaration objects, where element is the element that was passed
|
| - // to the constructor, if not null
|
| - // then, it calls element.renderNode.cascadedValueChanged
|
| - // for each property on the object
|
| -
|
| - // the inherited getDeclarations() method returns all the frame
|
| - // styles followed by all the persistent styles, in insertion order
|
| -}
|
| -
|
| -class RenderNodeStyleDeclarationList : AbstractStyleDeclarationList {
|
| - constructor (RenderNode? renderNode);
|
| - readonly attribute RenderNode? renderNode;
|
| -
|
| - // as StyleDeclarations are added and removed, the RenderNodeStyleDeclarationList
|
| - // calls register(renderNode) and unregister(renderNode) respectively on those
|
| - // StyleDeclaration objects, where renderNode is the RenderNode that was passed
|
| - // to the constructor, if not null
|
| - // then, it calls renderNode.cascadedValueChanged
|
| - // for each property on the object
|
| -}
|
| -
|
| -class StyleDeclaration {
|
| - constructor ();
|
| -
|
| - void markDirty(PropertyHandle property);
|
| - // this indicates that the cascaded value of the property thinks
|
| - // it will now have a different result (as opposed to the cascaded
|
| - // value itself having changed)
|
| - // invoke element.renderNode.cascadedValueDirty(property, pseudoElement); for each
|
| - // currently registered consumer element/pseudoElement pair
|
| -
|
| - void register((Element or RenderNode) consumer, String pseudoElement = ''); // O(1)
|
| - void unregister((Element or RenderNode) consumer, String pseudoElement = ''); // O(N)
|
| - // registers an element/pseudoElement or renderNode/pseudoElement pair with
|
| - // this StyleDeclaration so that a property/value on the style declaration
|
| - // is marked dirty, the relevant render node is informed and can then update
|
| - // its property cache accordingly
|
| -
|
| -
|
| - getter AbstractStyleValue? (PropertyHandle property);
|
| - // looks up the Property object for /property/, and returns its value
|
| - // null if property is missing
|
| -
|
| - setter void (PropertyHandle property, AbstractStyleValue value);
|
| - // verify that value.parentNode is null
|
| - // if there is no Property object for /property/, creates one
|
| - // else calls its update() method to change the value
|
| - // update value's parentNode
|
| - // invoke consumer.renderNode.cascadedValueChanged(property); for each
|
| - // currently registered consumer
|
| -
|
| - void remove(PropertyHandle property);
|
| - // drops the Property object for /property/ from this StyleDeclaration object
|
| - // invoke consumer.renderNode.cascadedValueChanged(property); for each
|
| - // currently registered consumer
|
| -}
|
| -```
|
| -
|
| -Rule Matching
|
| --------------
|
| -
|
| -```javascript
|
| -class Rule {
|
| - constructor ();
|
| - attribute SelectorQuery selector; // O(1)
|
| - attribute String pseudoElement; // O(1)
|
| - attribute StyleDeclaration styles; // O(1)
|
| -}
|
| -```
|
| -
|
| -Each frame, at some defined point relative to requestAnimationFrame(),
|
| -if a Rule has started applying, or a Rule stopped applying, to an
|
| -element, dart:sky calls thatElement.style.clearFrameStyles() and then,
|
| -for each Rule that now applies, calls
|
| -thatElement.style.addFrameStyles() with the relevant StyleDeclaration
|
| -and pseudoElement from each such Rule.
|
| -
|
| -
|
| -Update the render tree
|
| -----------------------
|
| -
|
| -Simultaneously walk the tree rooted at the application's element
|
| -tree's root node, taking into account shadow trees and child
|
| -distribution, and the tree rooted at that Root node's RenderNode.
|
| -
|
| -If you come across a node that doesn't have an assigned RenderNode,
|
| -then create one, placing it in the appropriate place in the RenderTree
|
| -tree, after any nodes marked isGhost=true, with ownerLayoutManager
|
| -pointing to the parent RenderNode's layoutManager, if it has one, and,
|
| -if it has one and autoreap is false on that layout manager, mark the
|
| -new node "isNew". (This means that when a node is marked isNew, the
|
| -layout manager has already laid out at least one frame.)
|
| -
|
| -For each element, if the node's needsManager is true, call
|
| -getLayoutManager() on the element, and if that's not null, and if the
|
| -returned class isn't the same class as the current layoutManager, if
|
| -any, construct the given class and assign it to the RenderNode's
|
| -layoutManager, then set all the child RenderNodes' ownerLayoutManager
|
| -to that object; if it returns null, and that node already has a
|
| -layoutManager, then set isGhost=true for that node and all its
|
| -children (without changing the layoutManager). Otherwise, if it
|
| -returned null and there's already no layoutManager, remove the node
|
| -from the tree. Then, in any case, clear the needsManager bit.
|
| -
|
| -When an Element or Text node is to be removed from its parent, and it
|
| -has a renderNode, and that renderNode has an ownerLayoutManager with
|
| -autoreap=false, then before actually removing the node, the node's
|
| -renderNode should be marked isGhost=true, and all the
|
| -StyleDeclarations in the relevant ElementStyleDeclarationList should
|
| -be added to the RenderNode's overrideStyles for use later (creating a
|
| -RenderNodeStyleDeclarationList if necessary).
|
| -
|
| -When an Element is to be removed from its parent, regardless of the
|
| -above, the node's renderNode attribute should be nulled out.
|
| -
|
| -When a RenderNode is added with isNew=true, call its parent
|
| -RenderNode's LayoutManager's childAdded() callback. When a a
|
| -RenderNode has its isGhost property set to true, then call it's parent
|
| -RenderNode's LayoutManager's childRemoved() callback.
|
| -
|
| -
|
| -```javascript
|
| -dictionary PropertySettings {
|
| - String? name = null; // null if the property can't be set from a <style> block
|
| - StyleGrammar? grammar = null; // must be non-null if name is non-null; must be null otherwise
|
| - Boolean inherited = false;
|
| - any initialValue = null;
|
| - Boolean needsManager = false;
|
| - Boolean needsLayout = false;
|
| - Boolean needsPaint = false;
|
| - // PropertyHandle propertyHandle; // assigned by registerProperty
|
| - // Integer dependencyBit; // assigned by StyleValueResolverSettings.dependsOn()
|
| -}
|
| -
|
| -dictionary GetPropertySettings {
|
| - String pseudoElement = '';
|
| - Boolean forceCache = false;
|
| - // if set to true, will return the cached value if any, or null otherwise
|
| - // this is used by transitions to figure out what to transition from
|
| -}
|
| -
|
| -class RenderNode { // implemented in C++ with no virtual tables
|
| - // this is generated before layout
|
| - readonly attribute String text;
|
| - readonly attribute Node? parentNode;
|
| - readonly attribute Node? firstChild;
|
| - readonly attribute Node? nextSibling;
|
| -
|
| - // internal state:
|
| - // - back pointer to backing Node, if we're not a ghost
|
| - // - cache of resolved property values, mapping as follows:
|
| - // - pseudoElement, property => StyleValue object, resolved value, StyleValueResolverSettings, cascade dirty bit, value dirty bit
|
| - // - property state map (initially empty), as follows:
|
| - // - pseudoElement, property => object
|
| -
|
| - any getProperty(PropertyHandle property, GetPropertySettings? settings = null);
|
| - // looking at the cached data for the given pseudoElement:
|
| - // if there's a cached value:
|
| - // if settings.forceCache is true, return the cached value
|
| - // if neither dirty bit is set, return the cached value
|
| - // if the cascade dirty bit is not set (value dirty is set) then
|
| - // - clear any pending lifetime-enforcing tasks for this
|
| - // property/pseudoElement pair on this render node
|
| - // - resolve the value using the same StyleValue object
|
| - // - with firstTime=false on the resolver settings
|
| - // - with the cached state object if any
|
| - // - jump to "cache" below
|
| - // if settings.forceCache is true, return null
|
| - // - clear any pending lifetime-enforcing tasks for this
|
| - // property/pseudoElement pair on this render node
|
| - // - if there's an override declaration with the property (with
|
| - // the pseudo or without), then get the value object from there
|
| - // and jump to "resolve" below.
|
| - // - if there's an element and it has a style declaration with
|
| - // the property (with the pseudo or without), then get the
|
| - // value object from there and jump to "resolve" below.
|
| - // - if it's not an inherited property, or if there's no parent,
|
| - // then get the default value and jump to "resolve" below.
|
| - // - call the parent render node's getProperty() with the same
|
| - // property but no settings, then cache that value as the value
|
| - // for this element with the given pseudoElement, with no
|
| - // StyleValue object, no resolver settings, and set the state
|
| - // to null.
|
| - // resolve:
|
| - // - get a new resolver settings object (or reset an existing one)
|
| - // - if the obtained StyleValue object is different than the
|
| - // cached StyleValue object, or if there is no cached
|
| - // object, then set the resolver settings to
|
| - // firstTime=true, otherwise it's the same object and set
|
| - // firstTime=false.
|
| - // - set the resolver settings' state to the current state
|
| - // for this pseudoElement/property combination
|
| - // - using the obtained StyleValue object, call resolve(),
|
| - // passing it this node and the resolver settings object.
|
| - // - jump to "cache" below
|
| - // cache:
|
| - // - update the cache with the obtained value and resolver
|
| - // settings
|
| - // - reset the dirty bits
|
| - // - if the resolver settings' getShouldSaveState() method
|
| - // returns false, then discard any cached state, otherwise,
|
| - // cache the new state
|
| - // - if the resolver settings' lifetime is not infinity, then
|
| - // queue a lifetime-enforcing task for the appropriate time
|
| - // in the future which calls cascadedValueDirty for this
|
| - // property/pseudoElement pair on this render node
|
| -
|
| - attribute RenderNodeStyleDeclarationList overrideStyles;
|
| - // mutable; initially null
|
| - // this is used when isGhost is true, and can also be used more generally to
|
| - // override styles from the layout manager (e.g. to animate a new node into view)
|
| - // this is the only arbitrarily mutable state of a RenderNode object
|
| -
|
| - private void cascadedValueChanged(PropertyHandle property, String pseudoElement = '');
|
| - private void cascadedValueDirty(PropertyHandle property, String pseudoElement = '');
|
| - // - set the appropriate dirty bit on the cached data for this property/pseudoElement pair
|
| - // - cascade dirty for cascadedValueChanged
|
| - // - value dirty for cascadedValueDirty
|
| - // - if the property is needsManager, set needsManager to true
|
| - // - if the property is needsLayout, set needsLayout to true and walk up the
|
| - // tree setting descendantNeedsLayout
|
| - // - if the property is needsPaint, add the node to the list of nodes that need painting
|
| - // - if the property has a dependencyBit defined, then check the cache of all the
|
| - // properties on this RenderNode, and the cache for the property in all the child
|
| - // nodes and, if pseudoElement is '', the pseudoElements of this node, and,
|
| - // if any of them have the relevant dependency bit set, then call
|
| - // thatRenderNode.cascadedValueDirty(thatProperty, thatPseudoElement)
|
| - // - if the property is inherited, then for each child node, and, if pseudoElement
|
| - // is '', the pseudoElements of this node, if the cached value for this property
|
| - // is present but has no StyleValue, call thatNode.cascadedValueChanged(property, thatPseudoElement)
|
| -
|
| - readonly attribute Boolean needsManager;
|
| - // means that a property with needsManager:true has changed on this node
|
| -
|
| - readonly attribute Boolean needsLayout;
|
| - // means that either needsManager is true or a property with needsLayout:true has changed on this node
|
| - // needsLayout is set to false by the ownerLayoutManager's default layout() method
|
| -
|
| - readonly attribute Boolean descendantNeedsLayout;
|
| - // means that some child of this node has needsLayout set to true
|
| - // descendantNeedsLayout is set to false by the ownerLayoutManager's default layout() method
|
| -
|
| - readonly attribute LayoutManager layoutManager;
|
| - readonly attribute LayoutManager ownerLayoutManager; // defaults to the parentNode.layoutManager
|
| - // if you are not the ownerLayoutManager, then ignore this RenderNode in layout() and paintChildren()
|
| - // using walkChildren() does this for you
|
| -
|
| - // only the ownerLayoutManager can change these
|
| - readonly attribute Float x; // relative to left edge of ownerLayoutManager
|
| - readonly attribute Float y; // relative to top edge of ownerLayoutManager
|
| - readonly attribute Float width;
|
| - readonly attribute Float height;
|
| - readonly attribute Boolean isNew; // node has just been added (and maybe you want to animate it in)
|
| - readonly attribute Boolean isGhost; // node has just been removed (and maybe you want to animate it away)
|
| -}
|
| -```
|
| -
|
| -The flattened tree is represented as a hierarchy of Node objects. For
|
| -any element that only contains text node children, the "text" property
|
| -is set accordingly. For elements with mixed text node and non-text
|
| -node children, each run of text nodes is represented as a separate
|
| -Node with the "text" property set accordingly and the styles set as if
|
| -the Node inherited everything inheritable from its parent.
|
| -
|
| -
|
| -Layout
|
| -------
|
| -
|
| -dart:sky registers 'display' as follows:
|
| -
|
| -```javascript
|
| - {
|
| - name: 'display',
|
| - grammar: sky.DisplayStyleGrammar,
|
| - inherited: false,
|
| - initialValue: sky.BlockLayoutManager,
|
| - needsManager: true,
|
| - }
|
| -```
|
| -
|
| -The following API is then used to add new layout manager types to 'display':
|
| -
|
| -```javascript
|
| -void registerLayoutManager(String displayValue, LayoutManagerConstructor? layoutManager);
|
| -```
|
| -
|
| -dart:sky by default registers:
|
| -
|
| -- 'block': sky.BlockLayoutManager
|
| -- 'paragraph': sky.ParagraphLayoutManager
|
| -- 'inline': sky.InlineLayoutManager
|
| -- 'none': null
|
| -
|
| -
|
| -Layout managers inherit from the following API:
|
| -
|
| -```javascript
|
| -callback LayoutManagerConstructor LayoutManager (RenderNode node);
|
| -
|
| -class LayoutManager : EventTarget {
|
| - readonly attribute RenderNode node;
|
| - constructor LayoutManager(RenderNode node);
|
| - // sets needsManager to false on the node
|
| -
|
| - readonly attribute Boolean autoreap;
|
| - // defaults to true
|
| - // when true, children that are added don't get set to isNew=true
|
| - // when true, children that are removed don't get set to isGhost=true, they're just removed
|
| -
|
| - virtual Array<EventTarget> getEventDispatchChain(); // O(N) in number of this.node's ancestors // implements EventTarget.getEventDispatchChain()
|
| - // let result = [];
|
| - // let node = this.node;
|
| - // while (node && node.layoutManager) {
|
| - // result.push(node.layoutManager);
|
| - // node = node.parentNode;
|
| - // }
|
| - // return result;
|
| -
|
| - void setProperty(RenderNode node, PropertyHandle property, any value, String pseudoElement = ''); // O(1)
|
| - // replaces the value that getProperty() would return on that node with /value/
|
| - // this also clears the dependency bits, dirty bits, and sets the property state to null
|
| - // this also clears any relevant lifetime-enforcing tasks
|
| -
|
| - void take(RenderNode victim); // sets victim.ownerLayoutManager = this;
|
| - // assert: victim hasn't been take()n yet during this layout
|
| - // assert: an ancestor of victim has node.layoutManager == this (aka, victim is a descendant of this.node)
|
| -
|
| - virtual void release(RenderNode victim);
|
| - // called when the RenderNode was removed from the tree
|
| -
|
| - virtual void childAdded(RenderNode child);
|
| - virtual void childRemoved(RenderNode child);
|
| - // called when a child has its isNew or isGhost attributes set respectively
|
| -
|
| - void setChildPosition(child, x, y); // sets child.x, child.y
|
| - void setChildX(child, y); // sets child.x
|
| - void setChildY(child, y); // sets child.y
|
| - void setChildSize(child, width, height); // sets child.width, child.height
|
| - void setChildWidth(child, width); // sets child.width
|
| - void setChildHeight(child, height); // sets child.height
|
| - // assert: child.ownerLayoutManager == this
|
| - // for setChildSize/Width/Height: if the new dimension is different than the last assumed dimensions, and
|
| - // any RenderNodes with an ownerLayoutManager==this have cached values for getProperty() that are marked
|
| - // as layout-dependent, mark them as dirty with cascadedValueDirty()
|
| -
|
| - void assumeDimensions(Float width, Float height);
|
| - // sets the assumed dimensions for calls to getProperty() on RenderNodes that have this as an ownerLayoutManager,
|
| - // by updating renderNode width/height;
|
| - // if the new dimension is different than the last assumed dimensions, and any RenderNodes with an
|
| - // ownerLayoutManager==this have cached values for getProperty() that are marked as layout-dependent, mark them
|
| - // as dirty with cascadedValueDirty()
|
| -
|
| - virtual LayoutValueRange getIntrinsicWidth(Float? defaultWidth = null);
|
| - /*
|
| - function getIntrinsicWidth(defaultWidth) {
|
| - if (defaultWidth == null) {
|
| - defaultWidth = this.node.getProperty('width');
|
| - if (typeof defaultWidth != 'number')
|
| - defaultWidth = 0;
|
| - }
|
| - let minWidth = this.node.getProperty('min-width');
|
| - if (typeof minWidth != 'number')
|
| - minWidth = 0;
|
| - let maxWidth = this.node.getProperty('max-width');
|
| - if (typeof maxWidth != 'number')
|
| - maxWidth = Infinity;
|
| - if (maxWidth < minWidth)
|
| - maxWidth = minWidth;
|
| - if (defaultWidth > maxWidth)
|
| - defaultWidth = maxWidth;
|
| - if (defaultWidth < minWidth)
|
| - defaultWidth = minWidth;
|
| - return {
|
| - minimum: minWidth,
|
| - value: defaultWidth,
|
| - maximum: maxWidth,
|
| - };
|
| - }
|
| - */
|
| -
|
| - virtual LayoutValueRange getIntrinsicHeight(Float? defaultHeight = null);
|
| - /*
|
| - function getIntrinsicHeight(defaultHeight) {
|
| - if (defaultHeight == null) {
|
| - defaultHeight = this.node.getProperty('height');
|
| - if (typeof defaultHeight != 'number')
|
| - defaultHeight = 0;
|
| - }
|
| - let minHeight = this.node.getProperty('min-height');
|
| - if (typeof minHeight != 'number')
|
| - minHeight = 0;
|
| - let maxHeight = this.node.getProperty('max-height');
|
| - if (typeof maxHeight != 'number')
|
| - maxHeight = Infinity;
|
| - if (maxHeight < minHeight)
|
| - maxHeight = minHeight;
|
| - if (defaultHeight > maxHeight)
|
| - defaultHeight = maxHeight;
|
| - if (defaultHeight < minHeight)
|
| - defaultHeight = minHeight;
|
| - return {
|
| - minimum: minHeight,
|
| - value: defaultHeight,
|
| - maximum: maxHeight,
|
| - };
|
| - }
|
| - */
|
| -
|
| - void welcomeChild(child);
|
| - // assert: this == child.ownerLayoutManager
|
| - // assert: child.isNew is true
|
| - // resets child.isNew
|
| - void reapChild(child);
|
| - // assert: this == child.ownerLayoutManager
|
| - // assert: child.isGhost is true
|
| - // removes the RenderNode from its parent if isGhost is true
|
| -
|
| - Generator<RenderNode> walkChildren();
|
| - // returns a generator that iterates over the children, skipping any whose ownerLayoutManager is not |this|
|
| -
|
| - Generator<RenderNode> walkChildrenBackwards();
|
| - // returns a generator that iterates over the children backwards, skipping any whose ownerLayoutManager is not |this|
|
| -
|
| - void markAsLaidOut(); // sets this.node.needsLayout and this.node.descendantNeedsLayout to false
|
| - virtual Dimensions layout(Float? width, Float? height);
|
| - // if width is null, set width to getIntrinsicWidth().value
|
| - // if height is null, set height to getIntrinsicHeight().value
|
| - // call this.assumeDimensions(width, height);
|
| - // call this.layoutChildren(width, height);
|
| - // call markAsLaidOut();
|
| - // return { width: width, height: height }
|
| - // - this should always call this.markAsLaidOut() to reset
|
| - // needsLayout and descendantNeedsLayout
|
| - // - the return value should include the final value for whichever
|
| - // of the width and height arguments that is null
|
| - // - subclasses that want to make 'auto' values dependent on the
|
| - // children should override this entirely, rather than
|
| - // overriding layoutChildren; but see layoutChildren()'s notes
|
| - // for how to do this
|
| -
|
| - virtual void layoutChildren(Float width, Float height);
|
| - // default implementation does nothing
|
| - // - override only this (and not layout()) if you want to lay out
|
| - // children but not have the children affect your dimensions
|
| - // - always call setChildSize() and setChildPosition() after
|
| - // calling a child's layout() method
|
| - // - if the child has needsLayout or if you need to have it
|
| - // autosize, call its ownerLayoutManager's layout() method
|
| - // - otherwise if the child has needs descendantNeedsLayout, call
|
| - // layoutDescendants()
|
| -
|
| - virtual Dimensions layoutDescendants();
|
| - // assert: node.needsLayout is false, node.descendantNeedsLayout is true
|
| - // walk children:
|
| - // - if it has needsLayout, call its layout() method with its
|
| - // current width and height, then call setChildSize() with
|
| - // those same dimensions
|
| - // - else, if it has descendantNeedsLayout, call its
|
| - // layoutDescendants() method
|
| - // call markAsLaidOut();
|
| - // - override this if you use take() to control more children, in
|
| - // which case you should call their methods too
|
| - // - this should always call this.markAsLaidOut() to reset
|
| - // needsLayout and descendantNeedsLayout
|
| -
|
| - virtual void paint(RenderingSurface canvas);
|
| - // set a clip rect on the canvas for rect(0,0,this.width,this.height)
|
| - // (? we don't really have to do this; consider shadows...)
|
| - // call this.paintChildren(canvas)
|
| - // (the default implementation doesn't paint anything on top of the children)
|
| - // unset the clip
|
| - // - this gets called by the system if:
|
| - // - you are in your parent's current display list and it's in its parent's and so on up to the top, and
|
| - // - you haven't had paint() called since the last time you were dirtied
|
| - // - the following things make you dirty:
|
| - // - dimensions of your RenderNode changed
|
| - // - one of your properties with needsLayout or needsPaint changed
|
| -
|
| - virtual void paintChildren(RenderingSurface canvas);
|
| - // for each child returned by walkChildren():
|
| - // if child bounds intersects our bounds:
|
| - // call canvas.paintChild(child);
|
| - // - you should skip children that will be clipped out of yourself because they're outside your bounds
|
| - // - if you transform the canvas, you'll have to implement your own version of paintChildren() so
|
| - // that you don't skip the children that are visible in the new coordinate space but wouldn't be
|
| - // without the transform
|
| -
|
| - virtual RenderNode hitTest(Float x, Float y);
|
| - // default implementation uses the node's children nodes' x, y,
|
| - // width, and height, skipping any that have width=0 or height=0, or
|
| - // whose ownerLayoutManager is not |this|
|
| - // default implementation walks the tree backwards from its built-in order
|
| - // if no child is hit, then return this.node
|
| - // override this if you changed your children's z-order, or if you used take() to
|
| - // hoist some descendants up to be your responsibility, or if your children aren't
|
| - // rectangular (e.g. you lay them out in a hex grid)
|
| - // make sure to offset the value you pass your children: child.layoutManager.hitTest(x-child.x, y-child.y)
|
| -}
|
| -
|
| -dictionary LayoutValueRange {
|
| - // negative values here should be treated as zero
|
| - Float minimum = 0;
|
| - Float value = 0; // ideal desired width; if it's not in the range minimum .. maximum then it overrides minimum and maximum
|
| - (Float or Infinity) maximum = Infinity;
|
| -}
|
| -
|
| -dictionary Dimensions {
|
| - Float width = 0;
|
| - Float height = 0;
|
| -}
|
| -```
|
| -
|
| -
|
| -Paint
|
| ------
|
| -
|
| -Sky has a list of RenderNodes that need painting. When a RenderNode is
|
| -created, it's added to this list. (See also needsPaint for another
|
| -time it is added to the list.)
|
| -
|
| -```javascript
|
| -callback void Painter (RenderNode node, RenderingSurface canvas);
|
| -
|
| -class RenderingSurface {
|
| -
|
| - // ... (API similar to <canvas>'s 2D API)
|
| -
|
| - void paintChild(RenderNode node);
|
| - // inserts a "paint this child" instruction in this canvas's display list.
|
| - // the child's display list, transformed by the child's x and y coordinates, will be inserted into this
|
| - // display list during painting.
|
| -}
|
| -```
|
| -
|
| -
|
| -The default framework provides global hooks for extending the painting of:
|
| -
|
| - - borders
|
| - - backgrounds
|
| -
|
| -These are called during the default framework's layout managers'
|
| -paint() functions. They are also made available so that other people
|
| -can call them from their paint() functions.
|
| -
|
| -
|
| -
|
| -Default Styles
|
| ---------------
|
| -
|
| -In the constructors for the default elements, they add to themselves
|
| -StyleDeclaration objects as follows:
|
| -
|
| -* ``span``
|
| -* ``a``
|
| - These all add to themselves the same declaration as follows:
|
| -```javascript
|
| -let d = new StyleDeclaration();
|
| -d[pDisplay] = new ObjectStyleValue(InlineLayoutManager);
|
| -this.style.addStyles(d);
|
| -```
|
| -
|
| -* ``t``
|
| - This adds to itself the declaration as follows:
|
| -```javascript
|
| -let d = new StyleDeclaration();
|
| -d[pDisplay] = new ObjectStyleValue(ParagraphLayoutManager);
|
| -this.style.addStyles(d);
|
| -```
|
| -
|
| -The other elements don't have any default styles.
|
| -
|
| -These declarations are all shared between all the elements (so e.g. if
|
| -you reach in and change the declaration that was added to a ``span``
|
| -element, you're going to change the styles of all the other
|
| -default-hidden elements). (In other words, in the code snippets above,
|
| -the ``d`` variable is initialised in shared code, and only the
|
| -addStyles() call is per-element.)
|
|
|