Future of styles and GUI controls?

Actually I meant “does it work with CSS width?” Unless I run into a roadblock, my thought is to set control parameters in a way that they don’t flow down the hierarchy at all through the control logic and replace it with CSS. Or at least some combination in cases where the flowdown in Control properties matches CSS.

OMG. Sorry, I misunderstood. Honestly, I wouldn’t know. It’s not easy to extrapolate everything that can be done and could happen. At least, not for my small brain :brain: :sweat_smile:… I can imagine that at some point, even with strong ‘design thinking’ and ‘planification’, we might encounter some ‘challenges’ on the way. The simple notion of ‘responsive’ which is now core to css, is something that’s been introduced (only partly) to the BJS GUI at a later stage. I guess the only way to really understand what challenges we will face, would be to start make a very base, test it and see from there…best way to work it right and (hopefully) not having to dump and restart the entire logic. Although, this also means longer dev and more efforts for testing. Of course, my opinion only :smile: May be ask some others @Tricotou @jamessimo ?

It’s quite a slog to review all the CSS properties. I’m less than a third through (in the d’s alphabetically), but I’ve noticed a few things that let me know I’m on the right track.

  1. inheritance is strictly defined for each property.
  2. in the general case of properties that are Inherited, the framework I have will mostly work
  3. there are a few cases where it’s not clear if properties that are inherited do so directly from their parent
  4. I don’t know if or if I need to accomodate “revert,” which looks like it needs the concept of “CSS Layers,” which I don’t have yet.
  5. the other type of “inheritance” is when a percentage value is a percentage of a parent computed property. This is not always the same named property as the referencer. I’ve got the ability to calculate properties with individual functions per property, so this should be easy to accommodate.
  6. layout and position properties need a ton of logic to derive.
  7. CSS Selectors are absolutely needed. Inheritance doesn’t cover it all.

This kind of answers my “width” question about inheritance, answered by #6 above.

OMG, indeed it is :sweat_smile: You should have based on css 1 :rofl:

for:
#1:
It is. It is now. It has been extended over time (as far as I remember).

#3:
I believe the logic generally is that they inheritate from the hierarchy. If there’s no change in the property in the direct parent or if it is unset, properties would propagate from the parent (parent of parent). That is of course, if this property actually applies (to a specific container or control).

#4:
Honestly, I would actually disregard this aspect at this moment. It’s too complex and might not even relate since, in essence, the GUI is an in-built with scene interaction. We have other means, logics and methods to ‘revert’ or set ‘initial’.

#5:
I hope you’re right there. I would have thought this thingy would eventually become a bother.

#6:
I can only agree :sweat_smile:

I’m sorry I’m not much of a support at this moment. I’ve got very little time for this (or for BJS in general). All I can really do is send you ‘strength’ and tell you how admirative :smiling_face_with_three_hearts: I am of you wanting to undertake this burden :grin: I hope you won’t get fed-up with it before it actually translates into some improvement. Even a base to be further extended would already be a huge step forward (in my opinion). Meanwhile, have a great day :sunglasses:

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.

Hi,
Hope you are well.
I can see the amazing progress you’re doing and the route you are taking… That all sounds VERY promising. As I said before, my only hope is that you won’t get overwhelmed by the scope of the project :sweat_smile:

Just quickly, for ::

I would humbly (and safely) have the Team choose for this one (if you don’t mind?) cc @PatrickRyan … this way you’ll push the responsibility to the core team :grin: :joy: Sidenote: jokes apart, I’m thinking ‘long time and core feature integration here’ :innocent:

Luckily, the URL that defines a namespace doesn’t need any particular file (or any file at all) at the location. Some people place an RDDL there. In which case, we may want to version the URL and put an RDDL where it points. I would of course write that RDDL, after I learn what that is. And, yes, I would want the core team (not me) to at least confirm the URL is acceptable or specify one if they have a preference.

Progress:

First, terminology:

  • StyleSheet: A Ruleset, in the form of a text file or string.
  • Ruleset: an ordered collection of Rules
  • Rule: a statement or directive that, for example, defines or declares Layers, imported StyleSheets, or Style Rules.
  • Style Rule: a [Selector, Declaration Block] pair.
  • Origin: an ordered collection of Layers
  • Layer: a named and ordered collection of Style Rules.
  • Selector: the classic type.class#id[attr=value] or combinations thereof that determine whether the Declaration Block applies to an element
  • Declaration Block: an ordered collection of Declarations
  • Declaration: a property/value pair in the form of property:value;

