Merge pull request #1244 from chinmaygarde/master

Fix Build: Remove incorrect include of deleted picture_rasterizer.h
diff --git a/sky/engine/core/painting/Rect.dart b/sky/engine/core/painting/Rect.dart
index 11646b2..6763038 100644
--- a/sky/engine/core/painting/Rect.dart
+++ b/sky/engine/core/painting/Rect.dart
@@ -63,8 +63,8 @@
     return new Rect.fromLTRB(
       math.max(left, other.left),
       math.max(top, other.top),
-      math.max(right, other.right),
-      math.max(bottom, other.bottom));
+      math.min(right, other.right),
+      math.min(bottom, other.bottom));
   }
 
   /// The distance between the left and right edges of this rectangle
diff --git a/sky/packages/sky/lib/src/animation/animated_value.dart b/sky/packages/sky/lib/src/animation/animated_value.dart
index 8d4ed9f..68da613 100644
--- a/sky/packages/sky/lib/src/animation/animated_value.dart
+++ b/sky/packages/sky/lib/src/animation/animated_value.dart
@@ -30,9 +30,9 @@
 /// can be made to take longer in one direction that the other.
 class AnimationTiming {
   AnimationTiming({
-    this.interval,
+    this.interval: const Interval(0.0, 1.0),
     this.reverseInterval,
-    this.curve,
+    this.curve: linear,
     this.reverseCurve
   });
 
diff --git a/sky/packages/sky/lib/src/animation/curves.dart b/sky/packages/sky/lib/src/animation/curves.dart
index 791f45b..9fd00d3 100644
--- a/sky/packages/sky/lib/src/animation/curves.dart
+++ b/sky/packages/sky/lib/src/animation/curves.dart
@@ -27,14 +27,9 @@
   double transform(double t) => t;
 }
 
-/// A curve that is initially 0.0, then linear, then 1.0
+/// A curve that is 0.0 until start, then linear from 0.0 to 1.0 at end, then 1.0
 class Interval implements Curve {
-  Interval(this.start, this.end) {
-    assert(start >= 0.0);
-    assert(start <= 1.0);
-    assert(end >= 0.0);
-    assert(end <= 1.0);
-  }
+  const Interval(this.start, this.end);
 
   /// The smallest value for which this interval is 0.0
   final double start;
@@ -43,6 +38,10 @@
   final double end;
 
   double transform(double t) {
+    assert(start >= 0.0);
+    assert(start <= 1.0);
+    assert(end >= 0.0);
+    assert(end <= 1.0);
     return ((t - start) / (end - start)).clamp(0.0, 1.0);
   }
 }
diff --git a/sky/packages/sky/lib/src/services/shell.dart b/sky/packages/sky/lib/src/services/shell.dart
index 88b2140..3f3f411 100644
--- a/sky/packages/sky/lib/src/services/shell.dart
+++ b/sky/packages/sky/lib/src/services/shell.dart
@@ -18,15 +18,33 @@
   return new ApplicationConnection(null, serviceProvider);
 }
 
