blob: fd758572f4b5ff606756dedc8d6f65a33a0ce33b [file] [log] [blame]
// 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::ask_box::AskBox;
use failure::{Error, ResultExt};
use fidl::encoding::Decodable;
use fidl::encoding::OutOfLine;
use fidl::endpoints::{create_proxy, ClientEnd, ServerEnd};
use fidl_fuchsia_math::{InsetF, RectF, SizeF};
use fidl_fuchsia_modular::{
AddMod, Intent, PuppetMasterMarker, PuppetMasterProxy, StoryCommand, StoryPuppetMasterProxy,
SurfaceArrangement, SurfaceDependency, SurfaceRelation,
};
use fidl_fuchsia_ui_gfx::{self as gfx, ColorRgba};
use fidl_fuchsia_ui_input::KeyboardEvent;
use fidl_fuchsia_ui_scenic::{SessionListenerMarker, SessionListenerRequest};
use fidl_fuchsia_ui_viewsv1::{
CustomFocusBehavior, ViewContainerListenerMarker, ViewContainerListenerRequest, ViewLayout,
ViewListenerMarker, ViewListenerRequest, ViewProperties,
};
use fuchsia_app::client::connect_to_service;
use fuchsia_async as fasync;
use fuchsia_scenic::{EntityNode, ImportNode, Material, Rectangle, SessionPtr, ShapeNode};
use fuchsia_zircon as zx;
use futures::{future::ready as fready, TryFutureExt, TryStreamExt};
use itertools::Itertools;
use parking_lot::Mutex;
use std::collections::BTreeMap;
use std::sync::Arc;
use std::time::SystemTime;
fn random_story_name() -> String {
let secs = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
Ok(n) => n.as_secs(),
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
};
format!("ermine-story-{}", secs)
}
fn random_mod_name() -> String {
let secs = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
Ok(n) => n.as_secs(),
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
};
format!("ermine-mod-{}", secs)
}
fn inset(rect: &mut RectF, border: f32) {
let inset = border.min(rect.width / 0.3).min(rect.height / 0.3);
rect.x += inset;
rect.y += inset;
let inset_width = inset * 2.0;
rect.width = rect.width - inset_width;
rect.height = rect.height - inset_width;
}
struct ViewData {
key: u32,
url: String,
story_id: String,
allow_focus: bool,
bounds: Option<RectF>,
host_node: EntityNode,
}
impl ViewData {
pub fn new(
key: u32,
url: String,
story_id: String,
allow_focus: bool,
host_node: EntityNode,
) -> ViewData {
ViewData {
key: key,
url: url,
story_id: story_id,
bounds: None,
allow_focus: allow_focus,
host_node: host_node,
}
}
}
pub struct ErmineView {
// Must keep the view proxy alive or the view goes away.
_view: fidl_fuchsia_ui_viewsv1::ViewProxy,
puppet_master: PuppetMasterProxy,
story_puppet_masters: BTreeMap<String, StoryPuppetMasterProxy>,
view_container: fidl_fuchsia_ui_viewsv1::ViewContainerProxy,
session: SessionPtr,
import_node: ImportNode,
background_node: ShapeNode,
container_node: EntityNode,
views: BTreeMap<u32, ViewData>,
width: f32,
height: f32,
ask_box: Option<AskBox>,
}
pub type ErmineViewPtr = Arc<Mutex<ErmineView>>;
impl ErmineView {
pub fn new(
view_listener_request: ServerEnd<ViewListenerMarker>,
view: fidl_fuchsia_ui_viewsv1::ViewProxy,
mine: zx::EventPair,
session: SessionPtr,
session_listener_server: ServerEnd<SessionListenerMarker>,
) -> Result<ErmineViewPtr, Error> {
let (view_container_proxy, view_container_request) = create_proxy()?;
view.get_container(view_container_request)?;
let puppet_master = connect_to_service::<PuppetMasterMarker>()?;
let view_controller = ErmineView {
_view: view,
puppet_master,
story_puppet_masters: BTreeMap::new(),
view_container: view_container_proxy,
session: session.clone(),
import_node: ImportNode::new(session.clone(), mine),
background_node: ShapeNode::new(session.clone()),
container_node: EntityNode::new(session.clone()),
views: BTreeMap::new(),
width: 0.0,
height: 0.0,
ask_box: None,
};
let view_controller = Arc::new(Mutex::new(view_controller));
Self::setup_session_listener(&view_controller, session_listener_server)?;
Self::setup_view_listener(&view_controller, view_listener_request)?;
Self::setup_view_container_listener(&view_controller)?;
Self::finish_setup_scene(&view_controller);
Ok(view_controller)
}
fn setup_session_listener(
view_controller: &ErmineViewPtr,
session_listener_server: ServerEnd<SessionListenerMarker>,
) -> Result<(), Error> {
let view_controller = view_controller.clone();
fasync::spawn(
session_listener_server
.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!("session listener error: {:?}", e)),
);
Ok(())
}
fn setup_view_listener(
view_controller: &ErmineViewPtr,
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_properies_changed(&properties);
fready(responder.send())
},
)
.unwrap_or_else(|e| eprintln!("view listener error: {:?}", e)),
);
Ok(())
}
fn setup_view_container_listener(view_controller: &ErmineViewPtr) -> Result<(), Error> {
let view_controller = view_controller.clone();
let (view_container_listener_client, view_container_listener_server) =
zx::Channel::create()?;
let view_container_listener = ClientEnd::new(view_container_listener_client);
let view_container_listener_request =
ServerEnd::<ViewContainerListenerMarker>::new(view_container_listener_server);
view_controller.lock().view_container.set_listener(Some(view_container_listener))?;
fasync::spawn(
view_container_listener_request
.into_stream()?
.try_for_each(move |event| match event {
ViewContainerListenerRequest::OnChildAttached { responder, .. } => {
view_controller.lock().update();
fready(responder.send())
}
ViewContainerListenerRequest::OnChildUnavailable { responder, child_key } => {
view_controller
.lock()
.remove_story(child_key)
.unwrap_or_else(|e| eprintln!("remove_story error: {:?}", e));
fready(responder.send())
}
})
.unwrap_or_else(|e| eprintln!("view listener error: {:?}", e)),
);
Ok(())
}
fn finish_setup_scene(view_controller: &ErmineViewPtr) {
let vc = view_controller.lock();
vc.setup_scene();
vc.present();
}
fn setup_scene(&self) {
self.import_node.resource().set_event_mask(gfx::METRICS_EVENT_MASK);
self.import_node.add_child(&self.background_node);
self.import_node.add_child(&self.container_node);
let material = Material::new(self.session.clone());
material.set_color(ColorRgba { red: 0xb3, green: 0x1b, blue: 0x1b, alpha: 0x80 });
self.background_node.set_material(&material);
}
fn update(&mut self) {
let center_x = self.width * 0.5;
let center_y = self.height * 0.5;
self.background_node.set_shape(&Rectangle::new(
self.session.clone(),
self.width,
self.height,
));
self.background_node.set_translation(center_x, center_y, 0.0);
self.present();
}
fn present(&self) {
fasync::spawn(
self.session
.lock()
.present(0)
.map_ok(|_| ())
.unwrap_or_else(|e| eprintln!("present error: {:?}", e)),
);
}
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 handle_properies_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();
}
}
pub fn add_child_view_for_story_attach(
&mut self,
key: u32,
story_id: String,
view_holder_token: zx::EventPair,
) -> Result<(), Error> {
let host_node = EntityNode::new(self.session.clone());
let host_import_token = host_node.export_as_request();
self.view_container.add_child2(key, view_holder_token, host_import_token)?;
self.import_node.add_child(&host_node);
let view_data = ViewData::new(key, "".to_string(), story_id, true, host_node);
self.views.insert(key, view_data);
self.update();
self.layout()?;
Ok(())
}
pub fn remove_view_for_story(&mut self, story_id: &String) -> Result<(), Error> {
let result = self.views.iter().find(|(_key, view)| view.story_id == *story_id);
if let Some((key, _view)) = result {
self.remove_story(*key)?;
}
Ok(())
}
pub fn remove_story(&mut self, key: u32) -> Result<(), Error> {
if self.views.remove(&key).is_some() {
self.view_container.remove_child(key, None).unwrap_or_else(|e| {
eprintln!("view_container.remove_child failed for key {} with {}", key, e);
});
self.layout()?;
self.update();
}
Ok(())
}
pub fn list_stories(&self) -> (Vec<u32>, Vec<String>, Vec<SizeF>, Vec<bool>) {
let mut keys = Vec::new();
let mut urls = Vec::new();
let mut sizes = Vec::new();
let mut fs = Vec::new();
for (key, view) in &self.views {
let bounds =
view.bounds.as_ref().unwrap_or(&RectF { x: 0.0, y: 0.0, width: 0.0, height: 0.0 });
keys.push(*key);
urls.push(view.url.clone());
sizes.push(SizeF { width: bounds.width, height: bounds.height });
fs.push(true);
}
(keys, urls, sizes, fs)
}
pub fn handle_hot_key(&mut self, event: &KeyboardEvent, key_to_use: u32) -> Result<(), Error> {
if event.code_point == 0x20 {
if let Some(ask_box) = self.ask_box.as_mut() {
ask_box.focus(&self.view_container)?;
} else {
self.ask_box = Some(AskBox::new(
key_to_use,
&self.session,
&self.view_container,
&self.import_node,
)?);
self.update();
self.layout()?;
}
}
Ok(())
}
pub fn remove_ask_box(&mut self) {
if let Some(mut ask_box) = self.ask_box.take() {
ask_box
.remove(&self.view_container)
.unwrap_or_else(|e| eprintln!("ask_box.remove error: {:?}", e));
}
}
pub fn handle_suggestion(&mut self, text: &str) -> Result<(), Error> {
let story_name = random_story_name();
let package = format!("fuchsia-pkg://fuchsia.com/{}#meta/{}.cmx", text, text);
let (story_puppet_master, story_puppet_master_end) =
create_proxy().context("handle_suggestion control_story")?;
self.puppet_master.control_story(&story_name, story_puppet_master_end)?;
let mut commands = [StoryCommand::AddMod(AddMod {
mod_name_transitional: Some(random_mod_name()),
intent: Intent { action: None, handler: Some(package), parameters: None },
surface_parent_mod_name: None,
surface_relation: SurfaceRelation {
arrangement: SurfaceArrangement::None,
dependency: SurfaceDependency::None,
emphasis: 1.0,
},
..AddMod::new_empty()
})];
story_puppet_master
.enqueue(&mut commands.iter_mut())
.context("handle_suggestion story_puppet_master.enqueue")?;
let f = story_puppet_master.execute();
fasync::spawn(
f.map_ok(move |_| {}).unwrap_or_else(|e| eprintln!("puppetmaster error: {:?}", e)),
);
self.story_puppet_masters.insert(story_name, story_puppet_master);
Ok(())
}
pub fn layout(&mut self) -> Result<(), Error> {
if !self.views.is_empty() {
let num_tiles = self.views.len();
let columns = (num_tiles as f32).sqrt().ceil() as usize;
let rows = (columns + num_tiles - 1) / columns;
let tile_height = (self.height / rows as f32).floor();
for (row_index, view_chunk) in
self.views.iter_mut().chunks(columns).into_iter().enumerate()
{
let tiles_in_row = if row_index == rows - 1 && (num_tiles % columns) != 0 {
num_tiles % columns
} else {
columns
};
let tile_width = (self.width / tiles_in_row as f32).floor();
for (column_index, (_key, view)) in view_chunk.enumerate() {
let mut tile_bounds = RectF {
height: tile_height,
width: tile_width,
x: column_index as f32 * tile_width,
y: row_index as f32 * tile_height,
};
inset(&mut tile_bounds, 10.0);
let mut view_properties = ViewProperties {
custom_focus_behavior: Some(Box::new(CustomFocusBehavior {
allow_focus: view.allow_focus,
})),
view_layout: Some(Box::new(ViewLayout {
inset: InsetF { bottom: 0.0, left: 0.0, right: 0.0, top: 0.0 },
size: SizeF { width: tile_bounds.width, height: tile_bounds.height },
})),
};
self.view_container
.set_child_properties(view.key, Some(OutOfLine(&mut view_properties)))?;
view.host_node.set_translation(tile_bounds.x, tile_bounds.y, 0.0);
view.bounds = Some(tile_bounds);
}
}
}
if let Some(ask_box) = self.ask_box.as_ref() {
ask_box.layout(&self.view_container, self.width, self.height)?;
}
Ok(())
}
}