Commit d45bf145 authored by Hixie's avatar Hixie

HomogeneousViewport support for Theme.of()

Previously, RenderObjectElements didn't support being marked dirty. This
is fine, except for MixedViewport and HomogeneousViewport, which have
builder functions to which they hand themselves as a BuildContext. If
those builder functions call, e.g., Theme.of(), then when the theme
changes, the Inherited logic tries to tell the RenderObjectElement
object that its dependencies changed and that doesn't go down well.

This patch fixes this by making RenderObjectElement a BuildableElement,
and making MixedViewport and HomogeneousViewport hook into that to
rebuild themselves appropriately.

Also, this was only found at all because ThemeData didn't implement
operator==, so we were aggressively marking the entire tree dirty all
the time. That's fixed here too.

Also, I changed card_collection.dart to have more features to make this
easier to test. This found bugs #1524, #1522, #1528, #1529, #1530, #1531.
parent 3eaefd81
...@@ -9,11 +9,13 @@ import 'package:sky/painting.dart'; ...@@ -9,11 +9,13 @@ import 'package:sky/painting.dart';
import 'package:sky/widgets.dart'; import 'package:sky/widgets.dart';
class CardModel { class CardModel {
CardModel(this.value, this.height, this.color); CardModel(this.value, this.height) {
label = "Item $value";
}
int value; int value;
double height; double height;
Color color; int get color => ((value % 9) + 1) * 100;
String get label => "Item $value"; String label;
Key get key => new ObjectKey(this); Key get key => new ObjectKey(this);
} }
...@@ -36,6 +38,7 @@ class CardCollectionState extends State<CardCollection> { ...@@ -36,6 +38,7 @@ class CardCollectionState extends State<CardCollection> {
final TextStyle backgroundTextStyle = final TextStyle backgroundTextStyle =
Typography.white.title.copyWith(textAlign: TextAlign.center); Typography.white.title.copyWith(textAlign: TextAlign.center);
Map<int, Color> _primaryColor = Colors.deepPurple;
List<CardModel> _cardModels; List<CardModel> _cardModels;
DismissDirection _dismissDirection = DismissDirection.horizontal; DismissDirection _dismissDirection = DismissDirection.horizontal;
bool _snapToCenter = false; bool _snapToCenter = false;
...@@ -50,19 +53,13 @@ class CardCollectionState extends State<CardCollection> { ...@@ -50,19 +53,13 @@ class CardCollectionState extends State<CardCollection> {
48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0, 48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0,
48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0 48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0
]; ];
_cardModels = new List.generate(cardHeights.length, (i) { _cardModels = new List.generate(cardHeights.length, (i) => new CardModel(i, cardHeights[i]));
Color color = Color.lerp(Colors.red[300], Colors.blue[900], i / cardHeights.length);
return new CardModel(i, cardHeights[i], color);
});
} }
void _initFixedSizedCardModels() { void _initFixedSizedCardModels() {
const int cardCount = 27; const int cardCount = 27;
const double cardHeight = 100.0; const double cardHeight = 100.0;
_cardModels = new List.generate(cardCount, (i) { _cardModels = new List.generate(cardCount, (i) => new CardModel(i, cardHeight));
Color color = Color.lerp(Colors.red[300], Colors.blue[900], i / cardCount);
return new CardModel(i, cardHeight, color);
});
} }
void _initCardModels() { void _initCardModels() {
...@@ -126,9 +123,14 @@ class CardCollectionState extends State<CardCollection> { ...@@ -126,9 +123,14 @@ class CardCollectionState extends State<CardCollection> {
buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards), buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards),
buildDrawerCheckbox("Let the sun shine", _sunshine, _toggleSunshine), buildDrawerCheckbox("Let the sun shine", _sunshine, _toggleSunshine),
new DrawerDivider(), new DrawerDivider(),
buildDrawerRadioItem(DismissDirection.horizontal, 'action/code'), buildDrawerRadioItem("Deep Purple", Colors.deepPurple, _primaryColor, _selectColor),
buildDrawerRadioItem(DismissDirection.left, 'navigation/arrow_back'), buildDrawerRadioItem("Green", Colors.green, _primaryColor, _selectColor),
buildDrawerRadioItem(DismissDirection.right, 'navigation/arrow_forward'), buildDrawerRadioItem("Amber", Colors.amber, _primaryColor, _selectColor),
buildDrawerRadioItem("Teal", Colors.teal, _primaryColor, _selectColor),
new DrawerDivider(),
buildDrawerRadioItem("Dismiss horizontally", DismissDirection.horizontal, _dismissDirection, _changeDismissDirection, icon: 'action/code'),
buildDrawerRadioItem("Dismiss left", DismissDirection.left, _dismissDirection, _changeDismissDirection, icon: 'navigation/arrow_back'),
buildDrawerRadioItem("Dismiss right", DismissDirection.right, _dismissDirection, _changeDismissDirection, icon: 'navigation/arrow_forward'),
]) ])
) )
); );
...@@ -158,6 +160,12 @@ class CardCollectionState extends State<CardCollection> { ...@@ -158,6 +160,12 @@ class CardCollectionState extends State<CardCollection> {
}); });
} }
void _selectColor(selection) {
setState(() {
_primaryColor = selection;
});
}
void _changeDismissDirection(DismissDirection newDismissDirection) { void _changeDismissDirection(DismissDirection newDismissDirection) {
setState(() { setState(() {
_dismissDirection = newDismissDirection; _dismissDirection = newDismissDirection;
...@@ -175,16 +183,16 @@ class CardCollectionState extends State<CardCollection> { ...@@ -175,16 +183,16 @@ class CardCollectionState extends State<CardCollection> {
); );
} }
Widget buildDrawerRadioItem(DismissDirection direction, String icon) { Widget buildDrawerRadioItem(String label, itemValue, currentValue, RadioValueChanged onChanged, { String icon }) {
return new DrawerItem( return new DrawerItem(
icon: icon, icon: icon,
onPressed: () { _changeDismissDirection(direction); }, onPressed: () { onChanged(itemValue); },
child: new Row([ child: new Row([
new Flexible(child: new Text(_dismissDirectionText(direction))), new Flexible(child: new Text(label)),
new Radio( new Radio(
value: direction, value: itemValue,
onChanged: _changeDismissDirection, groupValue: currentValue,
groupValue: _dismissDirection onChanged: onChanged
) )
]) ])
); );
...@@ -210,11 +218,22 @@ class CardCollectionState extends State<CardCollection> { ...@@ -210,11 +218,22 @@ class CardCollectionState extends State<CardCollection> {
onResized: () { _invalidator([index]); }, onResized: () { _invalidator([index]); },
onDismissed: () { dismissCard(cardModel); }, onDismissed: () { dismissCard(cardModel); },
child: new Card( child: new Card(
color: cardModel.color, color: Theme.of(context).primarySwatch[cardModel.color],
child: new Container( child: new Container(
height: cardModel.height, height: cardModel.height,
padding: const EdgeDims.all(8.0), padding: const EdgeDims.all(8.0),
child: new Center(child: new Text(cardModel.label, style: cardLabelStyle)) child: new Center(
child: new DefaultTextStyle(
style: cardLabelStyle,
child: new Input(
key: new GlobalObjectKey(cardModel),
initialValue: cardModel.label,
onChanged: (String value) {
cardModel.label = value;
}
)
)
)
) )
) )
); );
...@@ -341,9 +360,14 @@ class CardCollectionState extends State<CardCollection> { ...@@ -341,9 +360,14 @@ class CardCollectionState extends State<CardCollection> {
body = new Stack([body, indicator]); body = new Stack([body, indicator]);
} }
return new Scaffold( return new Theme(
toolBar: buildToolBar(), data: new ThemeData(
body: body primarySwatch: _primaryColor
),
child: new Scaffold(
toolBar: buildToolBar(),
body: body
)
); );
} }
} }
...@@ -351,11 +375,6 @@ class CardCollectionState extends State<CardCollection> { ...@@ -351,11 +375,6 @@ class CardCollectionState extends State<CardCollection> {
void main() { void main() {
runApp(new App( runApp(new App(
title: 'Cards', title: 'Cards',
theme: new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.blue,
accentColor: Colors.redAccent[200]
),
routes: { routes: {
'/': (RouteArguments args) => new CardCollection(navigator: args.navigator), '/': (RouteArguments args) => new CardCollection(navigator: args.navigator),
} }
......
...@@ -79,4 +79,38 @@ class ThemeData { ...@@ -79,4 +79,38 @@ class ThemeData {
Color get accentColor => _accentColor; Color get accentColor => _accentColor;
final ThemeBrightness accentColorBrightness; final ThemeBrightness accentColorBrightness;
bool operator==(Object other) {
if (other.runtimeType != runtimeType)
return false;
ThemeData otherData = other;
return (otherData.brightness == brightness) &&
(otherData.primarySwatch == primarySwatch) &&
(otherData.canvasColor == canvasColor) &&
(otherData.cardColor == cardColor) &&
(otherData.dividerColor == dividerColor) &&
(otherData.hintColor == hintColor) &&
(otherData.highlightColor == highlightColor) &&
(otherData.selectedColor == selectedColor) &&
(otherData.hintOpacity == hintOpacity) &&
(otherData.text == text) &&
(otherData.primaryColorBrightness == primaryColorBrightness) &&
(otherData.accentColorBrightness == accentColorBrightness);
}
int get hashCode {
int value = 373;
value = 37 * value + brightness.hashCode;
value = 37 * value + primarySwatch.hashCode;
value = 37 * value + canvasColor.hashCode;
value = 37 * value + cardColor.hashCode;
value = 37 * value + dividerColor.hashCode;
value = 37 * value + hintColor.hashCode;
value = 37 * value + highlightColor.hashCode;
value = 37 * value + selectedColor.hashCode;
value = 37 * value + hintOpacity.hashCode;
value = 37 * value + text.hashCode;
value = 37 * value + primaryColorBrightness.hashCode;
value = 37 * value + accentColorBrightness.hashCode;
return value;
}
} }
...@@ -37,8 +37,8 @@ class HomogeneousViewport extends RenderObjectWidget { ...@@ -37,8 +37,8 @@ class HomogeneousViewport extends RenderObjectWidget {
RenderBlockViewport createRenderObject() => new RenderBlockViewport(); RenderBlockViewport createRenderObject() => new RenderBlockViewport();
bool isLayoutDifferentThan(HomogeneousViewport oldWidget) { bool isLayoutDifferentThan(HomogeneousViewport oldWidget) {
// changing the builder doesn't imply the layout changed
return itemsWrap != oldWidget.itemsWrap || return itemsWrap != oldWidget.itemsWrap ||
itemsWrap != oldWidget.itemsWrap ||
itemExtent != oldWidget.itemExtent || itemExtent != oldWidget.itemExtent ||
itemCount != oldWidget.itemCount || itemCount != oldWidget.itemCount ||
direction != oldWidget.direction || direction != oldWidget.direction ||
...@@ -89,6 +89,10 @@ class _HomogeneousViewportElement extends RenderObjectElement<HomogeneousViewpor ...@@ -89,6 +89,10 @@ class _HomogeneousViewportElement extends RenderObjectElement<HomogeneousViewpor
_updateChildren(); _updateChildren();
} }
void reinvokeBuilders() {
_updateChildren();
}
void layout(BoxConstraints constraints) { void layout(BoxConstraints constraints) {
// We enter a build scope (meaning that markNeedsBuild() is forbidden) // We enter a build scope (meaning that markNeedsBuild() is forbidden)
// because we are in the middle of layout and if we allowed people to set // because we are in the middle of layout and if we allowed people to set
......
...@@ -164,27 +164,31 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> { ...@@ -164,27 +164,31 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> {
if (changes != _ChangeDescription.none || !isValid) { if (changes != _ChangeDescription.none || !isValid) {
renderObject.markNeedsLayout(); renderObject.markNeedsLayout();
} else { } else {
// we just need to redraw our existing widgets as-is reinvokeBuilders();
if (_childrenByKey.length > 0) { }
assert(_firstVisibleChildIndex >= 0); }
assert(renderObject != null);
final int startIndex = _firstVisibleChildIndex; void reinvokeBuilders() {
int lastIndex = startIndex + _childrenByKey.length - 1; // we just need to redraw our existing widgets as-is
Element nextSibling = null; if (_childrenByKey.length > 0) {
for (int index = lastIndex; index > startIndex; index -= 1) { assert(_firstVisibleChildIndex >= 0);
final Widget newWidget = _buildWidgetAt(index); assert(renderObject != null);
final _ChildKey key = new _ChildKey.fromWidget(newWidget); final int startIndex = _firstVisibleChildIndex;
final Element oldElement = _childrenByKey[key]; int lastIndex = startIndex + _childrenByKey.length - 1;
assert(oldElement != null); Element nextSibling = null;
final Element newElement = updateChild(oldElement, newWidget, nextSibling); for (int index = lastIndex; index >= startIndex; index -= 1) {
assert(newElement != null); final Widget newWidget = _buildWidgetAt(index);
_childrenByKey[key] = newElement; final _ChildKey key = new _ChildKey.fromWidget(newWidget);
// Verify that it hasn't changed size. final Element oldElement = _childrenByKey[key];
// If this assertion fires, it means you didn't call "invalidate" assert(oldElement != null);
// before changing the size of one of your items. final Element newElement = updateChild(oldElement, newWidget, nextSibling);
assert(_debugIsSameSize(newElement, index, _lastLayoutConstraints)); assert(newElement != null);
nextSibling = newElement; _childrenByKey[key] = newElement;
} // Verify that it hasn't changed size.
// If this assertion fires, it means you didn't call "invalidate"
// before changing the size of one of your items.
assert(_debugIsSameSize(newElement, index, _lastLayoutConstraints));
nextSibling = newElement;
} }
} }
} }
......
...@@ -18,7 +18,6 @@ class RouteArguments { ...@@ -18,7 +18,6 @@ class RouteArguments {
typedef Widget RouteBuilder(RouteArguments args); typedef Widget RouteBuilder(RouteArguments args);
typedef RouteBuilder RouteGenerator(String name); typedef RouteBuilder RouteGenerator(String name);
typedef void StateRouteCallback(StateRoute route); typedef void StateRouteCallback(StateRoute route);
typedef void _RouteCallback(Route route);
class Navigator extends StatefulComponent { class Navigator extends StatefulComponent {
Navigator({ Navigator({
......
...@@ -12,7 +12,7 @@ import 'package:sky/src/widgets/theme.dart'; ...@@ -12,7 +12,7 @@ import 'package:sky/src/widgets/theme.dart';
const sky.Color _kLightOffColor = const sky.Color(0x8A000000); const sky.Color _kLightOffColor = const sky.Color(0x8A000000);
const sky.Color _kDarkOffColor = const sky.Color(0xB2FFFFFF); const sky.Color _kDarkOffColor = const sky.Color(0xB2FFFFFF);
typedef RadioValueChanged(Object value); typedef void RadioValueChanged(Object value);
class Radio extends StatelessComponent { class Radio extends StatelessComponent {
Radio({ Radio({
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment