blob: fed6308057ee7c25ed5262eb46a99ebbe00ae89d [file] [log] [blame]
// Copyright 2022 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 {
super::*, fidl_fuchsia_ui_pointer as fptr, fuchsia_zircon as zx, num, std::collections::HashSet,
};
const SCROLL_OFFSET_MULTIPLIER: i64 = 20;
impl PointerFusionState {
// Converts raw [fptr::MouseEvent]s to one or more [PointerEvent]s.
pub(super) fn fuse_mouse(&mut self, event: fptr::MouseEvent) -> Vec<PointerEvent> {
if let Some(ref device_info) = event.device_info {
self.mouse_device_info.insert(device_info.id.unwrap_or(0), device_info.clone());
}
if event.view_parameters.is_some() {
self.mouse_view_parameters = event.view_parameters;
}
if has_valid_mouse_sample(&event) && self.mouse_view_parameters.is_some() {
let sample = event.pointer_sample.as_ref().unwrap();
let id = sample.device_id.unwrap();
if self.mouse_device_info.contains_key(&id) {
let any_button_down = sample.pressed_buttons.is_some();
let phase = compute_mouse_phase(any_button_down, &mut self.mouse_down, id);
let pointer_event = create_mouse_draft(
&event,
phase,
self.mouse_view_parameters.as_ref().unwrap(),
self.mouse_device_info.get(&id).unwrap(),
self.pixel_ratio,
);
let sanitized_events = self.sanitize_pointer(pointer_event);
return sanitized_events;
}
}
vec![]
}
// Sanitizes the [PointerEvent] draft such that the resulting event stream is contextually
// correct. It may drop events or synthesize new events to keep the event stream sane.
//
// Note: It is still possible to craft an event stream that will cause an assert check to fail
// on debug builds.
fn sanitize_pointer(&mut self, mut event: PointerEvent) -> Vec<PointerEvent> {
let mut converted_pointers = vec![];
match event.signal_kind {
SignalKind::None => match event.phase {
// Drops the Cancel if the pointer is not previously added.
Phase::Cancel => {
if let Some(state) = self.pointer_states.get_mut(&event.device_id) {
assert!(state.is_down);
event.id = state.id;
// Synthesize a move event if the location does not match.
if state.is_location_changed(&event) {
let (physical_delta_x, physical_delta_y) = state.compute_delta(&event);
let move_event = PointerEvent {
physical_delta_x,
physical_delta_y,
phase: Phase::Move,
synthesized: true,
..event.clone()
};
state.physical_x = move_event.physical_x;
state.physical_y = move_event.physical_y;
converted_pointers.push(move_event);
}
state.is_down = false;
converted_pointers.push(event);
}
}
Phase::Add => {
assert!(!self.pointer_states.contains_key(&event.device_id));
let state = PointerState::from_event(&event);
self.pointer_states.insert(event.device_id, state);
converted_pointers.push(event);
}
Phase::Remove => {
assert!(self.pointer_states.contains_key(&event.device_id));
if let Some(state) = self.pointer_states.get_mut(&event.device_id) {
// Synthesize a Cancel event if pointer is down.
if state.is_down {
let mut cancel_event = event.clone();
cancel_event.phase = Phase::Cancel;
cancel_event.synthesized = true;
cancel_event.id = state.id;
state.is_down = false;
converted_pointers.push(cancel_event);
}
// Synthesize a hover event if the location does not match.
if state.is_location_changed(&event) {
let (physical_delta_x, physical_delta_y) = state.compute_delta(&event);
let hover_event = PointerEvent {
physical_delta_x,
physical_delta_y,
phase: Phase::Hover,
synthesized: true,
..event.clone()
};
state.physical_x = hover_event.physical_x;
state.physical_y = hover_event.physical_y;
converted_pointers.push(hover_event);
}
}
self.pointer_states.remove(&event.device_id);
converted_pointers.push(event);
}
Phase::Hover => {
let mut state = match self.pointer_states.get_mut(&event.device_id) {
Some(state) => *state,
None => {
// Synthesize add event if the pointer is not previously added.
let mut add_event = event.clone();
add_event.phase = Phase::Add;
add_event.synthesized = true;
let state = PointerState::from_event(&add_event);
self.pointer_states.insert(add_event.device_id, state);
converted_pointers.push(add_event);
state
}
};
assert!(!state.is_down);
if state.is_location_changed(&event) {
let (physical_delta_x, physical_delta_y) = state.compute_delta(&event);
event.physical_delta_x = physical_delta_x;
event.physical_delta_y = physical_delta_y;
state.physical_x = event.physical_x;
state.physical_y = event.physical_y;
converted_pointers.push(event);
}
}
Phase::Down => {
let mut state = match self.pointer_states.get_mut(&event.device_id) {
Some(state) => *state,
None => {
// Synthesize add event if the pointer is not previously added.
let mut add_event = event.clone();
add_event.phase = Phase::Add;
add_event.synthesized = true;
let state = PointerState::from_event(&add_event);
self.pointer_states.insert(add_event.device_id, state);
converted_pointers.push(add_event);
state
}
};
assert!(!state.is_down);
// Synthesize a hover event if the location does not match.
if state.is_location_changed(&event) {
let (physical_delta_x, physical_delta_y) = state.compute_delta(&event);
let hover_event = PointerEvent {
physical_delta_x,
physical_delta_y,
phase: Phase::Hover,
synthesized: true,
..event.clone()
};
state.physical_x = hover_event.physical_x;
state.physical_y = hover_event.physical_y;
converted_pointers.push(hover_event);
}
self.next_pointer_id += 1;
state.id = self.next_pointer_id;
state.is_down = true;
state.buttons = event.buttons;
self.pointer_states.insert(event.device_id, state);
converted_pointers.push(event);
}
Phase::Move => {
// Makes sure we have an existing pointer in down state
let mut state =
self.pointer_states.get_mut(&event.device_id).expect("State should exist");
assert!(state.is_down);
event.id = state.id;
// Skip this event if location does not change.
if state.is_location_changed(&event) || state.is_button_state_changed(&event) {
let (physical_delta_x, physical_delta_y) = state.compute_delta(&event);
event.physical_delta_x = physical_delta_x;
event.physical_delta_y = physical_delta_y;
state.physical_x = event.physical_x;
state.physical_y = event.physical_y;
state.buttons = event.buttons;
converted_pointers.push(event);
}
}
Phase::Up => {
// Makes sure we have an existing pointer in down state
let mut state =
self.pointer_states.get_mut(&event.device_id).expect("State should exist");
assert!(state.is_down);
event.id = state.id;
// Up phase should include which buttons where released.
let new_buttons = event.buttons;
event.buttons = state.buttons;
// Synthesize a move event if the location does not match.
if state.is_location_changed(&event) {
let (physical_delta_x, physical_delta_y) = state.compute_delta(&event);
let move_event = PointerEvent {
physical_delta_x,
physical_delta_y,
phase: Phase::Move,
synthesized: true,
..event.clone()
};
state.physical_x = move_event.physical_x;
state.physical_y = move_event.physical_y;
converted_pointers.push(move_event);
}
state.is_down = false;
state.buttons = new_buttons;
converted_pointers.push(event);
}
},
// Handle scroll events.
_ => {}
}
converted_pointers
}
}
fn compute_mouse_phase(any_button_down: bool, mouse_down: &mut HashSet<u32>, id: u32) -> Phase {
if !mouse_down.contains(&id) && !any_button_down {
return Phase::Hover;
} else if !mouse_down.contains(&id) && any_button_down {
mouse_down.insert(id);
return Phase::Down;
} else if mouse_down.contains(&id) && any_button_down {
return Phase::Move;
} else if mouse_down.contains(&id) && !any_button_down {
mouse_down.remove(&id);
return Phase::Up;
} else {
return Phase::Cancel;
}
}
fn create_mouse_draft(
event: &fptr::MouseEvent,
phase: Phase,
view_parameters: &fptr::ViewParameters,
device_info: &fptr::MouseDeviceInfo,
pixel_ratio: f32,
) -> PointerEvent {
assert!(has_valid_mouse_sample(event));
let sample = event.pointer_sample.as_ref().unwrap();
let mut pointer = PointerEvent::default();
pointer.timestamp = zx::Time::from_nanos(event.timestamp.unwrap_or(0));
pointer.phase = phase;
pointer.kind = DeviceKind::Mouse;
pointer.device_id = sample.device_id.unwrap_or(0);
let [logical_x, logical_y] =
viewport_to_view_coordinates(sample.position_in_viewport.unwrap(), view_parameters);
pointer.physical_x = logical_x * pixel_ratio;
pointer.physical_y = logical_y * pixel_ratio;
if sample.pressed_buttons.is_some() && device_info.buttons.is_some() {
let mut pointer_buttons: i64 = 0;
let pressed = sample.pressed_buttons.as_ref().unwrap();
let device_buttons = device_info.buttons.as_ref().unwrap();
for button_id in pressed {
if let Some(index) = device_buttons.iter().position(|&r| r == *button_id) {
pointer_buttons |= 1 << index;
}
}
pointer.buttons = pointer_buttons;
}
if sample.scroll_h.is_some()
|| sample.scroll_v.is_some()
|| sample.scroll_h_physical_pixel.is_some()
|| sample.scroll_v_physical_pixel.is_some()
{
let tick_x_20ths = sample.scroll_h.unwrap_or(0) * SCROLL_OFFSET_MULTIPLIER;
let tick_y_20ths = sample.scroll_v.unwrap_or(0) * SCROLL_OFFSET_MULTIPLIER;
let offset_x = sample.scroll_h_physical_pixel.unwrap_or(tick_x_20ths as f64);
let offset_y = sample.scroll_v_physical_pixel.unwrap_or(tick_y_20ths as f64);
pointer.scroll_delta_x = offset_x;
pointer.scroll_delta_y = offset_y;
}
pointer
}
fn has_valid_mouse_sample(event: &fptr::MouseEvent) -> bool {
if event.pointer_sample.is_none() {
return false;
}
let sample = event.pointer_sample.as_ref().unwrap();
sample.device_id.is_some()
&& sample.position_in_viewport.is_some()
&& (sample.pressed_buttons.is_none()
|| !sample.pressed_buttons.as_ref().unwrap().is_empty())
}
fn viewport_to_view_coordinates(
viewport_coordinates: [f32; 2],
view_parameters: &fptr::ViewParameters,
) -> [f32; 2] {
let viewport_to_view_transform = view_parameters.viewport_to_view_transform;
// The transform matrix is a FIDL array with matrix data in column-major
// order. For a matrix with data [a b c d e f g h i], and with the viewport
// coordinates expressed as homogeneous coordinates, the logical view
// coordinates are obtained with the following formula:
// |a d g| |x| |x'|
// |b e h| * |y| = |y'|
// |c f i| |1| |w'|
// which we then normalize based on the w component:
// if z' not zero: (x'/w', y'/w')
// else (x', y')
let m = viewport_to_view_transform;
let x = viewport_coordinates[0];
let y = viewport_coordinates[1];
let xp = m[0] * x + m[3] * y + m[6];
let yp = m[1] * x + m[4] * y + m[7];
let wp = m[2] * x + m[5] * y + m[8];
let [x, y] = if wp > EPSILON { [xp / wp, yp / wp] } else { [xp, yp] };
clamp_to_view_space(x, y, view_parameters)
}
fn clamp_to_view_space(x: f32, y: f32, p: &fptr::ViewParameters) -> [f32; 2] {
let min_x = p.view.min[0];
let min_y = p.view.min[1];
let max_x = p.view.max[0];
let max_y = p.view.max[1];
if min_x <= x && x < max_x && min_y <= y && y < max_y {
return [x, y]; // No clamping to perform.
}
// View boundary is [min_x, max_x) x [min_y, max_y). Note that min is
// inclusive, but max is exclusive - so we subtract epsilon.
let max_x_inclusive = max_x - EPSILON;
let max_y_inclusive = max_y - EPSILON;
let clamped_x = num::clamp(x, min_x, max_x_inclusive);
let clamped_y = num::clamp(y, min_y, max_y_inclusive);
return [clamped_x, clamped_y];
}