+// A replacement for requestService.  Implementations should return true
+// if they handled the request, or false if the request should fall through
+// to the default requestService.
+typedef bool OverrideRequestService(String url, Object proxy);
+
+// Set this to intercept calls to requestService and supply an alternative
+// implementation of a service (for example, a mock for testing).
+OverrideRequestService overrideRequestService;
+
 class _ShellImpl {
   _ShellImpl._();
 
   final ApplicationConnection _connection = _initConnection();
 
-  void requestService(String url, Object proxy) {
+  void _requestService(String url, Object proxy) {
     if (embedder.shell == null) _connection.requestService(proxy);
     else embedder.connectToService(url, proxy);
   }
+
+  void requestService(String url, Object proxy) {
+    if (overrideRequestService != null) {
+      if (overrideRequestService(url, proxy))
+        return;
+    }
+
+    _requestService(url, proxy);
+  }
 }
 
 final _ShellImpl shell = new _ShellImpl._();
diff --git a/sky/packages/sky/lib/src/widgets/dialog.dart b/sky/packages/sky/lib/src/widgets/dialog.dart
index 7d16f54..da5b194 100644
--- a/sky/packages/sky/lib/src/widgets/dialog.dart
+++ b/sky/packages/sky/lib/src/widgets/dialog.dart
@@ -139,21 +139,19 @@
   final Completer completer;
   final RouteBuilder builder;
 
-  Widget build(Navigator navigator, RouteBase route) => builder(navigator, route);
-  bool get isOpaque => false;
-
-  void popState([dynamic result]) {
-    completer.complete(result);
-  }
-
   Duration get transitionDuration => _kTransitionDuration;
-  TransitionBase buildTransition({ Key key, Widget child, WatchableAnimationPerformance performance }) {
+  bool get isOpaque => false;
+  Widget build(Key key, Navigator navigator, RouteBase route, WatchableAnimationPerformance performance) {
     return new FadeTransition(
       performance: performance,
       opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut),
-      child: child
+      child: builder(navigator, route)
     );
   }
+
+  void popState([dynamic result]) {
+    completer.complete(result);
+  }
 }
 
 Future showDialog(Navigator navigator, DialogBuilder builder) {
diff --git a/sky/packages/sky/lib/src/widgets/editable_text.dart b/sky/packages/sky/lib/src/widgets/editable_text.dart
index 82794c9..3280da4 100644
--- a/sky/packages/sky/lib/src/widgets/editable_text.dart
+++ b/sky/packages/sky/lib/src/widgets/editable_text.dart
@@ -205,7 +205,7 @@
 
     if (!value.composing.isValid) {
       // TODO(eseidel): This is the wrong height if empty!
-      return new Text(value.text, style: style);
+      return new Row([new Text(value.text, style: style)]);
     }
 
     TextStyle composingStyle = style.merge(const TextStyle(decoration: underline));
diff --git a/sky/packages/sky/lib/src/widgets/navigator.dart b/sky/packages/sky/lib/src/widgets/navigator.dart
index d432b01..7eeac86 100644
--- a/sky/packages/sky/lib/src/widgets/navigator.dart
+++ b/sky/packages/sky/lib/src/widgets/navigator.dart
@@ -13,40 +13,44 @@
 typedef void NotificationCallback();
 
 abstract class RouteBase {
-  Widget build(Navigator navigator, RouteBase route);
-  bool get isOpaque;
-  void popState([dynamic result]) { assert(result == null); }
-
   AnimationPerformance _performance;
   NotificationCallback onDismissed;
   NotificationCallback onCompleted;
+  AnimationPerformance createPerformance() {
+    AnimationPerformance result = new AnimationPerformance(duration: transitionDuration);
+    result.addStatusListener((AnimationStatus status) {
+      switch (status) {
+        case AnimationStatus.dismissed:
+          if (onDismissed != null)
+            onDismissed();
+          break;
+        case AnimationStatus.completed:
+          if (onCompleted != null)
+            onCompleted();
+          break;
+        default:
+          ;
+      }
+    });
+    return result;
+  }
   WatchableAnimationPerformance ensurePerformance({ Direction direction }) {
     assert(direction != null);
-    if (_performance == null) {
-      _performance = new AnimationPerformance(duration: transitionDuration);
-      _performance.addStatusListener((AnimationStatus status) {
-        switch (status) {
-          case AnimationStatus.dismissed:
-            if (onDismissed != null)
-              onDismissed();
-            break;
-          case AnimationStatus.completed:
-            if (onCompleted != null)
-              onCompleted();
-            break;
-          default:
-            ;
-        }
-      });
-    }
+    if (_performance == null)
+      _performance = createPerformance();
     AnimationStatus desiredStatus = direction == Direction.forward ? AnimationStatus.forward : AnimationStatus.reverse;
     if (_performance.status != desiredStatus)
       _performance.play(direction);
     return _performance.view;
   }
+  bool get isActuallyOpaque => _performance != null && _performance.isCompleted && isOpaque;
+
+  bool get hasContent => true; // set to false if you have nothing useful to return from build()
 
   Duration get transitionDuration;
-  TransitionBase buildTransition({ Key key, Widget child, WatchableAnimationPerformance performance });
+  bool get isOpaque;
+  Widget build(Key key, Navigator navigator, RouteBase route, WatchableAnimationPerformance performance);
+  void popState([dynamic result]) { assert(result == null); }
 
   String toString() => '$runtimeType()';
 }
@@ -59,11 +63,11 @@
   final String name;
   final RouteBuilder builder;
 
-  Widget build(Navigator navigator, RouteBase route) => builder(navigator, route);
   bool get isOpaque => true;
 
   Duration get transitionDuration => _kTransitionDuration;
-  TransitionBase buildTransition({ Key key, Widget child, WatchableAnimationPerformance performance }) {
+
+  Widget build(Key key, Navigator navigator, RouteBase route, WatchableAnimationPerformance performance) {
     // TODO(jackson): Hit testing should ignore transform
     // TODO(jackson): Block input unless content is interactive
     return new SlideTransition(
@@ -73,7 +77,7 @@
       child: new FadeTransition(
         performance: performance,
         opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut),
-        child: child
+        child: builder(navigator, route)
       )
     );
   }
@@ -88,7 +92,6 @@
   RouteBase route;
   StatefulComponent owner;
 
-  Widget build(Navigator navigator, RouteBase route) => null;
   bool get isOpaque => false;
 
   void popState([dynamic result]) {
@@ -97,23 +100,9 @@
       callback(this);
   }
 
-  // Custom state routes shouldn't be asked to construct a transition
-  Duration get transitionDuration {
-    assert(false);
-    return const Duration();
-  }
-  TransitionBase buildTransition({ Key key, Widget child, WatchableAnimationPerformance performance }) {
-    assert(false);
-    return null;
-  }
-}
-
-class HistoryEntry {
-  HistoryEntry({ this.route });
-  final RouteBase route;
-  bool fullyOpaque = false;
-  // TODO(jackson): Keep track of the requested transition
-  String toString() => "HistoryEntry($route, hashCode=$hashCode)";
+  bool get hasContent => false;
+  Duration get transitionDuration => const Duration();
+  Widget build(Key key, Navigator navigator, RouteBase route, WatchableAnimationPerformance performance) => null;
 }
 
 class NavigationState {
@@ -123,14 +112,14 @@
       if (route.name != null)
         namedRoutes[route.name] = route;
     }
