Merge pull request #1059 from vlidholt/master

Improvements to demo game
diff --git a/examples/widgets/card_collection.dart b/examples/widgets/card_collection.dart
index 1983cde..28708fc 100644
--- a/examples/widgets/card_collection.dart
+++ b/examples/widgets/card_collection.dart
@@ -23,8 +23,12 @@
   final TextStyle backgroundTextStyle =
     typography.white.title.copyWith(textAlign: TextAlign.center);
 
-  MixedViewportLayoutState layoutState = new MixedViewportLayoutState();
-  List<CardModel> cardModels;
+  MixedViewportLayoutState _layoutState = new MixedViewportLayoutState();
+  List<CardModel> _cardModels;
+  DismissDirection _dismissDirection = DismissDirection.horizontal;
+  bool _drawerShowing = false;
+  AnimationStatus _drawerStatus = AnimationStatus.dismissed;
+
 
   void initState() {
     List<double> cardHeights = <double>[
@@ -32,7 +36,7 @@
       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) {
       Color color = Color.lerp(colors.Red[300], colors.Blue[900], i / cardHeights.length);
       return new CardModel(i, cardHeights[i], color);
     });
@@ -40,20 +44,91 @@
   }
 
   void dismissCard(CardModel card) {
-    if (cardModels.contains(card)) {
+    if (_cardModels.contains(card)) {
       setState(() {
-        cardModels.remove(card);
+        _cardModels.remove(card);
       });
     }
   }
 
-  Widget builder(int index) {
-    if (index >= cardModels.length)
+  void _handleOpenDrawer() {
+    setState(() {
+      _drawerShowing = true;
+      _drawerStatus = AnimationStatus.forward;
+    });
+  }
+
+  void _handleDrawerDismissed() {
+    setState(() {
+      _drawerStatus = AnimationStatus.dismissed;
+    });
+  }
+
+  String dismissDirectionText(DismissDirection direction) {
+    String s = direction.toString();
+    return "dismiss ${s.substring(s.indexOf('.') + 1)}";
+  }
+
+  void changeDismissDirection(DismissDirection newDismissDirection) {
+    setState(() {
+      _dismissDirection = newDismissDirection;
+      _drawerStatus = AnimationStatus.dismissed;
+    });
+  }
+
+  Widget buildDrawer() {
+    if (_drawerStatus == AnimationStatus.dismissed)
       return null;
 
-    CardModel cardModel = cardModels[index];
+    Widget buildDrawerItem(DismissDirection direction, String icon) {
+      return new DrawerItem(
+        icon: icon,
+        onPressed: () { changeDismissDirection(direction); },
+        child: new Row([
+          new Flexible(child: new Text(dismissDirectionText(direction))),
+          new Radio(
+            value: direction,
+            onChanged: changeDismissDirection,
+            groupValue: _dismissDirection
+          )
+        ])
+      );
+    }
+
+    return new IconTheme(
+      data: const IconThemeData(color: IconThemeColor.black),
+      child: new Drawer(
+        level: 3,
+        showing: _drawerShowing,
+        onDismissed: _handleDrawerDismissed,
+        children: [
+          new DrawerHeader(child: new Text('Dismiss Direction')),
+          buildDrawerItem(DismissDirection.horizontal, 'action/code'),
+          buildDrawerItem(DismissDirection.left, 'navigation/arrow_back'),
+          buildDrawerItem(DismissDirection.right, 'navigation/arrow_forward')
+        ]
+      )
+    );
+  }
+
+  Widget buildToolBar() {
+    return new ToolBar(
+      left: new IconButton(icon: "navigation/menu", onPressed: _handleOpenDrawer),
+      center: new Text('Swipe Away'),
+      right: [
+        new Text(dismissDirectionText(_dismissDirection))
+      ]
+    );
+  }
+
+  Widget buildCard(int index) {
+    if (index >= _cardModels.length)
+      return null;
+
+    CardModel cardModel = _cardModels[index];
     Widget card = new Dismissable(
-      onResized: () { layoutState.invalidate([index]); },
+      direction: _dismissDirection,
+      onResized: () { _layoutState.invalidate([index]); },
       onDismissed: () { dismissCard(cardModel); },
       child: new Card(
         color: cardModel.color,
@@ -65,8 +140,28 @@
       )
     );
 
-    Widget backgroundText =
-      new Text("Swipe in either direction", style: backgroundTextStyle);
+    String backgroundMessage;
+    switch(_dismissDirection) {
+      case DismissDirection.horizontal:
+        backgroundMessage = "Swipe in either direction";
+        break;
+      case DismissDirection.left:
+        backgroundMessage = "Swipe left to dismiss";
+        break;
+      case DismissDirection.right:
+        backgroundMessage = "Swipe right to dismiss";
+        break;
+      default:
+        backgroundMessage = "Unsupported dismissDirection";
+    }
+
+    Widget leftArrowIcon =  new Icon(type: 'navigation/arrow_back', size: 36);
+    if (_dismissDirection == DismissDirection.right)
+      leftArrowIcon = new Opacity(opacity: 0.1, child: leftArrowIcon);
+
+    Widget rightArrowIcon =  new Icon(type: 'navigation/arrow_forward', size: 36);
+    if (_dismissDirection == DismissDirection.left)
+      rightArrowIcon = new Opacity(opacity: 0.1, child: rightArrowIcon);
 
     // The background Widget appears behind the Dismissable card when the card
     // moves to the left or right. The Positioned widget ensures that the
@@ -85,16 +180,20 @@
             height: cardModel.height,
             decoration: new BoxDecoration(backgroundColor: Theme.of(this).primaryColor),
             child: new Row([
-              new Icon(type: 'navigation/arrow_back', size: 36),
-              new Flexible(child: backgroundText),
-              new Icon(type: 'navigation/arrow_forward', size: 36)
+              leftArrowIcon,
+              new Flexible(child: new Text(backgroundMessage, style: backgroundTextStyle)),
+              rightArrowIcon
             ])
           )
         )
       )
     );
 
