This is a trimmed down version of the API in (style.md)[style.md] that is intended to be a stepping stone to the long-term world where there are no hard-coded properties in the engine.
The Sky style API looks like the following:
// all properties can be set as strings: element.style['color'] = 'blue'; // some properties have dedicated APIs // color element.style.color.red += 1; // 0..255 element.style.color.blue += 10; // 0..255 element.style.color.green = 255; // 0..255 element.style.color.alpha = 128; // 0..255 // transform element.style.transform..reset() ..translate(100, 100) ..rotate(PI/8) ..translate(-100, -100); element.style.transform.translate(10, 0); // height, width element.style.height.auto = true; if (element.style.height.auto) element.style.height.pixels = 10; element.style.height.pixels += 1; element.style.height.em = 1; // each property with a dedicated API defines a shorthand setter // style.transform takes a matrix: element.style.transform = new Matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); // style.color takes a 32bit int: element.style.color = 0xFF009900; // style.height and style.width takes pixels or the constant 'auto': element.style.height = auto; element.style.width = 100; // all properties with a dedicated API can also be set to null, inherit, or initial: element.style.transform = null; // unset the property element.style.color = initial; // set it to its initial value element.style.color = inherit; // make it get its parent's value // you can create a blank StyleDeclaration object: var style = new StyleDeclaration(); // you can replace an element's StyleDeclaration object wholesale: element.style = style; // you can clone a StyleDeclaration object: var style2 = new StyleDeclaration.clone(style);
The dart:sky library contains the following to define this API:
import 'dart:mirrors'; import 'dart:math'; typedef void StringSetter(Symbol propertySymbol, StyleDeclaration declaration, String value); typedef String StringGetter(Symbol propertySymbol, StyleDeclaration declaration); typedef Property ObjectConstructor(Symbol propertySymbol, StyleDeclaration declaration); class PropertyTable { const PropertyTable({this.symbol, this.inherited, this.stringGetter, this.stringSetter, this.objectConstructor}); final Symbol symbol; final bool inherited; final StringSetter stringSetter; final StringGetter stringGetter; final ObjectConstructor objectConstructor; } Map<Symbol, PropertyTable> _registeredProperties = new Map<Symbol, PropertyTable>(); void registerProperty(PropertyTable data) { assert(data.symbol is Symbol); assert(data.inherited is bool); assert(data.stringSetter is StringSetter); assert(data.stringGetter is StringGetter); assert(data.objectConstructor == null || data.objectConstructor is ObjectConstructor); assert(!_registeredProperties.containsKey(data.symbol)); _registeredProperties[data.symbol] = data; } @proxy class StyleDeclaration { StyleDeclaration() { this._init(); } StyleDeclaration.clone(StyleDeclaration template) { this.init(template); } external void _init([StyleDeclaration template]); // O(1) // This class has C++-backed internal state representing the // properties known to the system. It's assumed that Property // subclasses are also C++-backed and can directly manipulate this // internal state. // If the argument 'template' is provided, then this should be a clone // of the styles of the template StyleDeclaration operator [](String propertyName) { var propertySymbol = new Symbol(propertyName); if (_registeredProperties.containsKey(propertySymbol)) return _registeredProperties[propertySymbol].stringGetter(propertySymbol, this); throw new ArgumentError(propertyName); } operator []=(String propertyName, String newValue) { var propertySymbol = new Symbol(propertyName); if (_registeredProperties.containsKey(propertySymbol)) return _registeredProperties[propertySymbol].stringSetter(propertySymbol, this, newValue); throw new ArgumentError(propertyName); } // some properties expose dedicated APIs so you don't have to use string manipulation MapOfWeakReferences<Symbol, Property> _properties = new MapOfWeakReferences<Symbol, Property>(); noSuchMethod(Invocation invocation) { Symbol propertySymbol; if (invocation.isSetter) { // when it's a setter, the name will be "foo=" rather than "foo" String propertyName = MirrorSystem.getName(invocation.memberName); assert(propertyName[propertyName.length-1] == '='); propertySymbol = new Symbol(propertyName.substring(0, propertyName.length-1)); } else { propertySymbol = invocation.memberName; } Property property; if (!_properties.containsKey(propertySymbol)) { if (_registeredProperties.containsKey(propertySymbol)) { var constructor = _registeredProperties[propertySymbol].objectConstructor; if (constructor == null) return super.noSuchMethod(invocation); property = constructor(propertySymbol, this); } else { return super.noSuchMethod(invocation); } } else { property = _properties[propertySymbol]; } if (invocation.isMethod) { if (property is Function) return Function.apply(property as Function, invocation.positionalArguments, invocation.namedArguments); return super.noSuchMethod(invocation); } if (invocation.isSetter) return Function.apply(property.setter, invocation.positionalArguments, invocation.namedArguments); return property; } } const initial = const Object(); const inherit = const Object(); abstract class Property { Property(this.propertySymbol, this.declaration); final StyleDeclaration declaration; final Symbol propertySymbol; bool get inherited => _registeredProperties[propertySymbol].inherited; bool get initial => _isInitial(); void set initial (value) { if (value == true) return _setInitial(); throw new ArgumentError(value); } bool get inherit => _isInherit(); void set inherit (value) { if (value == true) return _setInherit(); throw new ArgumentError(value); } void setter(dynamic newValue) { switch (newValue) { case initial: _setInitial(); case inherit: _setInherit(); case null: _unset(); default: throw new ArgumentError(value); } } external bool _isInitial(); external void _setInitial(); external bool _isInherit(); external void _setInherit(); external void _unset(); }
Sky defines the following properties, currently as part of the core, but eventually this will be moved to the framework:
class LengthProperty extends Property { LengthProperty(Symbol propertySymbol, StyleDeclaration declaration) : super(propertySymbol, declaration); double get pixels => _getPixels(); void set pixels (value) => _setPixels(value); double get inches => _getPixels() / 96.0; void set inches (value) => _setPixels(value * 96.0); double get em => _getEm(); void set em (value) => _setEm(value); void setter(dynamic value) { if (value is num) return _setPixels(value.toDouble()); return super.setter(value); } external double _getPixels(); // throws StateError if the value isn't in pixels external void _setPixels(double value); external double _getEm(); // throws StateError if the value isn't in pixels external void _setEm(double value); } const auto = const Object(); class AutoLengthProperty extends LengthProperty { AutoLengthProperty(Symbol propertySymbol, StyleDeclaration declaration) : super(propertySymbol, declaration); bool get auto => _isAuto(); void set auto (value) { if (value == true) _setAuto(); throw new ArgumentError(value); } void setter(dynamic value) { if (value == auto) return _setAuto(); return super.setter(value); } external bool _isAuto(); external void _setAuto(); } class ColorProperty extends Property { ColorProperty(Symbol propertySymbol, StyleDeclaration declaration) : super(propertySymbol, declaration); int get alpha => _getRGBA() & 0xFF000000 >> 24; void set alpha (int value) => _setRGBA(_getRGBA() & 0x00FFFFFF + value << 24); int get red => _getRGBA() & 0x00FF0000 >> 16; void set red (int value) => _setRGBA(_getRGBA() & 0xFF00FFFF + value << 16); int get green => _getRGBA() & 0x0000FF00 >> 8; void set green (int value) => _setRGBA(_getRGBA() & 0xFFFF00FF + value << 8); int get blue => _getRGBA() & 0x000000FF >> 0; void set blue (int value) => _setRGBA(_getRGBA() & 0xFFFFFF00 + value << 0); int get rgba => _getRGBA(); void set rgba (int value) => _setRGBA(value); void setter(dynamic value) { if (value is int) return _setRGBA(value); return super.setter(value); } external int _getRGBA(); // throws StateError if the value isn't a color external void _setRGBA(int value); } class Matrix { const Matrix(this.a, this.b, this.c, this.d, this.e, this.f); // +- -+ // | a c e | // | b d f | // | 0 0 1 | // +- -+ final double a; final double b; final double c; final double d; final double e; final double f; } class TransformProperty extends Property { TransformProperty(Symbol propertySymbol, StyleDeclaration declaration) : super(propertySymbol, declaration); void reset() => setTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); void translate(double dx, double dy) => transform(1.0, 0.0, 0.0, 1.0, dx, dy); void scale(double dw, double dh) => transform(dw, 0.0, 0.0, dh, 0.0, 0.0); void rotate(double theta) => transform(cos(theta), -sin(theta), sin(theta), cos(theta), 0.0, 0.0); // there's no "transform" getter since it would always return a new Matrix // such that foo.transform == foo.transform would never be true // and foo.transform = bar; bar == foo.transform would also never be true // which is bad API external Matrix getTransform(); // throws StateError if the value isn't a matrix // returns a new matrix each time external void setTransform(a, b, c, d, e, f); external void transform(a, b, c, d, e, f); // throws StateError if the value isn't a matrix } external void autoLengthPropertyStringSetter(Symbol propertySymbol, StyleDeclaration declaration, String value); external String autoLengthPropertyStringGetter(Symbol propertySymbol, StyleDeclaration declaration); external void colorPropertyStringSetter(Symbol propertySymbol, StyleDeclaration declaration, String value); external String colorPropertyStringGetter(Symbol propertySymbol, StyleDeclaration declaration); external void transformPropertyStringSetter(Symbol propertySymbol, StyleDeclaration declaration, String value); external String transformPropertyStringGetter(Symbol propertySymbol, StyleDeclaration declaration); void _init() { registerProperty(new PropertyTable( symbol: #height, inherited: false, stringSetter: autoLengthPropertyStringSetter, stringGetter: autoLengthPropertyStringGetter, objectConstructor: (Symbol propertySymbol, StyleDeclaration declaration) => new AutoLengthProperty(propertySymbol, declaration))); registerProperty(new PropertyTable( symbol: #width, inherited: false, stringSetter: autoLengthPropertyStringSetter, stringGetter: autoLengthPropertyStringGetter, objectConstructor: (Symbol propertySymbol, StyleDeclaration declaration) => new AutoLengthProperty(propertySymbol, declaration))); registerProperty(new PropertyTable( symbol: #color, inherited: false, stringSetter: colorPropertyStringSetter, stringGetter: colorPropertyStringGetter, objectConstructor: (Symbol propertySymbol, StyleDeclaration declaration) => new ColorProperty(propertySymbol, declaration))); registerProperty(new PropertyTable( symbol: #transform, inherited: false, stringSetter: transformPropertyStringSetter, stringGetter: transformPropertyStringGetter, objectConstructor: (Symbol propertySymbol, StyleDeclaration declaration) => new TransformProperty(propertySymbol, declaration))); }