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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « sky/specs/apis.md ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 Sky Style Language 1 Sky Style Language
2 ================== 2 ==================
3 3
4 For now, the Sky style language is CSS with the following restrictions:
5
6 - No combinators
7 - Only = and ~= attribute selectors
8 - Lots of other selectors removed // TODO(ianh): list them
9 - Floats removed
10 - Lots of other layout models removed // TODO(ianh): list them
11
12
13 Planed changes 4 Planed changes
14 -------------- 5 --------------
15 6
16 Add //-to-end-of-line comments to be consistent with the script 7 Add //-to-end-of-line comments to be consistent with the script
17 language. 8 language.
18 9
19 Add a way to add new values, e.g. by default only support #RRGGBB 10
20 colours (or maybe only rgba() colours), but provide a way to enable 11 Style Parser
21 CSS4-like "color(red rgb(+ #004400))" stuff. 12 ------------
13
14 (this section is incomplete)
15
16 ### Tokenisation
17
18
19 #### Value parser
20
21
22 ##### **Value** state
23
24 If the current character is...
25
26 * '``;``': Consume the character and exit the value parser
27 successfully.
28
29 * '``@``': Consume the character and switch to the **at**
30 state.
31
32 * '``#``': Consume the character and switch to the **hash**
33 state.
34
35 * '``$``': Consume the character and switch to the **dollar**
36 state.
37
38 * '``%``': Consume the character and switch to the **percent**
39 state.
40
41 * '``&``': Consume the character and switch to the **ampersand**
42 state.
43
44 * '``'``': Set _value_ to the empty string, consume the character, and
45 switch to the **single-quoted string** state.
46
47 * '``"``': Set _value_ to the empty string, consume the character, and
48 switch to the **double-quoted string** state.
49
50 * '``-``': Consume the character, and switch to the **negative
51 integer** state.
52
53 * '``0``'-'``9``': Set _value_ to the decimal value of the current
54 character, consume the character, and switch to the **integer**
55 state.
56
57 * '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to the current
58 character, consume the character, and switch to the **identifier**
59 state.
60
61 * '``*``', '``^``', '``!``', '``?``', '``,``', '``/``', '``<``',
62 '``[``', '``)``', '``>``', '``]``', '``+``': Emit a symbol token
63 with the current character as the symbol, consume the character, and
64 stay in this state.
65
66 * Anything else: Consume the character and switch to the **error**
67 state.
68
69
70 ##### **At** state
71
72 * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to
73 the current character, create a literal token with the unit set to
74 ``@``, consume the character, and switch to the **literal** state.
75
76 * Anything else: Emit a symbol token with ``@`` as the symbol, and
77 switch to the **value** state without consuming the character.
78
79
80 ##### **Hash** state
81
82 * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to
83 the current character, create a literal token with the unit set to
84 ``@``, consume the character, and switch to the **literal** state.
85
86 * Anything else: Emit a symbol token with ``#`` as the symbol, and
87 switch to the **value** state without consuming the character.
88
89
90 ##### **Dollar** state
91
92 * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to
93 the current character, create a literal token with the unit set to
94 ``@``, consume the character, and switch to the **literal** state.
95
96 * Anything else: Emit a symbol token with ``$`` as the symbol, and
97 switch to the **value** state without consuming the character.
98
99
100 ##### **Percent** state
101
102 * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to
103 the current character, create a literal token with the unit set to
104 ``@``, consume the character, and switch to the **literal** state.
105
106 * Anything else: Emit a symbol token with ``%`` as the symbol, and
107 switch to the **value** state without consuming the character.
108
109
110 ##### **Ampersand** state
111
112 * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to
113 the current character, create a literal token with the unit set to
114 ``@``, consume the character, and switch to the **literal** state.
115
116 * Anything else: Emit a symbol token with ``&`` as the symbol, and
117 switch to the **value** state without consuming the character.
118
119
120 ##### TODO(ianh): more states...
121
122
123 ##### **Error** state
124
125 If the current character is...
126
127 * '``;``': Consume the character and exit the value parser in failure.
128
129 * Anything else: Consume the character and stay in this state.
130
131
132
133 Selectors
134 ---------
135
136 Sky Style uses whatever SelectorQuery. Maybe one day we'll make
137 SelectorQuery support being extended to support arbitrary selectors,
138 but for now, it supports:
139
140 tagname
141 #id
142 .class
143 [attrname]
144 [attrname=value]
145 :host
146
147 These can be combined (without whitespace), with at most one tagname,
148 as in:
149
150 tagname[attrname]#id:host.class.class[attrname=value]
151
152 In debug mode, giving two IDs, or the same selector twice (e.g. the
153 same classname), or specifying other redundant or conflicting
154 selectors (e.g. [foo][foo=bar], or [foo=bar][foo=baz]) will be
155 flagged.
156
157 Alternatively, a selector can be the following special value:
158
159 @document
160
161
162 Value Parser
163 ------------
164
165 class StyleToken {
166 constructor (String king, String value);
167 readonly attribute String kind;
168 // string
169 // identifier
170 // function (identifier + '(')
171 // number
172 // symbol (one of @#$%& if not immediately following numeric or preceding alphanumeric, or one of *^!?,/<[)>]+ or, if not followed by a digit, -)
173 // dimension (number + identifier or number + one of @#$%&)
174 // literal (one of @#$%& + alphanumeric)
175 readonly attribute String value;
176 readonly attribute String unit; // for 'dimension' type, this is the punctua tion or identifier that follows the number, for 'literal' type, this is the punc tuation that precedes it
177 }
178
179 class TokenSource {
180 constructor (Array<StyleToken> tokens);
181 IteratorResult next();
182 TokenSourceBookmark getBookmark();
183 void rewind(TokenSourceBookmark bookmark);
184 }
185 class TokenSourceBookmark {
186 constructor ();
187 // TokenSource stores unforgeable state on this object using symbols or a we akmap or some such
188 }
189
190 dictionary ParsedValue {
191 any value = null;
192 ValueResolver? resolver = null;
193 Boolean relativeDimension = false; // if true, e.g. for % lengths, the callb ack will be called again if an ancestor's dimensions change
194 Painter? painter = null;
195 }
196
197 // best practice convention: if you're creating a property with needsPaint, yo u should
198 // create a new style value type for it so that it can set the paint callback right;
199 // you should never use such a style type when parsing another property
200
201 callback any ParserCallback (TokenSource tokens);
202
203 class StyleValueType {
204 constructor ();
205 void addParser(ParserCallback parser);
206 any parse(TokenSource tokens, Boolean root = false);
207 // for each parser callback that was registered, in reverse
208 // order (most recently registered first), run these steps:
209 // let bookmark = tokens.getBookmark();
210 // try {
211 // let result = parser(tokens);
212 // if (root) {
213 // if (!tokens.next().done)
214 // throw new Error();
215 // }
216 // } except {
217 // tokens.rewind(bookmark);
218 // }
219 // (root is set when you need to parse the entire token stream to be valid)
220 }
221
222 // note: if you define a style value type that uses other style value types, e .g. a "length pair" that accepts two lengths, then
223 // if any of the subtypes have a resolver, you need to make sure you have a re solver that calls them to compute the final value
224
225 dictionary PropertySettings {
226 String name;
227 StyleValueType type; // the output from the parser is coerced to a ParsedVal ue
228 Boolean inherits = false;
229 any initialValue = null;
230 Boolean needsLayout = false;
231 Boolean needsPaint = false;
232 }
233
234 void registerProperty(PropertySettings propertySettings);
235 // when you register a new property, document the format that is expected to be cascaded
236 // (the output from the propertySettings.type parser's ParsedValue.value fie ld after the resolver, if any, has been called)
237
238 // sky:core exports a bunch of style value types so that people can
239 // extend them
240 attribute StyleValueType PositiveLengthOrInfinityStyleValueType;
241 attribute StyleValueType PositiveLengthOrAutoStyleValueType;
242 attribute StyleValueType PositiveLengthStyleValueType;
243 attribute StyleValueType DisplayStyleValueType;
244
245
246 Inline Styles
247 -------------
248
249 partial class Element {
250 readonly attribute StyleDeclarationList style;
251 }
252
253 class StyleDeclarationList {
254 constructor ();
255 void add(StyleDeclaration styles); // O(1) // in debug mode, throws if the dic tionary has any properties that aren't registered
256 void remove(StyleDeclaration styles); // O(N) in number of declarations
257 Array<StyleDeclaration> getDeclarations(); // O(N) in number of declarations
258 }
259
260 typedef StyleDeclaration Dictionary<ParsedValue>;
261
262
263 Rule Matching
264 -------------
265
266 partial class StyleElement {
267 Array<Rule> getRules(); // O(N) in rules
268 }
269
270 class Rule {
271 constructor ();
272 attribute SelectorQuery selector; // O(1)
273 attribute StyleDeclaration styles; // O(1)
274 }
275
276 Each frame, at some defined point relative to requestAnimationFrame():
277 - If a rule starts applying to an element, sky:core calls thatElement.style.add (rule.styles);
278 - If a rule stops applying to an element, sky:core calls thatElement.style.remo ve(rule.styles);
279
280
281 Cascade
282 -------
283
284 For each Element, the StyleDeclarationList is conceptually flattened
285 so that only the last declaration mentioning a property is left.
286
287 Create the flattened render tree as a tree of StyleNode objects
288 (described below). For each one, run the equivalent of the following
289 code:
290
291 var display = node.getProperty('display');
292 if (display) {
293 node.layoutManager = new display(node, ownerManager);
294 return true;
295 }
296 return false;
297
298 If that code returns false, then that node an all its descendants must
299 be dropped from the render tree.
300
301 If any node is removed in this pass relative to the previous pass, and
302 it has an ownerLayoutManager, then call
303 ``node.ownerLayoutManager.release(node)``
304 ...to notify the layout manager that the node went away, then set the
305 node's layoutManager and ownerLayoutManager attributes to null.
306
307 callback any ValueResolver (any value, String propertyName, StyleNode node, Fl oat containerWidth, Float containerHeight);
308
309 class StyleNode {
310 // this is generated before layout
311 readonly attribute String text;
312 readonly attribute Node? parentNode;
313 readonly attribute Node? firstChild;
314 readonly attribute Node? nextSibling;
315
316 // access to the results of the cascade
317 any getProperty(String name);
318 // if there's a cached value, return it
319 // otherwise, if there's an applicable ParsedValue, then
320 // if it has a resolver:
321 // call it
322 // cache the value
323 // if relativeDimension is true, then mark the value as provisional
324 // return the value
325 // otherwise use the ParsedValue's value; cache it; return it
326 // otherwise, if the property is inherited and there's a parent:
327 // get it from the parent; cache it; return it
328 // otherwise, get the default value; cache it; return it
329
330 readonly attribute Boolean needsLayout; // means that a property with needsL ayout:true has changed on this node or one of its descendants
331 readonly attribute LayoutManager layoutManager;
332
333 readonly attribute LayoutManager ownerLayoutManager; // defaults to the pare ntNode.layoutManager
334 // if you are not the ownerLayoutManager, then ignore this StyleNode in la yout() and paintChildren()
335 // using walkChildren() does this for you
336
337 readonly attribute Boolean needsPaint; // means that either needsLayout is t rue or a property with needsPaint:true has changed on this node or one of its de scendants
338
339 // only the ownerLayoutManager can change these
340 readonly attribute Float x;
341 readonly attribute Float y;
342 readonly attribute Float width;
343 readonly attribute Float height;
344 }
345
346 The flattened tree is represented as a hierarchy of Node objects. For
347 any element that only contains text node children, the "text" property
348 is set accordingly. For elements with mixed text node and non-text
349 node children, each run of text nodes is represented as a separate
350 Node with the "text" property set accordingly and the styles set as if
351 the Node inherited everything inheritable from its parent.
352
353
354 Layout
355 ------
356
357 sky:core registers 'display' as follows:
358
359 {
360 name: 'display',
361 type: sky.DisplayStyleValueType,
362 inherits: false,
363 initialValue: sky.BlockLayoutManager,
364 needsLayout: true,
365 }
366
367 The following API is then used to add new layout manager types to 'display':
368
369 void registerLayoutManager(String displayValue, LayoutManagerConstructor? layo utManager);
370
371 sky:core by default registers:
372
373 'block': sky.BlockLayoutManager
374 'paragraph': sky.ParagraphLayoutManager
375 'inline': sky.InlineLayoutManager
376 'none': null
377
378
379 Layout managers inherit from the following API:
380
381 class LayoutManager {
382 readonly attribute StyleNode node;
383 constructor LayoutManager(StyleNode node);
384
385 void take(StyleNode victim); // sets victim.ownerLayoutManager = this;
386 // assert: victim hasn't been take()n yet during this layout
387 // assert: victim.needsLayout == true
388 // assert: an ancestor of victim has needsLayout == this (aka, victim is a descendant of this.node)
389
390 virtual void release(StyleNode victim);
391 // called when the StyleNode was removed from the tree
392
393 void setChildPosition(child, x, y); // sets child.x, child.y
394 void setChildX(child, y); // sets child.x
395 void setChildY(child, y); // sets child.y
396 void setChildSize(child, width, height); // sets child.width, child.height
397 void setChildWidth(child, width); // sets child.width
398 void setChildHeight(child, height); // sets child.height
399 // these set needsPaint on the node and on any node impacted by this (?)
400 // for setChildSize/Width/Height: if the new dimension is different than t he last assumed dimensions, and
401 // any StyleNodes with an ownerLayoutManager==this have cached values for getProperty() that are marked
402 // as provisional, clear them
403
404 Generator<StyleNode> walkChildren();
405 // returns a generator that iterates over the children, skipping any whose ownerLayoutManager is not this
406
407 void paint(RenderingSurface canvas);
408 // set a clip rect on the canvas
409 // call the painter of each property, in order they were registered, which on this element has a painter
410 // call this.paintChildren()
411 // unset the clip
412
413 virtual void paintChildren(RenderingSurface canvas);
414 // just calls paint() for each child returned by walkChildren() whose need sPaint is true
415
416 void assumeDimensions(Float width, Float height);
417 // sets the assumed dimensions for calls to getProperty() on StyleNodes th at have this as an ownerLayoutManager
418 // if the new dimension is different than the last assumed dimensions, and any StyleNodes with an
419 // ownerLayoutManager==this have cached values for getProperty() that are marked as provisional, clear them
420 // TODO(ianh): should we force this to match the input to layout(), when c alled from inside layout() and when
421 // layout() has a forced width and/or height?
422
423 virtual LayoutValueRange getIntrinsicWidth(Float? defaultWidth = null);
424 // returns min-width, width, and max-width, normalised, defaulting to valu es given in LayoutValueRange
425 // if argument is provided, it overrides width
426
427 virtual LayoutValueRange getIntrinsicHeight(Float? defaultHeight = null);
428 // returns min-height, height, and max-height, normalised, defaulting to v alues given in LayoutValueRange
429 // if argument is provided, it overrides height
430
431 virtual Dimensions layout(Number? width, Number? height);
432 // returns { }
433 // the return value should include the final value for whichever of the wi dth and height arguments that is null
434 // TODO(ianh): should we just grab the width and height from assumeDimensi ons()?
435
436 }
437
438 dictionary LayoutValueRange {
439 // negative values here should be treated as zero
440 Float minimum = 0;
441 Float value = 0; // ideal desired width; if it's not in the range minimum .. maximum then it overrides minimum and maximum
442 (Float or Infinity) maximum = Infinity;
443 }
444
445 dictionary Dimensions {
446 Float width = 0;
447 Float height = 0;
448 }
449
450
451 Given a tree of StyleNode objects rooted at /node/, the application is
452 rendered as follows:
453
454 node.layoutManager.layout(screen.width, screen.height);
455 node.layoutManager.paint();
456
457
458
459 Paint
460 -----
461
462 callback void Painter (StyleNode node, RenderingSurface canvas);
463
464 class RenderingSurface {
465 // ...
466 }
467
468
469 Default Styles
470 --------------
471
472 In the constructors for the default elements, they add to themselves
473 StyleDeclaration objects as follows:
474
475 * ``import``
476 * ``template``
477 * ``style``
478 * ``script``
479 * ``content``
480 * ``title``
481 These all add to themselves the same declaration with value:
482 ``{ display: { value: null } }``
483
484 * ``img``
485 This adds to itself the declaration with value:
486 ``{ display: { value: sky.ImageElementLayoutManager } }``
487
488 * ``span``
489 * ``a``
490 These all add to themselves the same declaration with value:
491 ``{ display: { value: sky.InlineLayoutManager } }``
492
493 * ``iframe``
494 This adds to itself the declaration with value:
495 ``{ display: { value: sky.IFrameElementLayoutManager } }``
496
497 * ``t``
498 This adds to itself the declaration with value:
499 ``{ display: { value: sky.ParagraphLayoutManager } }``
500
501 * ``error``
502 This adds to itself the declaration with value:
503 ``{ display: { value: sky.ErrorLayoutManager } }``
504
505 The ``div`` element doesn't have any default styles.
506
507 These declarations are all shared between all the elements (so e.g. if
508 you reach in and change the declaration that was added to a ``title``
509 element, you're going to change the styles of all the other
510 default-hidden elements).
OLDNEW
« 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