blob: 9ef5a86de3204896e3c01e411b9f744882eff0b7 [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::{
app::App,
geometry::Size,
message::{make_message, Message},
scenic_utils::PresentationTime,
};
use failure::Error;
use fidl_fuchsia_ui_gfx::{self as gfx, Metrics, ViewProperties};
use fidl_fuchsia_ui_input::{
FocusEvent, InputEvent, KeyboardEvent, PointerEvent, SetHardKeyboardDeliveryCmd,
};
use fidl_fuchsia_ui_views::ViewToken;
use fuchsia_async::{self as fasync, Interval};
use fuchsia_scenic::{EntityNode, SessionPtr, View};
use fuchsia_zircon::Duration;
use fuchsia_zircon::{ClockId, Time};
use futures::{StreamExt, TryFutureExt};
/// 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,
}
/// enum that defines the animation behavior of the view
pub enum AnimationMode {
/// No automatic update, only on explicit calls to update
None,
/// Call update in preparation for every frame
EveryFrame,
/// Call update periodically based on duration
RefreshRate(Duration),
}
/// parameter struct passed to setup and update trait methods.
#[allow(missing_docs)]
pub struct ViewAssistantContext<'a> {
pub key: ViewKey,
pub view: &'a View,
pub root_node: &'a EntityNode,
pub session: &'a SessionPtr,
pub logical_size: Size,
pub size: Size,
pub metrics: Size,
pub presentation_time: PresentationTime,
pub messages: Vec<Message>,
}
impl<'a> ViewAssistantContext<'a> {
/// Queue up a message for delivery
pub fn queue_message(&mut self, message: Message) {
self.messages.push(message);
}
}
/// Trait that allows mod developers to customize the behavior of view controllers.
pub trait ViewAssistant {
/// 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 input events come from scenic to this view.
fn handle_input_event(
&mut self,
context: &mut ViewAssistantContext,
event: &InputEvent,
) -> Result<(), Error> {
match event {
InputEvent::Pointer(pointer_event) => {
self.handle_pointer_event(context, &pointer_event)
}
InputEvent::Keyboard(keyboard_event) => {
self.handle_keyboard_event(context, &keyboard_event)
}
InputEvent::Focus(focus_event) => self.handle_focus_event(context, &focus_event),
}
}
/// This method is called when input events come from scenic to this view.
fn handle_pointer_event(
&mut self,
_: &mut ViewAssistantContext,
_: &PointerEvent,
) -> Result<(), Error> {
Ok(())
}
/// This method is called when keyboard events come from scenic to this view.
fn handle_keyboard_event(
&mut self,
_: &mut ViewAssistantContext,
_: &KeyboardEvent,
) -> Result<(), Error> {
Ok(())
}
/// This method is called when focus events come from scenic to this view.
fn handle_focus_event(
&mut self,
_: &mut ViewAssistantContext,
_: &FocusEvent,
) -> Result<(), Error> {
Ok(())
}
/// 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: Message) {}
/// Initial animation mode for view
fn initial_animation_mode(&mut self) -> AnimationMode {
return AnimationMode::None;
}
}
/// Reference to a view assistant. _This type is likely to change in the future so
/// using this type alias might make for easier forward migration._
pub type ViewAssistantPtr = Box<dyn 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 {
key: ViewKey,
view: View,
root_node: EntityNode,
session: SessionPtr,
assistant: ViewAssistantPtr,
metrics: Size,
physical_size: Size,
logical_size: Size,
animation_mode: AnimationMode,
}
impl ViewController {
pub(crate) fn new(
key: ViewKey,
view_token: ViewToken,
session: SessionPtr,
mut view_assistant: ViewAssistantPtr,
) -> Result<ViewController, Error> {
let view = View::new(session.clone(), view_token, Some(String::from("Carnelian View")));
let root_node = EntityNode::new(session.clone());
root_node.resource().set_event_mask(gfx::METRICS_EVENT_MASK);
view.add_child(&root_node);
session.lock().enqueue(fidl_fuchsia_ui_scenic::Command::Input(
fidl_fuchsia_ui_input::Command::SetHardKeyboardDelivery(SetHardKeyboardDeliveryCmd {
delivery_request: true,
}),
));
let context = ViewAssistantContext {
key,
view: &view,
root_node: &root_node,
session: &session,
logical_size: Size::zero(),
size: Size::zero(),
metrics: Size::zero(),
presentation_time: Time::get(ClockId::Monotonic),
messages: Vec::new(),
};
view_assistant.setup(&context)?;
let initial_animation_mode = view_assistant.initial_animation_mode();
let view_controller = ViewController {
key,
view,
root_node,
session,
metrics: Size::zero(),
physical_size: Size::zero(),
logical_size: Size::zero(),
animation_mode: initial_animation_mode,
assistant: view_assistant,
};
Ok(view_controller)
}
/// Handler for Events on this ViewController's |Session|.
pub 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(event) => match event {
fidl_fuchsia_ui_gfx::Event::Metrics(event) => {
assert!(event.node_id == self.root_node.id());
self.handle_metrics_changed(&event.metrics);
}
fidl_fuchsia_ui_gfx::Event::ViewPropertiesChanged(event) => {
assert!(event.view_id == self.view.id());
self.handle_view_properties_changed(&event.properties);
}
_ => (),
},
fidl_fuchsia_ui_scenic::Event::Input(event) => {
let mut context = ViewAssistantContext {
key: self.key,
view: &self.view,
root_node: &self.root_node,
session: &self.session,
logical_size: self.logical_size,
size: self.physical_size,
metrics: self.metrics,
presentation_time: Time::get(ClockId::Monotonic),
messages: Vec::new(),
};
self.assistant
.handle_input_event(&mut context, &event)
.unwrap_or_else(|e| eprintln!("handle_event: {:?}", e));
for msg in context.messages {
self.send_message(msg);
}
self.update();
}
_ => (),
});
}
/// Informs Scenic of any changes made to this View's |Session|.
pub fn present(&self) {
fasync::spawn_local(
self.session
.lock()
.present(0)
.map_ok(|_| ())
.unwrap_or_else(|e| panic!("present error: {:?}", e)),
);
}
/// 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: Message) {
if let Some(view_msg) = msg.downcast_ref::<ViewMessages>() {
match view_msg {
ViewMessages::Update => {
self.update();
}
}
} else {
self.assistant.handle_message(msg);
}
}
fn update(&mut self) {
// Recompute our logical size based on the provided physical size and screen metrics.
self.logical_size = Size::new(
self.physical_size.width * self.metrics.width,
self.physical_size.height * self.metrics.height,
);
let context = ViewAssistantContext {
key: self.key,
view: &self.view,
root_node: &self.root_node,
session: &self.session,
logical_size: self.logical_size,
size: self.physical_size,
metrics: self.metrics,
presentation_time: Time::get(ClockId::Monotonic),
messages: Vec::new(),
};
self.assistant.update(&context).unwrap_or_else(|e| panic!("Update error: {:?}", e));
self.present();
}
fn handle_metrics_changed(&mut self, metrics: &Metrics) {
self.metrics = Size::new(metrics.scale_x, metrics.scale_y);
self.update();
}
fn handle_view_properties_changed(&mut self, properties: &ViewProperties) {
self.physical_size = Size::new(
properties.bounding_box.max.x - properties.bounding_box.min.x,
properties.bounding_box.max.y - properties.bounding_box.min.y,
);
self.update();
}
fn setup_timer(&self, duration: Duration) {
let key = self.key;
let timer = Interval::new(duration);
let f = timer
.map(move |_| {
App::with(|app| {
app.queue_message(key, make_message(ViewMessages::Update));
});
})
.collect::<()>();
fasync::spawn_local(f);
}
pub(crate) fn setup_animation_mode(&mut self) {
match self.animation_mode {
AnimationMode::None => {}
AnimationMode::EveryFrame => {
self.setup_timer(Duration::from_millis(10));
}
AnimationMode::RefreshRate(duration) => {
self.setup_timer(duration);
}
}
}
}