Future of styles and GUI controls?

Expanding BABYLON.GUI to use CSS

Here are my general thoughts on adapting CSS in the styling of Babylon Controls. Hang in there, this is quite a journey (and, really a very early step in this process). Feel free to comment on anything in here, but know that I may not be able to respond individually. I will read and consider every response.

BabylonNative

The intent is to support BabylonNative via a method to process a CSS string and assign Control properties from the CSS declarations within that string. The string can be sourced by the user from anywhere (fetch) or constructed manually.

Heavy use of parse-css.

// inline CSS parser from parse-css/parse-css.js at main · tabatkins/parse-css · GitHub
// for TypeScrpt version (untested), see hyperactive_css@v0.2.0 | Deno

Excerpts from parse-css github README:

This project implements a standards-based CSS Parser. I’m the editor of the CSS Syntax spec CSS Syntax Module Level 3, and need an implementation of it for testing purposes.

Its structure and coding style are instead meant to be very close to the spec, so that it’s easy to verify that the code matches the spec (and vice versa) and to make it easy, when the spec changes, to make the same change in the parser.

Note that the Syntax spec, and thus this parser, is extremely generic. It doesn’t have any specific knowledge of CSS rules, just the core syntax, so it won’t throw out invalid or unknown things.

@namespace ?

Propose the use of @namespacehttps://babylonjs.com/”.

The defined namespaces can be used to restrict the universal, type, and attribute selectors to only select elements within that namespace. The @namespace rule is generally only useful when dealing with documents containing multiple namespaces—such as HTML with inline SVG or MathML, or XML that mixes multiple vocabularies.

This hopefully will minimize conflicts if a babylon stylesheet is mixed with other CSS stylesheets. Here’s a decent overview of another designer considering namespaces for another project: Finding otherwise anonymous ˂style˃ elements using... - Marketing Nation

Note that the blog post author talks about using the following with getStyleSheetsByPrefix(“SlidesDotComCustomStyles”)

@namespace SlidesDotComCustomStyles "";

However, the prefix is not the definitive portion of the namespace. Rather, the URL is definitive, and the prefix is user defined.

In addition, this might enable the use of document.styleSheets to obtain the appropriate stylesheet imported on the HTML page. Only javascript imported stylesheets are not in document.styleSheets: import … with { type: “css” }
( see CSSStyleSheet - Web APIs | MDN)

The styleSheets read-only property of the Document interface returns a StyleSheetList of CSSStyleSheet objects, for stylesheets explicitly linked into or embedded in a document.

This leads to the following pseudocode:

isNamespace(stylesheet,namespace) {
    var namespaceFound = false;
    for (var rule on cssRules) {         
        if (cssRule is charspaceRule or importRule) continue;
        if (cssRule is namespaceRule and rule.namespace is namespace) {namespaceFound = true; break;}
        break; // stop looking if anything other than above at-rules
    }
    return namespaceFound;
}
const babylonSheets = document.styleSheets.filter(s=>isNamespace(s,"https://babylonjs.com/"))

Following the above, use CSSDOM to process the stylesheets as desired.

CSS Selectors

A basic selector takes the form of type.class#id[attr=value] where

  • type is a type of element (HTML: div, a, button,input). For Babylon, propose using the result of .getClassName()
  • class is a user-assigned grouping of elements with an assigned a “class” attribute. There is no current Babylon equivalent. For Babylon, propose adding a new attribute to the base Control object: cssClass, class, group, or similar.
  • id is a user-defined identifier intended but not required to be unique. This mirrors the Babylon Control.name property.

CSS Selectors include the concept of specificity in the final selection of which property assignments apply. Although this may not be significant to implement, it might be an easy addition to property selection.

Attribute Selector

An Attribute Selector is designed to select string attributes or words within. When an element’s attributes are all strings or words, this works well. Attribute selection includes the following types of matches: exists, =equals, *contains, ~contains exact word, |equals or hyphen-equals, ^prefix, $suffix, and i/s for case sensitivity.

However, Babylon Control properties are not generally strings. Does it make sense to extend “attributes” to match non-string Control properties? One way to partially implement this is to extend attribute selector in two ways. 1) allow numeric comparisons (e.g. < and > are obvious, but what about “between” or more general functions?) and 2) by extending to allow dotted attributes to indicate Control property path (e.g. position.x). Does this open up security or other problematic concerns?

CSS Properties

The original intent was to open up the ability to assign Control properties through stylesheets. One thought was to implement an inheritance model to allow the wholesale assignment of Control properties through the assignment of properties to a common parent. This may still be achievable, but through careful study of CSS (the document, tutorials, and specifications available at CSS: Cascading Style Sheets | MDN), there are some concerns.

Although much of CSS might be reduced or ignored as “not applicable,” there are still some areas that need to be carefully crafted. Babylon Control properties that control layout and size are of particular concern. One idea is to set Control properties nominally and non-interferingly. That is, can Babylon Control properties be set such that built-in layout and size properties are not automatically set? If so, then CSS properties could be designed to set all relevant Control properties without danger of those properties being overwritten by the Controls themselves or their parents. However, this also means the CSS properties are only as useful to the extent they are implemented. This could be a big development hurdle and precludes a slow ramp up in implementation. In CSS terms, this is the area of display types and, with outer type, how an element/Control participates in layout. While a Control’s inner type determines the layout of its children. Formatting context includes block, inline, flex, grid, table, multi-column and define how elements are layed out.

