blob: a895b5f2fc34e5adb9e181b35d0e9dbcc197b2c3 [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::strategies::framebuffer::DisplayId;
use crate::app::MessageInternal;
use crate::geometry::{IntPoint, Size};
use crate::input::{self, UserInputMessage};
use crate::message::Message;
use crate::render::Context;
use crate::scene::facets::FacetId;
use crate::scene::scene::Scene;
use crate::view::strategies::base::ViewStrategyPtr;
use crate::{IdFromRaw, MessageTarget};
use anyhow::{ensure, Error};
use euclid::size2;
use fuchsia_framebuffer::ImageId;
use fuchsia_trace::instant;
use fuchsia_zircon::{Event, Time};
use futures::channel::mpsc::{unbounded, UnboundedSender};
use std::fmt::{Display, Formatter};
pub(crate) mod strategies;
#[derive(Debug, Clone)]
pub struct DisplayInfo {
pub id: DisplayId,
pub horizontal_size_mm: u32,
pub vertical_size_mm: u32,
pub using_fallback_size: bool,
}
impl From<&fidl_fuchsia_hardware_display::Info> for DisplayInfo {
fn from(info: &fidl_fuchsia_hardware_display::Info) -> Self {
Self {
id: info.id.into(),
horizontal_size_mm: info.horizontal_size_mm,
vertical_size_mm: info.vertical_size_mm,
using_fallback_size: info.using_fallback_size,
}
}
}
/// parameter struct passed to setup and update trait methods.
pub struct ViewAssistantContext {
/// A unique key representing this view.
pub key: ViewKey,
/// The actual number of pixels in the view.
pub size: Size,
/// A factor representing pixel density. Use to
/// calculate sizes for things like fonts.
pub metrics: Size,
/// For render, the time the rendering will be presented. Currently
/// not implemented correctly.
pub presentation_time: Time,
/// When running in frame buffer mode, the number of buffers in
/// the buffer collection
pub buffer_count: Option<usize>,
/// The ID of the Image being rendered in a buffer in
/// preparation for being displayed. Used to keep track
/// of what content needs to be rendered for a particular image
/// in double or triple buffering configurations.
pub image_id: ImageId,
/// The index of the buffer in a buffer collection that is
/// being used as the contents of the image specified in
/// `image_id`.
pub image_index: u32,
/// Position of the mouse cursor when running directly on the
/// display coordinator.
pub mouse_cursor_position: Option<IntPoint>,
/// information about the hosting display when running directly on the
/// display coordinator.
pub display_info: Option<DisplayInfo>,
app_sender: UnboundedSender<MessageInternal>,
}
impl ViewAssistantContext {
/// Returns an empty ViewAssistantContext to enable testing with mocks
pub fn new_for_testing() -> Self {
let (unbounded_sender, _) = unbounded::<MessageInternal>();
Self {
key: Default::default(),
size: Default::default(),
metrics: Default::default(),
presentation_time: Default::default(),
buffer_count: Default::default(),
image_id: Default::default(),
image_index: Default::default(),
mouse_cursor_position: Default::default(),
display_info: Default::default(),
app_sender: unbounded_sender,
}
}
/// Queue up a message for delivery
pub fn queue_message(&mut self, message: Message) {
self.app_sender
.unbounded_send(MessageInternal::TargetedMessage(
MessageTarget::View(self.key),
message,
))
.expect("ViewAssistantContext::queue_message - unbounded_send");
}
/// Request that a render occur for this view at the next
/// appropriate time to render.
pub fn request_render(&self) {
self.app_sender
.unbounded_send(MessageInternal::RequestRender(self.key))
.expect("unbounded_send");
}
}
/// Trait that allows Carnelian developers to customize the behavior of views.
pub trait ViewAssistant {
/// This method is called once when a view is created.
#[allow(unused_variables)]
fn setup(&mut self, context: &ViewAssistantContext) -> Result<(), Error> {
Ok(())
}
#[allow(unused_variables)]
/// Implement this method to to handle when a view is resized.
fn resize(&mut self, new_size: &Size) -> Result<(), Error> {
Ok(())
}
#[allow(unused_variables)]
/// Implement this method to return a mutable reference to the scene that
/// represents the view.
fn get_scene(&mut self, size: Size) -> Option<&mut Scene> {
None
}
#[allow(unused_variables)]
/// Implement this method to return a mutable reference to the scene that
/// represents the view. Implement this one if you'll need the various
/// contexts to build a scene.
fn get_scene_with_contexts(
&mut self,
render_context: &mut Context,
view_context: &ViewAssistantContext,
) -> Option<&mut Scene> {
self.get_scene(view_context.size)
}
/// This method is called when a view needs to
/// be rendered.
fn render(
&mut self,
render_context: &mut Context,
buffer_ready_event: Event,
view_context: &ViewAssistantContext,
) -> Result<(), Error> {
if let Some(scene) = self.get_scene_with_contexts(render_context, view_context) {
scene.layout(view_context.size);
scene.render(render_context, buffer_ready_event, view_context)?;
if scene.is_animated() {
view_context.request_render();
}
Ok(())
} else {
anyhow::bail!("Assistant has ViewMode::Render but doesn't implement render or scene.")
}
}
/// This method is called when input events come to this view. The default implementation
/// calls specific methods for the type of event, so usually one does not need to implement
/// this method. Since the default methods for touch and mouse handle the pointer abstraction,
/// make sure to call them in an implementation of this method if you wish to use that
/// abstraction.
fn handle_input_event(
&mut self,
context: &mut ViewAssistantContext,
event: &input::Event,
) -> Result<(), Error> {
match &event.event_type {
input::EventType::Mouse(mouse_event) => {
self.handle_mouse_event(context, event, mouse_event)
}
input::EventType::Touch(touch_event) => {
self.handle_touch_event(context, event, touch_event)
}
input::EventType::Keyboard(keyboard_event) => {
self.handle_keyboard_event(context, event, keyboard_event)
}
input::EventType::ConsumerControl(consumer_control_event) => {
self.handle_consumer_control_event(context, event, consumer_control_event)
}
}
}
/// This method is called when mouse events come to this view.
/// ```no_run
/// # use anyhow::Error;
/// # use carnelian::{
/// # input::{self},IntPoint,
/// # ViewAssistant, ViewAssistantContext,
/// # };
/// # struct SampleViewAssistant { mouse_start: IntPoint};
/// impl ViewAssistant for SampleViewAssistant {
/// fn handle_mouse_event(
/// &mut self,
/// context: &mut ViewAssistantContext,
/// event: &input::Event,
/// mouse_event: &input::mouse::Event,
/// ) -> Result<(), Error> {
/// match &mouse_event.phase {
/// input::mouse::Phase::Down(button) => {
/// if button.is_primary() {
/// self.mouse_start = mouse_event.location
/// }
/// }
/// input::mouse::Phase::Moved => {}
/// input::mouse::Phase::Up(button) => {
/// if button.is_primary() {
/// println!("mouse moved {}", mouse_event.location - self.mouse_start);
/// }
/// }
/// _ => (),
/// }
/// Ok(())
/// }
/// }
/// ```
fn handle_mouse_event(
&mut self,
context: &mut ViewAssistantContext,
event: &input::Event,
mouse_event: &input::mouse::Event,
) -> Result<(), Error> {
if self.uses_pointer_events() {
if let Some(mouse_event) =
input::pointer::Event::new_from_mouse_event(&event.device_id, mouse_event)
{
self.handle_pointer_event(context, event, &mouse_event)
} else {
Ok(())
}
} else {
Ok(())
}
}
/// This method is called when touch events come to this view.
/// ```no_run
/// # use anyhow::Error;
/// # use carnelian::{
/// # input::{self},IntPoint,
/// # ViewAssistant, ViewAssistantContext,
/// # };
/// # struct SampleViewAssistant {};
/// impl ViewAssistant for SampleViewAssistant {
/// fn handle_touch_event(
/// &mut self,
/// context: &mut ViewAssistantContext,
/// event: &input::Event,
/// touch_event: &input::touch::Event,
/// ) -> Result<(), Error> {
/// for contact in &touch_event.contacts {
/// // Use contact.contact_id as a key to handle
/// // each contact individually
/// }
/// Ok(())
/// }
/// }
/// ```
fn handle_touch_event(
&mut self,
context: &mut ViewAssistantContext,
event: &input::Event,
touch_event: &input::touch::Event,
) -> Result<(), Error> {
if self.uses_pointer_events() {
for contact in &touch_event.contacts {
self.handle_pointer_event(
context,
event,
&input::pointer::Event::new_from_contact(contact),
)?;
}
Ok(())
} else {
Ok(())
}
}
/// This method is called when the view desires pointer events and a compatible
/// mouse or touch event comes to this view.
/// ```no_run
/// # use anyhow::Error;
/// # use carnelian::{
/// # input::{self},IntPoint,
/// # ViewAssistant, ViewAssistantContext,
/// # };
/// # #[derive(Default)]
/// # struct SampleViewAssistant {
/// # mouse_start: IntPoint,
/// # pointer_start: IntPoint,
/// # current_pointer_location: IntPoint,
/// # }
/// impl ViewAssistant for SampleViewAssistant {
/// fn handle_pointer_event(
/// &mut self,
/// context: &mut ViewAssistantContext,
/// event: &input::Event,
/// pointer_event: &input::pointer::Event,
/// ) -> Result<(), Error> {
/// match pointer_event.phase {
/// input::pointer::Phase::Down(pointer_location) => {
/// self.pointer_start = pointer_location
/// }
/// input::pointer::Phase::Moved(pointer_location) => {
/// self.current_pointer_location = pointer_location;
/// }
/// input::pointer::Phase::Up => {
/// println!(
/// "pointer moved {}",
/// self.current_pointer_location - self.pointer_start
/// );
/// }
/// _ => (),
/// }
/// Ok(())
/// }
/// }
/// ```
#[allow(unused_variables)]
fn handle_pointer_event(
&mut self,
context: &mut ViewAssistantContext,
event: &input::Event,
pointer_event: &input::pointer::Event,
) -> Result<(), Error> {
Ok(())
}
/// This method is called when keyboard events come to this view.
#[allow(unused_variables)]
fn handle_keyboard_event(
&mut self,
context: &mut ViewAssistantContext,
event: &input::Event,
keyboard_event: &input::keyboard::Event,
) -> Result<(), Error> {
Ok(())
}
/// This method is called when consumer control events come to this view.
#[allow(unused_variables)]
fn handle_consumer_control_event(
&mut self,
context: &mut ViewAssistantContext,
event: &input::Event,
consumer_control_event: &input::consumer_control::Event,
) -> Result<(), Error> {
Ok(())
}
/// This method is called when focus events come from Scenic to this view. It will be
/// called once when a Carnelian app is running directly on the frame buffer, as such
/// views are always focused. See the button sample for an one way to respond to focus.
#[allow(unused_variables)]
fn handle_focus_event(
&mut self,
context: &mut ViewAssistantContext,
focused: bool,
) -> 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.
/// ```no_run
/// # use anyhow::Error;
/// # use carnelian::{
/// # input::{self},
/// # IntPoint, Message, ViewAssistant, ViewAssistantContext,
/// # };
/// # #[derive(Default)]
/// # struct SampleViewAssistant {}
/// use fuchsia_zircon::Time;
/// pub enum SampleMessages {
/// Pressed(Time),
/// }
/// impl ViewAssistant for SampleViewAssistant {
/// fn handle_message(&mut self, message: Message) {
/// if let Some(sample_message) = message.downcast_ref::<SampleMessages>() {
/// match sample_message {
/// SampleMessages::Pressed(value) => {
/// println!("value = {:#?}", value);
/// }
/// }
/// }
/// }
/// }
/// ```
#[allow(unused_variables)]
fn handle_message(&mut self, message: Message) {}
/// Whether this view wants touch and mouse events abstracted as
/// [`input::pointer::Event`](./input/pointer/struct.Event.html). Defaults to true.
fn uses_pointer_events(&self) -> bool {
true
}
/// This method is called when running directly on the display and the ownership
/// of the display changes.
fn ownership_changed(&mut self, _owned: bool) -> Result<(), Error> {
Ok(())
}
/// This method is called after setup to get an offset to use when calculating
/// render time. It is only called once.
fn get_render_offset(&mut self) -> Option<i64> {
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.
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct ViewKey(pub u64);
impl IdFromRaw for ViewKey {
fn from_raw(id: u64) -> ViewKey {
ViewKey(id)
}
}
impl Display for ViewKey {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, "ViewKey({})", self.0)
}
}
#[derive(Debug)]
pub(crate) struct ViewDetails {
key: ViewKey,
metrics: Size,
physical_size: Size,
logical_size: Size,
}
/// 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(crate) struct ViewController {
key: ViewKey,
assistant: ViewAssistantPtr,
metrics: Size,
physical_size: Size,
logical_size: Size,
render_requested: bool,
strategy: ViewStrategyPtr,
app_sender: UnboundedSender<MessageInternal>,
}
impl ViewController {
pub async fn new_with_strategy(
key: ViewKey,
view_assistant: ViewAssistantPtr,
strategy: ViewStrategyPtr,
app_sender: UnboundedSender<MessageInternal>,
) -> Result<ViewController, Error> {
let metrics = strategy.initial_metrics();
let physical_size = strategy.initial_physical_size();
let logical_size = strategy.initial_logical_size();
let mut view_controller = ViewController {
key,
metrics,
physical_size,
logical_size,
render_requested: true,
assistant: view_assistant,
strategy,
app_sender,
};
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,
}
}
/// Informs Scenic of any changes made to this View's |Session|.
pub fn present(&mut self) {
self.strategy.present(&self.make_view_details());
}
pub fn send_update_message(&mut self) {
self.app_sender.unbounded_send(MessageInternal::Render(self.key)).expect("unbounded_send");
}
pub fn ownership_changed(&mut self, owned: bool) {
self.strategy.ownership_changed(owned);
self.assistant
.ownership_changed(owned)
.unwrap_or_else(|e| println!("ownership_changed error: {}", e));
}
pub fn drop_display_resources(&mut self) {
self.strategy.drop_display_resources();
}
pub fn request_render(&mut self) {
self.render_requested = true;
self.strategy.render_requested();
}
pub async fn render(&mut self) {
if self.render_requested {
// Recompute our physical size based on the provided logical size and screen metrics.
self.physical_size = size2(
self.logical_size.width * self.metrics.width,
self.logical_size.height * self.metrics.height,
);
if self.strategy.render(&self.make_view_details(), &mut self.assistant).await {
self.render_requested = false;
}
self.present();
}
}
pub fn present_done(&mut self, info: fidl_fuchsia_scenic_scheduling::FramePresentedInfo) {
self.strategy.present_done(&self.make_view_details(), &mut self.assistant, info);
}
pub fn handle_metrics_changed(&mut self, metrics: Size) {
if self.metrics != metrics {
instant!(
c"gfx",
c"ViewController::metrics_changed",
fuchsia_trace::Scope::Process,
"old_device_pixel_ratio_x" => self.metrics.width as f64,
"old_device_pixel_ratio_y" => self.metrics.height as f64,
"new_device_pixel_ratio_x" => metrics.width as f64,
"new_device_pixel_ratio_y" => metrics.height as f64
);
self.metrics = metrics;
self.render_requested = true;
self.send_update_message();
}
}
pub fn handle_size_changed(&mut self, new_size: Size) {
if self.logical_size != new_size {
instant!(
c"gfx",
c"ViewController::size_changed",
fuchsia_trace::Scope::Process,
"old_logical_width" => self.logical_size.width as f64,
"old_logical_height" => self.logical_size.height as f64,
"new_logical_width" => new_size.width as f64,
"new_logical_height" => new_size.height as f64
);
self.logical_size = new_size;
self.assistant
.resize(&new_size)
.unwrap_or_else(|e| println!("handle_size_changed error: {}", e));
self.render_requested = true;
self.send_update_message();
}
}
pub fn focus(&mut self, focus: bool) {
self.strategy.handle_focus(&self.make_view_details(), &mut self.assistant, focus);
}
fn handle_input_events_internal(
&mut self,
view_details: &ViewDetails,
events: Vec<input::Event>,
) -> Result<(), Error> {
let mut view_assistant_context = self.strategy.create_view_assistant_context(&view_details);
for event in events {
self.strategy.inspect_event(view_details, &event);
self.assistant.handle_input_event(&mut view_assistant_context, &event)?;
}
Ok(())
}
pub fn handle_user_input_message(
&mut self,
user_input_message: UserInputMessage,
) -> Result<(), Error> {
let view_details = self.make_view_details();
let events = self.strategy.convert_user_input_message(&view_details, user_input_message)?;
self.handle_input_events_internal(&view_details, events)?;
Ok(())
}
/// Handle input events that have been converted to Carnelian's format
pub fn handle_input_events(&mut self, events: Vec<input::Event>) -> Result<(), Error> {
let view_details = self.make_view_details();
self.handle_input_events_internal(&view_details, events)?;
Ok(())
}
/// This method sends an arbitrary message to this view to be
/// forwarded to the view assistant.
pub fn send_message(&mut self, msg: Message) {
self.assistant.handle_message(msg);
}
/// This method sends an arbitrary message to this view to be
/// forwarded to the view assistant.
pub fn send_facet_message(&mut self, facet_id: FacetId, msg: Message) -> Result<(), Error> {
let scene = self.assistant.get_scene(self.physical_size);
ensure!(scene.is_some(), "send_facet_message called on view not providing a scene");
let scene = scene.unwrap();
scene.send_message(&facet_id, msg);
Ok(())
}
pub fn image_freed(&mut self, image_id: u64, collection_id: u32) {
self.strategy.image_freed(image_id, collection_id);
}
pub fn handle_on_next_frame_begin(
&mut self,
info: &fidl_fuchsia_ui_composition::OnNextFrameBeginValues,
) {
self.strategy.handle_on_next_frame_begin(info);
}
pub async fn handle_display_coordinator_event(
&mut self,
event: fidl_fuchsia_hardware_display::CoordinatorEvent,
) {
self.strategy.handle_display_coordinator_event(event).await;
}
pub fn is_hosted_on_display(&self, display_id: DisplayId) -> bool {
self.strategy.is_hosted_on_display(display_id)
}
pub fn close(&mut self) {
self.strategy.close();
}
}