-    return new Stack([background, card], key: cardModel.key);
+    return new IconTheme(
+      key: cardModel.key,
+      data: const IconThemeData(color: IconThemeColor.white),
+      child: new Stack([background, card])
+    );
   }
 
   Widget build() {
@@ -102,26 +201,24 @@
       padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0),
       decoration: new BoxDecoration(backgroundColor: Theme.of(this).primarySwatch[50]),
       child: new ScrollableMixedWidgetList(
-        builder: builder,
-        token: cardModels.length,
-        layoutState: layoutState
+        builder: buildCard,
+        token: _cardModels.length,
+        layoutState: _layoutState
       )
     );
 
-    return new IconTheme(
-      data: const IconThemeData(color: IconThemeColor.white),
-      child: new Theme(
-        data: new ThemeData(
-          brightness: ThemeBrightness.light,
-          primarySwatch: colors.Blue,
-          accentColor: colors.RedAccent[200]
-        ),
-        child: new Title(
-          title: 'Cards',
-          child: new Scaffold(
-            toolbar: new ToolBar(center: new Text('Swipe Away')),
-            body: cardCollection
-          )
+    return new Theme(
+      data: new ThemeData(
+        brightness: ThemeBrightness.light,
+        primarySwatch: colors.Blue,
+        accentColor: colors.RedAccent[200]
+      ),
+      child: new Title(
+        title: 'Cards',
+        child: new Scaffold(
+          toolbar: buildToolBar(),
+          drawer: buildDrawer(),
+          body: cardCollection
         )
       )
     );
diff --git a/services/sky/document_view.cc b/services/sky/document_view.cc
index 8ce55d3..8632184 100644
--- a/services/sky/document_view.cc
+++ b/services/sky/document_view.cc
@@ -209,7 +209,9 @@
 
 void DocumentView::BeginFrame(base::TimeTicks frame_time) {
   if (sky_view_) {
-    sky_view_->BeginFrame(frame_time);
+    std::unique_ptr<LayerTree> layer_tree = sky_view_->BeginFrame(frame_time);
+    if (layer_tree)
+      current_layer_tree_ = std::move(layer_tree);
     root_layer_->SetSize(sky_view_->display_metrics().physical_size);
   }
 }
@@ -220,13 +222,8 @@
 }
 
 void DocumentView::PaintContents(SkCanvas* canvas, const gfx::Rect& clip) {
-  blink::WebRect rect(clip.x(), clip.y(), clip.width(), clip.height());
-
-  if (sky_view_) {
-    RefPtr<SkPicture> picture = sky_view_->Paint();
-    if (picture)
-      canvas->drawPicture(picture.get());
-  }
+  if (current_layer_tree_)
+    current_layer_tree_->root_layer()->Paint(canvas);
 }
 
 float DocumentView::GetDevicePixelRatio() const {
diff --git a/services/sky/document_view.h b/services/sky/document_view.h
index e67faa9..4d7e7e2 100644
--- a/services/sky/document_view.h
+++ b/services/sky/document_view.h
@@ -22,6 +22,7 @@
 #include "mojo/services/view_manager/public/cpp/view_observer.h"
 #include "services/sky/compositor/layer_client.h"
 #include "services/sky/compositor/layer_host_client.h"
+#include "sky/compositor/layer_tree.h"
 #include "sky/engine/public/platform/ServiceProvider.h"
 #include "sky/engine/public/sky/sky_view.h"
 #include "sky/engine/public/sky/sky_view_client.h"
@@ -128,6 +129,7 @@
   scoped_ptr<DartLibraryProviderImpl> library_provider_;
   scoped_ptr<LayerHost> layer_host_;
   scoped_refptr<TextureLayer> root_layer_;
+  std::unique_ptr<LayerTree> current_layer_tree_;  // TODO(abarth): Integrate //sky/compositor and //services/sky/compositor.
   RasterizerBitmap* bitmap_rasterizer_;  // Used for pixel tests.
   mojo::ServiceRegistryPtr service_registry_;
   scoped_ptr<mojo::StrongBinding<mojo::ServiceProvider>>
diff --git a/sky/compositor/BUILD.gn b/sky/compositor/BUILD.gn
index ffce6c0..c9aeb72 100644
--- a/sky/compositor/BUILD.gn
+++ b/sky/compositor/BUILD.gn
@@ -4,6 +4,8 @@
 
 source_set("compositor") {
   sources = [
+    "layer_tree.cc",
+    "layer_tree.h",
     "layer.cc",
     "layer.h",
   ]
diff --git a/sky/compositor/layer.h b/sky/compositor/layer.h
index 6dcd36e..60b3ded 100644
--- a/sky/compositor/layer.h
+++ b/sky/compositor/layer.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef SKY_COMPOSITOR_H_
-#define SKY_COMPOSITOR_H_
+#ifndef SKY_COMPOSITOR_LAYER_H_
+#define SKY_COMPOSITOR_LAYER_H_
 
 #include <memory>
 #include <vector>
@@ -170,4 +170,4 @@
 
 }  // namespace sky
 
-#endif  // SKY_COMPOSITOR_H_
+#endif  // SKY_COMPOSITOR_LAYER_H_
diff --git a/sky/compositor/layer_tree.cc b/sky/compositor/layer_tree.cc
new file mode 100644
index 0000000..4f1f574
--- /dev/null
+++ b/sky/compositor/layer_tree.cc
@@ -0,0 +1,17 @@
+// Copyright 2015 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.
+
+#include "sky/compositor/layer_tree.h"
+
+#include "sky/compositor/layer.h"
+
+namespace sky {
+
+LayerTree::LayerTree() {
+}
+
+LayerTree::~LayerTree() {
+}
+
+}  // namespace sky
diff --git a/sky/compositor/layer_tree.h b/sky/compositor/layer_tree.h
new file mode 100644
index 0000000..f3bde5a
--- /dev/null
+++ b/sky/compositor/layer_tree.h
@@ -0,0 +1,38 @@
+// Copyright 2015 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.
+
+#ifndef SKY_COMPOSITOR_LAYER_TREE_H_
+#define SKY_COMPOSITOR_LAYER_TREE_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "sky/compositor/layer.h"
+#include "third_party/skia/include/core/SkSize.h"
+
+namespace sky {
+
+class LayerTree {
+ public:
+  LayerTree();
+  ~LayerTree();
+
+  Layer* root_layer() const { return root_layer_.get(); }
+  void set_root_layer(std::unique_ptr<Layer> root_layer) {
+    root_layer_ = std::move(root_layer);
+  }
+
+  const SkISize& frame_size() const { return frame_size_; }
+  void set_frame_size(const SkISize& frame_size) { frame_size_ = frame_size; }
+
+ private:
+  SkISize frame_size_; // Physical pixels.
+  std::unique_ptr<Layer> root_layer_;
+
+  DISALLOW_COPY_AND_ASSIGN(LayerTree);
+};
+
+}  // namespace sky
+
+#endif  // SKY_COMPOSITOR_LAYER_TREE_H_
diff --git a/sky/engine/core/compositing/Scene.cpp b/sky/engine/core/compositing/Scene.cpp
index d58883f..f81cf26 100644
--- a/sky/engine/core/compositing/Scene.cpp
+++ b/sky/engine/core/compositing/Scene.cpp
@@ -16,22 +16,17 @@
 }
 
 Scene::Scene(std::unique_ptr<sky::Layer> rootLayer)
-    : m_rootLayer(std::move(rootLayer))
+    : m_layerTree(new sky::LayerTree())
 {
+    m_layerTree->set_root_layer(std::move(rootLayer));
 }
 
 Scene::~Scene()
 {
 }
 
-PassRefPtr<SkPicture> Scene::createPicture() const
-{
-    SkRTreeFactory rtreeFactory;
-    SkPictureRecorder pictureRecorder;
-    SkCanvas* canvas = pictureRecorder.beginRecording(m_rootLayer->paint_bounds(),
-        &rtreeFactory, SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag);
-    m_rootLayer->Paint(canvas);
-    return adoptRef(pictureRecorder.endRecording());
+std::unique_ptr<sky::LayerTree> Scene::takeLayerTree() {
+  return std::move(m_layerTree);
 }
 
 } // namespace blink
diff --git a/sky/engine/core/compositing/Scene.h b/sky/engine/core/compositing/Scene.h
index af2347f..94e4a5aa 100644
--- a/sky/engine/core/compositing/Scene.h
+++ b/sky/engine/core/compositing/Scene.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "sky/compositor/layer.h"
+#include "sky/compositor/layer_tree.h"
 #include "sky/engine/tonic/dart_wrappable.h"
 #include "sky/engine/wtf/PassRefPtr.h"
 #include "sky/engine/wtf/RefCounted.h"
@@ -21,14 +22,12 @@
     ~Scene() override;
     static PassRefPtr<Scene> create(std::unique_ptr<sky::Layer> rootLayer);
 
-    // While bootstraping the compositing system, we use an SkPicture instead
-    // of a layer tree to back the scene.
-    PassRefPtr<SkPicture> createPicture() const;
+    std::unique_ptr<sky::LayerTree> takeLayerTree();
 
 private:
     explicit Scene(std::unique_ptr<sky::Layer> rootLayer);
 
-    std::unique_ptr<sky::Layer> m_rootLayer;
+    std::unique_ptr<sky::LayerTree> m_layerTree;
 };
 
 } // namespace blink
