mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Specs: part 1 of the new approach to style
Review URL: https://codereview.chromium.org/959473002
This commit is contained in:
parent
3ed20172df
commit
1bdbdebfeb
351
specs/style2.md
Normal file
351
specs/style2.md
Normal file
@ -0,0 +1,351 @@
|
||||
Sky Style Language
|
||||
==================
|
||||
|
||||
Note: This is a work in progress that will eventually replace
|
||||
(style.md)[style.md].
|
||||
|
||||
The Sky style API looks like the following:
|
||||
|
||||
```dart
|
||||
|
||||
// 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:
|
||||
|
||||
```dart
|
||||
import 'dart:mirrors';
|
||||
import 'dart:math';
|
||||
|
||||
class WeakMap<Key, Value> {
|
||||
Expando<Value> _map = new Expando<Value>();
|
||||
operator[](Key key) => _map[key];
|
||||
operator[]=(Key key, Value value) => _map[key] = value;
|
||||
bool containsKey(Key key) => _map[key] != null;
|
||||
}
|
||||
|
||||
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
|
||||
WeakMap<Symbol, Property> _properties = new WeakMap<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 value) {
|
||||
if (value == initial)
|
||||
return _setInitial();
|
||||
if (value == inherit)
|
||||
return _setInitial();
|
||||
if (value == null)
|
||||
return _unset();
|
||||
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:
|
||||
|
||||
```dart
|
||||
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)));
|
||||
}
|
||||
```
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user