diff --git a/examples/stocks2/lib/stock_app.dart b/examples/stocks2/lib/stock_app.dart index 70e13900c93..27c15effb64 100644 --- a/examples/stocks2/lib/stock_app.dart +++ b/examples/stocks2/lib/stock_app.dart @@ -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) + ); } } diff --git a/examples/stocks2/lib/stock_home.dart b/examples/stocks2/lib/stock_home.dart index a0e8ce37150..0d9bbf93605 100644 --- a/examples/stocks2/lib/stock_home.dart +++ b/examples/stocks2/lib/stock_home.dart @@ -169,8 +169,7 @@ class StockHome extends Component { new IconButton( icon: 'navigation/more_vert_white', onPressed: _handleMenuShow) - ], - backgroundColor: colors.Purple[500] + ] ); } diff --git a/examples/stocks2/lib/stock_settings.dart b/examples/stocks2/lib/stock_settings.dart index 02c0af7f144..dedcd868463 100644 --- a/examples/stocks2/lib/stock_settings.dart +++ b/examples/stocks2/lib/stock_settings.dart @@ -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) ); } diff --git a/examples/widgets/sector.dart b/examples/widgets/sector.dart index d4f13ecfee7..24fb4a5c5e0 100644 --- a/examples/widgets/sector.dart +++ b/examples/widgets/sector.dart @@ -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 + ) ) ) ); diff --git a/examples/widgets/styled_text.dart b/examples/widgets/styled_text.dart index 4ce79bd63a6..6ca309fe4a1 100644 --- a/examples/widgets/styled_text.dart +++ b/examples/widgets/styled_text.dart @@ -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) + ) ) ); } diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index f8b4f72f02e..14eaeb0e600 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -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", diff --git a/sdk/lib/theme/theme_data.dart b/sdk/lib/theme/theme_data.dart new file mode 100644 index 00000000000..f5873978d49 --- /dev/null +++ b/sdk/lib/theme/theme_data.dart @@ -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 color; +} diff --git a/sdk/lib/theme/typography.dart b/sdk/lib/theme/typography.dart index ffb3e5d95cb..1cce6761aab 100644 --- a/sdk/lib/theme/typography.dart +++ b/sdk/lib/theme/typography.dart @@ -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) ); diff --git a/sdk/lib/widgets/checkbox.dart b/sdk/lib/widgets/checkbox.dart index 60fe090b4b0..489740463bc 100644 --- a/sdk/lib/widgets/checkbox.dart +++ b/sdk/lib/widgets/checkbox.dart @@ -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 diff --git a/sdk/lib/widgets/scaffold.dart b/sdk/lib/widgets/scaffold.dart index 8d6c4f24431..b98b3beef49 100644 --- a/sdk/lib/widgets/scaffold.dart +++ b/sdk/lib/widgets/scaffold.dart @@ -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(); } diff --git a/sdk/lib/widgets/theme.dart b/sdk/lib/widgets/theme.dart new file mode 100644 index 00000000000..09f3d0800f4 --- /dev/null +++ b/sdk/lib/widgets/theme.dart @@ -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; + } +} diff --git a/sdk/lib/widgets/tool_bar.dart b/sdk/lib/widgets/tool_bar.dart index 8bd377308e7..8fc2c7c04d5 100644 --- a/sdk/lib/widgets/tool_bar.dart +++ b/sdk/lib/widgets/tool_bar.dart @@ -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] ) ); diff --git a/sdk/lib/widgets/widget.dart b/sdk/lib/widgets/widget.dart index 57f5c16bb27..09dd8b10e8d 100644 --- a/sdk/lib/widgets/widget.dart +++ b/sdk/lib/widgets/widget.dart @@ -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 _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 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(); + } + + void set _traits(Map value) { + super._traits = new Map.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 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);