Skip to content

Commit

Permalink
WIP: Panning gesture in Portal
Browse files Browse the repository at this point in the history
  • Loading branch information
xorgy committed Aug 29, 2024
1 parent 8e2dddd commit bfbfac5
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 35 deletions.
17 changes: 16 additions & 1 deletion masonry/src/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ use crate::text::TextBrush;
use crate::text_helpers::{ImeChangeSignal, TextFieldRegistration};
use crate::tree_arena::ArenaMutChildren;
use crate::widget::{WidgetMut, WidgetState};
use crate::{AllowRawMut, CursorIcon, Insets, Point, Rect, Size, Widget, WidgetId, WidgetPod};
use crate::{
AllowRawMut, CursorIcon, Insets, Point, Rect, Size, TouchEvent, Widget, WidgetId, WidgetPod,
};

/// A macro for implementing methods on multiple contexts.
///
Expand Down Expand Up @@ -591,6 +593,19 @@ impl EventCtx<'_> {
self.global_state.pointer_capture_target = Some(self.widget_state.id);
}

// TODO: process captures
// TODO: clean up captures
pub fn capture_touch(&mut self, event: &TouchEvent) {
debug_assert!(
self.allow_pointer_capture,
"Error in #{}: event does not allow pointer capture",
self.widget_id().to_raw(),
);
self.global_state
.touch_capture_targets
.insert(event.state().id(), self.widget_id());
}

pub fn release_pointer(&mut self) {
self.global_state.pointer_capture_target = None;
}
Expand Down
98 changes: 88 additions & 10 deletions masonry/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@ use crate::kurbo::Rect;
// TODO - See issue https://github.com/linebender/xilem/issues/367
use crate::WidgetId;

use dpi::LogicalUnit;
use vello::kurbo::Vec2;

use std::path::PathBuf;
use std::time::Instant;

use winit::event::{Force, Ime, KeyEvent, Modifiers};
use winit::event::{Force, Ime, KeyEvent, Modifiers, TouchPhase};
use winit::keyboard::ModifiersState;

// TODO - Occluded(bool) event
Expand Down Expand Up @@ -217,13 +221,6 @@ pub struct AccessEvent {
pub data: Option<accesskit::ActionData>,
}

#[derive(Debug, Clone)]
pub enum PointerType {
Mouse,
Touch,
Pen,
}

#[derive(Debug, Clone)]
pub struct PointerState {
// TODO
Expand All @@ -235,7 +232,89 @@ pub struct PointerState {
pub count: u8,
pub focus: bool,
pub force: Option<Force>,
pub pointer_type: PointerType,
}

#[derive(Debug, Clone)]
pub enum TouchEvent {
Start(TouchState),
Move(TouchState),
End(TouchState),
Cancel(TouchState),
}

impl TouchEvent {
pub fn state(&self) -> &TouchState {
match self {
Self::Start(state) | Self::Move(state) | Self::End(state) | Self::Cancel(state) => {
state
}
}
}

pub fn position(&self) -> LogicalPosition<f64> {
self.state().position()
}
}

#[derive(Debug, Clone, Copy)]
pub struct TouchFrame {
pub time: Instant,
pub position: LogicalPosition<f64>,
pub force: Option<Force>,
}

#[derive(Debug, Clone)]
pub struct TouchState {
id: u64,
frames: Vec<TouchFrame>,
}

impl TouchState {
pub fn new(id: u64, frame: TouchFrame) -> Self {
Self {
id,
frames: Vec::from([frame]),
}
}

pub fn push(&mut self, frame: TouchFrame) {
self.frames.push(frame);
}

pub fn last_frame(&self) -> &TouchFrame {
self.frames
.last()
.expect("TouchState has at least one frame")
}

pub fn first_frame(&self) -> &TouchFrame {
self.frames
.first()
.expect("TouchState has at least one frame")
}

pub fn velocity(&self) -> (LogicalUnit<f64>, LogicalUnit<f64>) {
// TODO: impl
(0.0.into(), 0.0.into())
}

pub fn position(&self) -> LogicalPosition<f64> {
self.last_frame().position
}

pub fn displacement(&self) -> Vec2 {
let LogicalPosition::<f64> { x: ax, y: ay } = self.first_frame().position;
let LogicalPosition::<f64> { x: bx, y: by } = self.last_frame().position;
Vec2::new(ax - bx, ay - by)
}

pub fn force(&self) -> Option<Force> {
self.last_frame().force
}

pub fn id(&self) -> u64 {
self.id
}
}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -495,7 +574,6 @@ impl PointerState {
count: 0,
focus: false,
force: None,
pointer_type: PointerType::Mouse,
}
}
}
Expand Down
46 changes: 44 additions & 2 deletions masonry/src/event_loop_runner.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0

use std::collections::BTreeMap;
use std::num::NonZeroUsize;
use std::sync::Arc;
use std::time::Instant;

use accesskit_winit::Adapter;
use tracing::{debug, warn};
Expand All @@ -21,9 +23,9 @@ use winit::window::{Window, WindowAttributes, WindowId};