diff --git a/sky/engine/core/view/View.cpp b/sky/engine/core/view/View.cpp
index c9dbc07..dcb4b39 100644
--- a/sky/engine/core/view/View.cpp
+++ b/sky/engine/core/view/View.cpp
@@ -65,12 +65,13 @@
         m_eventCallback->handleEvent(event.get());
 }
 
-void View::beginFrame(base::TimeTicks frameTime)
+std::unique_ptr<sky::LayerTree> View::beginFrame(base::TimeTicks frameTime)
 {
     if (!m_frameCallback)
-        return;
+        return nullptr;
     double frameTimeMS = (frameTime - base::TimeTicks()).InMillisecondsF();
     m_frameCallback->handleEvent(frameTimeMS);
+    return m_scene ? m_scene->takeLayerTree() : nullptr;
 }
 
 } // namespace blink
diff --git a/sky/engine/core/view/View.h b/sky/engine/core/view/View.h
index d8e7757..aa89a14 100644
--- a/sky/engine/core/view/View.h
+++ b/sky/engine/core/view/View.h
@@ -35,9 +35,6 @@
     double width() const;
     double height() const;
 
-    Picture* picture() const { return m_picture.get(); }
-    void setPicture(Picture* picture) { m_picture = picture; }
-
     Scene* scene() const { return m_scene.get(); }
     void setScene(Scene* scene) { m_scene = scene; }
 
