[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"
- },
- ]
-}