Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(20)

Unified Diff: sky/specs/style.md

Issue 716013002: Specs: Initial hack at extensible style/layout (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « sky/specs/apis.md ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: sky/specs/style.md
diff --git a/sky/specs/style.md b/sky/specs/style.md
index 21c84cd2e71f18ba5f5b90398fb0b058827ab3e6..93c9d4a7b3404478b9cc6c5d278a1377cb02835b 100644
--- a/sky/specs/style.md
+++ b/sky/specs/style.md
@@ -1,21 +1,510 @@
Sky Style Language
==================
-For now, the Sky style language is CSS with the following restrictions:
-
-- No combinators
-- Only = and ~= attribute selectors
-- Lots of other selectors removed // TODO(ianh): list them
-- Floats removed
-- Lots of other layout models removed // TODO(ianh): list them
-
-
Planed changes
--------------
Add //-to-end-of-line comments to be consistent with the script
language.
-Add a way to add new values, e.g. by default only support #RRGGBB
-colours (or maybe only rgba() colours), but provide a way to enable
-CSS4-like "color(red rgb(+ #004400))" stuff.
+
+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:
+
+ tagname
+ #id
+ .class
+ [attrname]
+ [attrname=value]
+ :host
+
+These can be combined (without whitespace), with at most one tagname,
+as in:
+
+ tagname[attrname]#id:host.class.class[attrname=value]
+
+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 following special value:
+
+ @document
+
+
+Value Parser
+------------
+
+ 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
+ }
+
+ dictionary ParsedValue {
+ any value = null;
+ ValueResolver? resolver = null;
+ Boolean relativeDimension = false; // if true, e.g. for % lengths, the callback will be called again if an ancestor's dimensions change
+ Painter? painter = null;
+ }
+
+ // best practice convention: if you're creating a property with needsPaint, you should
+ // create a new style value type for it so that it can set the paint callback right;
+ // you should never use such a style type when parsing another property
+
+ callback any ParserCallback (TokenSource tokens);
+
+ class StyleValueType {
+ constructor ();
+ void addParser(ParserCallback parser);
+ any 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)
+ }
+
+ // note: if you define a style value type that uses other style value types, e.g. a "length pair" that accepts two lengths, then
+ // if any of the subtypes have a resolver, you need to make sure you have a resolver that calls them to compute the final value
+
+ dictionary PropertySettings {
+ String name;
+ StyleValueType type; // the output from the parser is coerced to a ParsedValue
+ Boolean inherits = false;
+ any initialValue = null;
+ Boolean needsLayout = false;
+ Boolean needsPaint = false;
+ }
+
+ void registerProperty(PropertySettings propertySettings);
+ // when you register a new property, document the format that is expected to be cascaded
+ // (the output from the propertySettings.type parser's ParsedValue.value field after the resolver, if any, has been called)
+
+ // sky:core exports a bunch of style value types so that people can
+ // extend them
+ attribute StyleValueType PositiveLengthOrInfinityStyleValueType;
+ attribute StyleValueType PositiveLengthOrAutoStyleValueType;
+ attribute StyleValueType PositiveLengthStyleValueType;
+ attribute StyleValueType DisplayStyleValueType;
+
+
+Inline Styles
+-------------
+
+partial class Element {
+ readonly attribute StyleDeclarationList style;
+}
+
+class StyleDeclarationList {
+ constructor ();
+ void add(StyleDeclaration styles); // O(1) // in debug mode, throws if the dictionary has any properties that aren't registered
+ void remove(StyleDeclaration styles); // O(N) in number of declarations
+ Array<StyleDeclaration> getDeclarations(); // O(N) in number of declarations
+}
+
+typedef StyleDeclaration Dictionary<ParsedValue>;
+
+
+Rule Matching
+-------------
+
+partial class StyleElement {
+ Array<Rule> getRules(); // O(N) in rules
+}
+
+class Rule {
+ constructor ();
+ attribute SelectorQuery selector; // 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);
+ - If a rule stops applying to an element, sky:core calls thatElement.style.remove(rule.styles);
+
+
+Cascade
+-------
+
+For each Element, the StyleDeclarationList is conceptually flattened
+so that only the last declaration mentioning a property is left.
+
+Create the flattened render tree as a tree of StyleNode objects
+(described below). For each one, run the equivalent of the following
+code:
+
+ var display = node.getProperty('display');
+ if (display) {
+ node.layoutManager = new display(node, ownerManager);
+ return true;
+ }
+ return false;
+
+If that code returns false, then that node an all its descendants must
+be dropped from the render tree.
+
+If any node is removed in this pass relative to the previous pass, and
+it has an ownerLayoutManager, then call
+ ``node.ownerLayoutManager.release(node)``
+...to notify the layout manager that the node went away, then set the
+node's layoutManager and ownerLayoutManager attributes to null.
+
+ callback any ValueResolver (any value, String propertyName, StyleNode node, Float containerWidth, Float containerHeight);
+
+ class StyleNode {
+ // this is generated before layout
+ readonly attribute String text;
+ readonly attribute Node? parentNode;
+ readonly attribute Node? firstChild;
+ readonly attribute Node? nextSibling;
+
+ // access to the results of the cascade
+ any getProperty(String name);
+ // if there's a cached value, return it
+ // otherwise, if there's an applicable ParsedValue, then
+ // if it has a resolver:
+ // call it
+ // cache the value
+ // if relativeDimension is true, then mark the value as provisional
+ // return the value
+ // otherwise use the ParsedValue's value; cache it; return it
+ // otherwise, if the property is inherited and there's a parent:
+ // get it from the parent; cache it; return it
+ // otherwise, get the default value; cache it; return it
+
+ readonly attribute Boolean needsLayout; // means that a property with needsLayout:true has changed on this node or one of its descendants
+ readonly attribute LayoutManager layoutManager;
+
+ readonly attribute LayoutManager ownerLayoutManager; // defaults to the parentNode.layoutManager
+ // if you are not the ownerLayoutManager, then ignore this StyleNode in layout() and paintChildren()
+ // using walkChildren() does this for you
+
+ readonly attribute Boolean needsPaint; // means that either needsLayout is true or a property with needsPaint:true has changed on this node or one of its descendants
+
+ // only the ownerLayoutManager can change these
+ readonly attribute Float x;
+ readonly attribute Float y;
+ readonly attribute Float width;
+ readonly attribute Float height;
+ }
+
+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:
+
+ {
+ name: 'display',
+ type: sky.DisplayStyleValueType,
+ inherits: false,
+ initialValue: sky.BlockLayoutManager,
+ needsLayout: true,
+ }
+
+The following API is then used to add new layout manager types to 'display':
+
+ 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:
+
+ class LayoutManager {
+ readonly attribute StyleNode node;
+ constructor LayoutManager(StyleNode node);
+
+ void take(StyleNode 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 needsLayout == this (aka, victim is a descendant of this.node)
+
+ virtual void release(StyleNode victim);
+ // called when the StyleNode 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
+ // these set needsPaint on the node and on any node impacted by this (?)
+ // for setChildSize/Width/Height: if the new dimension is different than the last assumed dimensions, and
+ // any StyleNodes with an ownerLayoutManager==this have cached values for getProperty() that are marked
+ // as provisional, clear them
+
+ Generator<StyleNode> walkChildren();
+ // returns a generator that iterates over the children, skipping any whose ownerLayoutManager is not this
+
+ void paint(RenderingSurface canvas);
+ // set a clip rect on the canvas
+ // call the painter of each property, in order they were registered, which on this element has a painter
+ // call this.paintChildren()
+ // unset the clip
+
+ virtual void paintChildren(RenderingSurface canvas);
+ // just calls paint() for each child returned by walkChildren() whose needsPaint is true
+
+ void assumeDimensions(Float width, Float height);
+ // sets the assumed dimensions for calls to getProperty() on StyleNodes that have this as an ownerLayoutManager
+ // if the new dimension is different than the last assumed dimensions, and any StyleNodes with an
+ // ownerLayoutManager==this have cached values for getProperty() that are marked as provisional, 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);
+ // returns min-width, width, and max-width, normalised, defaulting to values given in LayoutValueRange
+ // if argument is provided, it overrides width
+
+ virtual LayoutValueRange getIntrinsicHeight(Float? defaultHeight = null);
+ // returns min-height, height, and max-height, normalised, defaulting to values given in LayoutValueRange
+ // if argument is provided, it overrides height
+
+ virtual Dimensions layout(Number? width, Number? height);
+ // returns { }
+ // the return value should include the final value for whichever of the width and height arguments that is null
+ // TODO(ianh): should we just grab the width and height from assumeDimensions()?
+
+ }
+
+ 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;
+ }
+
+
+Given a tree of StyleNode objects rooted at /node/, the application is
+rendered as follows:
+
+ node.layoutManager.layout(screen.width, screen.height);
+ node.layoutManager.paint();
+
+
+
+Paint
+-----
+
+ callback void Painter (StyleNode node, RenderingSurface canvas);
+
+ class RenderingSurface {
+ // ...
+ }
+
+
+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:
+ ``{ display: { value: null } }``
+
+* ``img``
+ This adds to itself the declaration with value:
+ ``{ display: { value: sky.ImageElementLayoutManager } }``
+
+* ``span``
+* ``a``
+ These all add to themselves the same declaration with value:
+ ``{ display: { value: sky.InlineLayoutManager } }``
+
+* ``iframe``
+ This adds to itself the declaration with value:
+ ``{ display: { value: sky.IFrameElementLayoutManager } }``
+
+* ``t``
+ This adds to itself the declaration with value:
+ ``{ display: { value: sky.ParagraphLayoutManager } }``
+
+* ``error``
+ This adds to itself the declaration with value:
+ ``{ 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).
« no previous file with comments | « sky/specs/apis.md ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698