-    history.add(new HistoryEntry(route: routes[0]));
+    history.add(routes[0]);
   }
 
-  List<HistoryEntry> history = new List<HistoryEntry>();
+  List<RouteBase> history = new List<RouteBase>();
   int historyIndex = 0;
   Map<String, RouteBase> namedRoutes = new Map<String, RouteBase>();
 
-  RouteBase get currentRoute => history[historyIndex].route;
+  RouteBase get currentRoute => history[historyIndex];
   bool hasPrevious() => historyIndex > 0;
 
   void pushNamed(String name) {
@@ -141,22 +130,20 @@
 
   void push(RouteBase route) {
     assert(!_debugCurrentlyHaveRoute(route));
-    HistoryEntry historyEntry = new HistoryEntry(route: route);
-    history.insert(historyIndex + 1, historyEntry);
+    history.insert(historyIndex + 1, route);
     historyIndex++;
   }
 
   void pop([dynamic result]) {
     if (historyIndex > 0) {
-      HistoryEntry entry = history[historyIndex];
-      entry.route.popState(result);
-      entry.fullyOpaque = false;
+      RouteBase route = history[historyIndex];
+      route.popState(result);
       historyIndex--;
     }
   }
 
   bool _debugCurrentlyHaveRoute(RouteBase route) {
-    return history.any((entry) => entry.route == route);
+    return history.any((candidate) => candidate == route);
   }
 }
 
