| // 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, TestSender, ViewMode}, |
| canvas::{Canvas, MappingPixelSink}, |
| geometry::{IntSize, Size}, |
| message::{make_message, Message}, |
| scenic_utils::PresentationTime, |
| }; |
| use failure::Error; |
| use fidl_fuchsia_images as images; |
| 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, HostImageCycler, SessionPtr, View}; |
| use fuchsia_zircon::Duration; |
| use fuchsia_zircon::{ClockId, Time}; |
| use futures::{StreamExt, TryFutureExt}; |
| use mapped_vmo::Mapping; |
| use std::{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, |
| } |
| |
| /// enum that defines the animation behavior of the view |
| #[derive(Clone, Copy, Debug)] |
| 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. |
| pub struct ViewAssistantContext<'a> { |
| /// A unique key representing this view. |
| pub key: ViewKey, |
| /// The size taking screen density into account. |
| pub logical_size: Size, |
| /// The actual number of pixels in the view. |
| pub size: Size, |
| /// The factor between logical size and size. |
| pub metrics: Size, |
| /// For update, the time this will be presented. |
| pub presentation_time: PresentationTime, |
| /// For views in Scenic mode, this will contain resources for |
| /// interacting with Scenic. |
| pub scenic_resources: Option<&'a ScenicResources>, |
| /// For views in canvas mode, this will contain a canvas |
| /// to be used for drawing. |
| pub canvas: Option<&'a RefCell<Canvas<MappingPixelSink>>>, |
| |
| 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); |
| } |
| |
| /// Get the root node for scenic based apps |
| pub fn root_node(&self) -> &EntityNode { |
| &self.scenic_resources.as_ref().unwrap().root_node |
| } |
| |
| /// Get the session for scenic based apps |
| pub fn session(&self) -> &SessionPtr { |
| &self.scenic_resources.as_ref().unwrap().session |
| } |
| } |
| |
| /// 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; |
| |
| pub struct ScenicResources { |
| #[allow(unused)] |
| view: View, |
| root_node: EntityNode, |
| session: SessionPtr, |
| } |
| |
| impl ScenicResources { |
| fn new(session: &SessionPtr, view_token: ViewToken) -> ScenicResources { |
| 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, |
| }), |
| )); |
| ScenicResources { view, root_node, session: session.clone() } |
| } |
| } |
| |
| struct ScenicCanvasResources { |
| image_cycler: HostImageCycler, |
| } |
| |
| struct FramebufferResources { |
| canvas: RefCell<Canvas<MappingPixelSink>>, |
| } |
| |
| trait ViewStrategy { |
| fn setup(&mut self, _view_details: &ViewDetails, _view_assistant: &mut ViewAssistantPtr); |
| fn update(&mut self, view_details: &ViewDetails, view_assistant: &mut ViewAssistantPtr); |
| fn present(&mut self); |
| fn validate_root_node_id(&self, _: u32) -> bool { |
| true |
| } |
| fn validate_view_id(&self, _: u32) -> bool { |
| true |
| } |
| fn handle_input_event( |
| &mut self, |
| _view_details: &ViewDetails, |
| _view_assistant: &mut ViewAssistantPtr, |
| _: &fidl_fuchsia_ui_input::InputEvent, |
| ) -> Vec<Message>; |
| } |
| |
| type ViewStrategyPtr = Box<dyn ViewStrategy>; |
| |
| struct ScenicViewStrategy { |
| scenic_resources: ScenicResources, |
| } |
| |
| fn scenic_present(scenic_resources: &ScenicResources) { |
| fasync::spawn_local( |
| scenic_resources |
| .session |
| .lock() |
| .present(0) |
| .map_ok(|_| ()) |
| .unwrap_or_else(|e| panic!("present error: {:?}", e)), |
| ); |
| } |
| |
| impl ScenicViewStrategy { |
| fn new(session: &SessionPtr, view_token: ViewToken) -> ViewStrategyPtr { |
| let scenic_resources = ScenicResources::new(session, view_token); |
| Box::new(ScenicViewStrategy { scenic_resources }) |
| } |
| |
| fn make_view_assistant_context(&self, view_details: &ViewDetails) -> ViewAssistantContext { |
| ViewAssistantContext { |
| key: view_details.key, |
| logical_size: view_details.logical_size, |
| size: view_details.physical_size, |
| metrics: view_details.metrics, |
| presentation_time: Time::get(ClockId::Monotonic), |
| messages: Vec::new(), |
| scenic_resources: Some(&self.scenic_resources), |
| canvas: None, |
| } |
| } |
| } |
| |
| impl ViewStrategy for ScenicViewStrategy { |
| fn setup(&mut self, view_details: &ViewDetails, view_assistant: &mut ViewAssistantPtr) { |
| let context = self.make_view_assistant_context(view_details); |
| view_assistant.setup(&context).unwrap_or_else(|e| panic!("Setup error: {:?}", e)); |
| } |
| |
| fn update(&mut self, view_details: &ViewDetails, view_assistant: &mut ViewAssistantPtr) { |
| let context = self.make_view_assistant_context(view_details); |
| view_assistant.update(&context).unwrap_or_else(|e| panic!("Update error: {:?}", e)); |
| } |
| |
| fn present(&mut self) { |
| scenic_present(&self.scenic_resources); |
| } |
| |
| fn handle_input_event( |
| &mut self, |
| view_details: &ViewDetails, |
| view_assistant: &mut ViewAssistantPtr, |
| event: &fidl_fuchsia_ui_input::InputEvent, |
| ) -> Vec<Message> { |
| let mut context = self.make_view_assistant_context(view_details); |
| |
| view_assistant |
| .handle_input_event(&mut context, &event) |
| .unwrap_or_else(|e| eprintln!("handle_event: {:?}", e)); |
| |
| context.messages |
| } |
| } |
| |
| struct ScenicCanvasViewStrategy { |
| #[allow(unused)] |
| scenic_resources: ScenicResources, |
| canvas_resources: ScenicCanvasResources, |
| } |
| |
| impl ScenicCanvasViewStrategy { |
| fn new(session: &SessionPtr, view_token: ViewToken) -> ViewStrategyPtr { |
| let scenic_resources = ScenicResources::new(session, view_token); |
| let image_cycler = HostImageCycler::new(session.clone()); |
| scenic_resources.root_node.add_child(&image_cycler.node()); |
| let canvas_resources = ScenicCanvasResources { image_cycler }; |
| let strat = ScenicCanvasViewStrategy { scenic_resources, canvas_resources }; |
| Box::new(strat) |
| } |
| |
| fn make_view_assistant_context<'a>( |
| view_details: &ViewDetails, |
| canvas: Option<&'a RefCell<Canvas<MappingPixelSink>>>, |
| ) -> ViewAssistantContext<'a> { |
| ViewAssistantContext { |
| key: view_details.key, |
| logical_size: view_details.logical_size, |
| size: view_details.physical_size, |
| metrics: view_details.metrics, |
| presentation_time: Time::get(ClockId::Monotonic), |
| messages: Vec::new(), |
| scenic_resources: None, |
| canvas: canvas, |
| } |
| } |
| } |
| |
| impl ViewStrategy for ScenicCanvasViewStrategy { |
| fn setup(&mut self, view_details: &ViewDetails, view_assistant: &mut ViewAssistantPtr) { |
| let canvas_context = |
| ScenicCanvasViewStrategy::make_view_assistant_context(view_details, None); |
| view_assistant.setup(&canvas_context).unwrap_or_else(|e| panic!("Setup error: {:?}", e)); |
| } |
| |
| fn update(&mut self, view_details: &ViewDetails, view_assistant: &mut ViewAssistantPtr) { |
| let size = view_details.physical_size.floor().to_u32(); |
| if size.width > 0 && size.height > 0 { |
| let center_x = view_details.physical_size.width * 0.5; |
| let center_y = view_details.physical_size.height * 0.5; |
| let image_cycler = &mut self.canvas_resources.image_cycler; |
| image_cycler.node().set_translation(center_x, center_y, -0.1); |
| let stride = size.width * 4; |
| |
| // Create a description of this pixel buffer that |
| // Scenic can understand. |
| let info = images::ImageInfo { |
| transform: images::Transform::Normal, |
| width: size.width, |
| height: size.height, |
| stride: stride, |
| pixel_format: images::PixelFormat::Bgra8, |
| color_space: images::ColorSpace::Srgb, |
| tiling: images::Tiling::Linear, |
| alpha_format: images::AlphaFormat::Opaque, |
| }; |
| |
| // Grab an image buffer from the cycler |
| let guard = image_cycler.acquire(info).expect("failed to allocate buffer"); |
| // Create a canvas to render into the buffer |
| let canvas = RefCell::new(Canvas::new( |
| view_details.physical_size.to_i32(), |
| MappingPixelSink::new(&guard.image().mapping()), |
| stride, |
| 4, |
| )); |
| |
| let canvas_context = |
| ScenicCanvasViewStrategy::make_view_assistant_context(view_details, Some(&canvas)); |
| view_assistant |
| .update(&canvas_context) |
| .unwrap_or_else(|e| panic!("Update error: {:?}", e)); |
| } |
| } |
| |
| fn present(&mut self) { |
| scenic_present(&self.scenic_resources); |
| } |
| |
| fn handle_input_event( |
| &mut self, |
| view_details: &ViewDetails, |
| view_assistant: &mut ViewAssistantPtr, |
| event: &fidl_fuchsia_ui_input::InputEvent, |
| ) -> Vec<Message> { |
| let mut canvas_context = |
| ScenicCanvasViewStrategy::make_view_assistant_context(view_details, None); |
| view_assistant |
| .handle_input_event(&mut canvas_context, &event) |
| .unwrap_or_else(|e| eprintln!("handle_event: {:?}", e)); |
| |
| canvas_context.messages |
| } |
| } |
| |
| struct FrameBufferViewStrategy { |
| framebuffer_resources: FramebufferResources, |
| } |
| |
| impl FrameBufferViewStrategy { |
| fn new( |
| size: &IntSize, |
| mapping: &Arc<Mapping>, |
| pixel_size: u32, |
| _pixel_format: fuchsia_framebuffer::PixelFormat, |
| ) -> ViewStrategyPtr { |
| let stride = size.width as u32 * pixel_size; |
| let canvas = Canvas::new(*size, MappingPixelSink::new(mapping), stride, pixel_size); |
| let framebuffer_resources = FramebufferResources { canvas: RefCell::new(canvas) }; |
| Box::new(FrameBufferViewStrategy { framebuffer_resources }) |
| } |
| |
| fn make_context(&mut self, view_details: &ViewDetails) -> ViewAssistantContext { |
| ViewAssistantContext { |
| key: view_details.key, |
| logical_size: view_details.logical_size, |
| size: view_details.physical_size, |
| metrics: view_details.metrics, |
| presentation_time: Time::get(ClockId::Monotonic), |
| messages: Vec::new(), |
| scenic_resources: None, |
| canvas: Some(&self.framebuffer_resources.canvas), |
| } |
| } |
| } |
| |
| impl ViewStrategy for FrameBufferViewStrategy { |
| fn setup(&mut self, view_details: &ViewDetails, view_assistant: &mut ViewAssistantPtr) { |
| let framebuffer_context = self.make_context(view_details); |
| view_assistant |
| .setup(&framebuffer_context) |
| .unwrap_or_else(|e| panic!("Setup error: {:?}", e)); |
| } |
| |
| fn update(&mut self, view_details: &ViewDetails, view_assistant: &mut ViewAssistantPtr) { |
| let framebuffer_context = self.make_context(view_details); |
| view_assistant |
| .update(&framebuffer_context) |
| .unwrap_or_else(|e| panic!("Update error: {:?}", e)); |
| } |
| |
| fn present(&mut self) { |
| // this function intentionally left blank |
| } |
| |
| fn handle_input_event( |
| &mut self, |
| _view_details: &ViewDetails, |
| _view_assistant: &mut ViewAssistantPtr, |
| _event: &fidl_fuchsia_ui_input::InputEvent, |
| ) -> Vec<Message> { |
| panic!("Not yet implemented"); |
| } |
| } |
| |
| struct ViewDetails { |
| key: ViewKey, |
| metrics: Size, |
| physical_size: Size, |
| logical_size: Size, |
| #[allow(unused)] |
| animation_mode: AnimationMode, |
| } |
| |
| /// 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, |
| assistant: ViewAssistantPtr, |
| metrics: Size, |
| physical_size: Size, |
| logical_size: Size, |
| animation_mode: AnimationMode, |
| strategy: ViewStrategyPtr, |
| test_sender: Option<TestSender>, |
| } |
| |
| impl ViewController { |
| pub(crate) fn new( |
| key: ViewKey, |
| view_token: ViewToken, |
| mode: ViewMode, |
| session: SessionPtr, |
| mut view_assistant: ViewAssistantPtr, |
| test_sender: Option<TestSender>, |
| ) -> Result<ViewController, Error> { |
| let strategy = if mode == ViewMode::Canvas { |
| ScenicCanvasViewStrategy::new(&session, view_token) |
| } else { |
| ScenicViewStrategy::new(&session, view_token) |
| }; |
| |
| let initial_animation_mode = view_assistant.initial_animation_mode(); |
| |
| let mut view_controller = ViewController { |
| key, |
| metrics: Size::zero(), |
| physical_size: Size::zero(), |
| logical_size: Size::zero(), |
| animation_mode: initial_animation_mode, |
| assistant: view_assistant, |
| strategy, |
| test_sender: test_sender, |
| }; |
| |
| view_controller |
| .strategy |
| .setup(&view_controller.make_view_details(), &mut view_controller.assistant); |
| |
| Ok(view_controller) |
| } |
| |
| /// new_with_frame_buffer |
| pub fn new_with_frame_buffer( |
| key: ViewKey, |
| size: IntSize, |
| pixel_size: u32, |
| pixel_format: fuchsia_framebuffer::PixelFormat, |
| mapping: Arc<Mapping>, |
| mut view_assistant: ViewAssistantPtr, |
| ) -> Result<ViewController, Error> { |
| let strategy = FrameBufferViewStrategy::new(&size, &mapping, pixel_size, pixel_format); |
| let initial_animation_mode = view_assistant.initial_animation_mode(); |
| let mut view_controller = ViewController { |
| key, |
| metrics: Size::new(1.0, 1.0), |
| physical_size: size.to_f32(), |
| logical_size: Size::zero(), |
| animation_mode: initial_animation_mode, |
| assistant: view_assistant, |
| strategy, |
| test_sender: None, |
| }; |
| |
| view_controller |
| .strategy |
| .setup(&view_controller.make_view_details(), &mut view_controller.assistant); |
| |
| Ok(view_controller) |
| } |
| |
| fn make_view_details(&self) -> ViewDetails { |
| ViewDetails { |
| key: self.key, |
| metrics: self.metrics, |
| physical_size: self.physical_size, |
| logical_size: self.logical_size, |
| animation_mode: self.animation_mode, |
| } |
| } |
| |
| /// Informs Scenic of any changes made to this View's |Session|. |
| pub fn present(&mut self) { |
| self.strategy.present(); |
| } |
| |
| pub(crate) 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, |
| ); |
| |
| self.strategy.update(&self.make_view_details(), &mut self.assistant); |
| self.present(); |
| if let Some(sender) = self.test_sender.take() { |
| sender.send(Ok(())).unwrap_or_else(|err| println!("sending failed {:?}", err)); |
| } |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| /// 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!(self.strategy.validate_root_node_id(event.node_id)); |
| self.handle_metrics_changed(&event.metrics); |
| } |
| fidl_fuchsia_ui_gfx::Event::ViewPropertiesChanged(event) => { |
| assert!(self.strategy.validate_view_id(event.view_id)); |
| self.handle_view_properties_changed(&event.properties); |
| } |
| _ => (), |
| }, |
| fidl_fuchsia_ui_scenic::Event::Input(event) => { |
| let messages = self.strategy.handle_input_event( |
| &self.make_view_details(), |
| &mut self.assistant, |
| &event, |
| ); |
| for msg in messages { |
| self.send_message(msg); |
| } |
| 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: Message) { |
| if let Some(view_msg) = msg.downcast_ref::<ViewMessages>() { |
| match view_msg { |
| ViewMessages::Update => { |
| self.update(); |
| } |
| } |
| } else { |
| self.assistant.handle_message(msg); |
| } |
| } |
| } |