Add a new Theme widget to control color and text color of apps

R=abarth@chromium.org, abarth, hixie

Review URL: https://codereview.chromium.org/1194743003.
This commit is contained in:
Collin Jackson 2015-06-22 12:10:30 -07:00
parent 8a5bd9b676
commit 884b4e36dc
13 changed files with 237 additions and 92 deletions

View File

@ -3,8 +3,12 @@
// found in the LICENSE file.
import 'package:sky/rendering/sky_binding.dart';
import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/theme/theme_data.dart';
import 'package:sky/theme/typography.dart' as typography;
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/navigator.dart';
import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/widget.dart';
import 'stock_data.dart';
@ -46,7 +50,10 @@ class StocksApp extends App {
}
Widget build() {
return new Navigator(_navigationState);
return new Theme(
data: new ThemeData(color: colors.Purple, text: typography.white),
child: new Navigator(_navigationState)
);
}
}

View File

@ -169,8 +169,7 @@ class StockHome extends Component {
new IconButton(
icon: 'navigation/more_vert_white',
onPressed: _handleMenuShow)
],
backgroundColor: colors.Purple[500]
]
);
}

View File

@ -3,13 +3,13 @@
// found in the LICENSE file.
import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/theme/typography.dart' as typography;
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/checkbox.dart';
import 'package:sky/widgets/icon_button.dart';
import 'package:sky/widgets/menu_item.dart';
import 'package:sky/widgets/navigator.dart';
import 'package:sky/widgets/scaffold.dart';
import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/tool_bar.dart';
class StockSettings extends Component {
@ -30,8 +30,7 @@ class StockSettings extends Component {
left: new IconButton(
icon: 'navigation/arrow_back_white',
onPressed: _navigator.pop),
center: new Text('Settings', style: typography.white.title),
backgroundColor: colors.Purple[500]
center: new Text('Settings', style: Theme.of(this).text.title)
);
}

View File

