| Sky Style Language |
| ================== |
| |
| 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 "@document", |
| optionally followed by a pseudo-element, as in: |
| |
| ```css |
| @document::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 |
| | | |
| | +-- PixelLengthStyleValue |
| | | |
| | +-- EmLengthStyleValue |
| | | |
| | +-- VHLengthStyleValue |
| | | |
| | +-- CalcLengthStyleValue |
| | |
| +-- ColorStyleValue |
| | | |
| | +-- RGBColorStyleValue |
| | | |
| | +-- AnimatableColorStyleValue |
| | |
| +-- AbstractStringStyleValue |
| | | |
| | +-- IdentifierStyleValue |
| | | | |
| | | +-- AnimatableIdentifierStyleValue |
| | | |
| | +-- URLStyleValue |
| | | | |
| | | +-- AnimatableURLStyleValue |
| | | |
| | +-- StringStyleValue |
| | | |
| | +-- AnimatableStringStyleValue |
| | |
| +-- PrimitiveValuesListStyleValue |
| */ |
| |
| abstract class StyleNode { |
| abstract void markDirty(); |
| } |
| |
| class StyleValueResolverSettings { |
| // this is used as an "out" parameter for 'resolve()' below |
| constructor(); |
| void reset(); // resets values to defaults so that object can be reused |
| attribute Boolean layoutDependent; // default to false |
| // set this if the value should be recomputed each time the ownerLayoutManager's dimensions change, rather than being precomputed |
| |
| // 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 |
| } |
| |
| class Property : StyleNode { |
| constructor (StyleDeclaration parentNode, PropertyHandle property, AbstractStyleValue? initialValue = null); |
| readonly attribute StyleDeclaration 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); |
| readonly 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 { |
| Float resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettings? settings = null); |
| } |
| |
| // ... |
| |
| dictionary PropertySettings { |
| String name; |
| StyleGrammar grammar; |
| 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); |
| |
| // sky:core exports a bunch of style grammars so that people can extend them |
| attribute StyleGrammar PositiveLengthOrInfinityStyleGrammar; // resolves to Float |
| attribute StyleGrammar PositiveLengthOrAutoStyleGrammar; // resolves to Float or null |
| attribute StyleGrammar PositiveLengthStyleGrammar; // resolves to Float |
| attribute StyleGrammar NumberGrammar; // resolves to Float |
| attribute StyleGrammar ColorGrammar; // resolves to object with 'red', 'green', 'blue', and 'alpha' properties each of which is a Float 0..1 |
| attribute StyleGrammar DisplayStyleGrammar; // resolves to null or LayoutManager constructor |
| ``` |
| |
| Inline Styles |
| ------------- |
| |
| ```javascript |
| class StyleDeclarationList { |
| constructor (Element? element); |
| |
| // There are two batches of styles in a StyleDeclarationList. |
| |
| // The first batch is the per-frame styles. These get 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 add styles themselves. |
| void addFrameStyles(StyleDeclaration styles, String? pseudoElement = null); // O(1) |
| void clearFrameStyles(); |
| |
| // The second batch is the persistent styles. |
| // Once added, they remain forever until removed. |
| void addPersistentStyles(StyleDeclaration styles, String? pseudoElement = null); // O(1) |
| void removePersistentStyles(StyleDeclaration styles, String? pseudoElement = null); // O(N) in number of declarations |
| |
| // as StyleDeclaration are added and removed here, the StyleDeclarationList 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.cascadedValueAdded/cascadedValueRemoved for each property on the object |
| |
| // This returns all the frame styles followed by all the persistent styles, in insertion order. |
| Array<StyleDeclaration> getDeclarations(String? pseudoElement = null); // O(N) in number of declarations |
| } |
| |
| class StyleDeclaration { |
| 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 element, String? pseudoElement = null); // O(1) |
| void unregister(Element element, String? pseudoElement = null); // O(N) |
| // registers an element/pseudoElement pair with this StyleDeclaration so that when |
| // a property/value on the style declaration is marked dirty, the element |
| // is informed and can then clear its property cache |
| |
| 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); |
| // if there is no Property object for /property/, creates one |
| // else calls its update() method to change the value |
| // if the value changed: |
| // invoke consumer.renderNode.cascadedValueChanged(property); for each |
| // currently registered consumer |
| // if the value is new: |
| // invoke consumer.renderNode.cascadedValueAdded(property); for each |
| // currently registered consumer |
| |
| void remove(PropertyHandle property); |
| // drops the Property object for /property/ from this StyleDeclaration object |
| // invoke consumer.renderNode.cascadedValueRemoved(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 starts applying to an element, sky:core calls thatElement.style.add(rule.styles, rule.pseudoElement); |
| - If a rule stops applying to an element, sky:core calls thatElement.style.remove(rule.styles, rule.pseudoElement); |
| |
| TODO(ianh): fix the above so that rule order is maintained |
| |
| |
| Cascade |
| ------- |
| |
| Simultaneously walk the tree rooted at the application Document, |
| taking into account shadow trees and child distribution, and the tree |
| rooted at the document's RenderNode. |
| |
| If you come across a node that doesn't have an assigned RenderNode, |
| then create one and mark it "isNew", and place it in the appropriate |
| place in the RenderTree tree, after any nodes marked isGhost. |
| |
| 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 the relevant |
| StyleDeclarationList should be flattened and the values stored on the |
| RenderNode for use later. |
| |
| When an Element is to be removed from its parent, regardless of the |
| above, the node's renderNode attribute should be nulled out. |
| |
| |
| ```javascript |
| callback any ValueResolver (any value, String propertyName, RenderNode node, Float containerWidth, Float containerHeight); |
| |
| 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; |
| |
| any getProperty(PropertyHandle property, String? pseudoElement = null); |
| // looking at the cached data for the given pseudoElement: |
| // if there's a cached value, return it |
| // otherwise, figure out which StyleValue we're going to be using, in this order: |
| // - if we're isGhost, look out our cached declarations |
| // - look at this element's StyleDeclarations for the given pseudo-element (if any) |
| // - look at this element's StyleDeclarations with no pseudo-element |
| // - if it's an inherited property and there's a parent |
| // - call getProperty() on the parent |
| // otherwise use the default value |
| // resolve the StyleValue giving it the property and node in question |
| // cache the value, along with the StyleValueResolverSettings |
| |
| private void cascadedValueAdded(PropertyHandle property, String? pseudoElement = null); |
| private void cascadedValueRemoved(PropertyHandle property, String? pseudoElement = null); |
| private void cascadedValueChanged(PropertyHandle property, String? pseudoElement = null); |
| private void cascadedValueDirty(PropertyHandle property, String? pseudoElement = null); |
| // - clear the cached data for this property/pseudoElement pair |
| // - 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 null) or the pseudoElements |
| // and if any of them have the relevant dependency bit set then call |
| // thatRenderNode.cascadedValueDirty(thatProperty, thatPseudoElement) |
| // - if the property is inherited: |
| // - call this.cascadedValueDirty(property, eachPseudoElement) |
| // - call eachChildRenderNode.cascadedValueDirty(property, null) |
| |
| 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 |
| ------ |
| |
| sky:core 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); |
| ``` |
| |
| sky:core by default registers: |
| |
| - 'block': sky.BlockLayoutManager |
| - 'paragraph': sky.ParagraphLayoutManager |
| - 'inline': sky.InlineLayoutManager |
| - 'none': null |
| |
| |
| Layout managers inherit from the following API: |
| |
| ```javascript |
| 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, any children that are isNew are automatically welcomed by the default layout() |
| // when true, children that are removd 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 = null); // O(1) |
| // if called from an adjustProperties() method during the property adjustment phase, |
| // replaces the value that getProperty() would return on that node with /value/ |
| |
| virtual void adjustProperties(); |
| // called before layout; can call setProperty to set new values |
| // note that this happens after the cascade so inheritance isn't applied to this new value |
| // also note that the value you set is a post-computation value, not an AbstractStyleValue descendant |
| // so e.g. you can have an AnimatableColorStyleValue, get its value, and push it into setProperty() |
| // but you can't push the AnimatableColorStyleValue directly in, it won't do what you expect |
| |
| void take(RenderNode victim); // sets victim.ownerLayoutManager = this; |
| // assert: victim hasn't been take()n yet during this layout |
| // assert: victim.needsLayout == true |
| // 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 |
| |
| 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 |
| // 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, clear them |
| void welcomeChild(child); // resets child.isNew |
| void reapChild(child); // resets child.isGhost |
| |
| 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 assumeDimensions(Float width, Float height); |
| // sets the assumed dimensions for calls to getProperty() on RenderNodes that have this as an ownerLayoutManager |
| // 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, clear them |
| // TODO(ianh): should we force this to match the input to layout(), when called from inside layout() and when |
| // layout() has a forced width and/or height? |
| |
| 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 markAsLaidOut(); // sets this.node.needsLayout and this.node.descendantNeedsLayout to false |
| virtual Dimensions layout(Float? width, Float? height); |
| // call markAsLaidOut(); |
| // if autoreap is true: use walkChildren() to call welcomeChild() and reapChild() on each child |
| // if width is null, set width to getIntrinsicWidth().value |
| // if height is null, set width height getIntrinsicHeight().value |
| // call this.assumeDimensions(width, height); |
| // call this.layoutChildren(width, height); |
| // return { width: width, height: height } |
| // - this should always call this.markAsLaidOut() to reset needsLayout |
| // - 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 |
| |
| virtual void layoutChildren(Float width, Float height); |
| // default implementation does nothing |
| // - override this if you want to lay out children but not have the children affect your dimensions |
| |
| 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 the painter of each property, in order they were registered, which on this element has a painter |
| // 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. |
| |
| ```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: |
| |
| * ``import`` |
| * ``template`` |
| * ``style`` |
| * ``script`` |
| * ``content`` |
| * ``title`` |
| These all add to themselves the same declaration with value: |
| ```javascript |
| { display: { value: null } } |
| ``` |
| |
| * ``img`` |
| This adds to itself the declaration with value: |
| ```javascript |
| { display: { value: sky.ImageElementLayoutManager } } |
| ``` |
| |
| * ``span`` |
| * ``a`` |
| These all add to themselves the same declaration with value: |
| ```javascript |
| { display: { value: sky.InlineLayoutManager } } |
| ``` |
| |
| * ``iframe`` |
| This adds to itself the declaration with value: |
| ```javascript |
| { display: { value: sky.IFrameElementLayoutManager } } |
| ``` |
| |
| * ``t`` |
| This adds to itself the declaration with value: |
| ```javascript |
| { display: { value: sky.ParagraphLayoutManager } } |
| ``` |
| |
| * ``error`` |
| This adds to itself the declaration with value: |
| ```javascript |
| { display: { value: sky.ErrorLayoutManager } } |
| ``` |
| |
| The ``div`` element doesn'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 ``title`` |
| element, you're going to change the styles of all the other |
| default-hidden elements). |