blob: 21c90e7bff7bdfa0fc1753de7553ac583ae43da8 [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::{FrameBufferPtr, MessageInternal},
geometry::{IntPoint, Size},
input::{self},
message::Message,
render::Context,
view::strategies::base::ViewStrategyPtr,
};
use anyhow::Error;
use euclid::size2;
use fuchsia_framebuffer::ImageId;
use fuchsia_scenic::View;
use fuchsia_zircon::{Duration, Event, Time};
use futures::channel::mpsc::UnboundedSender;
pub(crate) mod strategies;
/// 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,
/// Frame buffer
pub frame_buffer: Option<FrameBufferPtr>,
pub mouse_cursor_position: Option<IntPoint>,
messages: Vec<Message>,
app_sender: UnboundedSender<MessageInternal>,
}
impl ViewAssistantContext {
/// Queue up a message for delivery
pub fn queue_message(&mut self, message: Message) {
self.messages.push(message);
}
/// 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)]
fn resize(&mut self, new_size: &Size) -> Result<(), Error> {
Ok(())
}
/// This method is called when a view needs to
/// be rendered.
#[allow(unused_variables)]
fn render(
&mut self,
render_context: &mut Context,
buffer_ready_event: Event,
view_context: &ViewAssistantContext,
) -> Result<(), Error> {
anyhow::bail!("Assistant has ViewMode::Render but doesn't implement render.")
}
/// 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
}
}
/// 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;
#[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 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(crate) 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(crate) fn send_update_message(&mut self) {
self.app_sender.unbounded_send(MessageInternal::Render(self.key)).expect("unbounded_send");
}
pub(crate) fn request_render(&mut self) {
self.render_requested = true;
self.strategy.render_requested();
}
pub(crate) async fn render(&mut self) {
if self.render_requested {
// Recompute our logical size based on the provided physical size and screen metrics.
self.logical_size = size2(
self.physical_size.width * self.metrics.width,
self.physical_size.height * self.metrics.height,
);
if self.strategy.render(&self.make_view_details(), &mut self.assistant).await {
self.render_requested = false;
}
self.present();
}
}
pub(crate) fn present_submitted(
&mut self,
info: fidl_fuchsia_scenic_scheduling::FuturePresentationTimes,
) {
self.strategy.present_submitted(&self.make_view_details(), &mut self.assistant, info);
}
pub(crate) fn present_done(
&mut self,
info: fidl_fuchsia_scenic_scheduling::FramePresentedInfo,
) {
self.strategy.present_done(&self.make_view_details(), &mut self.assistant, info);
}
pub(crate) fn handle_metrics_changed(&mut self, metrics: Size) {
self.metrics = metrics;
self.render_requested = true;
self.send_update_message();
}
pub(crate) fn handle_size_changed(&mut self, new_size: Size) {
self.physical_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(crate) fn focus(&mut self, focus: bool) {
self.strategy.handle_focus(&self.make_view_details(), &mut self.assistant, focus);
}
/// Dispatch scenic input events
pub fn handle_scenic_input_event(&mut self, event: fidl_fuchsia_ui_input::InputEvent) {
match event {
fidl_fuchsia_ui_input::InputEvent::Focus(focus_event) => {
self.focus(focus_event.focused);
}
_ => {
let messages = self.strategy.handle_scenic_input_event(
&self.make_view_details(),
&mut self.assistant,
&event,
);
for msg in messages {
self.send_message(msg);
}
}
}
}
/// Dispatch scenic key events
pub fn handle_scenic_key_event(&mut self, event: fidl_fuchsia_ui_input3::KeyEvent) {
let messages = self.strategy.handle_scenic_key_event(
&self.make_view_details(),
&mut self.assistant,
&event,
);
for msg in messages {
self.send_message(msg);
}
}
/// Handle input events that have been converted to Carnelian's format
pub fn handle_input_events(&mut self, events: Vec<input::Event>) {
for event in events {
let messages = self.strategy.handle_input_event(
&self.make_view_details(),
&mut self.assistant,
&event,
);
for msg in messages {
self.send_message(msg);
}
}
}
/// 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);
}
pub(crate) fn image_freed(&mut self, image_id: u64, collection_id: u32) {
self.strategy.image_freed(image_id, collection_id);
}
pub fn handle_vsync_parameters_changed(&mut self, phase: Time, interval: Duration) {
self.strategy.handle_vsync_parameters_changed(phase, interval);
}
pub fn handle_vsync_cookie(&mut self, cookie: u64) {
self.strategy.handle_vsync_cookie(cookie);
}
}