Skip to content

Commit 5638f58

Browse files
committed
Add PointerCrusher
1 parent 1885aa4 commit 5638f58

File tree

5 files changed

+147
-23
lines changed

5 files changed

+147
-23
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ bitflags = "2"
5858
tracing = "0.1.37"
5959
accesskit = "0.12"
6060
fnv = "1.0.7"
61+
instant = { version = "0.1.6", features = ["wasm-bindgen"] }
6162

6263
[dependencies.glazier]
6364
git = "https://github.com/linebender/glazier"

src/app_main.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use std::any::Any;
1616

1717
use accesskit::TreeUpdate;
1818
use glazier::{
19-
Application, Cursor, IdleToken, PointerEvent, Region, Scalable, WinHandler, WindowBuilder,
20-
WindowHandle,
19+
Application, Cursor, IdleToken, PointerEvent, PointerType, Region, Scalable, WinHandler,
20+
WindowBuilder, WindowHandle,
2121
};
2222
use vello::{
2323
kurbo::{Affine, Size},
@@ -26,7 +26,7 @@ use vello::{
2626
AaSupport, RenderParams, Renderer, RendererOptions, Scene,
2727
};
2828

29-
use crate::{app::App, view::View, widget::Event};
29+
use crate::{app::App, view::View, widget::Event, widget::PointerCrusher};
3030

3131
// This is a bit of a hack just to get a window launched. The real version
3232
// would deal with multiple windows and have other ways to configure things.
@@ -44,6 +44,7 @@ struct MainState<T, V: View<T>> {
4444
renderer: Option<Renderer>,
4545
scene: Scene,
4646
counter: u64,
47+
main_pointer: PointerCrusher,
4748
}
4849

4950
impl<T: Send + 'static, V: View<T> + 'static> AppLauncher<T, V> {
@@ -108,23 +109,34 @@ impl<T: Send + 'static, V: View<T> + 'static> WinHandler for MainState<T, V> {
108109
}
109110

110111
fn pointer_down(&mut self, event: &PointerEvent) {
111-
self.app.window_event(Event::MouseDown(event.into()));
112+
self.main_pointer.mods(event.modifiers);
113+
self.app
114+
.window_event(Event::MouseDown(self.main_pointer.pressed(event.button)));
112115
self.handle.invalidate();
113116
}
114117

115118
fn pointer_up(&mut self, event: &PointerEvent) {
116-
self.app.window_event(Event::MouseUp(event.into()));
119+
self.main_pointer.mods(event.modifiers);
120+
self.app
121+
.window_event(Event::MouseUp(self.main_pointer.released(event.button)));
117122
self.handle.invalidate();
118123
}
119124

120125
fn pointer_move(&mut self, event: &PointerEvent) {
121-
self.app.window_event(Event::MouseMove(event.into()));
126+
self.main_pointer.mods(event.modifiers);
127+
self.app
128+
.window_event(Event::MouseMove(self.main_pointer.moved(event.pos)));
122129
self.handle.invalidate();
123130
self.handle.set_cursor(&Cursor::Arrow);
124131
}
125132

126133
fn wheel(&mut self, event: &PointerEvent) {
127-
self.app.window_event(Event::MouseWheel(event.into()));
134+
self.main_pointer.mods(event.modifiers);
135+
if let PointerType::Mouse(ref info) = event.pointer_type {
136+
self.app
137+
.window_event(Event::MouseWheel(self.main_pointer.wheel(info.wheel_delta)));
138+
}
139+
128140
self.handle.invalidate();
129141
}
130142

@@ -164,6 +176,7 @@ where
164176
renderer: None,
165177
scene: Scene::default(),
166178
counter: 0,
179+
main_pointer: PointerCrusher::new(),
167180
}
168181
}
169182

src/widget/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub use box_constraints::BoxConstraints;
3333
pub use button::Button;
3434
pub use contexts::{AccessCx, CxState, EventCx, LayoutCx, LifeCycleCx, PaintCx, UpdateCx};
3535
pub use linear_layout::LinearLayout;
36-
pub use raw_event::{Event, LifeCycle, MouseEvent, ViewContext};
36+
pub use raw_event::{Event, LifeCycle, MouseEvent, PointerCrusher, ViewContext};
3737
pub use switch::Switch;
3838
pub use text::TextWidget;
3939
pub use widget::{AnyWidget, Widget};

src/widget/raw_event.rs

Lines changed: 124 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
//! Note: arguably this module should be renamed, perhaps we should use
1818
//! "event" for this level and maybe "message" at the View level.
1919
20-
use glazier::{Modifiers, PointerButton, PointerButtons, PointerType};
20+
use glazier::{Modifiers, PointerButton, PointerButtons};
2121
use vello::kurbo::{Point, Rect, Vec2};
2222

2323
#[derive(Debug, Clone)]
@@ -58,21 +58,17 @@ pub struct ViewContext {
5858
pub mouse_position: Option<Point>,
5959
}
6060