@@ -50,7 +47,7 @@
 
     void setDisplayMetrics(const SkyDisplayMetrics& metrics);
     void handleInputEvent(PassRefPtr<Event> event);
-    void beginFrame(base::TimeTicks frameTime);
+    std::unique_ptr<sky::LayerTree> beginFrame(base::TimeTicks frameTime);
 
 private:
     explicit View(const base::Closure& scheduleFrameCallback);
@@ -60,7 +57,6 @@
     OwnPtr<EventCallback> m_eventCallback;
     OwnPtr<VoidCallback> m_metricsChangedCallback;
     OwnPtr<FrameCallback> m_frameCallback;
-    RefPtr<Picture> m_picture;
     RefPtr<Scene> m_scene;
 };
 
diff --git a/sky/engine/core/view/View.idl b/sky/engine/core/view/View.idl
index 569ed21..3c5e025 100644
--- a/sky/engine/core/view/View.idl
+++ b/sky/engine/core/view/View.idl
@@ -14,7 +14,6 @@
   readonly attribute double width;
   readonly attribute double height;
 
-  attribute Picture picture;
   attribute Scene scene;
 
   void setEventCallback(EventCallback callback);
diff --git a/sky/engine/public/sky/BUILD.gn b/sky/engine/public/sky/BUILD.gn
index 19c32b2..9b7a274 100644
--- a/sky/engine/public/sky/BUILD.gn
+++ b/sky/engine/public/sky/BUILD.gn
@@ -8,6 +8,7 @@
     "//mojo/public/cpp/system",
     "//mojo/services/network/public/interfaces",
     "//skia",
+    "//sky/compositor",
     "//sky/engine/core",
     "//sky/engine/platform",
     "//sky/engine/wtf",
diff --git a/sky/engine/public/sky/sky_view.cc b/sky/engine/public/sky/sky_view.cc
index de5cc2d..54809ac 100644
--- a/sky/engine/public/sky/sky_view.cc
+++ b/sky/engine/public/sky/sky_view.cc
@@ -66,16 +66,8 @@
   dart_controller_->RunFromSnapshot(snapshot.Pass());
 }
 
