blob: cb4def3aff550efeadadb5a2e5a0e62df6736d36 [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,
futures::lock::Mutex,
input::input_device,
input::input_handler::InputHandler,
input::touch,
input::{Position, Size},
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 size of the display, used to compute the sent touch location.
display_size: Size,
/// 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),
event_time,
} => {
self.handle_touch_event(touch_event, touch_device_descriptor, *event_time).await;
}
_ => {}
}
vec![input_event]
}
}
impl TouchPointerHack {
pub fn new(
display_size: Size,
event_scale: f32,
listeners: Arc<Mutex<Vec<PointerCaptureListenerHackProxy>>>,
) -> Self {
TouchPointerHack { display_size, event_scale, listeners }
}
async fn handle_touch_event(
&self,
touch_event: &touch::TouchEvent,
touch_descriptor: &touch::TouchDeviceDescriptor,
event_time: input_device::EventTime,
) {
// 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,
event_time,
&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,
event_time: input_device::EventTime,
touch_descriptor: &touch::TouchDeviceDescriptor,
) -> fidl_ui_input::PointerEvent {
let position =
self.device_coordinate_from_contact(&contact, &touch_descriptor) * self.event_scale;
fidl_ui_input::PointerEvent {
event_time: event_time,
device_id: touch_descriptor.device_id,
pointer_id: contact.id,
type_: fidl_ui_input::PointerEventType::Touch,
phase,
x: position.x,
y: position.y,
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,
) -> Position {
if let Some(contact_descriptor) = touch_descriptor.contacts.first() {
let range = Position {
x: (contact_descriptor.x_range.max - contact_descriptor.x_range.min) as f32,
y: (contact_descriptor.y_range.max - contact_descriptor.y_range.min) as f32,
};
if range == Position::zero() {
return contact.position;
}
let normalized = contact.position / range;
normalized * self.display_size
} else {
return contact.position;
}
}
}