Sky Style Language

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)));
}