To make implementation more streamlined, we can abstract a Layer able to contain unlayered Style Rules. Initially, we’ll exclude nesting blocks of layers or declarations.

My current thought is for Selectors to be optional. However, I have to follow up on Selectors Level 3 statement “An empty selector, containing no sequence of simple selectors and no pseudo-element, is an invalid selector.” and “(In the case of CSS, the entire rule in which the [invalid] selector is used is dropped.)”

The definitions above will naturally convert to classes and/or syntax parsing constructs.

class CSS_Layers

Using parse-css, CSS_Layers.addStyleSheet(string) parses a stylesheet for layers, selectors, and declarations. it could be called “CSS_Origin” because it implements a single origin, but prefer “CSS_Layers” (plural because it captures the multiple layers in a StyleSheet) and expect Layers is more familiar than Origin. The class captures layerOrder, including unlayered rulesets, and structures declarations in a way that optimizes the Cascade: testing each selector on each element and applying each highest-precedence property value to that element.

The current implementation of CSS_Layers is a Map() of a layerName:StyleRules where StyleRules is an Array() of [Selector,DelarationBlock] pairs and DeclarationBlock is an Array() of [Property,Value] pairs. I don’t currently see around the need to traverse for each element each StyleRules array and generate a list of DeclarationBlocks containing potentially-applicable declarations. Once Specificity of each Selector is determined, then the precedence order of the StyleRules within the Layer is defined.

Selectors Level 3 says “Selectors have been optimized for use with HTML and XML, and are designed to be usable in performance-critical code.”

Origin

As is, CSS_Layers closely mirrors the Author Origin: “the style origin which contains all of the styles which are part of the document, whether embedded within the HTML or loaded from an external stylesheet file.” User-Agent and User Origins are Layers, sort of, with slightly (one?) different semantics (e.g. the effect of a property value of “revert” depends on its origin).
If needed, CSS Origin could be implemented as a class that uses CSS_Layers or, perhaps, contained within CSS_Layers. It will likely depend on 1) how much an Origin uses all aspects of CSS_Layers and 2) the integration needed for assigning values. I’m leaning toward option 2.

Refer to the diagram at @layer - CSS | MDN or this direct link to the SVG

It shows only “Author declarations” as having layers, while other Origins are shown monolithically. Within the monolithic non-Author Origins, identical selection and specificity rules apply. Also, property value of “revert,” if/when implemented, would need access to these other Origins to make a final determination of which declaration applies.

This means that containing the declaration selection mechanism is likely more straightforward within a single class. Given that Transition, Important, and Animaton declarations will also need to be in separate ordered “origin-like” constructs and those are parsed from the same StyleSheets as other author declarations, organizing User-Agent and User Origins (if any) within CSS_Layers begins to make a lot of sense.

The diagram doesn’t indicate whether Animation Declarations or Transition Declarations can come from any Origin. Nor whether they can be marked important. Do they exist only within Author Declarations and are only of normal importance? The diagram is also missing the subtlety of “author presentational hint origin” described in CSS Cascade 4.

All of this can be ignored on the initial implementation of CSS_Layers, but it’s nice to know there’s a spot for expanding to include these additional CSS concepts.

Shorthand Properties

“If a shorthand is specified as one of the CSS-wide keywords [css-values-3], it sets all of its sub-properties to that keyword, including any that are reset-only sub-properties.”

I’m hopeful, but not yet confident, that this means I can expand shorthand properties during parsing.

Attribute Selectors

Previously, I speculated whether attribute selectors could be extended to include Control object properties. It might be sufficient to match numeric or string properties but what about properties not easily comparable or represented by a string or multiple words or, if extended, numbers?

Specificity

Here, we review the Specificity of a simple selector, represented by a triplet of integer values.

ID, CLASS, TYPE

  • ID counts ID specifiers (normatively matches a single element in a “compliant document”)
  • CLASS counts .class, attribute, and :pseudo-class specifiers
  • TYPE counts element type and ::pseudo-element specifiers