-void SkyView::BeginFrame(base::TimeTicks frame_time) {
-  view_->beginFrame(frame_time);
-}
-
-PassRefPtr<SkPicture> SkyView::Paint() {
-  if (Scene* scene = view_->scene())
-    return scene->createPicture();
-  if (Picture* picture = view_->picture())
-    return picture->toSkia();
-  return nullptr;
+std::unique_ptr<sky::LayerTree> SkyView::BeginFrame(base::TimeTicks frame_time) {
+  return view_->beginFrame(frame_time);
 }
 
 void SkyView::HandleInputEvent(const WebInputEvent& inputEvent) {
diff --git a/sky/engine/public/sky/sky_view.h b/sky/engine/public/sky/sky_view.h
index 8eaae79..a5b0231 100644
--- a/sky/engine/public/sky/sky_view.h
+++ b/sky/engine/public/sky/sky_view.h
@@ -11,6 +11,7 @@
 #include "base/time/time.h"
 #include "mojo/public/cpp/system/data_pipe.h"
 #include "mojo/services/network/public/interfaces/url_loader.mojom.h"
+#include "sky/compositor/layer_tree.h"
 #include "sky/engine/public/platform/WebCommon.h"
 #include "sky/engine/public/platform/WebURL.h"
 #include "sky/engine/public/platform/sky_display_metrics.h"
@@ -33,7 +34,8 @@
 
   const SkyDisplayMetrics& display_metrics() const { return display_metrics_; }
   void SetDisplayMetrics(const SkyDisplayMetrics& metrics);
-  void BeginFrame(base::TimeTicks frame_time);
+
+  std::unique_ptr<sky::LayerTree> BeginFrame(base::TimeTicks frame_time);
 
   void CreateView(const String& name);
 
@@ -42,7 +44,6 @@
   void RunFromSnapshot(const WebString& name,
                        mojo::ScopedDataPipeConsumerHandle snapshot);
 
-  PassRefPtr<SkPicture> Paint();
   void HandleInputEvent(const WebInputEvent& event);
 
   void StartDartTracing();
diff --git a/sky/packages/sky/lib/painting/box_painter.dart b/sky/packages/sky/lib/painting/box_painter.dart
index 7fb94f5..0cb2efd 100644
--- a/sky/packages/sky/lib/painting/box_painter.dart
+++ b/sky/packages/sky/lib/painting/box_painter.dart
@@ -45,6 +45,8 @@
   /// The offset from the left
   final double left;
 
+  bool get isNonNegative => top >= 0.0 && right >= 0.0 && bottom >= 0.0 && left >= 0.0;
+
   bool operator ==(other) {
     if (identical(this, other))
       return true;
diff --git a/sky/packages/sky/lib/src/rendering/shifted_box.dart b/sky/packages/sky/lib/src/rendering/shifted_box.dart
index a7c32f6..1376b83 100644
--- a/sky/packages/sky/lib/src/rendering/shifted_box.dart
+++ b/sky/packages/sky/lib/src/rendering/shifted_box.dart
@@ -79,6 +79,7 @@
   EdgeDims get padding => _padding;
   void set padding (EdgeDims value) {
     assert(value != null);
+    assert(value.isNonNegative);
     if (_padding == value)
       return;
     _padding = value;
diff --git a/sky/packages/sky/lib/src/widgets/basic.dart b/sky/packages/sky/lib/src/widgets/basic.dart
index 956ad20..b986c2a 100644
--- a/sky/packages/sky/lib/src/widgets/basic.dart
+++ b/sky/packages/sky/lib/src/widgets/basic.dart
@@ -393,7 +393,10 @@
     this.margin,
     this.padding,
     this.transform
-  }) : super(key: key);
+  }) : super(key: key) {
+    assert(margin == null || margin.isNonNegative);
+    assert(padding == null || padding.isNonNegative);
+  }
 
   final Widget child;
   final BoxConstraints constraints;
diff --git a/sky/packages/sky/lib/src/widgets/dismissable.dart b/sky/packages/sky/lib/src/widgets/dismissable.dart
index fa511a7..892dbcf 100644
--- a/sky/packages/sky/lib/src/widgets/dismissable.dart
+++ b/sky/packages/sky/lib/src/widgets/dismissable.dart
@@ -20,6 +20,15 @@
 const double _kFlingVelocityScale = 1.0 / 300.0;
 const double _kDismissCardThreshold = 0.4;
 
+enum DismissDirection {
+  vertical,
+  horizontal,
+  left,
+  right,
+  up,
+  down
+}
+
 typedef void ResizedCallback();
 typedef void DismissedCallback();
 
@@ -29,41 +38,51 @@
     Key key,
     this.child,
     this.onResized,
-    this.onDismissed
-    // TODO(hansmuller): direction
+    this.onDismissed,
+    this.direction: DismissDirection.horizontal
   }) : super(key: key);
 
   Widget child;
   ResizedCallback onResized;
   DismissedCallback onDismissed;
+  DismissDirection direction;
 
   AnimationPerformance _fadePerformance;
   AnimationPerformance _resizePerformance;
 
   Size _size;
-  double _dragX = 0.0;
+  double _dragExtent = 0.0;
   bool _dragUnderway = false;
 
   void initState() {
     _fadePerformance = new AnimationPerformance(duration: _kCardDismissFadeout);
   }
 
+  void syncConstructorArguments(Dismissable source) {
+    child = source.child;
+    onResized = source.onResized;
+    onDismissed = source.onDismissed;
+    direction = source.direction;
+  }
+
+  bool get _directionIsYAxis {
+    return
+      direction == DismissDirection.vertical ||
+      direction == DismissDirection.up ||
+      direction == DismissDirection.down;
+  }
+
   void _handleFadeCompleted() {
     if (!_dragUnderway)
       _startResizePerformance();
   }
 
