| OLD | NEW |
| (Empty) |
| 1 Sky Style Language | |
| 2 ================== | |
| 3 | |
| 4 THIS IS NOT UP TO DATE | |
| 5 | |
| 6 DO NOT IMPLEMENT THIS YET | |
| 7 | |
| 8 It has not been Dartified. | |
| 9 | |
| 10 It has not been converted to have a sane element.style API. | |
| 11 | |
| 12 It has not been converted to have layout be interruptible using Futures. | |
| 13 | |
| 14 | |
| 15 Planed changes | |
| 16 -------------- | |
| 17 | |
| 18 Add //-to-end-of-line comments to be consistent with the script | |
| 19 language. | |
| 20 | |
| 21 | |
| 22 Style Parser | |
| 23 ------------ | |
| 24 | |
| 25 (this section is incomplete) | |
| 26 | |
| 27 ### Tokenisation | |
| 28 | |
| 29 | |
| 30 #### Value parser | |
| 31 | |
| 32 | |
| 33 ##### **Value** state | |
| 34 | |
| 35 If the current character is... | |
| 36 | |
| 37 * '``;``': Consume the character and exit the value parser | |
| 38 successfully. | |
| 39 | |
| 40 * '``@``': Consume the character and switch to the **at** | |
| 41 state. | |
| 42 | |
| 43 * '``#``': Consume the character and switch to the **hash** | |
| 44 state. | |
| 45 | |
| 46 * '``$``': Consume the character and switch to the **dollar** | |
| 47 state. | |
| 48 | |
| 49 * '``%``': Consume the character and switch to the **percent** | |
| 50 state. | |
| 51 | |
| 52 * '``&``': Consume the character and switch to the **ampersand** | |
| 53 state. | |
| 54 | |
| 55 * '``'``': Set _value_ to the empty string, consume the character, and | |
| 56 switch to the **single-quoted string** state. | |
| 57 | |
| 58 * '``"``': Set _value_ to the empty string, consume the character, and | |
| 59 switch to the **double-quoted string** state. | |
| 60 | |
| 61 * '``-``': Consume the character, and switch to the **negative | |
| 62 integer** state. | |
| 63 | |
| 64 * '``0``'-'``9``': Set _value_ to the decimal value of the current | |
| 65 character, consume the character, and switch to the **integer** | |
| 66 state. | |
| 67 | |
| 68 * '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to the current | |
| 69 character, consume the character, and switch to the **identifier** | |
| 70 state. | |
| 71 | |
| 72 * '``*``', '``^``', '``!``', '``?``', '``,``', '``/``', '``<``', | |
| 73 '``[``', '``)``', '``>``', '``]``', '``+``': Emit a symbol token | |
| 74 with the current character as the symbol, consume the character, and | |
| 75 stay in this state. | |
| 76 | |
| 77 * Anything else: Consume the character and switch to the **error** | |
| 78 state. | |
| 79 | |
| 80 | |
| 81 ##### **At** state | |
| 82 | |
| 83 * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to | |
| 84 the current character, create a literal token with the unit set to | |
| 85 ``@``, consume the character, and switch to the **literal** state. | |
| 86 | |
| 87 * Anything else: Emit a symbol token with ``@`` as the symbol, and | |
| 88 switch to the **value** state without consuming the character. | |
| 89 | |
| 90 | |
| 91 ##### **Hash** state | |
| 92 | |
| 93 * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to | |
| 94 the current character, create a literal token with the unit set to | |
| 95 ``@``, consume the character, and switch to the **literal** state. | |
| 96 | |
| 97 * Anything else: Emit a symbol token with ``#`` as the symbol, and | |
| 98 switch to the **value** state without consuming the character. | |
| 99 | |
| 100 | |
| 101 ##### **Dollar** state | |
| 102 | |
| 103 * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to | |
| 104 the current character, create a literal token with the unit set to | |
| 105 ``@``, consume the character, and switch to the **literal** state. | |
| 106 | |
| 107 * Anything else: Emit a symbol token with ``$`` as the symbol, and | |
| 108 switch to the **value** state without consuming the character. | |
| 109 | |
| 110 | |
| 111 ##### **Percent** state | |
| 112 | |
| 113 * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to | |
| 114 the current character, create a literal token with the unit set to | |
| 115 ``@``, consume the character, and switch to the **literal** state. | |
| 116 | |
| 117 * Anything else: Emit a symbol token with ``%`` as the symbol, and | |
| 118 switch to the **value** state without consuming the character. | |
| 119 | |
| 120 | |
| 121 ##### **Ampersand** state | |
| 122 | |
| 123 * '``0``'-'``9``', '``a``'-'``z``', '``A``'-'``Z``': Set _value_ to | |
| 124 the current character, create a literal token with the unit set to | |
| 125 ``@``, consume the character, and switch to the **literal** state. | |
| 126 | |
| 127 * Anything else: Emit a symbol token with ``&`` as the symbol, and | |
| 128 switch to the **value** state without consuming the character. | |
| 129 | |
| 130 | |
| 131 ##### TODO(ianh): more states... | |
| 132 | |
| 133 | |
| 134 ##### **Error** state | |
| 135 | |
| 136 If the current character is... | |
| 137 | |
| 138 * '``;``': Consume the character and exit the value parser in failure. | |
| 139 | |
| 140 * Anything else: Consume the character and stay in this state. | |
| 141 | |
| 142 | |
| 143 | |
| 144 Selectors | |
| 145 --------- | |
| 146 | |
| 147 Sky Style uses whatever SelectorQuery. Maybe one day we'll make | |
| 148 SelectorQuery support being extended to support arbitrary selectors, | |
| 149 but for now, it supports: | |
| 150 | |
| 151 ```css | |
| 152 tagname | |
| 153 #id | |
| 154 .class | |
| 155 [attrname] | |
| 156 [attrname=value] | |
| 157 :host ("host" string is fixed) | |
| 158 ::pseudo-element | |
| 159 ``` | |
| 160 | |
| 161 These can be combined (without whitespace), with at most one tagname | |
| 162 (must be first) and at most one pseudo-element (must be last) as in: | |
| 163 | |
| 164 ```css | |
| 165 tagname[attrname]#id:host.class.class[attrname=value]::foo | |
| 166 ``` | |
| 167 | |
| 168 In debug mode, giving two IDs, or the same selector twice (e.g. the | |
| 169 same classname), or specifying other redundant or conflicting | |
| 170 selectors (e.g. [foo][foo=bar], or [foo=bar][foo=baz]) will be | |
| 171 flagged. | |
| 172 | |
| 173 Alternatively, a selector can be the special value "@root", | |
| 174 optionally followed by a pseudo-element, as in: | |
| 175 | |
| 176 ```css | |
| 177 @root::bar | |
| 178 ``` | |
| 179 | |
| 180 | |
| 181 Value Parser | |
| 182 ------------ | |
| 183 | |
| 184 ```javascript | |
| 185 class StyleToken { | |
| 186 constructor (String king, String value); | |
| 187 readonly attribute String kind; | |
| 188 // string | |
| 189 // identifier | |
| 190 // function (identifier + '(') | |
| 191 // number | |
| 192 // symbol (one of @#$%& if not immediately following numeric or preceding a
lphanumeric, or one of *^!?,/<[)>]+ or, if not followed by a digit, -) | |
| 193 // dimension (number + identifier or number + one of @#$%&) | |
| 194 // literal (one of @#$%& + alphanumeric) | |
| 195 readonly attribute String value; | |
| 196 readonly attribute String unit; // for 'dimension' type, this is the punctuati
on or identifier that follows the number, for 'literal' type, this is the punctu
ation that precedes it | |
| 197 } | |
| 198 | |
| 199 class TokenSource { | |
| 200 constructor (Array<StyleToken> tokens); | |
| 201 IteratorResult next(); | |
| 202 TokenSourceBookmark getBookmark(); | |
| 203 void rewind(TokenSourceBookmark bookmark); | |
| 204 } | |
| 205 | |
| 206 class TokenSourceBookmark { | |
| 207 constructor (); | |
| 208 // TokenSource stores unforgeable state on this object using symbols or a weak
map or some such | |
| 209 } | |
| 210 | |
| 211 callback ParserCallback = AbstractStyleValue (TokenSource tokens); // return if
successful, throw if not | |
| 212 | |
| 213 class StyleGrammar { | |
| 214 constructor (); | |
| 215 void addParser(ParserCallback parser); | |
| 216 AbstractStyleValue parse(TokenSource tokens, Boolean root = false); | |
| 217 // for each parser callback that was registered, in reverse | |
| 218 // order (most recently registered first), run these steps: | |
| 219 // let bookmark = tokens.getBookmark(); | |
| 220 // try { | |
| 221 // let result = parser(tokens); | |
| 222 // if (root) { | |
| 223 // if (!tokens.next().done) | |
| 224 // throw new Error(); | |
| 225 // } | |
| 226 // } except { | |
| 227 // tokens.rewind(bookmark); | |
| 228 // } | |
| 229 // (root is set when you need to parse the entire token stream to be valid) | |
| 230 } | |
| 231 | |
| 232 /* | |
| 233 StyleNode | |
| 234 | | |
| 235 +-- Property | |
| 236 | | |
| 237 +-- AbstractStyleValue | |
| 238 | | |
| 239 +-- NumericStyleValue | |
| 240 | | | |
| 241 | +-- AnimatableNumericStyleValue* | |
| 242 | | |
| 243 +-- LengthStyleValue | |
| 244 | | | |
| 245 | +-- AnimatableLengthStyleValue* | |
| 246 | | | |
| 247 | +-- TransitionLengthStyleValue* | |
| 248 | | | |
| 249 | +-- PixelLengthStyleValue | |
| 250 | | | |
| 251 | +-- EmLengthStyleValue* | |
| 252 | | | |
| 253 | +-- VHLengthStyleValue* | |
| 254 | | | |
| 255 | +-- CalcLengthStyleValue* | |
| 256 | | |
| 257 +-- ColorStyleValue | |
| 258 | | | |
| 259 | +-- RGBColorStyleValue | |
| 260 | | | |
| 261 | +-- AnimatableColorStyleValue* | |
| 262 | | |
| 263 +-- AbstractOpaqueStyleValue | |
| 264 | | | |
| 265 | +-- IdentifierStyleValue | |
| 266 | | | | |
| 267 | | +-- AnimatableIdentifierStyleValue* | |
| 268 | | | |
| 269 | +-- URLStyleValue* | |
| 270 | | | | |
| 271 | | +-- AnimatableURLStyleValue* | |
| 272 | | | |
| 273 | +-- StringStyleValue* | |
| 274 | | | | |
| 275 | | +-- AnimatableStringStyleValue* | |
| 276 | | | |
| 277 | +-- ObjectStyleValue | |
| 278 | | |
| 279 +-- PrimitiveValuesListStyleValue* | |
| 280 */ | |
| 281 ``` | |
| 282 | |
| 283 The types marked with * in the list above are not part of dart:sky, | |
| 284 and are only shown here to illustrate what kinds of extensions are | |
| 285 possible and where they would fit. | |
| 286 | |
| 287 TODO(ianh): consider removing 'StyleValue' from these class names | |
| 288 | |
| 289 ```javascript | |
| 290 abstract class StyleNode { | |
| 291 abstract void markDirty(); | |
| 292 } | |
| 293 | |
| 294 dictionary StyleValueResolverSettingsSettings { | |
| 295 Boolean firstTime = false; | |
| 296 any state = null; | |
| 297 } | |
| 298 | |
| 299 class StyleValueResolverSettings { | |
| 300 // this is used as an "out" parameter for 'resolve()' below | |
| 301 constructor(StyleValueResolverSettingsSettings initial); | |
| 302 void reset(StyleValueResolverSettingsSettings initial); | |
| 303 // sets firstTime and state to given values | |
| 304 // sets layoutDependent to false | |
| 305 // sets dependencies to empty set | |
| 306 // sets lifetime to Infinity | |
| 307 | |
| 308 readonly attribute Boolean firstTime; | |
| 309 // true if this is the first time this property is being resolved for this e
lement, | |
| 310 // or if the last time it was resolved, the value was a different object | |
| 311 | |
| 312 // attribute Boolean layoutDependent | |
| 313 void setLayoutDependent(); | |
| 314 // call this if the value should be recomputed each time the ownerLayoutMana
ger's dimensions change, rather than being cached | |
| 315 Boolean getLayoutDependent(); | |
| 316 // returns true if setLayoutDependent has been called since the last reset() | |
| 317 | |
| 318 // attribute "BitField" dependencies; // defaults to no bits set | |
| 319 void dependsOn(PropertyHandle property); | |
| 320 // if the given property doesn't have a dependency bit assigned: | |
| 321 // - assign the next bit to the property | |
| 322 // - if there's no bits left, throw | |
| 323 // set the bit on this StyleValueResolverSettings's dependencies bitfield | |
| 324 Array<PropertyHandle> getDependencies(); | |
| 325 // returns an array of the PropertyHandle values for the bits that are set i
n dependencies | |
| 326 | |
| 327 // attribute (Float or Infinity) lifetime; | |
| 328 void setLifetime(Float age); | |
| 329 // if the new value is less than the current value of lifetime, update the c
urrent value | |
| 330 (Float or Infinity) getLifetime(); | |
| 331 // return current value of lfietime | |
| 332 | |
| 333 attribute any state; // initially null, can be set to store value for this Ren
derNode/property pair | |
| 334 // for example, TransitioningColorStyleValue would store | |
| 335 // { | |
| 336 // initial: /* color at time of transition */, | |
| 337 // target: /* color at end of transition */, | |
| 338 // start: /* time at start of transition */, | |
| 339 // } | |
| 340 // ...which would enable it to update appropriately, and would also | |
| 341 // let other transitions that come later know that you were half-way | |
| 342 // through a transition so they can shorten their time accordingly | |
| 343 // | |
| 344 // best practices: if you're storing values on the state object, | |
| 345 // then remove the values once they are no longer needed. For | |
| 346 // example, when your transition ends, set the object to null. | |
| 347 // | |
| 348 // best practices: if you're a style value that contains multiple | |
| 349 // style values, then before you call their resolve you should | |
| 350 // replace the state with a state that is specific to them, and | |
| 351 // when you get it back you should insert that value into your | |
| 352 // state somehow. For example, in a resolve()r with two child | |
| 353 // style values a and b: | |
| 354 // let ourState; | |
| 355 // if (settings.firstTime) | |
| 356 // ourState = { a: null, b: null }; | |
| 357 // else | |
| 358 // ourState = settings.state; | |
| 359 // settings.state = ourState.a; | |
| 360 // let aResult = a.resolve(node, settings); | |
| 361 // ourState.a = settings.state; | |
| 362 // settings.state = ourState.b; | |
| 363 // let aResult = b.resolve(node, settings); | |
| 364 // ourState.b = settings.state; | |
| 365 // settings.state = ourState; | |
| 366 // return a + b; // or whatever | |
| 367 // | |
| 368 // best practices: if you're a style value that contains multiple | |
| 369 // style values, and all those style values are storing null, then | |
| 370 // store null yourself, instead of storing many nulls of your own. | |
| 371 | |
| 372 // attribute Boolean wasStateSet; | |
| 373 Boolean getShouldSaveState(); | |
| 374 // returns true if state is not null, and either state was set | |
| 375 // since the last reset, or firstTime is false. | |
| 376 | |
| 377 } | |
| 378 | |
| 379 class Property : StyleNode { | |
| 380 constructor (AbstractStyleDeclaration parentNode, PropertyHandle property, Abs
tractStyleValue? initialValue = null); | |
| 381 readonly attribute AbstractStyleDeclaration parentNode; | |
| 382 readonly attribute PropertyHandle property; | |
| 383 readonly attribute AbstractStyleValue value; | |
| 384 | |
| 385 void setValue(AbstractStyleValue? newValue); | |
| 386 // updates value and calls markDirty() | |
| 387 | |
| 388 void markDirty(); | |
| 389 // call parentNode.markDirty(property); | |
| 390 | |
| 391 abstract any resolve(RenderNode node, StyleValueResolverSettings? settings = n
ull); | |
| 392 // if value is null, returns null | |
| 393 // otherwise, returns value.resolve(property, node, settings) | |
| 394 } | |
| 395 | |
| 396 abstract class AbstractStyleValue : StyleNode { | |
| 397 abstract constructor(StyleNode? parentNode = null); | |
| 398 attribute StyleNode? parentNode; | |
| 399 | |
| 400 void markDirty(); | |
| 401 // call this.parentNode.markDirty() | |
| 402 | |
| 403 abstract any resolve(PropertyHandle property, RenderNode node, StyleValueResol
verSettings? settings = null); | |
| 404 } | |
| 405 | |
| 406 abstract class LengthStyleValue : AbstractStyleValue { | |
| 407 abstract Float resolve(PropertyHandle property, RenderNode node, StyleValueRes
olverSettings? settings = null); | |
| 408 } | |
| 409 | |
| 410 class PixelLengthStyleValue : LengthStyleValue { | |
| 411 constructor(Float number, StyleNode? parentNode = null); | |
| 412 attribute Float value; | |
| 413 // on setting, calls markDirty(); | |
| 414 Float resolve(PropertyHandle property, RenderNode node, StyleValueResolverSett
ings? settings = null); | |
| 415 // return value | |
| 416 } | |
| 417 | |
| 418 typedef RawColor Float; // TODO(ianh): figure out what Color should be | |
| 419 class ColorStyleValue : LengthStyleValue { | |
| 420 constructor(Float red, Float green, Float blue, Float alpha, StyleNode? parent
Node = null); | |
| 421 // ... color API ... | |
| 422 RawColor resolve(PropertyHandle property, RenderNode node, StyleValueResolverS
ettings? settings = null); | |
| 423 } | |
| 424 | |
| 425 class AbstractOpaqueStyleValue : AbstractStyleValue { | |
| 426 abstract constructor(any value, StyleNode? parentNode = null); | |
| 427 attribute any value; | |
| 428 // on setting, calls markDirty(); | |
| 429 any resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettin
gs? settings = null); | |
| 430 // returns value | |
| 431 } | |
| 432 | |
| 433 class IdentifierStyleValue : AbstractOpaqueStyleValue { | |
| 434 constructor(String value, StyleNode? parentNode = null); | |
| 435 // calls superclass constructor | |
| 436 } | |
| 437 | |
| 438 /* | |
| 439 class AnimatableIdentifierStyleValue : AbstractOpaqueStyleValue { | |
| 440 constructor(String value, String newValue, AnimationFunction player, StyleNode
? parentNode = null); | |
| 441 readonly attribute String newValue; | |
| 442 readonly attribute AnimationFunction player; | |
| 443 any resolve(PropertyHandle property, RenderNode node, StyleValueResolverSettin
gs? settings = null); | |
| 444 } | |
| 445 */ | |
| 446 | |
| 447 class ObjectStyleValue : AbstractOpaqueStyleValue { | |
| 448 constructor(any value, StyleNode? parentNode = null); | |
| 449 // calls superclass constructor | |
| 450 } | |
| 451 | |
| 452 dictionary PropertySettings { | |
| 453 String? name = null; // null if the property can't be set from a <style> block | |
| 454 StyleGrammar? grammar = null; // must be non-null if name is non-null; must be
null otherwise | |
| 455 Boolean inherited = false; | |
| 456 any initialValue = null; | |
| 457 Boolean needsManager = false; | |
| 458 Boolean needsLayout = false; | |
| 459 Boolean needsPaint = false; | |
| 460 // PropertyHandle propertyHandle; // assigned by registerProperty | |
| 461 // Integer dependencyBit; // assigned by StyleValueResolverSettings.dependsOn(
) | |
| 462 } | |
| 463 typedef PropertyHandle Integer; | |
| 464 PropertyHandle registerProperty(PropertySettings propertySettings); | |
| 465 // registers a property with the given settings, and returns an integer >= 0 | |
| 466 // that can be used to refer to this property | |
| 467 | |
| 468 // dart:sky exports a bunch of style grammars so that people can extend them | |
| 469 attribute StyleGrammar PositiveLengthOrInfinityStyleGrammar; // resolves to Leng
thStyleValue | |
| 470 attribute StyleGrammar PositiveLengthOrAutoStyleGrammar; // resolves to LengthSt
yleValue or IdentifierStyleValue (with value 'auto') | |
| 471 attribute StyleGrammar PositiveLengthStyleGrammar; // resolves to LengthStyleVal
ue | |
| 472 attribute StyleGrammar NumberGrammar; // resolves to NumericStyleValue | |
| 473 attribute StyleGrammar ColorGrammar; // resolves to ColorStyleValue | |
| 474 attribute StyleGrammar DisplayStyleGrammar; // resolves to ObjectStyleValue | |
| 475 ``` | |
| 476 | |
| 477 Inline Styles | |
| 478 ------------- | |
| 479 | |
| 480 ```javascript | |
| 481 abstract class AbstractStyleDeclarationList { | |
| 482 void addStyles(StyleDeclaration styles, String pseudoElement = ''); // O(1) | |
| 483 void removeStyles(StyleDeclaration styles, String pseudoElement = ''); // O(N)
in number of declarations | |
| 484 Array<StyleDeclaration> getDeclarations(String pseudoElement = ''); // O(N) in
number of declarations | |
| 485 } | |
| 486 | |
| 487 class ElementStyleDeclarationList : AbstractStyleDeclarationList { | |
| 488 constructor (Element? element); | |
| 489 readonly attribute Element? element; | |
| 490 | |
| 491 // there are two batches of styles in an ElementStyleDeclarationList. | |
| 492 | |
| 493 // the first batch is the per-frame styles; these get (conceptually) | |
| 494 // cleared each frame, after which all the matching rules in relevant | |
| 495 // <style> blocks get added back in, followed by all the animation- | |
| 496 // derived rules; scripts can also add styles themselves, but they are | |
| 497 // dropped after the next frame | |
| 498 void addFrameStyles(StyleDeclaration styles, String pseudoElement = ''); // O(
1) | |
| 499 void clearFrameStyles(); | |
| 500 | |
| 501 // the second batch is the persistent styles, which remain until removed; | |
| 502 // they are accessed via the AbstractStyleDeclarationList accessors | |
| 503 | |
| 504 // as StyleDeclarations are added and removed, the ElementStyleDeclarationList | |
| 505 // calls register(element) and unregister(element) respectively on those | |
| 506 // StyleDeclaration objects, where element is the element that was passed | |
| 507 // to the constructor, if not null | |
| 508 // then, it calls element.renderNode.cascadedValueChanged | |
| 509 // for each property on the object | |
| 510 | |
| 511 // the inherited getDeclarations() method returns all the frame | |
| 512 // styles followed by all the persistent styles, in insertion order | |
| 513 } | |
| 514 | |
| 515 class RenderNodeStyleDeclarationList : AbstractStyleDeclarationList { | |
| 516 constructor (RenderNode? renderNode); | |
| 517 readonly attribute RenderNode? renderNode; | |
| 518 | |
| 519 // as StyleDeclarations are added and removed, the RenderNodeStyleDeclarationL
ist | |
| 520 // calls register(renderNode) and unregister(renderNode) respectively on those | |
| 521 // StyleDeclaration objects, where renderNode is the RenderNode that was passe
d | |
| 522 // to the constructor, if not null | |
| 523 // then, it calls renderNode.cascadedValueChanged | |
| 524 // for each property on the object | |
| 525 } | |
| 526 | |
| 527 class StyleDeclaration { | |
| 528 constructor (); | |
| 529 | |
| 530 void markDirty(PropertyHandle property); | |
| 531 // this indicates that the cascaded value of the property thinks | |
| 532 // it will now have a different result (as opposed to the cascaded | |
| 533 // value itself having changed) | |
| 534 // invoke element.renderNode.cascadedValueDirty(property, pseudoElement); fo
r each | |
| 535 // currently registered consumer element/pseudoElement pair | |
| 536 | |
| 537 void register((Element or RenderNode) consumer, String pseudoElement = ''); //
O(1) | |
| 538 void unregister((Element or RenderNode) consumer, String pseudoElement = '');
// O(N) | |
| 539 // registers an element/pseudoElement or renderNode/pseudoElement pair with | |
| 540 // this StyleDeclaration so that a property/value on the style declaration | |
| 541 // is marked dirty, the relevant render node is informed and can then update | |
| 542 // its property cache accordingly | |
| 543 | |
| 544 | |
| 545 getter AbstractStyleValue? (PropertyHandle property); | |
| 546 // looks up the Property object for /property/, and returns its value | |
| 547 // null if property is missing | |
| 548 | |
| 549 setter void (PropertyHandle property, AbstractStyleValue value); | |
| 550 // verify that value.parentNode is null | |
| 551 // if there is no Property object for /property/, creates one | |
| 552 // else calls its update() method to change the value | |
| 553 // update value's parentNode | |
| 554 // invoke consumer.renderNode.cascadedValueChanged(property); for each | |
| 555 // currently registered consumer | |
| 556 | |
| 557 void remove(PropertyHandle property); | |
| 558 // drops the Property object for /property/ from this StyleDeclaration objec
t | |
| 559 // invoke consumer.renderNode.cascadedValueChanged(property); for each | |
| 560 // currently registered consumer | |
| 561 } | |
| 562 ``` | |
| 563 | |
| 564 Rule Matching | |
| 565 ------------- | |
| 566 | |
| 567 ```javascript | |
| 568 class Rule { | |
| 569 constructor (); | |
| 570 attribute SelectorQuery selector; // O(1) | |
| 571 attribute String pseudoElement; // O(1) | |
| 572 attribute StyleDeclaration styles; // O(1) | |
| 573 } | |
| 574 ``` | |
| 575 | |
| 576 Each frame, at some defined point relative to requestAnimationFrame(), | |
| 577 if a Rule has started applying, or a Rule stopped applying, to an | |
| 578 element, dart:sky calls thatElement.style.clearFrameStyles() and then, | |
| 579 for each Rule that now applies, calls | |
| 580 thatElement.style.addFrameStyles() with the relevant StyleDeclaration | |
| 581 and pseudoElement from each such Rule. | |
| 582 | |
| 583 | |
| 584 Update the render tree | |
| 585 ---------------------- | |
| 586 | |
| 587 Simultaneously walk the tree rooted at the application's element | |
| 588 tree's root node, taking into account shadow trees and child | |
| 589 distribution, and the tree rooted at that Root node's RenderNode. | |
| 590 | |
| 591 If you come across a node that doesn't have an assigned RenderNode, | |
| 592 then create one, placing it in the appropriate place in the RenderTree | |
| 593 tree, after any nodes marked isGhost=true, with ownerLayoutManager | |
| 594 pointing to the parent RenderNode's layoutManager, if it has one, and, | |
| 595 if it has one and autoreap is false on that layout manager, mark the | |
| 596 new node "isNew". (This means that when a node is marked isNew, the | |
| 597 layout manager has already laid out at least one frame.) | |
| 598 | |
| 599 For each element, if the node's needsManager is true, call | |
| 600 getLayoutManager() on the element, and if that's not null, and if the | |
| 601 returned class isn't the same class as the current layoutManager, if | |
| 602 any, construct the given class and assign it to the RenderNode's | |
| 603 layoutManager, then set all the child RenderNodes' ownerLayoutManager | |
| 604 to that object; if it returns null, and that node already has a | |
| 605 layoutManager, then set isGhost=true for that node and all its | |
| 606 children (without changing the layoutManager). Otherwise, if it | |
| 607 returned null and there's already no layoutManager, remove the node | |
| 608 from the tree. Then, in any case, clear the needsManager bit. | |
| 609 | |
| 610 When an Element or Text node is to be removed from its parent, and it | |
| 611 has a renderNode, and that renderNode has an ownerLayoutManager with | |
| 612 autoreap=false, then before actually removing the node, the node's | |
| 613 renderNode should be marked isGhost=true, and all the | |
| 614 StyleDeclarations in the relevant ElementStyleDeclarationList should | |
| 615 be added to the RenderNode's overrideStyles for use later (creating a | |
| 616 RenderNodeStyleDeclarationList if necessary). | |
| 617 | |
| 618 When an Element is to be removed from its parent, regardless of the | |
| 619 above, the node's renderNode attribute should be nulled out. | |
| 620 | |
| 621 When a RenderNode is added with isNew=true, call its parent | |
| 622 RenderNode's LayoutManager's childAdded() callback. When a a | |
| 623 RenderNode has its isGhost property set to true, then call it's parent | |
| 624 RenderNode's LayoutManager's childRemoved() callback. | |
| 625 | |
| 626 | |
| 627 ```javascript | |
| 628 dictionary PropertySettings { | |
| 629 String? name = null; // null if the property can't be set from a <style> block | |
| 630 StyleGrammar? grammar = null; // must be non-null if name is non-null; must be
null otherwise | |
| 631 Boolean inherited = false; | |
| 632 any initialValue = null; | |
| 633 Boolean needsManager = false; | |
| 634 Boolean needsLayout = false; | |
| 635 Boolean needsPaint = false; | |
| 636 // PropertyHandle propertyHandle; // assigned by registerProperty | |
| 637 // Integer dependencyBit; // assigned by StyleValueResolverSettings.dependsOn(
) | |
| 638 } | |
| 639 | |
| 640 dictionary GetPropertySettings { | |
| 641 String pseudoElement = ''; | |
| 642 Boolean forceCache = false; | |
| 643 // if set to true, will return the cached value if any, or null otherwise | |
| 644 // this is used by transitions to figure out what to transition from | |
| 645 } | |
| 646 | |
| 647 class RenderNode { // implemented in C++ with no virtual tables | |
| 648 // this is generated before layout | |
| 649 readonly attribute String text; | |
| 650 readonly attribute Node? parentNode; | |
| 651 readonly attribute Node? firstChild; | |
| 652 readonly attribute Node? nextSibling; | |
| 653 | |
| 654 // internal state: | |
| 655 // - back pointer to backing Node, if we're not a ghost | |
| 656 // - cache of resolved property values, mapping as follows: | |
| 657 // - pseudoElement, property => StyleValue object, resolved value, StyleVal
ueResolverSettings, cascade dirty bit, value dirty bit | |
| 658 // - property state map (initially empty), as follows: | |
| 659 // - pseudoElement, property => object | |
| 660 | |
| 661 any getProperty(PropertyHandle property, GetPropertySettings? settings = null)
; | |
| 662 // looking at the cached data for the given pseudoElement: | |
| 663 // if there's a cached value: | |
| 664 // if settings.forceCache is true, return the cached value | |
| 665 // if neither dirty bit is set, return the cached value | |
| 666 // if the cascade dirty bit is not set (value dirty is set) then | |
| 667 // - clear any pending lifetime-enforcing tasks for this | |
| 668 // property/pseudoElement pair on this render node | |
| 669 // - resolve the value using the same StyleValue object | |
| 670 // - with firstTime=false on the resolver settings | |
| 671 // - with the cached state object if any | |
| 672 // - jump to "cache" below | |
| 673 // if settings.forceCache is true, return null | |
| 674 // - clear any pending lifetime-enforcing tasks for this | |
| 675 // property/pseudoElement pair on this render node | |
| 676 // - if there's an override declaration with the property (with | |
| 677 // the pseudo or without), then get the value object from there | |
| 678 // and jump to "resolve" below. | |
| 679 // - if there's an element and it has a style declaration with | |
| 680 // the property (with the pseudo or without), then get the | |
| 681 // value object from there and jump to "resolve" below. | |
| 682 // - if it's not an inherited property, or if there's no parent, | |
| 683 // then get the default value and jump to "resolve" below. | |
| 684 // - call the parent render node's getProperty() with the same | |
| 685 // property but no settings, then cache that value as the value | |
| 686 // for this element with the given pseudoElement, with no | |
| 687 // StyleValue object, no resolver settings, and set the state | |
| 688 // to null. | |
| 689 // resolve: | |
| 690 // - get a new resolver settings object (or reset an existing one) | |
| 691 // - if the obtained StyleValue object is different than the | |
| 692 // cached StyleValue object, or if there is no cached | |
| 693 // object, then set the resolver settings to | |
| 694 // firstTime=true, otherwise it's the same object and set | |
| 695 // firstTime=false. | |
| 696 // - set the resolver settings' state to the current state | |
| 697 // for this pseudoElement/property combination | |
| 698 // - using the obtained StyleValue object, call resolve(), | |
| 699 // passing it this node and the resolver settings object. | |
| 700 // - jump to "cache" below | |
| 701 // cache: | |
| 702 // - update the cache with the obtained value and resolver | |
| 703 // settings | |
| 704 // - reset the dirty bits | |
| 705 // - if the resolver settings' getShouldSaveState() method | |
| 706 // returns false, then discard any cached state, otherwise, | |
| 707 // cache the new state | |
| 708 // - if the resolver settings' lifetime is not infinity, then | |
| 709 // queue a lifetime-enforcing task for the appropriate time | |
| 710 // in the future which calls cascadedValueDirty for this | |
| 711 // property/pseudoElement pair on this render node | |
| 712 | |
| 713 attribute RenderNodeStyleDeclarationList overrideStyles; | |
| 714 // mutable; initially null | |
| 715 // this is used when isGhost is true, and can also be used more generally t
o | |
| 716 // override styles from the layout manager (e.g. to animate a new node into
view) | |
| 717 // this is the only arbitrarily mutable state of a RenderNode object | |
| 718 | |
| 719 private void cascadedValueChanged(PropertyHandle property, String pseudoElemen
t = ''); | |
| 720 private void cascadedValueDirty(PropertyHandle property, String pseudoElement
= ''); | |
| 721 // - set the appropriate dirty bit on the cached data for this property/pseu
doElement pair | |
| 722 // - cascade dirty for cascadedValueChanged | |
| 723 // - value dirty for cascadedValueDirty | |
| 724 // - if the property is needsManager, set needsManager to true | |
| 725 // - if the property is needsLayout, set needsLayout to true and walk up the | |
| 726 // tree setting descendantNeedsLayout | |
| 727 // - if the property is needsPaint, add the node to the list of nodes that n
eed painting | |
| 728 // - if the property has a dependencyBit defined, then check the cache of al
l the | |
| 729 // properties on this RenderNode, and the cache for the property in all th
e child | |
| 730 // nodes and, if pseudoElement is '', the pseudoElements of this node, and
, | |
| 731 // if any of them have the relevant dependency bit set, then call | |
| 732 // thatRenderNode.cascadedValueDirty(thatProperty, thatPseudoElement) | |
| 733 // - if the property is inherited, then for each child node, and, if pseudoE
lement | |
| 734 // is '', the pseudoElements of this node, if the cached value for this pr
operty | |
| 735 // is present but has no StyleValue, call thatNode.cascadedValueChanged(pr
operty, thatPseudoElement) | |
| 736 | |
| 737 readonly attribute Boolean needsManager; | |
| 738 // means that a property with needsManager:true has changed on this node | |
| 739 | |
| 740 readonly attribute Boolean needsLayout; | |
| 741 // means that either needsManager is true or a property with needsLayout:tru
e has changed on this node | |
| 742 // needsLayout is set to false by the ownerLayoutManager's default layout()
method | |
| 743 | |
| 744 readonly attribute Boolean descendantNeedsLayout; | |
| 745 // means that some child of this node has needsLayout set to true | |
| 746 // descendantNeedsLayout is set to false by the ownerLayoutManager's default
layout() method | |
| 747 | |
| 748 readonly attribute LayoutManager layoutManager; | |
| 749 readonly attribute LayoutManager ownerLayoutManager; // defaults to the parent
Node.layoutManager | |
| 750 // if you are not the ownerLayoutManager, then ignore this RenderNode in lay
out() and paintChildren() | |
| 751 // using walkChildren() does this for you | |
| 752 | |
| 753 // only the ownerLayoutManager can change these | |
| 754 readonly attribute Float x; // relative to left edge of ownerLayoutManager | |
| 755 readonly attribute Float y; // relative to top edge of ownerLayoutManager | |
| 756 readonly attribute Float width; | |
| 757 readonly attribute Float height; | |
| 758 readonly attribute Boolean isNew; // node has just been added (and maybe you w
ant to animate it in) | |
| 759 readonly attribute Boolean isGhost; // node has just been removed (and maybe y
ou want to animate it away) | |
| 760 } | |
| 761 ``` | |
| 762 | |
| 763 The flattened tree is represented as a hierarchy of Node objects. For | |
| 764 any element that only contains text node children, the "text" property | |
| 765 is set accordingly. For elements with mixed text node and non-text | |
| 766 node children, each run of text nodes is represented as a separate | |
| 767 Node with the "text" property set accordingly and the styles set as if | |
| 768 the Node inherited everything inheritable from its parent. | |
| 769 | |
| 770 | |
| 771 Layout | |
| 772 ------ | |
| 773 | |
| 774 dart:sky registers 'display' as follows: | |
| 775 | |
| 776 ```javascript | |
| 777 { | |
| 778 name: 'display', | |
| 779 grammar: sky.DisplayStyleGrammar, | |
| 780 inherited: false, | |
| 781 initialValue: sky.BlockLayoutManager, | |
| 782 needsManager: true, | |
| 783 } | |
| 784 ``` | |
| 785 | |
| 786 The following API is then used to add new layout manager types to 'display': | |
| 787 | |
| 788 ```javascript | |
| 789 void registerLayoutManager(String displayValue, LayoutManagerConstructor? layout
Manager); | |
| 790 ``` | |
| 791 | |
| 792 dart:sky by default registers: | |
| 793 | |
| 794 - 'block': sky.BlockLayoutManager | |
| 795 - 'paragraph': sky.ParagraphLayoutManager | |
| 796 - 'inline': sky.InlineLayoutManager | |
| 797 - 'none': null | |
| 798 | |
| 799 | |
| 800 Layout managers inherit from the following API: | |
| 801 | |
| 802 ```javascript | |
| 803 callback LayoutManagerConstructor LayoutManager (RenderNode node); | |
| 804 | |
| 805 class LayoutManager : EventTarget { | |
| 806 readonly attribute RenderNode node; | |
| 807 constructor LayoutManager(RenderNode node); | |
| 808 // sets needsManager to false on the node | |
| 809 | |
| 810 readonly attribute Boolean autoreap; | |
| 811 // defaults to true | |
| 812 // when true, children that are added don't get set to isNew=true | |
| 813 // when true, children that are removed don't get set to isGhost=true, they'
re just removed | |
| 814 | |
| 815 virtual Array<EventTarget> getEventDispatchChain(); // O(N) in number of this.
node's ancestors // implements EventTarget.getEventDispatchChain() | |
| 816 // let result = []; | |
| 817 // let node = this.node; | |
| 818 // while (node && node.layoutManager) { | |
| 819 // result.push(node.layoutManager); | |
| 820 // node = node.parentNode; | |
| 821 // } | |
| 822 // return result; | |
| 823 | |
| 824 void setProperty(RenderNode node, PropertyHandle property, any value, String p
seudoElement = ''); // O(1) | |
| 825 // replaces the value that getProperty() would return on that node with /val
ue/ | |
| 826 // this also clears the dependency bits, dirty bits, and sets the property s
tate to null | |
| 827 // this also clears any relevant lifetime-enforcing tasks | |
| 828 | |
| 829 void take(RenderNode victim); // sets victim.ownerLayoutManager = this; | |
| 830 // assert: victim hasn't been take()n yet during this layout | |
| 831 // assert: an ancestor of victim has node.layoutManager == this (aka, victim
is a descendant of this.node) | |
| 832 | |
| 833 virtual void release(RenderNode victim); | |
| 834 // called when the RenderNode was removed from the tree | |
| 835 | |
| 836 virtual void childAdded(RenderNode child); | |
| 837 virtual void childRemoved(RenderNode child); | |
| 838 // called when a child has its isNew or isGhost attributes set respectively | |
| 839 | |
| 840 void setChildPosition(child, x, y); // sets child.x, child.y | |
| 841 void setChildX(child, y); // sets child.x | |
| 842 void setChildY(child, y); // sets child.y | |
| 843 void setChildSize(child, width, height); // sets child.width, child.height | |
| 844 void setChildWidth(child, width); // sets child.width | |
| 845 void setChildHeight(child, height); // sets child.height | |
| 846 // assert: child.ownerLayoutManager == this | |
| 847 // for setChildSize/Width/Height: if the new dimension is different than the
last assumed dimensions, and | |
| 848 // any RenderNodes with an ownerLayoutManager==this have cached values for g
etProperty() that are marked | |
| 849 // as layout-dependent, mark them as dirty with cascadedValueDirty() | |
| 850 | |
| 851 void assumeDimensions(Float width, Float height); | |
| 852 // sets the assumed dimensions for calls to getProperty() on RenderNodes tha
t have this as an ownerLayoutManager, | |
| 853 // by updating renderNode width/height; | |
| 854 // if the new dimension is different than the last assumed dimensions, and a
ny RenderNodes with an | |
| 855 // ownerLayoutManager==this have cached values for getProperty() that are ma
rked as layout-dependent, mark them | |
| 856 // as dirty with cascadedValueDirty() | |
| 857 | |
| 858 virtual LayoutValueRange getIntrinsicWidth(Float? defaultWidth = null); | |
| 859 /* | |
| 860 function getIntrinsicWidth(defaultWidth) { | |
| 861 if (defaultWidth == null) { | |
| 862 defaultWidth = this.node.getProperty('width'); | |
| 863 if (typeof defaultWidth != 'number') | |
| 864 defaultWidth = 0; | |
| 865 } | |
| 866 let minWidth = this.node.getProperty('min-width'); | |
| 867 if (typeof minWidth != 'number') | |
| 868 minWidth = 0; | |
| 869 let maxWidth = this.node.getProperty('max-width'); | |
| 870 if (typeof maxWidth != 'number') | |
| 871 maxWidth = Infinity; | |
| 872 if (maxWidth < minWidth) | |
| 873 maxWidth = minWidth; | |
| 874 if (defaultWidth > maxWidth) | |
| 875 defaultWidth = maxWidth; | |
| 876 if (defaultWidth < minWidth) | |
| 877 defaultWidth = minWidth; | |
| 878 return { | |
| 879 minimum: minWidth, | |
| 880 value: defaultWidth, | |
| 881 maximum: maxWidth, | |
| 882 }; | |
| 883 } | |
| 884 */ | |
| 885 | |
| 886 virtual LayoutValueRange getIntrinsicHeight(Float? defaultHeight = null); | |
| 887 /* | |
| 888 function getIntrinsicHeight(defaultHeight) { | |
| 889 if (defaultHeight == null) { | |
| 890 defaultHeight = this.node.getProperty('height'); | |
| 891 if (typeof defaultHeight != 'number') | |
| 892 defaultHeight = 0; | |
| 893 } | |
| 894 let minHeight = this.node.getProperty('min-height'); | |
| 895 if (typeof minHeight != 'number') | |
| 896 minHeight = 0; | |
| 897 let maxHeight = this.node.getProperty('max-height'); | |
| 898 if (typeof maxHeight != 'number') | |
| 899 maxHeight = Infinity; | |
| 900 if (maxHeight < minHeight) | |
| 901 maxHeight = minHeight; | |
| 902 if (defaultHeight > maxHeight) | |
| 903 defaultHeight = maxHeight; | |
| 904 if (defaultHeight < minHeight) | |
| 905 defaultHeight = minHeight; | |
| 906 return { | |
| 907 minimum: minHeight, | |
| 908 value: defaultHeight, | |
| 909 maximum: maxHeight, | |
| 910 }; | |
| 911 } | |
| 912 */ | |
| 913 | |
| 914 void welcomeChild(child); | |
| 915 // assert: this == child.ownerLayoutManager | |
| 916 // assert: child.isNew is true | |
| 917 // resets child.isNew | |
| 918 void reapChild(child); | |
| 919 // assert: this == child.ownerLayoutManager | |
| 920 // assert: child.isGhost is true | |
| 921 // removes the RenderNode from its parent if isGhost is true | |
| 922 | |
| 923 Generator<RenderNode> walkChildren(); | |
| 924 // returns a generator that iterates over the children, skipping any whose o
wnerLayoutManager is not |this| | |
| 925 | |
| 926 Generator<RenderNode> walkChildrenBackwards(); | |
| 927 // returns a generator that iterates over the children backwards, skipping a
ny whose ownerLayoutManager is not |this| | |
| 928 | |
| 929 void markAsLaidOut(); // sets this.node.needsLayout and this.node.descendantNe
edsLayout to false | |
| 930 virtual Dimensions layout(Float? width, Float? height); | |
| 931 // if width is null, set width to getIntrinsicWidth().value | |
| 932 // if height is null, set height to getIntrinsicHeight().value | |
| 933 // call this.assumeDimensions(width, height); | |
| 934 // call this.layoutChildren(width, height); | |
| 935 // call markAsLaidOut(); | |
| 936 // return { width: width, height: height } | |
| 937 // - this should always call this.markAsLaidOut() to reset | |
| 938 // needsLayout and descendantNeedsLayout | |
| 939 // - the return value should include the final value for whichever | |
| 940 // of the width and height arguments that is null | |
| 941 // - subclasses that want to make 'auto' values dependent on the | |
| 942 // children should override this entirely, rather than | |
| 943 // overriding layoutChildren; but see layoutChildren()'s notes | |
| 944 // for how to do this | |
| 945 | |
| 946 virtual void layoutChildren(Float width, Float height); | |
| 947 // default implementation does nothing | |
| 948 // - override only this (and not layout()) if you want to lay out | |
| 949 // children but not have the children affect your dimensions | |
| 950 // - always call setChildSize() and setChildPosition() after | |
| 951 // calling a child's layout() method | |
| 952 // - if the child has needsLayout or if you need to have it | |
| 953 // autosize, call its ownerLayoutManager's layout() method | |
| 954 // - otherwise if the child has needs descendantNeedsLayout, call | |
| 955 // layoutDescendants() | |
| 956 | |
| 957 virtual Dimensions layoutDescendants(); | |
| 958 // assert: node.needsLayout is false, node.descendantNeedsLayout is true | |
| 959 // walk children: | |
| 960 // - if it has needsLayout, call its layout() method with its | |
| 961 // current width and height, then call setChildSize() with | |
| 962 // those same dimensions | |
| 963 // - else, if it has descendantNeedsLayout, call its | |
| 964 // layoutDescendants() method | |
| 965 // call markAsLaidOut(); | |
| 966 // - override this if you use take() to control more children, in | |
| 967 // which case you should call their methods too | |
| 968 // - this should always call this.markAsLaidOut() to reset | |
| 969 // needsLayout and descendantNeedsLayout | |
| 970 | |
| 971 virtual void paint(RenderingSurface canvas); | |
| 972 // set a clip rect on the canvas for rect(0,0,this.width,this.height) | |
| 973 // (? we don't really have to do this; consider shadows...) | |
| 974 // call this.paintChildren(canvas) | |
| 975 // (the default implementation doesn't paint anything on top of the children
) | |
| 976 // unset the clip | |
| 977 // - this gets called by the system if: | |
| 978 // - you are in your parent's current display list and it's in its parent
's and so on up to the top, and | |
| 979 // - you haven't had paint() called since the last time you were dirtied | |
| 980 // - the following things make you dirty: | |
| 981 // - dimensions of your RenderNode changed | |
| 982 // - one of your properties with needsLayout or needsPaint changed | |
| 983 | |
| 984 virtual void paintChildren(RenderingSurface canvas); | |
| 985 // for each child returned by walkChildren(): | |
| 986 // if child bounds intersects our bounds: | |
| 987 // call canvas.paintChild(child); | |
| 988 // - you should skip children that will be clipped out of yourself because t
hey're outside your bounds | |
| 989 // - if you transform the canvas, you'll have to implement your own version
of paintChildren() so | |
| 990 // that you don't skip the children that are visible in the new coordinate
space but wouldn't be | |
| 991 // without the transform | |
| 992 | |
| 993 virtual RenderNode hitTest(Float x, Float y); | |
| 994 // default implementation uses the node's children nodes' x, y, | |
| 995 // width, and height, skipping any that have width=0 or height=0, or | |
| 996 // whose ownerLayoutManager is not |this| | |
| 997 // default implementation walks the tree backwards from its built-in order | |
| 998 // if no child is hit, then return this.node | |
| 999 // override this if you changed your children's z-order, or if you used take
() to | |
| 1000 // hoist some descendants up to be your responsibility, or if your children
aren't | |
| 1001 // rectangular (e.g. you lay them out in a hex grid) | |
| 1002 // make sure to offset the value you pass your children: child.layoutManager
.hitTest(x-child.x, y-child.y) | |
| 1003 } | |
| 1004 | |
| 1005 dictionary LayoutValueRange { | |
| 1006 // negative values here should be treated as zero | |
| 1007 Float minimum = 0; | |
| 1008 Float value = 0; // ideal desired width; if it's not in the range minimum .. m
aximum then it overrides minimum and maximum | |
| 1009 (Float or Infinity) maximum = Infinity; | |
| 1010 } | |
| 1011 | |
| 1012 dictionary Dimensions { | |
| 1013 Float width = 0; | |
| 1014 Float height = 0; | |
| 1015 } | |
| 1016 ``` | |
| 1017 | |
| 1018 | |
| 1019 Paint | |
| 1020 ----- | |
| 1021 | |
| 1022 Sky has a list of RenderNodes that need painting. When a RenderNode is | |
| 1023 created, it's added to this list. (See also needsPaint for another | |
| 1024 time it is added to the list.) | |
| 1025 | |
| 1026 ```javascript | |
| 1027 callback void Painter (RenderNode node, RenderingSurface canvas); | |
| 1028 | |
| 1029 class RenderingSurface { | |
| 1030 | |
| 1031 // ... (API similar to <canvas>'s 2D API) | |
| 1032 | |
| 1033 void paintChild(RenderNode node); | |
| 1034 // inserts a "paint this child" instruction in this canvas's display list. | |
| 1035 // the child's display list, transformed by the child's x and y coordinates,
will be inserted into this | |
| 1036 // display list during painting. | |
| 1037 } | |
| 1038 ``` | |
| 1039 | |
| 1040 | |
| 1041 The default framework provides global hooks for extending the painting of: | |
| 1042 | |
| 1043 - borders | |
| 1044 - backgrounds | |
| 1045 | |
| 1046 These are called during the default framework's layout managers' | |
| 1047 paint() functions. They are also made available so that other people | |
| 1048 can call them from their paint() functions. | |
| 1049 | |
| 1050 | |
| 1051 | |
| 1052 Default Styles | |
| 1053 -------------- | |
| 1054 | |
| 1055 In the constructors for the default elements, they add to themselves | |
| 1056 StyleDeclaration objects as follows: | |
| 1057 | |
| 1058 * ``span`` | |
| 1059 * ``a`` | |
| 1060 These all add to themselves the same declaration as follows: | |
| 1061 ```javascript | |
| 1062 let d = new StyleDeclaration(); | |
| 1063 d[pDisplay] = new ObjectStyleValue(InlineLayoutManager); | |
| 1064 this.style.addStyles(d); | |
| 1065 ``` | |
| 1066 | |
| 1067 * ``t`` | |
| 1068 This adds to itself the declaration as follows: | |
| 1069 ```javascript | |
| 1070 let d = new StyleDeclaration(); | |
| 1071 d[pDisplay] = new ObjectStyleValue(ParagraphLayoutManager); | |
| 1072 this.style.addStyles(d); | |
| 1073 ``` | |
| 1074 | |
| 1075 The other elements don't have any default styles. | |
| 1076 | |
| 1077 These declarations are all shared between all the elements (so e.g. if | |
| 1078 you reach in and change the declaration that was added to a ``span`` | |
| 1079 element, you're going to change the styles of all the other | |
| 1080 default-hidden elements). (In other words, in the code snippets above, | |
| 1081 the ``d`` variable is initialised in shared code, and only the | |
| 1082 addStyles() call is per-element.) | |
| OLD | NEW |