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> {
final RenderBoxToRenderSectorAdapter sectors = initCircle();
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() {
double deltaTheta;
var ring = (sectors.child as RenderSectorRing);
SectorDimensions currentSize = ring.getIntrinsicDimensions(const SectorConstraints(), ring.deltaRadius);
if (currentSize.deltaTheta >= kTwoPi - (math.PI * 0.2 + 0.05))
deltaTheta = kTwoPi - currentSize.deltaTheta;
else
deltaTheta = math.PI * rand.nextDouble() / 5.0 + 0.05;
Color color = new Color(((0xFF << 24) + rand.nextInt(0xFFFFFF)) | 0x808080);
ring.add(new RenderSolidColor(color, desiredDeltaTheta: deltaTheta));
updateEnabledState();
final double currentTheta = this.currentTheta;
if (currentTheta < kTwoPi) {
double deltaTheta;
if (currentTheta >= kTwoPi - (math.PI * 0.2 + 0.05))
deltaTheta = kTwoPi - currentTheta;
else
deltaTheta = math.PI * rand.nextDouble() / 5.0 + 0.05;
wantedSectorSizes.add(deltaTheta);
updateEnabledState();
}
}
void removeSector() {
(sectors.child as RenderSectorRing).remove((sectors.child as RenderSectorRing).lastChild);
updateEnabledState();
if (wantedSectorSizes.isNotEmpty) {
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) {
......@@ -60,10 +83,8 @@ class SectorAppState extends State<SectorApp> {
bool _enabledRemove = false;
void updateEnabledState() {
setState(() {
var ring = (sectors.child as RenderSectorRing);
SectorDimensions currentSize = ring.getIntrinsicDimensions(const SectorConstraints(), ring.deltaRadius);
_enabledAdd = currentSize.deltaTheta < kTwoPi;
_enabledRemove = ring.firstChild != null;
_enabledAdd = currentTheta < kTwoPi;
_enabledRemove = wantedSectorSizes.isNotEmpty;
});
}
......@@ -75,35 +96,35 @@ class SectorAppState extends State<SectorApp> {
child: new Row(
children: <Widget>[
new RaisedButton(
onPressed: _enabledAdd ? addSector : null,
child: new IntrinsicWidth(
child: new Row(
children: <Widget>[
new Container(
padding: new EdgeDims.all(4.0),
margin: new EdgeDims.only(right: 10.0),
child: new WidgetToRenderBoxAdapter(sectorAddIcon)
child: new WidgetToRenderBoxAdapter(renderBox: sectorAddIcon)
),
new Text('ADD SECTOR'),
]
)
),
onPressed: _enabledAdd ? addSector : null
)
),
new RaisedButton(
onPressed: _enabledRemove ? removeSector : null,
child: new IntrinsicWidth(
child: new Row(
children: <Widget>[
new Container(
padding: new EdgeDims.all(4.0),
margin: new EdgeDims.only(right: 10.0),
child: new WidgetToRenderBoxAdapter(sectorRemoveIcon)
child: new WidgetToRenderBoxAdapter(renderBox: sectorRemoveIcon)
),
new Text('REMOVE SECTOR'),
]
)
),
onPressed: _enabledRemove ? removeSector : null
)
)
),
],
justifyContent: FlexJustifyContent.spaceAround
)
......@@ -115,7 +136,10 @@ class SectorAppState extends State<SectorApp> {
border: new Border.all(color: new Color(0xFF000000))
),
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 {
/// widget enforces that restriction by keying itself using a [GlobalObjectKey]
/// for the given render object.
class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget {
WidgetToRenderBoxAdapter(RenderBox renderBox)
WidgetToRenderBoxAdapter({ RenderBox renderBox, this.onBuild })
: renderBox = renderBox,
// WidgetToRenderBoxAdapter objects are keyed to their render box. This
// prevents the widget being used in the widget hierarchy in two different
......@@ -1923,7 +1923,18 @@ class WidgetToRenderBoxAdapter extends LeafRenderObjectWidget {
/// The render box to place in the widget tree.
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;
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