-
-
Notifications
You must be signed in to change notification settings - Fork 194
Description
This came from some discussions with @manuhornung and others around Flutter's model vs our style model. IMO, one thing Flutter did really well in their design is their compositional component model (moving away from the uber-style grab bag of properties).
To clarify, what I mean by a compositional model, is instead of having a Style.[...] with arbitrary properties (like positioning, transform, etc), Flutter has more scoped widgets - like a Transform widget, or Positioned widget. This is nice in terms of discoverability - each of those widgets takes a relatively small and concise set of properties.
In addition, the Style.[..] model is coupled to a single layout system, whereas Flutter uses component composition to express a variety of layout models - this is very convenient for building UI apps.
The Style.[..] model has gotten us really far, but there are a few downsides I've seen so far:
- Discoverability: The use of Polymorphic Variants isn't as well supported with auto-complete (as far as I can tell), whereas specific properties would be more discoverable
- Discoverability: It's challenging to know all the potential properties, and how they interact, when using
Style.[..]. - Performance: We always have to
foldour style-list into a style-object:- I wonder if there could be a cheaper way to handle this? Merging is always challenging:Line 501 in 0eb56f4
let applyStyle = (style, styleRule) => Line 589 in 0eb56f4
let merge = (~source, ~target) => - Performance: Our
Nodeclass ends up having to do a lot of different things - recompute transforms, even if there is are no extra transforms, check the overflow property, etc.
I think the compositional model could help with some of the discoverability, by having a more constrained set of properties per-component, and help with the performance by giving each component a more constrained set of responsibilities (potentially minimizing extra transform calculations, overflow calculations, etc).
If people are on-board with moving to the compositional model vs the uber-style model, I'd propose tackling it incrementally, by removing style properties and replacing them with compositional components:
- The
transformproperty could become the responsibility ofTransform.Rotate/Transform.Scale, etc components ([WIP] API: Transform composite component #488) - The
positionandtop/left/right/bottomcould become the responsibility ofStackandPositionedcomponents (Feature - API: Some initial compositional components #483) - The
backgroundColorproperty could become thecolorproperty of aContainer - The
colorproperty could become thecolorproperty of aText -
paddingproperty could be moved to aPaddingcomponent - Our current flex row / column settings could be moved to
RowandColumncomponents -
borderproperties could be moved to aBordercomponent -
opacitycould be moved to anOpacitycomponent -
overflowcould be moved to aClipRectcomponent -
boxShadowcould be moved to aBoxShadowcomponent -
cursorand mouse event handling could be moved to aMouseInputcomponent - focus behavior and keyboard handling could be moved to a
KeyboardInputcomponent
In addition, this model would be amenable for future work:
- We could model accessibility (Proposal: Accessibility #293) with compositional components
- Gestures could use some sort of
GestureDetectorcomponent (potentially plugging into @jordwalke 's prototype) - We could model alternative layout strategies, if it makes sense. For this, it'd be nice if we had a way to statically type the component tree (ie, what children are allowed)
The ultimate goal would be to remove the style property entirely, and represent everything it modelled via new components, instead.
Some things to think about:
- Would each of these components need a
Nodein our scene-tree (aka, would they be primitive components?)? If so, they probably don't all need to generateLayoutNode's - we might need to take a look at that. Also, if we did model these asNode's - likely a lot of the 'core' behavior in theNodeclass could be factored across these new, smaller primitives. For example - thisOverflow.renderbehavior would only need to be used by the newClipRect's node.