-  void syncConstructorArguments(Dismissable source) {
-    child = source.child;
-    onResized = source.onResized;
-    onDismissed = source.onDismissed;
-  }
-
   Point get _activeCardDragEndPoint {
     if (!_isActive)
       return Point.origin;
     assert(_size != null);
-    return new Point(_dragX.sign * _size.width * _kDismissCardThreshold, 0.0);
+    double extent = _directionIsYAxis ? _size.height : _size.width;
+    return new Point(_dragExtent.sign * extent * _kDismissCardThreshold, 0.0);
   }
 
   bool get _isActive {
@@ -104,19 +123,38 @@
     if (_fadePerformance.isAnimating)
       return;
     _dragUnderway = true;
-    _dragX = 0.0;
+    _dragExtent = 0.0;
     _fadePerformance.progress = 0.0;
   }
 
   void _handleScrollUpdate(double scrollOffset) {
     if (!_isActive || _fadePerformance.isAnimating)
       return;
-    double oldDragX = _dragX;
-    _dragX -= scrollOffset;
-    if (oldDragX.sign != _dragX.sign)
+
+    double oldDragExtent = _dragExtent;
+    switch(direction) {
+      case DismissDirection.horizontal:
+      case DismissDirection.vertical:
+        _dragExtent -= scrollOffset;
+        break;
+
+      case DismissDirection.up:
+      case DismissDirection.left:
+        if (_dragExtent - scrollOffset < 0)
+          _dragExtent -= scrollOffset;
+        break;
+
+      case DismissDirection.down:
+      case DismissDirection.right:
+        if (_dragExtent - scrollOffset > 0)
+          _dragExtent -= scrollOffset;
+        break;
+    }
+
+    if (oldDragExtent.sign != _dragExtent.sign)
       setState(() {}); // Rebuild to update the new drag endpoint.
     if (!_fadePerformance.isAnimating)
-      _fadePerformance.progress = _dragX.abs() / (_size.width * _kDismissCardThreshold);
+      _fadePerformance.progress = _dragExtent.abs() / (_size.width * _kDismissCardThreshold);
   }
 
   _handleScrollEnd() {
@@ -129,10 +167,33 @@
       _fadePerformance.reverse();
   }
 