@@ -201,39 +188,25 @@
 
   Widget build() {
     List<Widget> visibleRoutes = new List<Widget>();
-    for (int i = 0; i < state.history.length; i++) {
-      // Avoid building routes that are not visible
-      if (i + 1 < state.history.length && state.history[i + 1].fullyOpaque)
+    for (int i = state.history.length-1; i >= 0; i -= 1) {
+      RouteBase route = state.history[i];
+      if (!route.hasContent)
         continue;
-      HistoryEntry historyEntry = state.history[i];
-      Widget child = historyEntry.route.build(this, historyEntry.route);
-      if (i == 0) {
-        visibleRoutes.add(child);
-        continue;
-      }
-      if (child == null)
-        continue;
-      WatchableAnimationPerformance performance = historyEntry.route.ensurePerformance(
+      WatchableAnimationPerformance performance = route.ensurePerformance(
         direction: (i <= state.historyIndex) ? Direction.forward : Direction.reverse
       );
-      historyEntry.route.onDismissed = () {
+      route.onDismissed = () {
         setState(() {
-          assert(state.history.contains(historyEntry));
-          state.history.remove(historyEntry);
+          assert(state.history.contains(route));
+          state.history.remove(route);
         });
       };
-      historyEntry.route.onCompleted = () {
-        setState(() {
-          historyEntry.fullyOpaque = historyEntry.route.isOpaque;
-        });
-      };
-      TransitionBase transition = historyEntry.route.buildTransition(
-        key: new ObjectKey(historyEntry),
-        child: child,
-        performance: performance
-      );
-      visibleRoutes.add(transition);
+      Key key = new ObjectKey(route);
+      Widget widget = route.build(key, this, route, performance);
+      visibleRoutes.add(widget);
+      if (route.isActuallyOpaque)
+        break;
     }
-    return new Focus(child: new Stack(visibleRoutes));
+    return new Focus(child: new Stack(visibleRoutes.reversed.toList()));
   }
 }
diff --git a/sky/packages/sky/lib/src/widgets/popup_menu.dart b/sky/packages/sky/lib/src/widgets/popup_menu.dart
index 71186a7..4dac20a 100644
--- a/sky/packages/sky/lib/src/widgets/popup_menu.dart
+++ b/sky/packages/sky/lib/src/widgets/popup_menu.dart
@@ -15,7 +15,7 @@
 import 'package:sky/src/widgets/transitions.dart';
 
 const Duration _kMenuDuration = const Duration(milliseconds: 300);
-double _kMenuCloseIntervalEnd = 2.0 / 3.0;
+const double _kMenuCloseIntervalEnd = 2.0 / 3.0;
 const double _kMenuWidthStep = 56.0;
 const double _kMenuMargin = 16.0; // 24.0 on tablet
 const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
diff --git a/sky/packages/sky/pubspec.yaml b/sky/packages/sky/pubspec.yaml
index bd043c8..fe20c39 100644
--- a/sky/packages/sky/pubspec.yaml
+++ b/sky/packages/sky/pubspec.yaml
@@ -1,5 +1,5 @@
 name: sky
-version: 0.0.45
+version: 0.0.46
 author: Chromium Authors <sky-dev@googlegroups.com>
 description: A framework for writing Sky applications
 homepage: https://github.com/domokit/sky_engine/tree/master/sky/packages/sky
@@ -9,8 +9,8 @@
   mojo_services: 0.0.23
   mojo: 0.0.23
   newton: ^0.1.2
-  sky_engine: ^0.0.22
-  sky_services: ^0.0.22
+  sky_engine: ^0.0.23
+  sky_services: ^0.0.23
   sky_tools: ^0.0.12
   vector_math: ^1.4.3
   intl: ^0.12.4+2
diff --git a/sky/packages/sky_engine/pubspec.yaml b/sky/packages/sky_engine/pubspec.yaml
index 0a200a5..964d6dd 100644
--- a/sky/packages/sky_engine/pubspec.yaml
+++ b/sky/packages/sky_engine/pubspec.yaml
@@ -1,5 +1,5 @@
 name: sky_engine
-version: 0.0.22
+version: 0.0.23
 author: Chromium Authors <sky-dev@googlegroups.com>
 description: Dart SDK extensions for dart:sky
 homepage: https://github.com/domokit/sky_engine
diff --git a/sky/packages/sky_services/pubspec.yaml b/sky/packages/sky_services/pubspec.yaml
index 6486be2..1903c99 100644
--- a/sky/packages/sky_services/pubspec.yaml
+++ b/sky/packages/sky_services/pubspec.yaml
@@ -1,5 +1,5 @@
 name: sky_services
-version: 0.0.22
+version: 0.0.23
 author: Chromium Authors <sky-dev@googlegroups.com>
 description: Mojom interfaces associated with Sky
 homepage: https://github.com/domokit/sky_engine/tree/master/sky/packages/sky_services
diff --git a/sky/unit/test/engine/rect_test_disabled.dart b/sky/unit/test/engine/rect_test_disabled.dart
new file mode 100644
index 0000000..14d7303
--- /dev/null
+++ b/sky/unit/test/engine/rect_test_disabled.dart
@@ -0,0 +1,33 @@
+import 'dart:sky';
+
+import 'package:test/test.dart';
+
+void main() {
+  test("rect accessors", () {
+    Rect r = new Rect.fromLTRB(1.0, 3.0, 5.0, 7.0);
+    expect(r.left, equals(1.0));
+    expect(r.top, equals(3.0));
+    expect(r.right, equals(5.0));
+    expect(r.bottom, equals(7.0));
+  });
+
+  test("rect created by width and height", () {
+    Rect r = new Rect.fromLTWH(1.0, 3.0, 5.0, 7.0);
+    expect(r.left, equals(1.0));
+    expect(r.top, equals(3.0));
+    expect(r.right, equals(6.0));
+    expect(r.bottom, equals(10.0));
+  });
+
+  test("rect intersection", () {
+    Rect r1 = new Rect.fromLTRB(0.0, 0.0, 100.0, 100.0);
+    Rect r2 = new Rect.fromLTRB(50.0, 50.0, 200.0, 200.0);
+    Rect r3 = r1.intersect(r2);
+    expect(r3.left, equals(50.0));
+    expect(r3.top, equals(50.0));
+    expect(r3.right, equals(100.0));
+    expect(r3.bottom, equals(100.0));
+    Rect r4 = r2.intersect(r1);
+    expect(r4, equals(r3));
+  });
+}
diff --git a/sky/unit/test/services/mock_services.dart b/sky/unit/test/services/mock_services.dart
new file mode 100644
index 0000000..264d597
--- /dev/null
+++ b/sky/unit/test/services/mock_services.dart
@@ -0,0 +1,31 @@
+import 'package:sky/src/services/shell.dart' as shell;
+
+// Tests can use ServiceMocker to register replacement implementations
+// of Mojo services.
+class _ServiceMocker {
+  _ServiceMocker() {
+    shell.overrideRequestService = _requestService;
+  }
+
+  // Map of interface names to mock implementations.
+  Map<String, Object> _interfaceMock = new Map<String, Object>();
+
+  bool _requestService(String url, Object proxy) {
+    Object mock = _interfaceMock[proxy.impl.name];
+    if (mock != null) {
+      // Replace the proxy's implementation of the service interface with the
+      // mock.
+      proxy.ptr = mock;
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  // Provide a mock implementation for a Mojo interface.
+  void registerMockService(String interfaceName, Object mock) {
+    _interfaceMock[interfaceName] = mock;
+  }
+}
+
+final _ServiceMocker serviceMocker = new _ServiceMocker();
diff --git a/sky/unit/test/widget/input_test.dart b/sky/unit/test/widget/input_test.dart
new file mode 100644
index 0000000..7628ccd
--- /dev/null
+++ b/sky/unit/test/widget/input_test.dart
@@ -0,0 +1,59 @@
+import 'package:mojo_services/keyboard/keyboard.mojom.dart';
+import 'package:sky/services.dart';
+import 'package:sky/widgets.dart';
+import 'package:test/test.dart';
+
+import 'widget_tester.dart';
+import '../services/mock_services.dart';
+
+class MockKeyboard implements KeyboardService {
+  KeyboardClient client;
+
+  void show(Object client, int type) {
+    this.client = client.impl;
+  }
+
+  void showByRequest() {}
+
+  void hide() {}
+}
+
+void main() {
+  test('Editable text has consistent width', () {
+    WidgetTester tester = new WidgetTester();
+
+    MockKeyboard mockKeyboard = new MockKeyboard();
+    serviceMocker.registerMockService(KeyboardServiceName, mockKeyboard);
+
+    GlobalKey inputKey = new GlobalKey();
+    String inputValue;
+
+    Widget builder() {
+      return new Center(
+        child: new Input(
+          key: inputKey,
+          placeholder: 'Placeholder',
+          onChanged: (value) { inputValue = value; }
+        )
+      );
+    }
+
+    tester.pumpFrame(builder);
+
+    Input input = tester.findWidget((Widget widget) => widget.key == inputKey);
+    Size emptyInputSize = input.renderObject.size;
+
+    // Simulate entry of text through the keyboard.
+    expect(mockKeyboard.client, isNotNull);
+    const String testValue = 'Test';
+    mockKeyboard.client.setComposingText(testValue, testValue.length);
+
+    // Check that the onChanged event handler fired.
+    expect(inputValue, equals(testValue));
+
+    tester.pumpFrame(builder);
+
+    // Check that the Input with text has the same size as the empty Input.
+    expect(input.renderObject.size, equals(emptyInputSize));
+  });
+}