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;
}
} }
...@@ -751,8 +751,7 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -751,8 +751,7 @@ abstract class Element<T extends Widget> implements BuildContext {
assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; }); assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; });
} }
/// Called when an Element is removed from the tree. /// Called when an Element is removed from the tree permanently.
/// Currently, an Element removed from the tree never returns.
void unmount() { void unmount() {
assert(_debugLifecycleState == _ElementLifecycle.inactive); assert(_debugLifecycleState == _ElementLifecycle.inactive);
assert(widget != null); assert(widget != null);
...@@ -824,25 +823,21 @@ class ErrorWidget extends LeafRenderObjectWidget { ...@@ -824,25 +823,21 @@ class ErrorWidget extends LeafRenderObjectWidget {
RenderBox createRenderObject() => new RenderErrorBox(); RenderBox createRenderObject() => new RenderErrorBox();
} }
typedef Widget WidgetBuilder(BuildContext context);
typedef void BuildScheduler(BuildableElement element); typedef void BuildScheduler(BuildableElement element);
/// Base class for the instantiation of StatelessComponent and StatefulComponent /// Base class for instantiations of widgets that have builders and can be
/// widgets. /// marked dirty.
abstract class BuildableElement<T extends Widget> extends Element<T> { abstract class BuildableElement<T extends Widget> extends Element<T> {
BuildableElement(T widget) : super(widget); BuildableElement(T widget) : super(widget);
WidgetBuilder _builder;
Element _child;
/// Returns true if the element has been marked as needing rebuilding. /// Returns true if the element has been marked as needing rebuilding.
bool get dirty => _dirty; bool get dirty => _dirty;
bool _dirty = true; bool _dirty = true;
// We to let component authors call setState from initState, didUpdateConfig, // We let component authors call setState from initState, didUpdateConfig, and
// and build even when state is locked because its convenient and a no-op // build even when state is locked because its convenient and a no-op anyway.
// anyway. This flag ensures that this convenience is only allowed on the // This flag ensures that this convenience is only allowed on the element
// element currently undergoing initState, didUpdateConfig, or build. // currently undergoing initState, didUpdateConfig, or build.
bool _debugAllowIgnoredCallsToMarkNeedsBuild = false; bool _debugAllowIgnoredCallsToMarkNeedsBuild = false;
bool _debugSetAllowIgnoredCallsToMarkNeedsBuild(bool value) { bool _debugSetAllowIgnoredCallsToMarkNeedsBuild(bool value) {
assert(_debugAllowIgnoredCallsToMarkNeedsBuild == !value); assert(_debugAllowIgnoredCallsToMarkNeedsBuild == !value);
...@@ -850,73 +845,12 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -850,73 +845,12 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
return true; return true;
} }
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_active);
rebuild();
assert(_child != null);
}
static BuildableElement _debugCurrentBuildTarget;
/// Reinvokes the build() method of the StatelessComponent object (for
/// stateless components) or the State object (for stateful components) and
/// then updates the widget tree.
///
/// Called automatically during mount() to generate the first build, by the
/// binding when scheduleBuild() has been called to mark this element dirty,
/// and by update() when the Widget has changed.
void rebuild() {
assert(_debugLifecycleState != _ElementLifecycle.initial);
if (!_active || !_dirty) {
_dirty = false;
return;
}
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(_debugStateLocked);
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
BuildableElement debugPreviousBuildTarget;
assert(() {
debugPreviousBuildTarget = _debugCurrentBuildTarget;
_debugCurrentBuildTarget = this;
return true;
});
Widget built;
try {
built = _builder(this);
assert(built != null);
} catch (e, stack) {
_debugReportException('building ${_widget}', e, stack);
built = new ErrorWidget();
} finally {
// We delay marking the element as clean until after calling _builder so
// that attempts to markNeedsBuild() during build() will be ignored.
_dirty = false;
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
}
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
_debugReportException('building ${_widget}', e, stack);
built = new ErrorWidget();
_child = updateChild(null, built, slot);
}
assert(() {
assert(_debugCurrentBuildTarget == this);
_debugCurrentBuildTarget = debugPreviousBuildTarget;
return true;
});
}
static BuildScheduler scheduleBuildFor; static BuildScheduler scheduleBuildFor;
static int _debugStateLockLevel = 0; static int _debugStateLockLevel = 0;
static bool get _debugStateLocked => _debugStateLockLevel > 0; static bool get _debugStateLocked => _debugStateLockLevel > 0;
static bool _debugBuilding = false; static bool _debugBuilding = false;
static BuildableElement _debugCurrentBuildTarget;
/// Establishes a scope in which component build functions can run. /// Establishes a scope in which component build functions can run.
/// ///
...@@ -989,17 +923,39 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -989,17 +923,39 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
scheduleBuildFor(this); scheduleBuildFor(this);
} }
void visitChildren(ElementVisitor visitor) { /// Called by the binding when scheduleBuild() has been called to mark this
if (_child != null) /// element dirty, and, in Components, by update() when the Widget has
visitor(_child); /// changed.
void rebuild() {
assert(_debugLifecycleState != _ElementLifecycle.initial);
if (!_active || !_dirty) {
_dirty = false;
return;
}
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(_debugStateLocked);
BuildableElement debugPreviousBuildTarget;
assert(() {
debugPreviousBuildTarget = _debugCurrentBuildTarget;
_debugCurrentBuildTarget = this;
return true;
});
try {
performRebuild();
} catch (e, stack) {
_debugReportException('rebuilding $this', e, stack);
} finally {
assert(() {
assert(_debugCurrentBuildTarget == this);
_debugCurrentBuildTarget = debugPreviousBuildTarget;
return true;
});
}
assert(!_dirty);
} }
bool detachChild(Element child) { /// Called by rebuild() after the appropriate checks have been made.
assert(child == _child); void performRebuild();
_deactivateChild(_child);
_child = null;
return true;
}
void dependenciesChanged() { void dependenciesChanged() {
markNeedsBuild(); markNeedsBuild();
...@@ -1012,8 +968,70 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -1012,8 +968,70 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
} }
} }
typedef Widget WidgetBuilder(BuildContext context);
/// Base class for the instantiation of StatelessComponent and StatefulComponent
/// widgets.
abstract class ComponentElement<T extends Widget> extends BuildableElement<T> {
ComponentElement(T widget) : super(widget);
WidgetBuilder _builder;
Element _child;
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_active);
rebuild();
assert(_child != null);
}
/// Reinvokes the build() method of the StatelessComponent object (for
/// stateless components) or the State object (for stateful components) and
/// then updates the widget tree.
///
/// Called automatically during mount() to generate the first build, and by
/// rebuild() when the element needs updating.
void performRebuild() {
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
Widget built;
try {
built = _builder(this);
assert(built != null);
} catch (e, stack) {
_debugReportException('building ${_widget}', e, stack);
built = new ErrorWidget();
} finally {
// We delay marking the element as clean until after calling _builder so
// that attempts to markNeedsBuild() during build() will be ignored.
_dirty = false;
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
}
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
_debugReportException('building ${_widget}', e, stack);
built = new ErrorWidget();
_child = updateChild(null, built, slot);
}
}
void visitChildren(ElementVisitor visitor) {
if (_child != null)
visitor(_child);
}
bool detachChild(Element child) {
assert(child == _child);
_deactivateChild(_child);
_child = null;
return true;
}
}
/// Instantiation of StatelessComponent widgets. /// Instantiation of StatelessComponent widgets.
class StatelessComponentElement<T extends StatelessComponent> extends BuildableElement<T> { class StatelessComponentElement<T extends StatelessComponent> extends ComponentElement<T> {
StatelessComponentElement(T widget) : super(widget) { StatelessComponentElement(T widget) : super(widget) {
_builder = widget.build; _builder = widget.build;
} }
...@@ -1028,7 +1046,7 @@ class StatelessComponentElement<T extends StatelessComponent> extends BuildableE ...@@ -1028,7 +1046,7 @@ class StatelessComponentElement<T extends StatelessComponent> extends BuildableE
} }
/// Instantiation of StatefulComponent widgets. /// Instantiation of StatefulComponent widgets.
class StatefulComponentElement<T extends StatefulComponent, U extends State<T>> extends BuildableElement<T> { class StatefulComponentElement<T extends StatefulComponent, U extends State<T>> extends ComponentElement<T> {
StatefulComponentElement(T widget) StatefulComponentElement(T widget)
: _state = widget.createState(), super(widget) { : _state = widget.createState(), super(widget) {
assert(_state._debugTypesAreRight(widget)); // can't use T and U, since normally we don't actually set those assert(_state._debugTypesAreRight(widget)); // can't use T and U, since normally we don't actually set those
...@@ -1142,8 +1160,10 @@ class InheritedElement extends StatelessComponentElement<InheritedWidget> { ...@@ -1142,8 +1160,10 @@ class InheritedElement extends StatelessComponentElement<InheritedWidget> {
InheritedWidget oldWidget = widget; InheritedWidget oldWidget = widget;
super.update(newWidget); super.update(newWidget);
assert(widget == newWidget); assert(widget == newWidget);
if (widget.updateShouldNotify(oldWidget)) if (widget.updateShouldNotify(oldWidget)) {
assert(widget != oldWidget);
_notifyDescendants(); _notifyDescendants();
}
} }
void _notifyDescendants() { void _notifyDescendants() {
...@@ -1161,7 +1181,7 @@ class InheritedElement extends StatelessComponentElement<InheritedWidget> { ...@@ -1161,7 +1181,7 @@ class InheritedElement extends StatelessComponentElement<InheritedWidget> {
} }
/// Base class for instantiations of RenderObjectWidget subclasses /// Base class for instantiations of RenderObjectWidget subclasses
abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element<T> { abstract class RenderObjectElement<T extends RenderObjectWidget> extends BuildableElement<T> {
RenderObjectElement(T widget) RenderObjectElement(T widget)
: _renderObject = widget.createRenderObject(), super(widget); : _renderObject = widget.createRenderObject(), super(widget);
...@@ -1195,6 +1215,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element ...@@ -1195,6 +1215,7 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
ParentDataElement parentDataElement = _findAncestorParentDataElement(); ParentDataElement parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null) if (parentDataElement != null)
updateParentData(parentDataElement.widget); updateParentData(parentDataElement.widget);
_dirty = false;
} }
void update(T newWidget) { void update(T newWidget) {
...@@ -1202,6 +1223,26 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element ...@@ -1202,6 +1223,26 @@ abstract class RenderObjectElement<T extends RenderObjectWidget> extends Element
super.update(newWidget); super.update(newWidget);
assert(widget == newWidget); assert(widget == newWidget);
widget.updateRenderObject(renderObject, oldWidget); widget.updateRenderObject(renderObject, oldWidget);
_dirty = false;
}
void performRebuild() {
reinvokeBuilders();
_dirty = false;
}
void reinvokeBuilders() {
// There's no way to mark a normal RenderObjectElement dirty.
// We inherit from BuildableElement so that subclasses can themselves
// implement reinvokeBuilders() if they do provide a way to mark themeselves
// dirty, e.g. if they have a builder callback. (Builder callbacks have a
// 'BuildContext' argument which you can pass to Theme.of() and other
// InheritedWidget APIs which eventually trigger a rebuild.)
print('${runtimeType} failed to implement reinvokeBuilders(), but got marked dirty');
assert(() {
'reinvokeBuilders() not implemented';
return false;
});
} }
/// Utility function for subclasses that have one or more lists of children. /// Utility function for subclasses that have one or more lists of children.
......
...@@ -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