use crate::app_driver::{AppDriver, DriverCtx};
use crate::dpi::LogicalPosition;
use crate::event::{PointerButton, PointerState, WindowEvent};
use crate::event::{PointerButton, PointerState, TouchFrame, TouchState, WindowEvent};
use crate::render_root::{self, RenderRoot, WindowSizePolicy};
use crate::{PointerEvent, TextEvent, Widget, WidgetId};
use crate::{Handled, PointerEvent, TextEvent, TouchEvent, Widget, WidgetId};

#[derive(Debug)]
pub enum MasonryUserEvent {
Expand Down Expand Up @@ -74,6 +76,7 @@ pub struct MasonryState<'a> {
render_cx: RenderContext,
render_root: RenderRoot,
pointer_state: PointerState,
touches: BTreeMap<u64, TouchState>,
renderer: Option<Renderer>,
// TODO: Winit doesn't seem to let us create these proxies from within the loop
// The reasons for this are unclear
Expand Down Expand Up @@ -237,6 +240,7 @@ impl MasonryState<'_> {
),
renderer: None,
pointer_state: PointerState::empty(),
touches: Default::default(),
proxy: event_loop.create_proxy(),

window: WindowState::Uninitialized(window),
Expand Down Expand Up @@ -516,10 +520,48 @@ impl MasonryState<'_> {
location,
phase,
force,
id,
..
}) => {
// FIXME: This is naïve and should be refined for actual use.
// It will also interact with gesture discrimination.
let frame = TouchFrame {
time: Instant::now(),
position: location.to_logical(window.scale_factor()),
force,
};
let state = self
.touches
.entry(id)
.and_modify(|v| v.push(frame))
.or_insert_with(|| TouchState::new(id, frame));

// Try to dispatch as touch
let handled = match phase {
winit::event::TouchPhase::Started => self
.render_root
.handle_touch_event(TouchEvent::Start(state.clone())),
winit::event::TouchPhase::Ended => match self.touches.remove_entry(&id) {
Some((_, state)) => self
.render_root
.handle_touch_event(TouchEvent::End(state.clone())),
_ => Handled::No,
},
winit::event::TouchPhase::Moved => self
.render_root
.handle_touch_event(TouchEvent::Move(state.clone())),
winit::event::TouchPhase::Cancelled => match self.touches.remove_entry(&id) {
Some((_, state)) => self
.render_root
.handle_touch_event(TouchEvent::Cancel(state.clone())),
_ => Handled::No,
},
};

if handled == Handled::Yes {
return;
}

self.pointer_state.physical_position = location;
self.pointer_state.position = location.to_logical(window.scale_factor());
self.pointer_state.force = force;
Expand Down
2 changes: 1 addition & 1 deletion masonry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ pub use contexts::{
};
pub use event::{
AccessEvent, InternalLifeCycle, LifeCycle, PointerButton, PointerEvent, PointerState,
StatusChange, TextEvent, WindowEvent, WindowTheme,
StatusChange, TextEvent, TouchEvent, WindowEvent, WindowTheme,
};
pub use kurbo::{Affine, Insets, Point, Rect, Size, Vec2};
pub use parley::layout::Alignment as TextAlignment;
Expand Down
55 changes: 54 additions & 1 deletion masonry/src/passes/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use winit::keyboard::{KeyCode, PhysicalKey};
use crate::passes::merge_state_up;
use crate::render_root::RenderRoot;
use crate::{
AccessEvent, EventCtx, Handled, PointerEvent, TextEvent, Widget, WidgetId, WidgetState,
AccessEvent, EventCtx, Handled, PointerEvent, TextEvent, TouchEvent, Widget, WidgetId,
WidgetState,
};

fn get_target_widget(
Expand Down Expand Up @@ -119,6 +120,58 @@ pub(crate) fn root_on_pointer_event(
handled
}

pub(crate) fn root_on_touch_event(
root: &mut RenderRoot,
root_state: &mut WidgetState,
event: &TouchEvent,
) -> Handled {
let pos = event.position();

// Descend from the root when dispatching touches
// to allow portals and other containers to process gestures
let mut is_handled = false;
if let Some(target_widget_id) = root
.get_root_widget()
.find_widget_at_pos((pos.x, pos.y).into())
.map(|widget| widget.id())
{
// println!("{:?}", root.widget_arena.path_of(target_widget_id));
for widget_id in root.widget_arena.path_of(target_widget_id).iter().rev() {
let (widget_mut, state_mut) = root.widget_arena.get_pair_mut(*widget_id);

let mut ctx = EventCtx {
global_state: &mut root.state,
widget_state: state_mut.item,
widget_state_children: state_mut.children,
widget_children: widget_mut.children,
allow_pointer_capture: matches!(event, TouchEvent::Start(..)),
is_handled: false,
request_pan_to_child: None,
};
let widget = widget_mut.item;

if !is_handled {
trace!(
"Widget '{}' #{} visited",
widget.short_type_name(),
widget_id.to_raw(),
);

widget.on_touch_event(&mut ctx, event);
is_handled = ctx.is_handled;
if is_handled {
break;
}
}
}
}

// Pass root widget state to synthetic state create at beginning of pass
root_state.merge_up(root.widget_arena.get_state_mut(root.root.id()).item);

Handled::from(is_handled)
}

pub(crate) fn root_on_text_event(
root: &mut RenderRoot,
root_state: &mut WidgetState,
Expand Down
Loading

0 comments on commit bfbfac5

Please sign in to comment.