[carnelian] Rename fuchsia-ui to carnelian

Keep fuchsia-ui around until topaz clients are moved over
for a soft transition.

Testing: manual, ran carnelian examples.
Change-Id: Idf9636788ea4b9b129af7bc41dd5b61a642f916d
diff --git a/packages/examples/scenic b/packages/examples/scenic
index e2d55d7..c5254d5 100644
--- a/packages/examples/scenic
+++ b/packages/examples/scenic
@@ -19,7 +19,7 @@
         "//garnet/examples/ui:spinning_square_view",
         "//garnet/examples/ui:tile_view",
         "//garnet/examples/ui:yuv_to_image_pipe",
-        "//garnet/public/rust/fuchsia-ui:spinning_square_rs",
-        "//garnet/public/rust/fuchsia-ui:embedding_rs"
+        "//garnet/public/rust/carnelian:spinning_square_rs",
+        "//garnet/public/rust/carnelian:embedding_rs"
     ]
 }
diff --git a/public/rust/carnelian/BUILD.gn b/public/rust/carnelian/BUILD.gn
new file mode 100644
index 0000000..224473f
--- /dev/null
+++ b/public/rust/carnelian/BUILD.gn
@@ -0,0 +1,135 @@
+# Copyright 2018 The Fuchsia 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("//build/package.gni")
+import("//build/rust/rustc_binary.gni")
+import("//build/rust/rustc_library.gni")
+
+rustc_library("carnelian") {
+  name = "carnelian"
+  version = "0.1.0"
+  edition = "2018"
+  deps = [
+    "//garnet/public/fidl/fuchsia.fonts:fuchsia.fonts-rustc",
+    "//garnet/public/fidl/fuchsia.images:fuchsia.images-rustc",
+    "//garnet/public/fidl/fuchsia.math:fuchsia.math-rustc",
+    "//garnet/public/fidl/fuchsia.ui.app:fuchsia.ui.app-rustc",
+    "//garnet/public/fidl/fuchsia.ui.gfx:fuchsia.ui.gfx-rustc",
+    "//garnet/public/fidl/fuchsia.ui.scenic:fuchsia.ui.scenic-rustc",
+    "//garnet/public/fidl/fuchsia.ui.viewsv1:fuchsia.ui.viewsv1-rustc",
+    "//garnet/public/fidl/fuchsia.ui.viewsv1token:fuchsia.ui.viewsv1token-rustc",
+    "//garnet/public/lib/fidl/rust/fidl",
+    "//garnet/public/rust/fdio",
+    "//garnet/public/rust/fuchsia-app",
+    "//garnet/public/rust/fuchsia-async",
+    "//garnet/public/rust/fuchsia-scenic",
+    "//garnet/public/rust/fuchsia-zircon",
+    "//garnet/public/rust/shared-buffer",
+    "//third_party/rust-crates/rustc_deps:euclid",
+    "//third_party/rust-crates/rustc_deps:failure",
+    "//third_party/rust-crates/rustc_deps:font-rs",
+    "//third_party/rust-crates/rustc_deps:futures-preview",
+    "//third_party/rust-crates/rustc_deps:itertools",
+    "//third_party/rust-crates/rustc_deps:lazy_static",
+    "//third_party/rust-crates/rustc_deps:parking_lot",
+    "//zircon/public/fidl/fuchsia-mem:fuchsia-mem-rustc",
+  ]
+}
+
+rustc_binary("spinning_square_example") {
+  version = "0.1.0"
+  edition = "2018"
+  source_root = "examples/spinning_square.rs"
+  deps = [
+    "//garnet/public/fidl/fuchsia.ui.gfx:fuchsia.ui.gfx-rustc",
+    "//garnet/public/fidl/fuchsia.ui.scenic:fuchsia.ui.scenic-rustc",
+    "//garnet/public/fidl/fuchsia.ui.viewsv1:fuchsia.ui.viewsv1-rustc",
+    "//garnet/public/fidl/fuchsia.ui.viewsv1token:fuchsia.ui.viewsv1token-rustc",
+    "//garnet/public/lib/fidl/rust/fidl",
+    "//garnet/public/rust/carnelian",
+    "//garnet/public/rust/fdio",
+    "//garnet/public/rust/fuchsia-app",
+    "//garnet/public/rust/fuchsia-async",
+    "//garnet/public/rust/fuchsia-scenic",
+    "//garnet/public/rust/fuchsia-zircon",
+    "//garnet/public/rust/shared-buffer",
+    "//third_party/rust-crates/rustc_deps:euclid",
+    "//third_party/rust-crates/rustc_deps:failure",
+    "//third_party/rust-crates/rustc_deps:font-rs",
+    "//third_party/rust-crates/rustc_deps:futures-preview",
+    "//third_party/rust-crates/rustc_deps:itertools",
+    "//third_party/rust-crates/rustc_deps:lazy_static",
+    "//third_party/rust-crates/rustc_deps:parking_lot",
+    "//zircon/public/fidl/fuchsia-mem:fuchsia-mem-rustc",
+  ]
+}
+
+rustc_binary("embedding_example") {
+  version = "0.1.0"
+  edition = "2018"
+  source_root = "examples/embedding.rs"
+  deps = [
+    "//garnet/public/fidl/fuchsia.math:fuchsia.math-rustc",
+    "//garnet/public/fidl/fuchsia.ui.gfx:fuchsia.ui.gfx-rustc",
+    "//garnet/public/fidl/fuchsia.ui.scenic:fuchsia.ui.scenic-rustc",
+    "//garnet/public/fidl/fuchsia.ui.viewsv1:fuchsia.ui.viewsv1-rustc",
+    "//garnet/public/fidl/fuchsia.ui.viewsv1token:fuchsia.ui.viewsv1token-rustc",
+    "//garnet/public/lib/fidl/rust/fidl",
+    "//garnet/public/rust/carnelian",
+    "//garnet/public/rust/fdio",
+    "//garnet/public/rust/fuchsia-app",
+    "//garnet/public/rust/fuchsia-async",
+    "//garnet/public/rust/fuchsia-scenic",
+    "//garnet/public/rust/fuchsia-zircon",
+    "//garnet/public/rust/shared-buffer",
+    "//third_party/rust-crates/rustc_deps:euclid",
+    "//third_party/rust-crates/rustc_deps:failure",
+    "//third_party/rust-crates/rustc_deps:font-rs",
+    "//third_party/rust-crates/rustc_deps:futures-preview",
+    "//third_party/rust-crates/rustc_deps:itertools",
+    "//third_party/rust-crates/rustc_deps:lazy_static",
+    "//third_party/rust-crates/rustc_deps:parking_lot",
+    "//zircon/public/fidl/fuchsia-mem:fuchsia-mem-rustc",
+  ]
+}
+
+package("spinning_square_rs") {
+  deps = [
+    ":spinning_square_example",
+  ]
+
+  binaries = [
+    {
+      name = "app"
+      source = "rust_crates/spinning_square_example"
+    },
+  ]
+
+  meta = [
+    {
+      path = rebase_path("meta/example.cmx")
+      dest = "spinning_square_rs.cmx"
+    },
+  ]
+}
+
+package("embedding_rs") {
+  deps = [
+    ":embedding_example",
+  ]
+
+  binaries = [
+    {
+      name = "app"
+      source = "rust_crates/embedding_example"
+    },
+  ]
+
+  meta = [
+    {
+      path = rebase_path("meta/example.cmx")
+      dest = "embedding_rs.cmx"
+    },
+  ]
+}
diff --git a/public/rust/carnelian/MAINTAINERS b/public/rust/carnelian/MAINTAINERS
new file mode 100644
index 0000000..f64c6d6
--- /dev/null
+++ b/public/rust/carnelian/MAINTAINERS
@@ -0,0 +1,2 @@
+dworsham@google.com
+robtsuk@google.com
diff --git a/public/rust/carnelian/README.md b/public/rust/carnelian/README.md
new file mode 100644
index 0000000..c216416
--- /dev/null
+++ b/public/rust/carnelian/README.md
@@ -0,0 +1,48 @@
+# Introduction
+
+Carnelian is a prototype framework for writing Fuchsia modules in Rust. It is primarily
+intended to be used as the basis of the Ermine session shell.
+
+# Tentative Roadmap
+
+1. Mouse, touch and keyboard input
+1. Text rendering with RustType
+1. Flutter-style flex-box layout
+1. Single-line text editor
+
+## Mouse, touch and keyboard input
+
+Design and implement a way to pass input events from Scenic to app and view assistants.
+
+## Text rendering with RustType
+
+Design and implement a way to render text with RustType and display it with scenic.
+
+## Flutter-style flex-box layout
+
+Implement the basics of flex box layout, similar to the way it is done in
+[Druid](https://docs.rs/druid/0.1.1/druid/).
+
+## Single-line text editor
+
+Design and implement a single-line text editor in Rust so that Ermine does not have to
+launch a Flutter mod for inputs to use to generate suggestions.
+
+# Future Areas
+
+## Command Handling
+
+Mature application frameworks usually have some mechanism for commands that might apply to
+multiple items in the view hierarchy to be handled by the most specific first and proceeding
+to less specific items. This command handling structure can also be used to show/enable menu
+items if Fuchsia ever has such a menu.
+
+## Complex Rendering
+
+Scenic currently allows easy rendering of shapes with colors or textures. One area of exploration
+is using the [Lyon path tessellation tool](https://github.com/nical/lyon) to turn arbitrary paths
+into triangle meshes for use with Scenic's mesh drawing commands.
+
+## Animation
+
+Design and implement a simple animation facility.
diff --git a/public/rust/fuchsia-ui/examples/embedding.rs b/public/rust/carnelian/examples/embedding.rs
similarity index 98%
rename from public/rust/fuchsia-ui/examples/embedding.rs
rename to public/rust/carnelian/examples/embedding.rs
index bc91b03..35eb697 100644
--- a/public/rust/fuchsia-ui/examples/embedding.rs
+++ b/public/rust/carnelian/examples/embedding.rs
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use carnelian::{App, AppAssistant, ViewAssistant, ViewAssistantContext, ViewAssistantPtr};
 use failure::Error;
 use fidl::encoding::OutOfLine;
 use fidl::endpoints::create_endpoints;
@@ -12,7 +13,6 @@
 use fidl_fuchsia_ui_viewsv1token::ViewOwnerMarker;
 use fuchsia_app::client::{App as LaunchedApp, Launcher};
 use fuchsia_scenic::{EntityNode, ImportNode, Material, Rectangle, SessionPtr, ShapeNode};
-use fuchsia_ui::{App, AppAssistant, ViewAssistant, ViewAssistantContext, ViewAssistantPtr};
 use itertools::Itertools;
 use parking_lot::Mutex;
 use std::collections::BTreeMap;
diff --git a/public/rust/fuchsia-ui/examples/spinning_square.rs b/public/rust/carnelian/examples/spinning_square.rs
similarity index 99%
rename from public/rust/fuchsia-ui/examples/spinning_square.rs
rename to public/rust/carnelian/examples/spinning_square.rs
index 45b4efe..6f94d57 100644
--- a/public/rust/fuchsia-ui/examples/spinning_square.rs
+++ b/public/rust/carnelian/examples/spinning_square.rs
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+use carnelian::{
+    App, AppAssistant, ViewAssistant, ViewAssistantContext, ViewAssistantPtr, ViewKey,
+    ViewMessages, APP,
+};
 use failure::Error;
 use fidl_fuchsia_ui_gfx::{self as gfx, ColorRgba};
 use fuchsia_async::{self as fasync, Interval};
 use fuchsia_scenic::{Material, Rectangle, SessionPtr, ShapeNode};
-use fuchsia_ui::{
-    App, AppAssistant, ViewAssistant, ViewAssistantContext, ViewAssistantPtr, ViewKey,
-    ViewMessages, APP,
-};
 use fuchsia_zircon::{ClockId, Duration, Time};
 use futures::StreamExt;
 use parking_lot::Mutex;
diff --git a/public/rust/carnelian/meta/example.cmx b/public/rust/carnelian/meta/example.cmx
new file mode 100644
index 0000000..b429269
--- /dev/null
+++ b/public/rust/carnelian/meta/example.cmx
@@ -0,0 +1,16 @@
+{
+    "program": {
+        "binary": "bin/app"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.sys.Environment",
+            "fuchsia.sys.Launcher",
+            "fuchsia.tracelink.Registry",
+            "fuchsia.ui.policy.Presenter",
+            "fuchsia.ui.policy.Presenter2",
+            "fuchsia.ui.scenic.Scenic",
+            "fuchsia.ui.viewsv1.ViewManager"
+        ]
+    }
+}
diff --git a/public/rust/carnelian/src/app.rs b/public/rust/carnelian/src/app.rs
new file mode 100644
index 0000000..02d9e1b
--- /dev/null
+++ b/public/rust/carnelian/src/app.rs
@@ -0,0 +1,171 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::view::{NewViewParams, ViewAssistantPtr, ViewController, ViewControllerPtr, ViewKey};
+use failure::{Error, ResultExt};
+use fidl::endpoints::{RequestStream, ServerEnd, ServiceMarker};
+use fidl_fuchsia_ui_app as viewsv2;
+use fidl_fuchsia_ui_viewsv1::{
+    ViewManagerMarker, ViewManagerProxy, ViewProviderMarker, ViewProviderRequest::CreateView,
+    ViewProviderRequestStream,
+};
+use fidl_fuchsia_ui_viewsv1token::ViewOwnerMarker;
+use fuchsia_app::{self as component, client::connect_to_service, server::FdioServer};
+use fuchsia_async as fasync;
+use fuchsia_scenic::SessionPtr;
+use fuchsia_zircon::EventPair;
+use futures::{TryFutureExt, TryStreamExt};
+use lazy_static::lazy_static;
+use parking_lot::Mutex;
+use std::{any::Any, collections::BTreeMap, sync::Arc};
+
+/// Trait that a mod author must implement. Currently responsible for creating
+/// a view assistant when the Fuchsia view framework requests that the mod create
+/// a view.
+pub trait AppAssistant: Send {
+    /// This method is responsible for setting up the AppAssistant implementation.
+    /// _It's not clear if this is going to so useful, as anything that isn't
+    /// initialized in the creation of the structure implementing AppAssistant
+    /// is going to have to be represented as an `Option`, which is awkward._
+    fn setup(&mut self) -> Result<(), Error>;
+
+    /// Called when the Fuchsia view system requests that a view be created.
+    fn create_view_assistant(&mut self, session: &SessionPtr) -> Result<ViewAssistantPtr, Error>;
+}
+
+pub type AppAssistantPtr = Mutex<Box<dyn AppAssistant>>;
+
+/// Struct that implements module-wide responsibilties, currently limited
+/// to creating views on request.
+pub struct App {
+    pub(crate) view_manager: ViewManagerProxy,
+    view_controllers: BTreeMap<ViewKey, ViewControllerPtr>,
+    next_key: ViewKey,
+    assistant: Option<AppAssistantPtr>,
+}
+
+/// Reference to the singleton app. _This type is likely to change in the future so
+/// using this type alias might make for easier forward migration._
+pub type AppPtr = Arc<Mutex<App>>;
+
+lazy_static! {
+    /// Singleton reference to the running application
+    pub static ref APP: AppPtr = App::new().expect("Failed to create app");
+}
+
+impl App {
+    fn new() -> Result<AppPtr, Error> {
+        let view_manager = connect_to_service::<ViewManagerMarker>()?;
+        Ok(Arc::new(Mutex::new(App {
+            view_manager,
+            view_controllers: BTreeMap::new(),
+            next_key: 0,
+            assistant: None,
+        })))
+    }
+
+    /// Starts an application based on Carnelian. The `assistant` parameter will
+    /// be used to create new views when asked to do so by the Fuchsia view system.
+    pub fn run(assistant: Box<AppAssistant>) -> Result<(), Error> {
+        let mut executor = fasync::Executor::new().context("Error creating executor")?;
+
+        APP.lock().set_assistant(Mutex::new(assistant));
+
+        let fut = Self::start_services(&APP)?;
+
+        APP.lock().assistant.as_ref().unwrap().lock().setup()?;
+
+        executor.run_singlethreaded(fut)?;
+
+        Ok(())
+    }
+
+    fn set_assistant(&mut self, assistant: AppAssistantPtr) {
+        self.assistant = Some(assistant);
+    }
+
+    /// Method for app and view assistants to use from closures in
+    /// order to reconnect with a specific `ViewController`.
+    pub fn find_view_controller(&self, key: ViewKey) -> Option<&ViewControllerPtr> {
+        self.view_controllers.get(&key)
+    }
+
+    /// Send a message to a specific view controller. Messages not handled by the ViewController
+    /// will be forwarded to the `ViewControllerAssistant`.
+    pub fn send_message(&mut self, target: ViewKey, msg: &Any) {
+        if let Some(view) = self.view_controllers.get(&target) {
+            view.lock().send_message(msg);
+        }
+    }
+
+    pub(crate) fn create_view_assistant(
+        &mut self, session: &SessionPtr,
+    ) -> Result<ViewAssistantPtr, Error> {
+        Ok(self
+            .assistant
+            .as_ref()
+            .unwrap()
+            .lock()
+            .create_view_assistant(session)?)
+    }
+
+    fn create_view(&mut self, req: ServerEnd<ViewOwnerMarker>) -> Result<(), Error> {
+        let view_controller = ViewController::new(self, NewViewParams::V1(req), self.next_key)?;
+        self.view_controllers.insert(self.next_key, view_controller);
+        self.next_key += 1;
+        Ok(())
+    }
+
+    fn create_view2(&mut self, view_token: EventPair) -> Result<(), Error> {
+        let view_controller =
+            ViewController::new(self, NewViewParams::V2(view_token), self.next_key)?;
+        self.view_controllers.insert(self.next_key, view_controller);
+        self.next_key += 1;
+        Ok(())
+    }
+
+    fn spawn_view_provider_server(chan: fasync::Channel, app: &AppPtr) {
+        let app = app.clone();
+        fasync::spawn(
+            ViewProviderRequestStream::from_channel(chan)
+                .try_for_each(move |req| {
+                    let CreateView { view_owner, .. } = req;
+                    app.lock()
+                        .create_view(view_owner)
+                        .unwrap_or_else(|e| eprintln!("create_view error: {:?}", e));
+                    futures::future::ready(Ok(()))
+                })
+                .unwrap_or_else(|e| eprintln!("error running view_provider server: {:?}", e)),
+        )
+    }
+
+    fn spawn_v2_view_provider_server(chan: fasync::Channel, app: &AppPtr) {
+        let app = app.clone();
+        fasync::spawn(
+            viewsv2::ViewProviderRequestStream::from_channel(chan)
+                .try_for_each(move |req| {
+                    let viewsv2::ViewProviderRequest::CreateView { token, .. } = req;
+                    app.lock()
+                        .create_view2(token)
+                        .unwrap_or_else(|e| eprintln!("create_view2 error: {:?}", e));
+                    futures::future::ready(Ok(()))
+                })
+                .unwrap_or_else(|e| eprintln!("error running V2 view_provider server: {:?}", e)),
+        )
+    }
+
+    fn start_services(app: &AppPtr) -> Result<FdioServer, Error> {
+        let app_view_provider = app.clone();
+        let app_view_provider2 = app.clone();
+        let services_server = component::server::ServicesServer::new();
+        let services_server = services_server
+            .add_service((ViewProviderMarker::NAME, move |channel| {
+                Self::spawn_view_provider_server(channel, &app_view_provider);
+            }))
+            .add_service((viewsv2::ViewProviderMarker::NAME, move |channel| {
+                Self::spawn_v2_view_provider_server(channel, &app_view_provider2);
+            }));
+        Ok(services_server.start()?)
+    }
+}
diff --git a/public/rust/carnelian/src/canvas.rs b/public/rust/carnelian/src/canvas.rs
new file mode 100644
index 0000000..486a8fc
--- /dev/null
+++ b/public/rust/carnelian/src/canvas.rs
@@ -0,0 +1,302 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use font_rs::font::{parse, Font, FontError, GlyphBitmap};
+use shared_buffer::SharedBuffer;
+use std::{cmp::max, collections::HashMap};
+
+/// Struct representing an RGBA color value
+#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
+#[allow(missing_docs)]
+pub struct Color {
+    pub r: u8,
+    pub g: u8,
+    pub b: u8,
+    pub a: u8,
+}
+
+/// Struct representing an location
+#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
+#[allow(missing_docs)]
+pub struct Point {
+    pub x: u32,
+    pub y: u32,
+}
+
+/// Struct representing an size
+#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
+#[allow(missing_docs)]
+pub struct Size {
+    pub width: u32,
+    pub height: u32,
+}
+
+/// Struct representing a rectangle area.
+#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
+#[allow(missing_docs)]
+pub struct Rect {
+    pub left: u32,
+    pub top: u32,
+    pub right: u32,
+    pub bottom: u32,
+}
+
+/// Struct combining a foreground and background color.
+#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
+#[allow(missing_docs)]
+pub struct Paint {
+    pub fg: Color,
+    pub bg: Color,
+}
+
+/// Opaque type representing a glyph ID.
+#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
+struct Glyph(u16);
+
+/// Struct representing a glyph at a specific size.
+#[derive(Hash, Eq, PartialEq, Debug)]
+struct GlyphDescriptor {
+    size: u32,
+    glyph: Glyph,
+}
+
+/// Struct containing a font and a cache of renderered glyphs.
+pub struct FontFace<'a> {
+    font: Font<'a>,
+    glyph_cache: HashMap<GlyphDescriptor, GlyphBitmap>,
+}
+
+/// Struct containint font, size and baseline.
+#[allow(missing_docs)]
+pub struct FontDescription<'a, 'b: 'a> {
+    pub face: &'a mut FontFace<'b>,
+    pub size: u32,
+    pub baseline: i32,
+}
+
+#[allow(missing_docs)]
+impl<'a> FontFace<'a> {
+    pub fn new(data: &'a [u8]) -> Result<FontFace<'a>, FontError> {
+        Ok(FontFace {
+            font: parse(data)?,
+            glyph_cache: HashMap::new(),
+        })
+    }
+
+    fn get_glyph(&mut self, glyph: Glyph, size: u32) -> &GlyphBitmap {
+        let font = &self.font;
+        let Glyph(glyph_id) = glyph;
+        self.glyph_cache
+            .entry(GlyphDescriptor { size, glyph })
+            .or_insert_with(|| font.render_glyph(glyph_id, size).unwrap())
+    }
+
+    fn lookup_glyph(&self, scalar: char) -> Option<Glyph> {
+        self.font.lookup_glyph_id(scalar as u32).map(|id| Glyph(id))
+    }
+}
+
+const BYTES_PER_PIXEL: u32 = 4;
+
+/// Trait abstracting a target to which pixels can be written.
+pub trait PixelSink {
+    /// Write an RGBA pixel at an x,y location in the sink.
+    fn write_pixel_at_location(&mut self, x: u32, y: u32, value: &[u8]);
+    /// Write an RGBA pixel at a byte offset within the sink.
+    fn write_pixel_at_offset(&mut self, offset: usize, value: &[u8]);
+}
+
+/// Pixel sink targetting a shared buffer.
+pub struct SharedBufferPixelSink {
+    buffer: SharedBuffer,
+    stride: u32,
+}
+
+impl PixelSink for SharedBufferPixelSink {
+    fn write_pixel_at_location(&mut self, x: u32, y: u32, value: &[u8]) {
+        let offset = (y * self.stride + x * BYTES_PER_PIXEL) as usize;
+        self.write_pixel_at_offset(offset, &value);
+    }
+
+    fn write_pixel_at_offset(&mut self, offset: usize, value: &[u8]) {
+        self.buffer.write_at(offset, &value);
+    }
+}
+
+/// Canvas is used to do simple graphics and text rendering into a
+/// SharedBuffer that can then be displayed using Scenic or
+/// Display Manager.
+pub struct Canvas<T: PixelSink> {
+    // Assumes a pixel format of BGRA8 and a color space of sRGB.
+    pixel_sink: T,
+    stride: u32,
+}
+
+impl<T: PixelSink> Canvas<T> {
+    /// Create a canvas targetting a shared buffer with stride.
+    pub fn new(buffer: SharedBuffer, stride: u32) -> Canvas<SharedBufferPixelSink> {
+        let sink = SharedBufferPixelSink { buffer, stride };
+        Canvas {
+            pixel_sink: sink,
+            stride,
+        }
+    }
+
+    /// Create a canvas targetting a particular pixel sink and
+    /// with a specific row stride in bytes.
+    pub fn new_with_sink(pixel_sink: T, stride: u32) -> Canvas<T> {
+        Canvas { pixel_sink, stride }
+    }
+
+    #[inline]
+    /// Update the pixel at a particular byte offset with a particular
+    /// color.
+    fn write_color_at_offset(&mut self, offset: usize, color: Color) {
+        let pixel = [color.b, color.g, color.r, color.a];
+        self.pixel_sink.write_pixel_at_offset(offset, &pixel);
+    }
+
+    #[inline]
+    fn set_pixel_at_offset(&mut self, offset: usize, value: u8, paint: &Paint) {
+        match value {
+            0 => (),
+            255 => self.write_color_at_offset(offset, paint.fg),
+            _ => {
+                let fg = &paint.fg;
+                let bg = &paint.bg;
+                let a = ((value as u32) * (fg.a as u32)) >> 8;
+                let blend_factor = ((bg.a as u32) * (255 - a)) >> 8;
+                let pixel = [
+                    (((bg.b as u32) * blend_factor + (fg.b as u32) * a) >> 8) as u8,
+                    (((bg.g as u32) * blend_factor + (fg.g as u32) * a) >> 8) as u8,
+                    (((bg.r as u32) * blend_factor + (fg.r as u32) * a) >> 8) as u8,
+                    (blend_factor + (fg.a as u32)) as u8,
+                ];
+                self.pixel_sink.write_pixel_at_offset(offset, &pixel);
+            }
+        }
+    }
+
+    fn draw_glyph_at(&mut self, glyph: &GlyphBitmap, x: i32, y: i32, paint: &Paint) {
+        let glyph_data = &glyph.data.as_slice();
+        let col_stride = BYTES_PER_PIXEL as i32;
+        let row_stride = self.stride as i32;
+        let mut row_offset = y * row_stride + x * col_stride;
+        for glyph_row in glyph_data.chunks(glyph.width) {
+            let mut offset = row_offset;
+            if offset > 0 {
+                for pixel in glyph_row {
+                    let value = *pixel;
+                    self.set_pixel_at_offset(offset as usize, value, paint);
+                    offset += col_stride;
+                }
+            }
+            row_offset += row_stride;
+        }
+    }
+
+    /// Fill a rectangle with a particular color.
+    pub fn fill_rect(&mut self, rect: &Rect, color: Color) {
+        let col_stride = BYTES_PER_PIXEL;
+        let row_stride = self.stride;
+        for y in rect.top..rect.bottom {
+            for x in rect.left..rect.right {
+                let offset = y * row_stride + x * col_stride;
+                self.write_color_at_offset(offset as usize, color);
+            }
+        }
+    }
+
+    /// Draw line of text `text` at location `point` with foreground and background colors specified
+    /// by `paint` and with the typographic characterists in `font`. This method uses
+    /// fixed size cells of size `size` for each character.
+    pub fn fill_text_cells(
+        &mut self, text: &str, point: Point, size: Size, font: &mut FontDescription, paint: &Paint,
+    ) {
+        let mut x = point.x;
+        let advance = size.width;
+        for scalar in text.chars() {
+            let cell = Rect {
+                left: x,
+                top: point.y,
+                right: x + advance,
+                bottom: point.y + size.height,
+            };
+            self.fill_rect(&cell, paint.bg);
+            if scalar != ' ' {
+                if let Some(glyph_id) = font.face.lookup_glyph(scalar) {
+                    let glyph = font.face.get_glyph(glyph_id, font.size);
+                    let x = cell.left as i32 + glyph.left;
+                    let y = cell.top as i32 + font.baseline + glyph.top;
+                    self.draw_glyph_at(glyph, x, y, paint);
+                }
+            }
+            x += advance;
+        }
+    }
+
+    /// Draw line of text `text` at location `point` with foreground and background colors specified
+    /// by `paint` and with the typographic characterists in `font`.
+    pub fn fill_text(
+        &mut self, text: &str, point: Point, font: &mut FontDescription, paint: &Paint,
+    ) {
+        let mut x = point.x;
+        let padding: u32 = max(font.size / 16, 2);
+        for scalar in text.chars() {
+            if scalar != ' ' {
+                if let Some(glyph_id) = font.face.lookup_glyph(scalar) {
+                    let glyph = font.face.get_glyph(glyph_id, font.size);
+                    let glyph_x = x as i32 + glyph.left;
+                    let y = point.y as i32 + font.baseline + glyph.top;
+                    let cell = Rect {
+                        left: x,
+                        top: point.y,
+                        right: x + glyph.width as u32,
+                        bottom: point.y + glyph.height as u32,
+                    };
+                    self.fill_rect(&cell, paint.bg);
+                    self.draw_glyph_at(glyph, glyph_x, y, paint);
+                    x += glyph.width as u32 + padding;
+                }
+            } else {
+                let space_width = (font.size / 3) + padding;
+                let cell = Rect {
+                    left: x,
+                    top: point.y,
+                    right: x + space_width,
+                    bottom: point.y + font.size,
+                };
+                self.fill_rect(&cell, paint.bg);
+                x += space_width;
+            }
+        }
+    }
+
+    /// Measure a line of text `text` and with the typographic characterists in `font`.
+    /// Returns a tuple containing the measured width and height.
+    pub fn measure_text(&mut self, text: &str, font: &mut FontDescription) -> (i32, i32) {
+        let mut max_top = 0;
+        let mut x = 0;
+        const EMPIRICALLY_CHOSEN_PADDING_AMOUNT_DIVISOR: i32 = 16;
+        const EMPIRICALLY_CHOSEN_MINIMUM_PADDING: i32 = 2;
+        let padding: i32 = max(
+            font.size as i32 / EMPIRICALLY_CHOSEN_PADDING_AMOUNT_DIVISOR,
+            EMPIRICALLY_CHOSEN_MINIMUM_PADDING,
+        );
+        for one_char in text.chars() {
+            if one_char != ' ' {
+                if let Some(glyph_id) = font.face.lookup_glyph(one_char) {
+                    let glyph = font.face.get_glyph(glyph_id, font.size as u32);
+                    max_top = max(max_top, -glyph.top);
+                    x += glyph.width as i32 + padding;
+                }
+            } else {
+                const EMPIRICALLY_CHOSEN_SPACE_CHARACTER_WIDTH_DIVIDER: u32 = 3;
+                x +=
+                    (font.size / EMPIRICALLY_CHOSEN_SPACE_CHARACTER_WIDTH_DIVIDER) as i32 + padding;
+            }
+        }
+        (x, max_top)
+    }
+}
diff --git a/public/rust/carnelian/src/lib.rs b/public/rust/carnelian/src/lib.rs
new file mode 100644
index 0000000..fa4243c
--- /dev/null
+++ b/public/rust/carnelian/src/lib.rs
@@ -0,0 +1,28 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Carnelian
+//!
+//! Carnelian is a prototype framework for writing
+//! [Fuchsia](https://fuchsia.googlesource.com/docs/+/HEAD/the-book/README.md)
+//! [modules](https://fuchsia.googlesource.com/docs/+/HEAD/glossary.md#module) in
+//! [Rust](https://www.rust-lang.org/).
+
+#![deny(missing_docs)]
+
+mod app;
+mod canvas;
+mod view;
+
+pub use crate::{
+    app::{App, AppAssistant, AppPtr, APP},
+    canvas::{
+        Canvas, Color, FontDescription, FontFace, Paint, PixelSink, Point, Rect,
+        SharedBufferPixelSink, Size,
+    },
+    view::{
+        ViewAssistant, ViewAssistantContext, ViewAssistantPtr, ViewController, ViewKey,
+        ViewMessages,
+    },
+};
diff --git a/public/rust/carnelian/src/view.rs b/public/rust/carnelian/src/view.rs
new file mode 100644
index 0000000..efbd1f3
--- /dev/null
+++ b/public/rust/carnelian/src/view.rs
@@ -0,0 +1,250 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::app::App;
+use failure::Error;
+use fidl::endpoints::{create_endpoints, create_proxy, ServerEnd};
+use fidl_fuchsia_ui_gfx as gfx;
+use fidl_fuchsia_ui_scenic::{SessionListenerMarker, SessionListenerRequest};
+use fidl_fuchsia_ui_viewsv1::{ViewListenerMarker, ViewListenerRequest};
+use fidl_fuchsia_ui_viewsv1token::ViewOwnerMarker;
+use fuchsia_async as fasync;
+use fuchsia_scenic::{ImportNode, Session, SessionPtr};
+use fuchsia_zircon::{self as zx, EventPair};
+use futures::{TryFutureExt, TryStreamExt};
+use parking_lot::Mutex;
+use std::{any::Any, cell::RefCell, sync::Arc};
+
+/// enum that defines all messages sent with `App::send_message` that
+/// the view struct will understand and process.
+pub enum ViewMessages {
+    /// Message that requests that a view redraw itself.
+    Update,
+}
+
+/// parameter struct passed to setup and update trait methods.
+#[allow(missing_docs)]
+pub struct ViewAssistantContext<'a> {
+    pub view_container: &'a mut fidl_fuchsia_ui_viewsv1::ViewContainerProxy,
+    pub import_node: &'a ImportNode,
+    pub session: &'a SessionPtr,
+    pub key: ViewKey,
+    pub width: f32,
+    pub height: f32,
+}
+
+/// Trait that allows mod developers to customize the behavior of view controllers.
+pub trait ViewAssistant: Send {
+    /// This method is called once when a view is created. It is a good point to create scenic
+    /// commands that apply throughout the lifetime of the view.
+    fn setup(&mut self, context: &ViewAssistantContext) -> Result<(), Error>;
+
+    /// This method is called when a view controller has been asked to update the view.
+    fn update(&mut self, context: &ViewAssistantContext) -> Result<(), Error>;
+
+    /// This method is called when `App::send_message` is called with the associated
+    /// view controller's `ViewKey` and the view controller does not handle the message.
+    fn handle_message(&mut self, message: &Any);
+}
+
+/// Reference to an app assistant. _This type is likely to change in the future so
+/// using this type alias might make for easier forward migration._
+pub type ViewAssistantPtr = Mutex<RefCell<Box<ViewAssistant>>>;
+
+/// Key identifying a view.
+pub type ViewKey = u64;
+
+/// This struct takes care of all the boilerplate needed for implementing a Fuchsia
+/// view, forwarding the interesting implementation points to a struct implementing
+/// the `ViewAssistant` trait.
+pub struct ViewController {
+    #[allow(unused)]
+    view: fidl_fuchsia_ui_viewsv1::ViewProxy,
+    view_container: fidl_fuchsia_ui_viewsv1::ViewContainerProxy,
+    session: SessionPtr,
+    import_node: ImportNode,
+    width: f32,
+    height: f32,
+    #[allow(unused)]
+    key: ViewKey,
+    assistant: ViewAssistantPtr,
+}
+
+pub(crate) enum NewViewParams {
+    V1(ServerEnd<ViewOwnerMarker>),
+    V2(EventPair),
+}
+
+impl ViewController {
+    pub(crate) fn new(
+        app: &mut App, req: NewViewParams, key: ViewKey,
+    ) -> Result<ViewControllerPtr, Error> {
+        let (view, view_server_end) = create_proxy()?;
+        let (view_listener, view_listener_request) = create_endpoints()?;
+        let (mine, theirs) = zx::EventPair::create()?;
+        match req {
+            NewViewParams::V1(req) => {
+                app.view_manager
+                    .create_view(view_server_end, req, view_listener, theirs, None)?;
+            }
+            NewViewParams::V2(view_token) => {
+                app.view_manager.create_view2(
+                    view_server_end,
+                    view_token,
+                    view_listener,
+                    theirs,
+                    None,
+                )?;
+            }
+        }
+        let (scenic, scenic_request) = create_proxy()?;
+        app.view_manager.get_scenic(scenic_request)?;
+        let (session_listener, session_listener_request) = create_endpoints()?;
+        let (session_proxy, session_request) = create_proxy()?;
+        scenic.create_session(session_request, Some(session_listener))?;
+        let session = Session::new(session_proxy);
+
+        let view_assistant = app.create_view_assistant(&session)?;
+
+        let mut import_node = ImportNode::new(session.clone(), mine);
+
+        let (mut view_container, view_container_request) = create_proxy()?;
+
+        view.get_container(view_container_request)?;
+
+        let context = ViewAssistantContext {
+            view_container: &mut view_container,
+            import_node: &mut import_node,
+            session: &session,
+            key,
+            width: 0.0,
+            height: 0.0,
+        };
+        view_assistant.lock().borrow_mut().setup(&context)?;
+
+        let view_controller = ViewController {
+            view,
+            view_container: view_container,
+            session,
+            import_node,
+            height: 0.0,
+            width: 0.0,
+            key,
+            assistant: view_assistant,
+        };
+
+        let view_controller = Arc::new(Mutex::new(view_controller));
+
+        Self::setup_session_listener(&view_controller, session_listener_request)?;
+        Self::setup_view_listener(&view_controller, view_listener_request)?;
+
+        Ok(view_controller)
+    }
+
+    fn setup_session_listener(
+        view_controller: &ViewControllerPtr,
+        session_listener_request: ServerEnd<SessionListenerMarker>,
+    ) -> Result<(), Error> {
+        let view_controller = view_controller.clone();
+        fasync::spawn(
+            session_listener_request
+                .into_stream()?
+                .map_ok(move |request| match request {
+                    SessionListenerRequest::OnScenicEvent { events, .. } => {
+                        view_controller.lock().handle_session_events(events)
+                    }
+                    _ => (),
+                })
+                .try_collect::<()>()
+                .unwrap_or_else(|e| eprintln!("view listener error: {:?}", e)),
+        );
+
+        Ok(())
+    }
+
+    fn setup_view_listener(
+        view_controller: &ViewControllerPtr, view_listener_request: ServerEnd<ViewListenerMarker>,
+    ) -> Result<(), Error> {
+        let view_controller = view_controller.clone();
+        fasync::spawn(
+            view_listener_request
+                .into_stream()?
+                .try_for_each(
+                    move |ViewListenerRequest::OnPropertiesChanged {
+                              properties,
+                              responder,
+                          }| {
+                        view_controller
+                            .lock()
+                            .handle_properties_changed(&properties);
+                        futures::future::ready(responder.send())
+                    },
+                )
+                .unwrap_or_else(|e| eprintln!("view listener error: {:?}", e)),
+        );
+
+        Ok(())
+    }
+
+    fn update(&mut self) {
+        let context = ViewAssistantContext {
+            view_container: &mut self.view_container,
+            import_node: &mut self.import_node,
+            session: &self.session,
+            key: self.key,
+            width: self.width,
+            height: self.height,
+        };
+        self.assistant
+            .lock()
+            .borrow_mut()
+            .update(&context)
+            .unwrap_or_else(|e| eprintln!("Update error: {:?}", e));
+        self.present();
+    }
+
+    fn handle_session_events(&mut self, events: Vec<fidl_fuchsia_ui_scenic::Event>) {
+        events.iter().for_each(|event| match event {
+            fidl_fuchsia_ui_scenic::Event::Gfx(gfx::Event::Metrics(_event)) => {
+                self.update();
+            }
+            _ => (),
+        });
+    }
+
+    fn present(&self) {
+        fasync::spawn(
+            self.session
+                .lock()
+                .present(0)
+                .map_ok(|_| ())
+                .unwrap_or_else(|e| eprintln!("present error: {:?}", e)),
+        );
+    }
+
+    fn handle_properties_changed(&mut self, properties: &fidl_fuchsia_ui_viewsv1::ViewProperties) {
+        if let Some(ref view_properties) = properties.view_layout {
+            self.width = view_properties.size.width;
+            self.height = view_properties.size.height;
+            self.update();
+        }
+    }
+
+    /// This method sends an arbitrary message to this view. If it is not
+    /// handled directly by `ViewController::send_message` it will be forwarded
+    /// to the view assistant.
+    pub fn send_message(&mut self, msg: &Any) {
+        if let Some(view_msg) = msg.downcast_ref::<ViewMessages>() {
+            match view_msg {
+                ViewMessages::Update => {
+                    self.update();
+                }
+            }
+        } else {
+            self.assistant.lock().borrow_mut().handle_message(msg);
+        }
+    }
+}
+
+pub type ViewControllerPtr = Arc<Mutex<ViewController>>;
diff --git a/public/rust/fuchsia-ui/BUILD.gn b/public/rust/fuchsia-ui/BUILD.gn
index b4bd9b6..b6ce28d 100644
--- a/public/rust/fuchsia-ui/BUILD.gn
+++ b/public/rust/fuchsia-ui/BUILD.gn
@@ -36,99 +36,3 @@
     "//zircon/public/fidl/fuchsia-mem:fuchsia-mem-rustc",
   ]
 }
