blob: 8e3f36752c0f43c93191dafae0d7c0c379fb383b [file] [log] [blame]
// Copyright 2020 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 {
async_trait::async_trait,
fidl_fuchsia_ui_input as fidl_ui_input,
fidl_fuchsia_ui_policy::PointerCaptureListenerHackProxy,
fuchsia_zircon::{ClockId, Time},
futures::lock::Mutex,
input::input_device,
input::input_handler::InputHandler,
input::touch,
std::sync::Arc,
};
/// A [`TouchPointerHack`] observes touch events and sends them to observers.
///
/// Ermine cannot receive pointer events from Scenic for subviews (i.e., Flutter
/// applications it embeds), so this handler is used to send all pointer events directly
/// to Ermine.
///
/// Once Ermine has a way to observe these events directly from Scenic, this handler
/// can be removed.
pub struct TouchPointerHack {
/// The width of the display, used to compute the sent touch location.
display_width: f32,
/// The height of the display, used to compute the sent touch location.
display_height: f32,
/// The scale to apply to touch event before sending to listeners.
event_scale: f32,
/// The listeners to notify of touch events.
listeners: Arc<Mutex<Vec<PointerCaptureListenerHackProxy>>>,
}
#[async_trait]
impl InputHandler for TouchPointerHack {
async fn handle_input_event(
&mut self,
input_event: input_device::InputEvent,
) -> Vec<input_device::InputEvent> {
match &input_event {
input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Touch(touch_event),
device_descriptor:
input_device::InputDeviceDescriptor::Touch(touch_device_descriptor),
..
} => {
self.handle_touch_event(touch_event, touch_device_descriptor).await;
}
_ => {}
}
vec![input_event]
}
}
impl TouchPointerHack {
pub fn new(
display_width: f32,
display_height: f32,
event_scale: f32,
listeners: Arc<Mutex<Vec<PointerCaptureListenerHackProxy>>>,
) -> Self {
TouchPointerHack { display_width, display_height, event_scale, listeners }
}
async fn handle_touch_event(
&self,
touch_event: &touch::TouchEvent,
touch_descriptor: &touch::TouchDeviceDescriptor,
) {
// The order in which events are sent to clients.
let ordered_phases = vec![
fidl_ui_input::PointerEventPhase::Add,
fidl_ui_input::PointerEventPhase::Down,
fidl_ui_input::PointerEventPhase::Move,
fidl_ui_input::PointerEventPhase::Up,
fidl_ui_input::PointerEventPhase::Remove,
];
for phase in ordered_phases {
let contacts: Vec<touch::TouchContact> =
touch_event.contacts.get(&phase).map_or(vec![], |contacts| contacts.to_vec());
for contact in contacts {
let mut command =
self.create_pointer_input_command(phase, contact, &touch_descriptor);
let listeners = self.listeners.lock().await;
for listener in &mut listeners.iter() {
let listener = listener;
let _ = listener.on_pointer_event(&mut command);
}
}
}
}
/// Creates a pointer input command to send to listeners.
fn create_pointer_input_command(
&self,
phase: fidl_ui_input::PointerEventPhase,
contact: touch::TouchContact,
touch_descriptor: &touch::TouchDeviceDescriptor,
) -> fidl_ui_input::PointerEvent {
let (x, y) = self.device_coordinate_from_contact(&contact, &touch_descriptor);
fidl_ui_input::PointerEvent {
event_time: Time::get(ClockId::Monotonic).into_nanos() as u64,
device_id: touch_descriptor.device_id,
pointer_id: contact.id,
type_: fidl_ui_input::PointerEventType::Touch,
phase,
x: x * self.event_scale,
y: y * self.event_scale,
radius_major: 0.0,
radius_minor: 0.0,
buttons: 0,
}
}
/// Converts a touch coordinate in device coordinates into a display coordinate.
///
/// For example, if a touch device reports x in a range [0, 100], and the display
/// is [0, 200] pixels, a touch at [0, 75] would be converted to [0, 150].
fn device_coordinate_from_contact(
&self,
contact: &touch::TouchContact,
touch_descriptor: &touch::TouchDeviceDescriptor,
) -> (f32, f32) {
let default = (contact.position_x as f32, contact.position_y as f32);
if let Some(contact_descriptor) = touch_descriptor.contacts.first() {
let x_range = (contact_descriptor.x_range.max - contact_descriptor.x_range.min) as f32;
let y_range = (contact_descriptor.y_range.max - contact_descriptor.y_range.min) as f32;
if x_range == 0.0 || y_range == 0.0 {
return default;
}
let normalized_x = contact.position_x as f32 / x_range;
let normalized_y = contact.position_y as f32 / y_range;
(normalized_x * self.display_width as f32, normalized_y * self.display_height as f32)
} else {
return default;
}
}
}