-  bool _isHorizontalFlingGesture(sky.GestureEvent event) {
-    double vx = event.velocityX.abs();
-    double vy = event.velocityY.abs();
-    return vx - vy > _kMinFlingVelocityDelta && vx > _kMinFlingVelocity;
+  bool _isFlingGesture(sky.GestureEvent event) {
+    double vx = event.velocityX;
+    double vy = event.velocityY;
+    if (_directionIsYAxis) {
+      if (vy.abs() - vx.abs() < _kMinFlingVelocityDelta)
+        return false;
+      switch(direction) {
+        case DismissDirection.vertical:
+          return vy.abs() > _kMinFlingVelocity;
+        case DismissDirection.up:
+          return -vy > _kMinFlingVelocity;
+        default:
+          return vy > _kMinFlingVelocity;
+      }
+    } else {
+      if (vx.abs() - vy.abs() < _kMinFlingVelocityDelta)
+        return false;
+      switch(direction) {
+        case DismissDirection.horizontal:
+          return vx.abs() > _kMinFlingVelocity;
+        case DismissDirection.left:
+          return -vx > _kMinFlingVelocity;
+        default:
+          return vx > _kMinFlingVelocity;
+      }
+    }
+    return false;
   }
 
   EventDisposition _handleFlingStart(sky.GestureEvent event) {
@@ -142,9 +203,10 @@
     _dragUnderway = false;
     if (_fadePerformance.isCompleted) { // drag then fling
       _startResizePerformance();
-    } else if (_isHorizontalFlingGesture(event)) {
-      _dragX = event.velocityX.sign;
-      _fadePerformance.fling(velocity: event.velocityX.abs() * _kFlingVelocityScale);
+    } else if (_isFlingGesture(event)) {
+      double velocity = _directionIsYAxis ? event.velocityY : event.velocityX;
+      _dragExtent = velocity.sign;
+      _fadePerformance.fling(velocity: velocity.abs() * _kFlingVelocityScale);
     } else {
       _fadePerformance.reverse();
     }
@@ -160,8 +222,8 @@
 
   Widget build() {
     if (_resizePerformance != null) {
-      AnimatedValue<double> dismissHeight = new AnimatedValue<double>(
-        _size.height,
+      AnimatedValue<double> squashAxisExtent = new AnimatedValue<double>(
+        _directionIsYAxis ? _size.width : _size.height,
         end: 0.0,
         curve: ease,
         interval: _kCardDismissResizeInterval
@@ -170,13 +232,18 @@
       return new SquashTransition(
         performance: _resizePerformance,
         direction: Direction.forward,
-        height: dismissHeight);
+        width: _directionIsYAxis ? squashAxisExtent : null,
+        height: !_directionIsYAxis ? squashAxisExtent : null
+      );
     }
 
     return new GestureDetector(
-      onHorizontalScrollStart: _handleScrollStart,
-      onHorizontalScrollUpdate: _handleScrollUpdate,
-      onHorizontalScrollEnd: _handleScrollEnd,
+      onHorizontalScrollStart: _directionIsYAxis ? null : _handleScrollStart,
+      onHorizontalScrollUpdate: _directionIsYAxis ? null : _handleScrollUpdate,
+      onHorizontalScrollEnd: _directionIsYAxis ? null : _handleScrollEnd,
+      onVerticalScrollStart: _directionIsYAxis ? _handleScrollStart : null,
+      onVerticalScrollUpdate: _directionIsYAxis ? _handleScrollUpdate : null,
+      onVerticalScrollEnd: _directionIsYAxis ? _handleScrollEnd : null,
       child: new Listener(
         onGestureFlingStart: _handleFlingStart,
         child: new SizeObserver(
diff --git a/sky/shell/BUILD.gn b/sky/shell/BUILD.gn
index 4b8c350..aa417c9 100644
--- a/sky/shell/BUILD.gn
+++ b/sky/shell/BUILD.gn
@@ -18,6 +18,7 @@
   "//mojo/services/network/public/interfaces",
   "//services/asset_bundle:lib",
   "//skia",
+  "//sky/compositor",
   "//sky/engine",
   "//sky/engine/tonic",
   "//sky/engine/wtf",
diff --git a/sky/shell/gpu/rasterizer.cc b/sky/shell/gpu/rasterizer.cc
index d2e930d..391b585 100644
--- a/sky/shell/gpu/rasterizer.cc
+++ b/sky/shell/gpu/rasterizer.cc
@@ -5,6 +5,7 @@
 #include "sky/shell/gpu/rasterizer.h"
 
 #include "base/trace_event/trace_event.h"
+#include "sky/compositor/layer.h"
 #include "sky/shell/gpu/ganesh_context.h"
 #include "sky/shell/gpu/ganesh_surface.h"
 #include "sky/shell/gpu/picture_serializer.h"
@@ -35,33 +36,31 @@
   CHECK(surface_) << "GLSurface required.";
 }
 
-void Rasterizer::Draw(PassRefPtr<SkPicture> picture, gfx::Size size) {
+void Rasterizer::Draw(scoped_ptr<LayerTree> layer_tree) {
   TRACE_EVENT0("sky", "Rasterizer::Draw");
 
   if (!surface_)
     return;
 
+  gfx::Size size(layer_tree->frame_size().width(),
+                 layer_tree->frame_size().height());
+
   if (surface_->GetSize() != size)
     surface_->Resize(size);
 
   EnsureGLContext();
   CHECK(context_->MakeCurrent(surface_.get()));
   EnsureGaneshSurface(surface_->GetBackingFrameBufferObject(), size);
+  SkCanvas* canvas = ganesh_surface_->canvas();
 
-  DrawPicture(picture.get());
+  canvas->clear(SK_ColorBLACK);
+  layer_tree->root_layer()->Paint(canvas);
+  canvas->flush();
   surface_->SwapBuffers();
 
   // SerializePicture("/data/data/org.domokit.sky.shell/cache/layer0.skp", picture.get());
 }
 
-void Rasterizer::DrawPicture(SkPicture* picture) {
-  TRACE_EVENT0("sky", "Rasterizer::DrawPicture");
-  SkCanvas* canvas = ganesh_surface_->canvas();
-  canvas->clear(SK_ColorBLACK);
-  canvas->drawPicture(picture);
-  canvas->flush();
-}
-
 void Rasterizer::OnOutputSurfaceDestroyed() {
   if (context_) {
     CHECK(context_->MakeCurrent(surface_.get()));
diff --git a/sky/shell/gpu/rasterizer.h b/sky/shell/gpu/rasterizer.h
index ff8b687..21dcd0d 100644
--- a/sky/shell/gpu/rasterizer.h
+++ b/sky/shell/gpu/rasterizer.h
@@ -34,12 +34,11 @@
 
   void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) override;
   void OnOutputSurfaceDestroyed() override;
-  void Draw(PassRefPtr<SkPicture> picture, gfx::Size size) override;
+  void Draw(scoped_ptr<LayerTree> layer_tree) override;
 
  private:
   void EnsureGLContext();
   void EnsureGaneshSurface(intptr_t window_fbo, const gfx::Size& size);
-  void DrawPicture(SkPicture* picture);
 
   scoped_refptr<gfx::GLShareGroup> share_group_;
   scoped_refptr<gfx::GLSurface> surface_;
diff --git a/sky/shell/gpu_delegate.h b/sky/shell/gpu_delegate.h
index 43cca28..b3bbe54 100644
--- a/sky/shell/gpu_delegate.h
+++ b/sky/shell/gpu_delegate.h
@@ -5,7 +5,10 @@
 #ifndef SKY_SHELL_GPU_DELEGATE_H_
 #define SKY_SHELL_GPU_DELEGATE_H_
 
-#include "sky/engine/wtf/PassRefPtr.h"
+#include <memory>
+
+#include "base/memory/scoped_ptr.h"
+#include "sky/compositor/layer_tree.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/native_widget_types.h"
 
@@ -18,7 +21,7 @@
  public:
   virtual void OnAcceleratedWidgetAvailable(gfx::AcceleratedWidget widget) = 0;
   virtual void OnOutputSurfaceDestroyed() = 0;
-  virtual void Draw(PassRefPtr<SkPicture> picture, gfx::Size size) = 0;
+  virtual void Draw(scoped_ptr<LayerTree> layer_tree) = 0;
 
  protected:
   virtual ~GPUDelegate();
diff --git a/sky/shell/ui/animator.cc b/sky/shell/ui/animator.cc
index 1fc3754..9ee517d 100644
--- a/sky/shell/ui/animator.cc
+++ b/sky/shell/ui/animator.cc
@@ -71,13 +71,18 @@
   base::TimeTicks frame_time = time_stamp ?
       base::TimeTicks::FromInternalValue(time_stamp) : base::TimeTicks::Now();
 
-  engine_->BeginFrame(frame_time);
-  RefPtr<SkPicture> picture = engine_->Paint();
+  scoped_ptr<LayerTree> layer_tree =
+      make_scoped_ptr(engine_->BeginFrame(frame_time).release());
+
+  if (!layer_tree) {
+    OnFrameComplete();
+    return;
+  }
 
   config_.gpu_task_runner->PostTaskAndReply(
       FROM_HERE,
-      base::Bind(&GPUDelegate::Draw, config_.gpu_delegate, picture,
-                 engine_->physical_size()),
+      base::Bind(&GPUDelegate::Draw, config_.gpu_delegate,
+                 base::Passed(&layer_tree)),
       base::Bind(&Animator::OnFrameComplete, weak_factory_.GetWeakPtr()));
 }
 
diff --git a/sky/shell/ui/engine.cc b/sky/shell/ui/engine.cc
index b9a3efd..f3f4251 100644
--- a/sky/shell/ui/engine.cc
+++ b/sky/shell/ui/engine.cc
@@ -96,27 +96,18 @@
   blink::initialize(g_platform_impl);
 }
 
-void Engine::BeginFrame(base::TimeTicks frame_time) {
+std::unique_ptr<LayerTree> Engine::BeginFrame(base::TimeTicks frame_time) {
   TRACE_EVENT0("sky", "Engine::BeginFrame");
 
-  if (sky_view_)
-    sky_view_->BeginFrame(frame_time);
-}
+  if (!sky_view_)
+    return nullptr;
 
-PassRefPtr<SkPicture> Engine::Paint() {
-  TRACE_EVENT0("sky", "Engine::Paint");
-
-  if (sky_view_) {
-    RefPtr<SkPicture> picture = sky_view_->Paint();
-    if (picture)
-      return picture.release();
+  std::unique_ptr<LayerTree> layer_tree = sky_view_->BeginFrame(frame_time);
+  if (layer_tree) {
+    layer_tree->set_frame_size(SkISize::Make(physical_size_.width(),
+                                             physical_size_.height()));
   }
-
-  SkPictureRecorder recorder;
-  SkCanvas* canvas = recorder.beginRecording(
-      physical_size_.width(), physical_size_.height());
-  canvas->clear(SK_ColorBLACK);
-  return adoptRef(recorder.endRecordingAsPicture());
+  return layer_tree;
 }
 
 void Engine::ConnectToEngine(mojo::InterfaceRequest<SkyEngine> request) {
@@ -272,8 +263,6 @@
 }
 
 void Engine::SaveFrameToSkPicture(const base::FilePath& destination) {
-  PassRefPtr<SkPicture> picture = Paint();
-  SerializePicture(destination.AsUTF8Unsafe().c_str(), picture.get());
 }
 
 }  // namespace shell
diff --git a/sky/shell/ui/engine.h b/sky/shell/ui/engine.h
index 8b68230..794089f 100644
--- a/sky/shell/ui/engine.h
+++ b/sky/shell/ui/engine.h
@@ -55,9 +55,7 @@
 
   static void Init();
 
-  void BeginFrame(base::TimeTicks frame_time);
-  PassRefPtr<SkPicture> Paint();
-  const gfx::Size& physical_size() { return physical_size_; }
+  std::unique_ptr<LayerTree> BeginFrame(base::TimeTicks frame_time);
 
   void StartDartTracing();
   void StopDartTracing(mojo::ScopedDataPipeProducerHandle producer);