-
-rustc_binary("spinning_square_example") {
-  version = "0.1.0"
-  edition = "2018"
-  source_root = "examples/spinning_square.rs"
-  deps = [
-    "//garnet/public/fidl/fuchsia.ui.gfx:fuchsia.ui.gfx-rustc",
-    "//garnet/public/fidl/fuchsia.ui.scenic:fuchsia.ui.scenic-rustc",
-    "//garnet/public/fidl/fuchsia.ui.viewsv1:fuchsia.ui.viewsv1-rustc",
-    "//garnet/public/fidl/fuchsia.ui.viewsv1token:fuchsia.ui.viewsv1token-rustc",
-    "//garnet/public/lib/fidl/rust/fidl",
-    "//garnet/public/rust/fdio",
-    "//garnet/public/rust/fuchsia-app",
-    "//garnet/public/rust/fuchsia-async",
-    "//garnet/public/rust/fuchsia-scenic",
-    "//garnet/public/rust/fuchsia-ui",
-    "//garnet/public/rust/fuchsia-zircon",
-    "//garnet/public/rust/shared-buffer",
-    "//third_party/rust-crates/rustc_deps:euclid",
-    "//third_party/rust-crates/rustc_deps:failure",
-    "//third_party/rust-crates/rustc_deps:futures-preview",
-    "//third_party/rust-crates/rustc_deps:itertools",
-    "//third_party/rust-crates/rustc_deps:lazy_static",
-    "//third_party/rust-crates/rustc_deps:parking_lot",
-    "//zircon/public/fidl/fuchsia-mem:fuchsia-mem-rustc",
-  ]
-}
-
-rustc_binary("embedding_example") {
-  version = "0.1.0"
-  edition = "2018"
-  source_root = "examples/embedding.rs"
-  deps = [
-    "//garnet/public/fidl/fuchsia.math:fuchsia.math-rustc",
-    "//garnet/public/fidl/fuchsia.ui.gfx:fuchsia.ui.gfx-rustc",
-    "//garnet/public/fidl/fuchsia.ui.scenic:fuchsia.ui.scenic-rustc",
-    "//garnet/public/fidl/fuchsia.ui.viewsv1:fuchsia.ui.viewsv1-rustc",
-    "//garnet/public/fidl/fuchsia.ui.viewsv1token:fuchsia.ui.viewsv1token-rustc",
-    "//garnet/public/lib/fidl/rust/fidl",
-    "//garnet/public/rust/fdio",
-    "//garnet/public/rust/fuchsia-app",
-    "//garnet/public/rust/fuchsia-async",
-    "//garnet/public/rust/fuchsia-scenic",
-    "//garnet/public/rust/fuchsia-ui",
-    "//garnet/public/rust/fuchsia-zircon",
-    "//garnet/public/rust/shared-buffer",
-    "//third_party/rust-crates/rustc_deps:euclid",
-    "//third_party/rust-crates/rustc_deps:failure",
-    "//third_party/rust-crates/rustc_deps:futures-preview",
-    "//third_party/rust-crates/rustc_deps:itertools",
-    "//third_party/rust-crates/rustc_deps:lazy_static",
-    "//third_party/rust-crates/rustc_deps:parking_lot",
-    "//zircon/public/fidl/fuchsia-mem:fuchsia-mem-rustc",
-  ]
-}
-
-package("spinning_square_rs") {
-  deps = [
-    ":spinning_square_example",
-  ]
-
-  binaries = [
-    {
-      name = "app"
-      source = "rust_crates/spinning_square_example"
-    },
-  ]
-
-  meta = [
-    {
-      path = rebase_path("meta/example.cmx")
-      dest = "spinning_square_rs.cmx"
-    },
-  ]
-}
-
-
-package("embedding_rs") {
-  deps = [
-    ":embedding_example",
-  ]
-
-  binaries = [
-    {
-      name = "app"
-      source = "rust_crates/embedding_example"
-    },
-  ]
-
-  meta = [
-    {
-      path = rebase_path("meta/example.cmx")
-      dest = "embedding_rs.cmx"
-    },
-  ]
-}