@ -7,13 +7,15 @@ import 'dart:math' as math;
import 'package:sky/rendering/box.dart';
import 'package:sky/rendering/flex.dart';
import 'package:sky/rendering/sky_binding.dart';
import 'package:sky/theme/colors.dart';
import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/theme/edges.dart';
import 'package:sky/theme/typography.dart';
import 'package:sky/theme/theme_data.dart';
import 'package:sky/theme/typography.dart' as typography;
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/material.dart';
import 'package:sky/widgets/raised_button.dart';
import 'package:sky/widgets/scaffold.dart';
import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/tool_bar.dart';
import 'package:sky/widgets/widget.dart';
@ -74,61 +76,63 @@ class SectorApp extends App {
}
Widget build() {
return new Scaffold(
toolbar: new ToolBar(
center: new Text('Sector Layout in a Widget Tree', style: white.title),
backgroundColor: Blue[500]),
body: new Material(
edge: MaterialEdge.canvas,
child: new Flex([
new Container(
padding: new EdgeDims.symmetric(horizontal: 8.0, vertical: 25.0),
child: new Flex([
new RaisedButton(
enabled: enabledAdd,
child: new ShrinkWrapWidth(
child: new Flex([
new Container(
padding: new EdgeDims.all(4.0),
margin: new EdgeDims.only(right: 10.0),
child: new WidgetToRenderBoxAdapter(sectorAddIcon)
),
new Text('ADD SECTOR'),
])
return new Theme(
data: new ThemeData(color: colors.Blue, text: typography.white),
child: new Scaffold(
toolbar: new ToolBar(
center: new Text('Sector Layout in a Widget Tree', style: typography.white.title)),
body: new Material(
edge: MaterialEdge.canvas,
child: new Flex([
new Container(
padding: new EdgeDims.symmetric(horizontal: 8.0, vertical: 25.0),
child: new Flex([
new RaisedButton(
enabled: enabledAdd,
child: new ShrinkWrapWidth(
child: new Flex([
new Container(
padding: new EdgeDims.all(4.0),
margin: new EdgeDims.only(right: 10.0),
child: new WidgetToRenderBoxAdapter(sectorAddIcon)
),
new Text('ADD SECTOR'),
])
),
onPressed: addSector
),
onPressed: addSector
new RaisedButton(
enabled: enabledRemove,
child: new ShrinkWrapWidth(
child: new Flex([
new Container(
padding: new EdgeDims.all(4.0),
margin: new EdgeDims.only(right: 10.0),
child: new WidgetToRenderBoxAdapter(sectorRemoveIcon)
),
new Text('REMOVE SECTOR'),
])
),
onPressed: removeSector
)
],
justifyContent: FlexJustifyContent.spaceAround
)
),
new Flexible(
child: new Container(
margin: new EdgeDims.all(8.0),
decoration: new BoxDecoration(
border: new Border.all(new BorderSide(color: new Color(0xFF000000)))
),
new RaisedButton(
enabled: enabledRemove,
child: new ShrinkWrapWidth(
child: new Flex([
new Container(
padding: new EdgeDims.all(4.0),
margin: new EdgeDims.only(right: 10.0),
child: new WidgetToRenderBoxAdapter(sectorRemoveIcon)
),
new Text('REMOVE SECTOR'),
])
),
onPressed: removeSector
)
],
justifyContent: FlexJustifyContent.spaceAround
)
),
new Flexible(
child: new Container(
margin: new EdgeDims.all(8.0),
decoration: new BoxDecoration(
border: new Border.all(new BorderSide(color: new Color(0xFF000000)))
),
padding: new EdgeDims.all(8.0),
child: new WidgetToRenderBoxAdapter(sectors)
)
),
],
direction: FlexDirection.vertical,
justifyContent: FlexJustifyContent.spaceBetween
padding: new EdgeDims.all(8.0),
child: new WidgetToRenderBoxAdapter(sectors)
)
),
],
direction: FlexDirection.vertical,
justifyContent: FlexJustifyContent.spaceBetween
)
)
)
);

View File

@ -7,10 +7,12 @@ import 'package:sky/rendering/box.dart';
import 'package:sky/rendering/flex.dart';
import 'package:sky/rendering/sky_binding.dart';
import 'package:sky/theme/colors.dart';
import 'package:sky/theme/theme_data.dart';
import 'package:sky/theme/typography.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/material.dart';
import 'package:sky/widgets/scaffold.dart';
import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/tool_bar.dart';
import 'package:sky/widgets/widget.dart';
@ -105,11 +107,13 @@ HAL: This mission is too important for me to allow you to jeopardize it.''';
onPointerDown: toggleToTextFunction
);
return new Scaffold(
body: new Material(child: interactiveBody),
toolbar: new ToolBar(
center: new Text('Hal and Dave', style: white.title),
backgroundColor: Blue[500]
return new Theme(
data: new ThemeData(color: Blue, text: white),
child: new Scaffold(
body: new Material(child: interactiveBody),
toolbar: new ToolBar(
center: new Text('Hal and Dave', style: white.title)
)
)
);
}

View File

@ -90,6 +90,7 @@ dart_pkg("sdk") {
"lib/theme/colors.dart",
"lib/theme/edges.dart",
"lib/theme/shadows.dart",
"lib/theme/theme_data.dart",
"lib/theme/typography.dart",
"lib/theme/view_configuration.dart",
"lib/widgets/animated_component.dart",
@ -118,6 +119,7 @@ dart_pkg("sdk") {
"lib/widgets/scaffold.dart",
"lib/widgets/scrollable.dart",
"lib/widgets/switch.dart",
"lib/widgets/theme.dart",
"lib/widgets/toggleable.dart",
"lib/widgets/tool_bar.dart",
"lib/widgets/widget.dart",

View File

@ -0,0 +1,13 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:sky';
import 'typography.dart';
class ThemeData {
const ThemeData({ this.text, this.color });
final TextTheme text;
final Map<int, Color> color;
}

View File

@ -10,8 +10,8 @@ import '../painting/text_style.dart';
// TODO(eseidel): Font weights are supposed to be language relative!
// These values are for English-like text.
class _TextTheme {
_TextTheme(Color color54, Color color87)
class TextTheme {
TextTheme._(Color color54, Color color87)
: display4 = new TextStyle(fontSize: 112.0, fontWeight: FontWeight.w100, color: color54),
display3 = new TextStyle(fontSize: 56.0, fontWeight: FontWeight.w400, color: color54),
display2 = new TextStyle(fontSize: 45.0, fontWeight: FontWeight.w400, color: color54),
@ -38,13 +38,13 @@ class _TextTheme {
}
final _TextTheme black = new _TextTheme(
final TextTheme black = new TextTheme._(
const Color(0xFF757575),
const Color(0xFF212121)
);
final _TextTheme white = new _TextTheme(
final TextTheme white = new TextTheme._(
const Color(0xFF8A8A8A),
const Color(0xFFDEDEDE)
);

View File

@ -4,7 +4,7 @@
import 'dart:sky' as sky;
import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/widgets/theme.dart';
import 'basic.dart';
import 'toggleable.dart';
@ -12,8 +12,6 @@ export 'toggleable.dart' show ValueChanged;
const double _kMidpoint = 0.5;
const sky.Color _kUncheckedColor = const sky.Color(0x8A000000);
// TODO(jackson): This should change colors with the theme
sky.Color _kCheckedColor = colors.Purple[500];
const double _kEdgeSize = 20.0;
const double _kEdgeRadius = 1.0;
@ -59,10 +57,11 @@ class Checkbox extends Toggleable {
// Solid filled rrect
paint.setStyle(sky.PaintingStyle.strokeAndFill);
Color themeColor = Theme.of(this).color[500];
paint.color = new Color.fromARGB((t * 255).floor(),
_kCheckedColor.red,
_kCheckedColor.green,
_kCheckedColor.blue);
themeColor.red,
themeColor.green,
themeColor.blue);
canvas.drawRRect(rrect, paint);
// White inner check

View File

@ -181,6 +181,19 @@ class Scaffold extends RenderObjectWrapper {
RenderScaffold get root => super.root;
RenderScaffold createNode() => new RenderScaffold();
void walkChildren(WidgetTreeWalker walker) {
if (_toolbar != null)
walker(_toolbar);
if (_body != null)
walker(_body);
if (_statusBar != null)
walker(_statusBar);
if (_drawer != null)
walker(_drawer);
if (_floatingActionButton != null)
walker(_floatingActionButton);
}
void insertChildRoot(RenderObjectWrapper child, ScaffoldSlots slot) {
root[slot] = child != null ? child.root : null;
}
@ -194,16 +207,7 @@ class Scaffold extends RenderObjectWrapper {
}
void remove() {
if (_toolbar != null)
removeChild(_toolbar);
if (_body != null)
removeChild(_body);
if (_statusBar != null)
removeChild(_statusBar);
if (_drawer != null)
removeChild(_drawer);
if (_floatingActionButton != null)
removeChild(_floatingActionButton);
walkChildren((Widget child) => removeChild(child));
super.remove();
}

View File

@ -0,0 +1,28 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:sky/theme/theme_data.dart';
import 'basic.dart';
import 'widget.dart';
class Theme extends Inherited {
Theme({
String key,
this.data,
Widget child
}) : super(key: key, child: child) {
assert(child != null);
assert(data != null);
}
final ThemeData data;
static ThemeData of(Component component) {
Theme theme = component.inheritedOfType(Theme);
// If you hit this assert, you need to wrap your Component in a Theme
assert(theme != null);
return theme.data;
}
}

View File

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:sky/widgets/theme.dart';
import '../rendering/flex.dart';
import '../theme/shadows.dart';
import '../theme/view_configuration.dart';
@ -48,7 +50,7 @@ class ToolBar extends Component {
),
padding: new EdgeDims.symmetric(horizontal: 8.0),
decoration: new BoxDecoration(
backgroundColor: backgroundColor,
backgroundColor: backgroundColor == null ? Theme.of(this).color[500] : backgroundColor,
boxShadow: shadows[2]
)
);

View File

@ -18,14 +18,24 @@ export '../rendering/object.dart' show Point, Size, Rect, Color, Paint, Path;
final bool _shouldLogRenderDuration = false;
typedef void WidgetTreeWalker(Widget);
// All Effen nodes derive from Widget. All nodes have a _parent, a _key and
// can be sync'd.
abstract class Widget {
Widget({ String key }) : _key = key {
assert(this is AbstractWidgetRoot || this is App || _inRenderDirtyComponents); // you should not build the UI tree ahead of time, build it only during build()
assert(_isConstructedDuringBuild());
}
// TODO(jackson): Remove this workaround for limitation of Dart mixins
Widget._withKey(String key) : _key = key {
assert(_isConstructedDuringBuild());
}
// you should not build the UI tree ahead of time, build it only during build()
bool _isConstructedDuringBuild() => this is AbstractWidgetRoot || this is App || _inRenderDirtyComponents;
String _key;
String get key => _key;
@ -55,6 +65,9 @@ abstract class Widget {
}
}
// Override this if you have children and call walker on each child
void walkChildren(WidgetTreeWalker walker) { }
static void _notifyMountStatusChanged() {
try {
sky.tracing.begin("Widget._notifyMountStatusChanged");
@ -171,8 +184,16 @@ abstract class TagNode extends Widget {
TagNode(Widget child, { String key })
: this.child = child, super(key: key);
// TODO(jackson): Remove this workaround for limitation of Dart mixins
TagNode._withKey(Widget child, String key)
: this.child = child, super._withKey(key);
Widget child;
void walkChildren(WidgetTreeWalker walker) {
walker(child);
}
void _sync(Widget old, dynamic slot) {
Widget oldChild = old == null ? null : (old as TagNode).child;
child = syncChild(child, oldChild, slot);
@ -200,6 +221,56 @@ class ParentDataNode extends TagNode {
final ParentData parentData;
}
abstract class _Heir implements Widget {
Map<Type, Inherited> _traits;
Inherited inheritedOfType(Type type) => _traits[type];
static _Heir _getHeirAncestor(Widget widget) {
Widget ancestor = widget;
while (ancestor != null && !(ancestor is _Heir)) {
ancestor = ancestor.parent;
}
return ancestor as _Heir;
}
void _updateTraitsFromParent(Widget parent) {
_Heir ancestor = _getHeirAncestor(parent);
if (ancestor == null || ancestor._traits == null) return;
_updateTraits(ancestor._traits);
}
void _updateTraitsRecursively(Widget widget) {
if (widget is _Heir)
widget._updateTraits(_traits);
else
widget.walkChildren(_updateTraitsRecursively);
}
void _updateTraits(Map<Type, Inherited> newTraits) {
if (newTraits != _traits) {
_traits = newTraits;
walkChildren(_updateTraitsRecursively);
}
}
}
abstract class Inherited extends TagNode with _Heir {
Inherited({ String key, Widget child }) : super._withKey(child, key) {
_traits = new Map<Type, Inherited>();
}
void set _traits(Map<Type, Inherited> value) {
super._traits = new Map<Type, Inherited>.from(value)
..[runtimeType] = this;
}
// TODO(jackson): When Dart supports super in mixins we can move to _Heir
void setParent(Widget parent) {
_updateTraitsFromParent(parent);
super.setParent(parent);
}
}
typedef void GestureEventListener(sky.GestureEvent e);
typedef void PointerEventListener(sky.PointerEvent e);
typedef void EventListener(sky.Event e);
@ -291,13 +362,12 @@ class Listener extends TagNode {
}
abstract class Component extends Widget {
abstract class Component extends Widget with _Heir {
Component({ String key, bool stateful })
: _stateful = stateful != null ? stateful : false,
_order = _currentOrder + 1,
super(key: key);
super._withKey(key);
static Component _currentlyBuilding;
bool get _isBuilding => _currentlyBuilding == this;
@ -314,6 +384,12 @@ abstract class Component extends Widget {
super.didMount();
}
// TODO(jackson): When Dart supports super in mixins we can move to _Heir
void setParent(Widget parent) {
_updateTraitsFromParent(parent);
super.setParent(parent);
}
void remove() {
assert(_built != null);
assert(root != null);
@ -573,6 +649,10 @@ abstract class OneChildRenderObjectWrapper extends RenderObjectWrapper {
Widget _child;
Widget get child => _child;
void walkChildren(WidgetTreeWalker walker) {
walker(child);
}
void syncRenderObject(RenderObjectWrapper old) {
super.syncRenderObject(old);
Widget oldChild = old == null ? null : (old as OneChildRenderObjectWrapper).child;
@ -600,7 +680,6 @@ abstract class OneChildRenderObjectWrapper extends RenderObjectWrapper {
removeChild(child);
super.remove();
}
}
abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper {
@ -616,6 +695,11 @@ abstract class MultiChildRenderObjectWrapper extends RenderObjectWrapper {
final List<Widget> children;
void walkChildren(WidgetTreeWalker walker) {
for(Widget child in children)
walker(child);
}
void insertChildRoot(RenderObjectWrapper child, dynamic slot) {
final root = this.root; // TODO(ianh): Remove this once the analyzer is cleverer
assert(slot == null || slot is RenderObject);