61-
impl<'a> From<&'a glazier::PointerEvent> for MouseEvent {
62-
fn from(src: &glazier::PointerEvent) -> MouseEvent {
61+
impl Default for MouseEvent {
62+
fn default() -> Self {
6363
MouseEvent {
64-
pos: src.pos,
65-
window_pos: src.pos,
66-
buttons: src.buttons,
67-
mods: src.modifiers,
68-
count: src.count,
69-
focus: src.focus,
70-
button: src.button,
71-
wheel_delta: if let PointerType::Mouse(ref info) = src.pointer_type {
72-
info.wheel_delta
73-
} else {
74-
Vec2::ZERO
75-
},
64+
pos: Point::ZERO,
65+
window_pos: Point::ZERO,
66+
buttons: PointerButtons::new(),
67+
mods: Modifiers::default(),
68+
count: 0,
69+
focus: false,
70+
button: PointerButton::None,
71+
wheel_delta: Vec2::ZERO,
7672
}
7773
}
7874
}
@@ -87,3 +83,116 @@ impl ViewContext {
8783
}
8884
}
8985
}
86+
87+
/// Crush all pointer events into a single pointer that counts clicks
88+
/// and attaches positions to events that don't contain them.
89+
#[derive(Default)]
90+
pub struct PointerCrusher {
91+
e: MouseEvent,
92+
counter: ClickCounter,
93+
}
94+
95+
impl PointerCrusher {
96+
pub fn new() -> Self {
97+
PointerCrusher::default()
98+
}
99+
100+
pub fn mods(&mut self, mods: Modifiers) {
101+
self.e.mods = mods;
102+
}
103+
104+
pub fn pressed(&mut self, button: PointerButton) -> MouseEvent {
105+
self.e.wheel_delta = Vec2::ZERO;
106+
self.e.buttons.insert(button);
107+
self.e.count = self.counter.count_for_click(self.e.pos);
108+
self.e.button = button;
109+
self.e.clone()
110+
}
111+
112+
pub fn released(&mut self, button: PointerButton) -> MouseEvent {
113+
self.e.wheel_delta = Vec2::ZERO;
114+
self.e.buttons.remove(button);
115+
self.e.count = self.counter.count_for_click(self.e.pos);
116+
self.e.button = button;
117+
self.e.clone()
118+
}
119+
120+
pub fn moved(&mut self, pos: Point) -> MouseEvent {
121+
self.e.wheel_delta = Vec2::ZERO;
122+
self.e.button = PointerButton::None;
123+
self.e.pos = pos;
124+
self.e.window_pos = pos;
125+
self.e.clone()
126+
}
127+
128+
pub fn wheel(&mut self, wheel_delta: Vec2) -> MouseEvent {
129+
self.e.wheel_delta = wheel_delta;
130+
self.e.button = PointerButton::None;
131+
self.e.clone()
132+
}
133+
}
134+
135+
use instant::Instant;
136+
use std::cell::Cell;
137+
use std::time::Duration;
138+
139+
// This is the default timing on windows.
140+
const MULTI_CLICK_INTERVAL: Duration = Duration::from_millis(500);
141+
// the max distance between two clicks for them to count as a multi-click
142+
const MULTI_CLICK_MAX_DISTANCE: f64 = 5.0;
143+
144+
/// A small helper for determining the click-count of a mouse-down event.
145+
///
146+
/// Click-count is incremented if both the duration and distance between a pair
147+
/// of clicks are below some threshold.
148+
#[derive(Debug, Clone)]
149+
struct ClickCounter {
150+
max_interval: Cell<Duration>,
151+
max_distance: Cell<f64>,
152+
last_click: Cell<Instant>,
153+
last_pos: Cell<Point>,
154+
click_count: Cell<u8>,
155+
}
156+
157+
#[allow(dead_code)]
158+
impl ClickCounter {
159+
/// Create a new `ClickCounter` with the given interval and distance.
160+
pub fn new(max_interval: Duration, max_distance: f64) -> ClickCounter {
161+
ClickCounter {
162+
max_interval: Cell::new(max_interval),
163+
max_distance: Cell::new(max_distance),
164+
last_click: Cell::new(Instant::now()),
165+
click_count: Cell::new(0),
166+
last_pos: Cell::new(Point::new(f64::MAX, 0.0)),
167+
}
168+
}
169+
170+
pub fn set_interval_ms(&self, millis: u64) {
171+
self.max_interval.set(Duration::from_millis(millis));
172+
}
173+
174+
pub fn set_distance(&self, distance: f64) {
175+
self.max_distance.set(distance);
176+
}
177+
178+
/// Return the click count for a click occurring now, at the provided position.
179+
pub fn count_for_click(&self, click_pos: Point) -> u8 {
180+
let click_time = Instant::now();
181+
let last_time = self.last_click.replace(click_time);
182+
let last_pos = self.last_pos.replace(click_pos);
183+
let elapsed = click_time - last_time;
184+
let distance = last_pos.distance(click_pos);
185+
if elapsed > self.max_interval.get() || distance > self.max_distance.get() {
186+
self.click_count.set(0);
187+
}
188+
let click_count = self.click_count.get().saturating_add(1);
189+
self.click_count.set(click_count);
190+
click_count
191+
}
192+
}
193+
194+
impl Default for ClickCounter {
195+
fn default() -> Self {
196+
ClickCounter::new(MULTI_CLICK_INTERVAL, MULTI_CLICK_MAX_DISTANCE)
197+
}
198+
}

0 commit comments

Comments
 (0)