Control Properties

Including the ability to assign Control properties through CSS-like syntax might be specified as non-standard CSS properties. Propose defining Control properties with a preceding double hyphen (“–”). This defines the property as a “custom CSS property.” This could be expanded by using @property to define types, but that doesn’t seem necessary as the interpretation of all such properties are within the new classes designed to assign them. Custom properties are case sensitive by default, so there is the option to use “-” to precede a capital later (as is standard), or to use exact case.

CSS properties have a defined inheritance model where each property either inherits from a parent or a defined initial value and can be modified individually with identifiers inherit, initial, revert, etc. This inheritance model is designed in conjunction with the layout model so that many layout-related properties don’t need inheritance because they are defined by the flow model. This makes it difficult to find a middle ground, or any ground where the layout model is not fully implemented. Is there a path of implementation that is useful prior to full implementation of layout?

CSS Origin

Origin is the concept of CSS rules grouped as coming from user-agent (aka browser), author (code writer), and user (end user). This includes the ability to define certain rules as “important,” which changes the default ordering of implemented rules. If implemented at all, this might be adapted to default and code writer, though I don’t see a huge benefit from a full implementation of origin or importance.

CSS Layers

Named layers is a pretty fundamental aspect of CSS. It may be “easy” to implement, but is it necessary? To figure that out, let’s review an excerpt from MDN Webdocs. I’ve indicated my guess as to implementation importance with - not important, + probably useful or easy, or ++ must implement:

The user agent runs through several clearly defined steps to determine the values assigned to every property for every element.

  1. ++Relevance: Find all the declaration blocks with a selector match for each element.

  2. -Importance: Sort rules based on whether they are normal or important. Important styles are those that have the !important flag set.

  3. -Origin: Within each of the two importance buckets, sort rules by author, user, or user-agent origin.

  4. +Cascade layers: Within each of the six origin importance buckets, sort by cascade layer. The layer order for normal declarations is from the first layer created to the last, followed by unlayered normal styles. This order is inverted for important styles, with unlayered important styles having the lowest precedence.

  5. ++Specificity: For competing styles in the origin layer with precedence, sort declarations by specificity.

  6. +Scoping proximity: When two selectors in the origin layer with precedence have the same specificity, the property value within scoped rules with the smallest number of hops up the DOM hierarchy to the scope root wins. See How @scope conflicts are resolved for more details and an example.

  7. ++Order of appearance: When two selectors in the origin layer with precedence have the same specificity and scope proximity, the property value from the last declared selector with the highest specificity wins.

For each step, only the declarations “still in the running” move on to “compete” in the next step. If only one declaration is in the running, it “wins”, and the subsequent steps are moot.

Conclusion: implementing layers as a container for rulesets and as a fundamental selection mechanism allows 1) coding classes to roughly follow existing CSS StyleSheet syntax, and 2) extention to include importance and origin at a later time, if desired. We’ll start by assuming a single origin, probably ignoring importance, and reserving Scoping Proximity for implementation only if needed. This leads to the order of initially implementing the selection of element properties: basic selectors, basic layers, specificity, and order.

CSS Animation

Having recently reviewed Babylon Animation, I’m hopeful that CSS Animation can be parsed and converted to Babylon Animation. There’s a slight complication in the layer order because styles animation is higher than normal and lower than important. But if we’re ignoring importance and origin generally, then the resulting origin-importance ordering is “simple”:

  1. normal styles
  2. animations
  3. transitions

CSS Property Values

Strings and numbers are easy. Identifiers with a 1:1 correspondence to values are almost as easy. Colors are parsable from code I found to handle RGBA, named colors, and HSV. Other color spaces are harder (at the moment) so will be initially unimplemented. Function parsing beyond basic values is initially unlikely, but I’ll revisit at a later date. Gradients (except Cone) look doable, as do animations. URL parsing looks doable, but those needing subsequent fetching or processing will be implemented later. This likely affects images and @import rules.

Other notes

Filters might be implemented as Babylon PostProcessing, but that’s later as well. Paths might be possible. I’ve implemented SVG path processing into primatives before and might be needed here (possibly as an extention to Curve3). The implementation of SVG paths is initially desirable for clipping in the Image Control, but the Image Control uses a canvas element to draw an image and the canvas element allows specifying a clipping path using the SVG path string directly. The use of that path in inner and outer shadows will need investigating. It wasn’t immediately obvious how to fully implement inner shadows especially, or outer shadows that follow a path. I looked at vignette shader, but that seems implemented as a roundish shape currently though the equation in the shader though it might be adapted to other shapes, or at least to rectangular (maybe?). CSS 3d transforms look interesting, but the thought of adapting 3d Transformations from the “2d assumptions” that CSS makes (and the specification of perspective, camera, and origin) back into the 3d Babylon environment seems arduous and unnecessary.

2 Likes