Commit 867bbcc9 authored by Ian Hickson's avatar Ian Hickson

Provide a safe time to update a WidgetToRenderBoxAdapter

If you change the RenderObject tree between frames, you'll assert if
you subsequently hit test. So e.g. if you get two button presses back
to back, and you mutate the tree synchronously in response to the
first one, the second will assert.

This adds an onBuild callback to WidgetToRenderBoxAdapter to make it
easier to do the updates at the right time, i.e., during widget build.
It'll be called whenever you rebuild the WidgetToRenderBoxAdapter
itself, so all you have to do to use it is call setState() on whoever
is building the WidgetToRenderBoxAdapter.
parent f10f79f8
...@@ -25,22 +25,45 @@ class SectorAppState extends State<SectorApp> { ...@@ -25,22 +25,45 @@ class SectorAppState extends State<SectorApp> {
final RenderBoxToRenderSectorAdapter sectors = initCircle(); final RenderBoxToRenderSectorAdapter sectors = initCircle();
final math.Random rand = new math.Random(1); final math.Random rand = new math.Random(1);
List<double> wantedSectorSizes = <double>[];
List<double> actualSectorSizes = <double>[];
double get currentTheta => wantedSectorSizes.fold(0.0, (double total, double value) => total + value);
void addSector() { void addSector() {
double deltaTheta; final double currentTheta = this.currentTheta;
var ring = (sectors.child as RenderSectorRing); if (currentTheta < kTwoPi) {
SectorDimensions currentSize = ring.getIntrinsicDimensions(const SectorConstraints(), ring.deltaRadius); double deltaTheta;
if (currentSize.deltaTheta >= kTwoPi - (math.PI * 0.2 + 0.05)) if (currentTheta >= kTwoPi - (math.PI * 0.2 + 0.05))
deltaTheta = kTwoPi - currentSize.deltaTheta; deltaTheta = kTwoPi - currentTheta;
else else
deltaTheta = math.PI * rand.nextDouble() / 5.0 + 0.05; deltaTheta = math.PI * rand.nextDouble() / 5.0 + 0.05;
Color color = new Color(((0xFF << 24) + rand.nextInt(0xFFFFFF)) | 0x808080); wantedSectorSizes.add(deltaTheta);
ring.add(new RenderSolidColor(color, desiredDeltaTheta: deltaTheta)); updateEnabledState();
updateEnabledState(); }
} }
void removeSector() { void removeSector() {
(sectors.child as RenderSectorRing).remove((sectors.child as RenderSectorRing).lastChild); if (wantedSectorSizes.isNotEmpty) {
updateEnabledState(); wantedSectorSizes.removeLast();
updateEnabledState();
}
}
void doUpdates() {
int index = 0;
while (index < actualSectorSizes.length && index < wantedSectorSizes.length && actualSectorSizes[index] == wantedSectorSizes[index])
index += 1;
RenderSectorRing ring = sectors.child;
while (index < actualSectorSizes.length) {
ring.remove(ring.lastChild);
actualSectorSizes.removeLast();
}
while (index < wantedSectorSizes.length) {
Color color = new Color(((0xFF << 24) + rand.nextInt(0xFFFFFF)) | 0x808080);
ring.add(new RenderSolidColor(color, desiredDeltaTheta: wantedSectorSizes[index]));
actualSectorSizes.add(wantedSectorSizes[index]);
index += 1;
}
} }
static RenderBox initSector(Color color) { static RenderBox initSector(Color color) {
...@@ -60,10 +83,8 @@ class SectorAppState extends State<SectorApp> { ...@@ -60,10 +83,8 @@ class SectorAppState extends State<SectorApp> {
bool _enabledRemove = false; bool _enabledRemove = false;
void updateEnabledState() { void updateEnabledState() {
setState(() { setState(() {
var ring = (sectors.child as RenderSectorRing); _enabledAdd = currentTheta < kTwoPi;
SectorDimensions currentSize = ring.getIntrinsicDimensions(const SectorConstraints(), ring.deltaRadius); _enabledRemove = wantedSectorSizes.isNotEmpty;
_enabledAdd = currentSize.deltaTheta < kTwoPi;
_enabledRemove = ring.firstChild != null;
}); });
} }
...@@ -75,35 +96,35 @@ class SectorAppState extends State<SectorApp> { ...@@ -75,35 +96,35 @@ class SectorAppState extends State<SectorApp> {
child: new Row( child: new Row(
children: <Widget>[ children: <Widget>[
new RaisedButton( new RaisedButton(
onPressed: _enabledAdd ? addSector : null,
child: new IntrinsicWidth( child: new IntrinsicWidth(
child: new Row( child: new Row(
children: <Widget>[ children: <Widget>[
new Container( new Container(
padding: new EdgeDims.all(4.0), padding: new EdgeDims.all(4.0),
margin: new EdgeDims.only(right: 10.0), margin: new EdgeDims.only(right: 10.0),
child: new WidgetToRenderBoxAdapter(sectorAddIcon) child: new WidgetToRenderBoxAdapter(renderBox: sectorAddIcon)
), ),
new Text('ADD SECTOR'), new Text('ADD SECTOR'),
] ]
) )
), )
onPressed: _enabledAdd ? addSector : null
), ),
new RaisedButton( new RaisedButton(
onPressed: _enabledRemove ? removeSector : null,
child: new IntrinsicWidth( child: new IntrinsicWidth(
child: new Row( child: new Row(
children: <Widget>[ children: <Widget>[
new Container( new Container(
padding: new EdgeDims.all(4.0), padding: new EdgeDims.all(4.0),
margin: new EdgeDims.only(right: 10.0), margin: new EdgeDims.only(right: 10.0),
child: new WidgetToRenderBoxAdapter(sectorRemoveIcon) child: new WidgetToRenderBoxAdapter(renderBox: sectorRemoveIcon)
), ),
new Text('REMOVE SECTOR'), new Text('REMOVE SECTOR'),
] ]
) )
), )
onPressed: _enabledRemove ? removeSector : null ),
)
], ],
justifyContent: FlexJustifyContent.spaceAround justifyContent: FlexJustifyContent.spaceAround
) )
...@@ -115,7 +136,10 @@ class SectorAppState extends State<SectorApp> { ...@@ -115,7 +136,10 @@ class SectorAppState extends State<SectorApp> {
border: new Border.all(color: new Color(0xFF000000)) border: new Border.all(color: new Color(0xFF000000))
), ),
padding: new EdgeDims.all(8.0), padding: new EdgeDims.all(8.0),
child: new WidgetToRenderBoxAdapter(sectors) child: new WidgetToRenderBoxAdapter(
renderBox: sectors,
onBuild: doUpdates
)
) )
), ),
], ],
......
...@@ -1910,7 +1910,7 @@ class AssetImage extends StatelessComponent { ...@@ -1910,7 +1910,7 @@ class AssetImage extends StatelessComponent {
/// widget enforces that restriction by keying itself using a [GlobalObjectKey] /// widget enforces that restriction by keying itself using a [GlobalObjectKey]
/// for the given render object. /// for the given render object.
class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget { class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget {
WidgetToRenderBoxAdapter(RenderBox renderBox) WidgetToRenderBoxAdapter({ RenderBox renderBox, this.onBuild })
: renderBox = renderBox, : renderBox = renderBox,
// WidgetToRenderBoxAdapter objects are keyed to their render box. This // WidgetToRenderBoxAdapter objects are keyed to their render box. This
// prevents the widget being used in the widget hierarchy in two different // prevents the widget being used in the widget hierarchy in two different
...@@ -1923,7 +1923,18 @@ class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget { ...@@ -1923,7 +1923,18 @@ class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget {
/// The render box to place in the widget tree. /// The render box to place in the widget tree.
final RenderBox renderBox; final RenderBox renderBox;
/// Called when it is safe to update the render box and its descendants. If
/// you update the RenderObject subtree under this widget outside of
/// invocations of this callback, features like hit-testing will fail as the
/// tree will be dirty.
final VoidCallback onBuild;
RenderBox createRenderObject() => renderBox; RenderBox createRenderObject() => renderBox;
void updateRenderObject(RenderBox renderObject, WidgetToRenderBoxAdapter oldWidget) {
if (onBuild != null)
onBuild();
}
} }
......
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