Merge pull request #2508 from abarth/reparent_multichild
Support reparenting state from MultiChildRenderObjectElement
diff --git a/examples/layers/raw/canvas.dart b/examples/layers/raw/canvas.dart
index 7108aaa..bbf861c 100644
--- a/examples/layers/raw/canvas.dart
+++ b/examples/layers/raw/canvas.dart
@@ -69,18 +69,12 @@
ui.Scene composite(ui.Picture picture, ui.Rect paintBounds) {
final double devicePixelRatio = ui.window.devicePixelRatio;
- ui.Rect sceneBounds = new ui.Rect.fromLTWH(
- 0.0,
- 0.0,
- ui.window.size.width * devicePixelRatio,
- ui.window.size.height * devicePixelRatio
- );
Float64List deviceTransform = new Float64List(16)
..[0] = devicePixelRatio
..[5] = devicePixelRatio
..[10] = 1.0
..[15] = 1.0;
- ui.SceneBuilder sceneBuilder = new ui.SceneBuilder(sceneBounds)
+ ui.SceneBuilder sceneBuilder = new ui.SceneBuilder()
..pushTransform(deviceTransform)
..addPicture(ui.Offset.zero, picture)
..pop();
diff --git a/examples/layers/raw/hello_world.dart b/examples/layers/raw/hello_world.dart
index a9f1f3e..d624c28 100644
--- a/examples/layers/raw/hello_world.dart
+++ b/examples/layers/raw/hello_world.dart
@@ -28,7 +28,7 @@
));
final ui.Picture picture = recorder.endRecording();
- final ui.SceneBuilder sceneBuilder = new ui.SceneBuilder(physicalBounds)
+ final ui.SceneBuilder sceneBuilder = new ui.SceneBuilder()
// TODO(abarth): We should be able to add a picture without pushing a
// container layer first.
..pushClipRect(physicalBounds)
diff --git a/examples/layers/raw/spinning_square.dart b/examples/layers/raw/spinning_square.dart
index c4f464a..24b3e73 100644
--- a/examples/layers/raw/spinning_square.dart
+++ b/examples/layers/raw/spinning_square.dart
@@ -36,18 +36,12 @@
// COMPOSITE
final double devicePixelRatio = ui.window.devicePixelRatio;
- ui.Rect sceneBounds = new ui.Rect.fromLTWH(
- 0.0,
- 0.0,
- ui.window.size.width * devicePixelRatio,
- ui.window.size.height * devicePixelRatio
- );
Float64List deviceTransform = new Float64List(16)
..[0] = devicePixelRatio
..[5] = devicePixelRatio
..[10] = 1.0
..[15] = 1.0;
- ui.SceneBuilder sceneBuilder = new ui.SceneBuilder(sceneBounds)
+ ui.SceneBuilder sceneBuilder = new ui.SceneBuilder()
..pushTransform(deviceTransform)
..addPicture(ui.Offset.zero, picture)
..pop();
diff --git a/examples/layers/raw/text.dart b/examples/layers/raw/text.dart
index 24e90fb..d8e431f 100644
--- a/examples/layers/raw/text.dart
+++ b/examples/layers/raw/text.dart
@@ -28,18 +28,12 @@
ui.Scene composite(ui.Picture picture, ui.Rect paintBounds) {
final double devicePixelRatio = ui.window.devicePixelRatio;
- ui.Rect sceneBounds = new ui.Rect.fromLTWH(
- 0.0,
- 0.0,
- ui.window.size.width * devicePixelRatio,
- ui.window.size.height * devicePixelRatio
- );
Float64List deviceTransform = new Float64List(16)
..[0] = devicePixelRatio
..[5] = devicePixelRatio
..[10] = 1.0
..[15] = 1.0;
- ui.SceneBuilder sceneBuilder = new ui.SceneBuilder(sceneBounds)
+ ui.SceneBuilder sceneBuilder = new ui.SceneBuilder()
..pushTransform(deviceTransform)
..addPicture(ui.Offset.zero, picture)
..pop();
diff --git a/examples/layers/raw/touch_input.dart b/examples/layers/raw/touch_input.dart
index aca8938..b8569bf 100644
--- a/examples/layers/raw/touch_input.dart
+++ b/examples/layers/raw/touch_input.dart
@@ -48,12 +48,6 @@
// pixels, which are then scalled by the device pixel ratio before being drawn
// on the screen.
final double devicePixelRatio = ui.window.devicePixelRatio;
- ui.Rect sceneBounds = new ui.Rect.fromLTWH(
- 0.0,
- 0.0,
- ui.window.size.width * devicePixelRatio,
- ui.window.size.height * devicePixelRatio
- );
// This transform scales the x and y coordinates by the devicePixelRatio.
Float64List deviceTransform = new Float64List(16)
@@ -66,7 +60,7 @@
// transform that scale its children by the device pixel ratio. This transform
// lets us paint in "logical" pixels which are converted to device pixels by
// this scaling operation.
- ui.SceneBuilder sceneBuilder = new ui.SceneBuilder(sceneBounds)
+ ui.SceneBuilder sceneBuilder = new ui.SceneBuilder()
..pushTransform(deviceTransform)
..addPicture(ui.Offset.zero, picture)
..pop();
diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart
index 2effaf7..c6701d4 100644
--- a/packages/flutter/lib/src/material/popup_menu.dart
+++ b/packages/flutter/lib/src/material/popup_menu.dart
@@ -28,7 +28,7 @@
const double _kMenuWidthStep = 56.0;
const double _kMenuScreenPadding = 8.0;
-abstract class PopupMenuEntry<T> extends StatelessComponent {
+abstract class PopupMenuEntry<T> extends StatefulComponent {
PopupMenuEntry({ Key key }) : super(key: key);
double get height;
@@ -41,7 +41,11 @@
final double height;
- Widget build(BuildContext context) => new Divider(height: height);
+ _PopupMenuDividerState createState() => new _PopupMenuDividerState();
+}
+
+class _PopupMenuDividerState extends State<PopupMenuDivider> {
+ Widget build(BuildContext context) => new Divider(height: config.height);
}
class PopupMenuItem<T> extends PopupMenuEntry<T> {
@@ -58,20 +62,31 @@
double get height => _kMenuItemHeight;
+ _PopupMenuItemState<PopupMenuItem<T>> createState() => new _PopupMenuItemState<PopupMenuItem<T>>();
+}
+
+class _PopupMenuItemState<T extends PopupMenuItem> extends State<T> {
+ // Override this to put something else in the menu entry.
+ Widget buildChild() => config.child;
+
+ void onTap() {
+ Navigator.pop(context, config.value);
+ }
+
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
TextStyle style = theme.text.subhead;
- if (!enabled)
+ if (!config.enabled)
style = style.copyWith(color: theme.disabledColor);
Widget item = new DefaultTextStyle(
style: style,
child: new Baseline(
- baseline: height - _kBaselineOffsetFromBottom,
- child: child
+ baseline: config.height - _kBaselineOffsetFromBottom,
+ child: buildChild()
)
);
- if (!enabled) {
+ if (!config.enabled) {
final bool isDark = theme.brightness == ThemeBrightness.dark;
item = new IconTheme(
data: new IconThemeData(opacity: isDark ? 0.5 : 0.38),
@@ -79,11 +94,14 @@
);
}
- return new MergeSemantics(
- child: new Container(
- height: height,
- padding: const EdgeDims.symmetric(horizontal: _kMenuHorizontalPadding),
- child: item
+ return new InkWell(
+ onTap: config.enabled ? onTap : null,
+ child: new MergeSemantics(
+ child: new Container(
+ height: config.height,
+ padding: const EdgeDims.symmetric(horizontal: _kMenuHorizontalPadding),
+ child: item
+ )
)
);
}
@@ -93,19 +111,52 @@
CheckedPopupMenuItem({
Key key,
T value,
- checked: false,
+ this.checked: false,
bool enabled: true,
Widget child
}) : super(
key: key,
value: value,
enabled: enabled,
- child: new ListItem(
- enabled: enabled,
- left: new Icon(icon: checked ? Icons.done : null),
- primary: child
- )
+ child: child
);
+
+ final bool checked;
+
+ _CheckedPopupMenuItemState<T> createState() => new _CheckedPopupMenuItemState<T>();
+}
+
+class _CheckedPopupMenuItemState<T> extends _PopupMenuItemState<CheckedPopupMenuItem<T>> {
+ static const Duration _kFadeDuration = const Duration(milliseconds: 150);
+ AnimationController _controller;
+ Animation<double> get _opacity => _controller.view;
+
+ void initState() {
+ super.initState();
+ _controller = new AnimationController(duration: _kFadeDuration)
+ ..value = config.checked ? 1.0 : 0.0
+ ..addListener(() => setState(() { /* animation changed */ }));
+ }
+
+ void onTap() {
+ // This fades the checkmark in or out when tapped.
+ if (config.checked)
+ _controller.reverse();
+ else
+ _controller.forward();
+ super.onTap();
+ }
+
+ Widget buildChild() {
+ return new ListItem(
+ enabled: config.enabled,
+ left: new FadeTransition(
+ opacity: _opacity,
+ child: new Icon(icon: _controller.isDismissed ? null : Icons.done)
+ ),
+ primary: config.child
+ );
+ }
}
class _PopupMenu<T> extends StatelessComponent {
@@ -127,7 +178,6 @@
parent: route.animation,
curve: new Interval(start, end)
);
- final bool enabled = route.items[i].enabled;
Widget item = route.items[i];
if (route.initialValue != null && route.initialValue == route.items[i].value) {
item = new Container(
@@ -137,10 +187,7 @@
}
children.add(new FadeTransition(
opacity: opacity,
- child: new InkWell(
- onTap: enabled ? () { Navigator.pop(context, route.items[i].value); } : null,
- child: item
- )
+ child: item
));
}
diff --git a/packages/flutter/lib/src/rendering/view.dart b/packages/flutter/lib/src/rendering/view.dart
index 826caed..9a982ad 100644
--- a/packages/flutter/lib/src/rendering/view.dart
+++ b/packages/flutter/lib/src/rendering/view.dart
@@ -122,8 +122,7 @@
try {
final TransformLayer transformLayer = layer;
transformLayer.transform = _logicalToDeviceTransform;
- Rect bounds = Point.origin & (size * ui.window.devicePixelRatio);
- ui.SceneBuilder builder = new ui.SceneBuilder(bounds);
+ ui.SceneBuilder builder = new ui.SceneBuilder();
transformLayer.addToScene(builder, Offset.zero);
assert(layer == transformLayer);
ui.Scene scene = builder.build();
diff --git a/packages/flutter_tools/lib/src/service_protocol.dart b/packages/flutter_tools/lib/src/service_protocol.dart
new file mode 100644
index 0000000..ffbbdfa
--- /dev/null
+++ b/packages/flutter_tools/lib/src/service_protocol.dart
@@ -0,0 +1,52 @@
+// Copyright 2016 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:async';
+
+import 'device.dart';
+
+/// Discover service protocol ports on devices.
+class ServiceProtocolDiscovery {
+ /// [logReader] A [DeviceLogReader] to look for Observatory messages in.
+ ServiceProtocolDiscovery(DeviceLogReader logReader)
+ : _logReader = logReader {
+ assert(_logReader != null);
+ if (!_logReader.isReading)
+ _logReader.start();
+
+ _logReader.lines.listen(_onLine);
+ }
+
+ final DeviceLogReader _logReader;
+ Completer _completer = new Completer();
+
+ /// The [Future] returned by this function will complete when the next
+ /// service protocol port is found.
+ Future<int> nextPort() {
+ return _completer.future;
+ }
+
+ void _onLine(String line) {
+ int portNumber = 0;
+ if (line.startsWith('Observatory listening on http://')) {
+ try {
+ RegExp portExp = new RegExp(r"\d+.\d+.\d+.\d+:(\d+)");
+ var port = portExp.firstMatch(line).group(1);
+ portNumber = int.parse(port);
+ } catch (_) {
+ // Ignore errors.
+ }
+ }
+ if (portNumber != 0) {
+ _located(portNumber);
+ }
+ }
+
+ void _located(int port) {
+ assert(_completer != null);
+ assert(!_completer.isCompleted);
+ _completer.complete(port);
+ _completer = new Completer();
+ }
+}
diff --git a/packages/flutter_tools/test/service_protocol_test.dart b/packages/flutter_tools/test/service_protocol_test.dart
new file mode 100644
index 0000000..7c62e57
--- /dev/null
+++ b/packages/flutter_tools/test/service_protocol_test.dart
@@ -0,0 +1,47 @@
+// Copyright 2016 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:async';
+import 'package:test/test.dart';
+
+import 'package:flutter_tools/src/service_protocol.dart';
+
+import 'src/mocks.dart';
+
+main() => defineTests();
+
+defineTests() {
+ group('service_protocol', () {
+ test('Discovery Heartbeat', () async {
+ MockDeviceLogReader logReader = new MockDeviceLogReader();
+ ServiceProtocolDiscovery discoverer =
+ new ServiceProtocolDiscovery(logReader);
+ // Get next port future.
+ Future nextPort = discoverer.nextPort();
+ expect(nextPort, isNotNull);
+ // Inject some lines.
+ logReader.addLine('HELLO WORLD');
+ logReader.addLine(
+ 'Observatory listening on http://127.0.0.1:9999');
+ // Await the port.
+ expect(await nextPort, 9999);
+ // Get next port future.
+ nextPort = discoverer.nextPort();
+ logReader.addLine(
+ 'Observatory listening on http://127.0.0.1:3333');
+ expect(await nextPort, 3333);
+ // Get next port future.
+ nextPort = discoverer.nextPort();
+ // Inject some bad lines.
+ logReader.addLine('Observatory listening on http://127.0.0.1');
+ logReader.addLine('Observatory listening on http://127.0.0.1:');
+ logReader.addLine(
+ 'Observatory listening on http://127.0.0.1:apple');
+ int port = await nextPort.timeout(
+ const Duration(milliseconds: 100), onTimeout: () => 77);
+ // Expect the timeout port.
+ expect(port, 77);
+ });
+ });
+}
diff --git a/packages/flutter_tools/test/src/mocks.dart b/packages/flutter_tools/test/src/mocks.dart
index 38a4610..1d7b819 100644
--- a/packages/flutter_tools/test/src/mocks.dart
+++ b/packages/flutter_tools/test/src/mocks.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:async';
import 'package:flutter_tools/src/android/android_device.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/build_configuration.dart';
@@ -51,6 +52,39 @@
iOSSimulator: new MockIOSSimulator());
}
+class MockDeviceLogReader extends DeviceLogReader {
+ String get name => 'MockLogReader';
+
+ final StreamController<String> _linesStreamController =
+ new StreamController<String>.broadcast();
+
+ final Completer _finishedCompleter = new Completer();
+
+ Stream<String> get lines => _linesStreamController.stream;
+
+ void addLine(String line) {
+ _linesStreamController.add(line);
+ }
+
+ bool _started = false;
+
+ Future start() {
+ assert(!_started);
+ _started = true;
+ return new Future.value(this);
+ }
+
+ bool get isReading => _started;
+
+ Future stop() {
+ assert(_started);
+ _started = false;
+ return new Future.value(this);
+ }
+
+ Future get finished => _finishedCompleter.future;
+}
+
void applyMocksToCommand(FlutterCommand command) {
command
..applicationPackages = new MockApplicationPackageStore()