The specification includes only the Selector itself in the calculation of Specificity, which implies that the specificity does not need to be calculated separately for each element. This implies that only a Rule’s specificity need be used to sort Declaration Blocks within a Layer. Given that Layers have a declared order, and Origins have a fixed order, there doesn’t need to be any sorting of declaration blocks after an element determined to match the Selector if the Selectors are ordered by specificity. However, the specification isn’t explicit on how Combinators or a list of simple selectors can change Specificity based on the specific matching element. In addition, if the pre-calculated specificity is equal among several Selector matches for a specific element, there may be additional element-specific “tie breakers.” I’ll need to study additional documentation to find a workable path.

Other concepts in CSS Cascading and Inheritance Module Level 4

  • author presentational hint origin - a special Origin, usually unmentioned, can be placed in UA, or between User and Author Origins with “revert” treating it as Author Origin. However, the “language-defined” insertion point affects which origin-importances can override them.
  • encapsulation contexts
  • Property Aliasing
  • Value Aliasing (handling legacy values on parsing before assigning the declared value)
  • terminology: and
  • reference: CSS-SYNTAX-3
  • terminology: canonical unit - computed values are in canonical units, which are fixed ratios of other units, as I suspected.
  • terminology: A shorthand property sets all of its longhand sub-properties, exactly as if expanded in place.

Images_media_form_elements
contains info on Normalize.css as well as replaced elements
images and video, but also iframe, embed, fenced-frame
sometimes: option, audio, canvas, object
and objects inserted via .content property (“anonymous replaced elements”)

@RaananW, what do you think?

There is still a lot of work to be done to get to the point of assessing performance. Current work is setting up the structures needed to parse, evaluate, and re-evaluate property values. A top-down design approach is implemented to isolate functionality within classes, such as specificity calculation within CSS_Selector, layer order within CSS_Layers, and value calculation within CSS_Values. This defines the scope of lower-level class implementations and increases the likelihood of the implementation’s suitability, such as implementing value calculations that can be re-evaluated quickly when only a “parent value” changes.

That said, here is the current outline of classes and a rough indication of their implementation.

Implemented classes (in various stages of completion)

  • CSS_Layers: parses @ layer rules and Qualified Rules, creates and can sort an Array of CSS_StyleRule per layer (to output in layer, specificity, and appearance order)

  • CSS_StyleRule: contains selector and Array of CSS_Declaration

  • CSS_Selector: parses token array, calculates selectivity, and can match(element)

  • CSS_Declaration (borrowing structured object from parse-css with name, importance, and substituting CSS_Value)

  • CSS_Value: parses token array for values, units, functions and evaluates for computed value

    General algorithm 1, assigning properties (needs work^):
    
    for each element:
      +from CSS_Layers, get CSS_StyleRules in layer, specificity, appearance order:
          if selector represents element:
              ~add StyleRule to element-specific stylerule array
      for each element property:
          *find first applicable declaration from 1) inline style, then 2) this element's stylerule array
    

^ it’s unclear whether this is more efficient when done for each element or done for an element tree
+ this is where origin, importance, and animation/transition is sorted as well, likely during parsing
~ presumes pre-filtering Declaration Blocks by matching selectors is worth the space taken by per-element stylerule arrays
* it’s unclear how to further optimize the linear search through declaration arrays, unless there’s 1:1 style/property mapping

CSS_Value and recalculations

The current plan is to encode CSS equations within CSS_Value objects to optimize re-calculation speed, especially when values change but equations don’t. This will be done by encoding the equation into stack-based postfix format that references a list of CSS_Value objects as operands and crafted JavaScript functions as operators. The equation is then represented as a linear array of operators and operands that is traversed once per equation evaluation. Further, the linear array representing the equation could be encoded as an ArrayBuffer (specifically, a Uint16Array) if needed for space.

IDEA: retain computed values in CSS_Value objects. Evaluation chains based on registered dependents might be straightforward. If every operand is a CSS_Value object , lists of them (e.g. per element property, or per-calculation operand list) will maintain membership in the list and not need recreating.

A dependency tree can be maintained, or each CSS_Value evaluation can trigger re-evaluation for dependents. This breaks down for non-direct dependents. Still works, but re-evaluates more times than strictly necessary as non-direct dependents change. However, evaluation shortcuts based on re-evaluations resulting in exact same value require additional thinking.