Skip to content

Commit 59fc62f

Browse files
committed
Fix generic pointer input for graph view and edge cases for board view
1 parent b079486 commit 59fc62f

File tree

13 files changed

+175
-107
lines changed

13 files changed

+175
-107
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ features = [
4545
"ResizeObserver",
4646
"ResizeObserverEntry",
4747
"ResizeObserverSize",
48+
"TouchEvent",
4849
"PointerEvent",
4950
"MouseScrollEvent",
5051
"CanvasRenderingContext2d",

src/lib.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,4 @@ impl WiggersGraaf {
4545
StatefulViews::restart(&instance.stateful_views);
4646
Ok(instance)
4747
}
48-
49-
pub fn accumulate_translation(&self, delta_x: f32, delta_y: f32) {
50-
self.stateful_views
51-
.borrow()
52-
.accumulate_translation(delta_x, delta_y);
53-
}
54-
55-
pub fn accumulate_zoom(&self, zoom_movement: f32, target_x: f32, target_y: f32) {
56-
self.stateful_views
57-
.borrow()
58-
.accumulate_zoom(zoom_movement, target_x, target_y);
59-
}
6048
}
Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
// SPDX-FileCopyrightText: 2025 Menno van der Graaf <mennovandergraaf@hotmail.com>
22
// SPDX-License-Identifier: MIT
33

4-
use crate::views::controls::pointer_handler::{MouseHandler, PointerEvent};
4+
use crate::views::pointer_handler::{MouseHandler, PointerEvent};
55
use crate::views::utils::Coordinates;
66
use std::cell::RefCell;
77
use std::rc::{Rc, Weak};
88
use wasm_bindgen::JsValue;
99
use web_sys::HtmlElement;
1010

11-
pub mod pointer_handler;
12-
13-
pub struct PointerControls {
11+
pub struct Controls {
1412
on_event_cb: Box<OnPointerEventCb>,
1513
drag_pointer_index: Option<i32>,
1614
_pointer_handler: Rc<RefCell<MouseHandler>>,
@@ -25,7 +23,7 @@ pub enum ControlEvent {
2523
Up(),
2624
}
2725

28-
impl PointerControls {
26+
impl Controls {
2927
pub fn new(
3028
target: &HtmlElement,
3129
on_event_cb: Box<OnPointerEventCb>,
@@ -49,23 +47,30 @@ impl PointerControls {
4947
fn handle_event(&mut self, event: PointerEvent) -> bool {
5048
let mut handled = false;
5149
match event {
52-
PointerEvent::Down((index, coordinates)) => {
53-
if self.drag_pointer_index.is_none() {
50+
PointerEvent::Down((index, _timestamp, coordinates)) => {
51+
// If we were not dragging yet, then try to initiate a drag
52+
if self.drag_pointer_index.is_none()
53+
&& (self.on_event_cb)(ControlEvent::Down(coordinates))
54+
{
5455
self.drag_pointer_index = Some(index);
55-
handled = (self.on_event_cb)(ControlEvent::Down(coordinates));
56+
handled = true;
5657
}
5758
}
58-
PointerEvent::Up((index, _coordinates)) => {
59+
PointerEvent::Up((index, _timestamp, _coordinates)) => {
5960
if self.drag_pointer_index == Some(index) {
6061
self.drag_pointer_index = None;
6162
handled = (self.on_event_cb)(ControlEvent::Up());
6263
}
6364
}
64-
PointerEvent::Move((index, coordinates)) => {
65+
PointerEvent::Move((index, _timestamp, coordinates)) => {
6566
if self.drag_pointer_index == Some(index) {
6667
handled = (self.on_event_cb)(ControlEvent::Move(coordinates));
6768
}
6869
}
70+
PointerEvent::TouchMove() => {
71+
// Prevent default behavior of touchmove if pointer is down
72+
handled = self.drag_pointer_index.is_some()
73+
}
6974
PointerEvent::Wheel(_) => {}
7075
}
7176
handled

src/views/board_view/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
// SPDX-FileCopyrightText: 2025 Menno van der Graaf <mennovandergraaf@hotmail.com>
22
// SPDX-License-Identifier: MIT
3-
3+
mod controls;
44
mod layout;
55
mod renderer;
6-
pub(crate) mod visual_board;
6+
pub mod visual_board;
77

88
use crate::board::SlideMove;
99
use crate::graph;
10+
use crate::views::board_view::controls::{ControlEvent, Controls};
1011
use crate::views::board_view::layout::Layout;
1112
use crate::views::board_view::renderer::Renderer;
1213
use crate::views::board_view::visual_board::{
1314
AnimatableOffset, Animation, AnimationRepeatBehavior, DragEndResult, DragMove, VisualBoard,
1415
};
15-
use crate::views::controls::{ControlEvent, PointerControls};
1616
use crate::views::frame_scheduler::FrameScheduler;
1717
use crate::views::resize_observer::ResizeObserver;
1818
use crate::views::utils::{get_element_of_type, Size};
@@ -30,7 +30,7 @@ pub struct BoardView {
3030
on_drag_move_cb: Box<OnDragMoveCb>,
3131
frame_scheduler: FrameScheduler,
3232
_resize_observer: ResizeObserver,
33-
_pointer_controls: Rc<RefCell<PointerControls>>,
33+
_pointer_controls: Rc<RefCell<Controls>>,
3434
visual_board: VisualBoard,
3535
layout: Layout,
3636
renderer: Renderer,
@@ -65,7 +65,7 @@ impl BoardView {
6565
.resize(width, height);
6666
}),
6767
),
68-
_pointer_controls: PointerControls::new(
68+
_pointer_controls: Controls::new(
6969
&canvas,
7070
Box::new(move |event: ControlEvent| {
7171
self_ref_for_mouse_event_cb

src/views/board_view/visual_board.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -254,19 +254,18 @@ impl VisualBoard {
254254

255255
/// Start dragging the targeted piece, returns true if this piece can be dragged
256256
pub fn start_drag(&mut self, target: VisualCoordinates) -> bool {
257-
// Find if the cursor is targeting a piece, and find if this piece can be dragged
258-
let entry: Option<(board::Coordinates, bool)> = self
257+
// Find if the cursor is targeting a piece, and if that piece can be dragged
258+
let piece: Option<board::Coordinates> = self
259259
.pieces
260260
.iter_mut()
261-
.find(|(_, piece)| piece.rect.contains(target))
262-
.map(|(base_coordinates, piece)| (*base_coordinates, !piece.drag_moves.is_empty()));
261+
.find(|(_, piece)| piece.rect.contains(target) && !piece.drag_moves.is_empty())
262+
.map(|(base_coordinates, _)| *base_coordinates);
263263

264-
match entry {
264+
match piece {
265265
None => {
266266
self.highlight(&None);
267-
false
268267
}
269-
Some((base_coordinates, can_be_dragged)) => {
268+
Some(base_coordinates) => {
270269
// If we are currently running an animation, then clear it
271270
self.clear_animation();
272271
self.highlight(&Some(base_coordinates));
@@ -276,9 +275,9 @@ impl VisualBoard {
276275
target: base_coordinates,
277276
start_coordinates: None,
278277
});
279-
can_be_dragged
280278
}
281279
}
280+
piece.is_some()
282281
}
283282

284283
pub fn stop_drag(&mut self) -> DragEndResult {

src/views/frame_scheduler.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-FileCopyrightText: 2025 Menno van der Graaf <mennovandergraaf@hotmail.com>
22
// SPDX-License-Identifier: MIT
33

4+
use crate::views::utils::dom_high_res_timestamp_to_duration;
45
use std::cell::Cell;
56
use std::rc::Rc;
67
use std::time::Duration;
@@ -25,10 +26,7 @@ impl FrameScheduler {
2526
let frame_requested_clone = frame_requested.clone();
2627
let on_frame_closure = Closure::new(move |timestamp: f64| {
2728
frame_requested_clone.set(false);
28-
29-
// The DOMHighResTimeStamp is in milliseconds, convert it to a std time Duration
30-
let timestamp = Duration::from_micros((timestamp * 1000.0) as u64);
31-
on_frame_cb(timestamp);
29+
on_frame_cb(dom_high_res_timestamp_to_duration(timestamp));
3230
});
3331

3432
Self {

src/views/graph_view/controls.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// SPDX-FileCopyrightText: 2025 Menno van der Graaf <mennovandergraaf@hotmail.com>
2+
// SPDX-License-Identifier: MIT
3+
4+
use crate::views::pointer_handler::{MouseHandler, PointerEvent};
5+
use crate::views::utils::{Coordinates, Delta};
6+
use std::cell::RefCell;
7+
use std::rc::{Rc, Weak};
8+
use wasm_bindgen::JsValue;
9+
use web_sys::HtmlElement;
10+
11+
pub struct Controls {
12+
on_event_cb: Box<OnPointerEventCb>,
13+
// TODO(Menno 04.09.2025) Track multiple pointers for gestures
14+
drag_pointer_index: Option<i32>,
15+
previous_drag_coordinates: Coordinates,
16+
_pointer_handler: Rc<RefCell<MouseHandler>>,
17+
}
18+
19+
/// The callback type for the handler to call on a mouse event
20+
pub type OnPointerEventCb = dyn FnMut(ControlEvent);
21+
22+
pub enum ControlEvent {
23+
Down(Coordinates),
24+
Move(Delta),
25+
Up(),
26+
}
27+
28+
impl Controls {
29+
pub fn new(
30+
target: &HtmlElement,
31+
on_event_cb: Box<OnPointerEventCb>,
32+
) -> Result<Rc<RefCell<Self>>, JsValue> {
33+
Ok(Rc::new_cyclic(|self_ref: &Weak<RefCell<Self>>| {
34+
let self_ref = self_ref.clone();
35+
RefCell::new(Self {
36+
on_event_cb,
37+
drag_pointer_index: None,
38+
previous_drag_coordinates: Coordinates::zero(),
39+
_pointer_handler: MouseHandler::new(
40+
target,
41+
Box::new(move |event| -> bool {
42+
self_ref.upgrade().unwrap().borrow_mut().handle_event(event)
43+
}),
44+
)
45+
.expect("Couldn't register mouse handler"),
46+
})
47+
}))
48+
}
49+
50+
fn handle_event(&mut self, event: PointerEvent) -> bool {
51+
let mut handled = false;
52+
match event {
53+
PointerEvent::Down((index, _timestamp, coordinates)) => {
54+
if self.drag_pointer_index.is_none() {
55+
self.drag_pointer_index = Some(index);
56+
self.previous_drag_coordinates = coordinates;
57+
(self.on_event_cb)(ControlEvent::Down(coordinates));
58+
handled = true;
59+
}
60+
}
61+
PointerEvent::Up((index, _timestamp, _coordinates)) => {
62+
if self.drag_pointer_index == Some(index) {
63+
self.drag_pointer_index = None;
64+
(self.on_event_cb)(ControlEvent::Up());
65+
handled = true;
66+
}
67+
}
68+
PointerEvent::Move((index, _timestamp, coordinates)) => {
69+
if self.drag_pointer_index == Some(index) {
70+
let delta = coordinates - self.previous_drag_coordinates;
71+
(self.on_event_cb)(ControlEvent::Move(delta));
72+
self.previous_drag_coordinates = coordinates;
73+
handled = true;
74+
}
75+
}
76+
PointerEvent::TouchMove() => {
77+
handled = true;
78+
}
79+
PointerEvent::Wheel(_) => {}
80+
}
81+
handled
82+
}
83+
}

src/views/graph_view/mod.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::board::BoardId;
55
use crate::graph::Graph;
66
use crate::views::frame_scheduler::{FrameScheduler, OnFrameCb};
77
use crate::views::graph_view::arrangement::Arrangement;
8+
use crate::views::graph_view::controls::{ControlEvent, Controls};
89
use crate::views::graph_view::renderer::Renderer;
910
use crate::views::resize_observer::ResizeObserver;
1011
use crate::views::utils::get_element_of_type;
@@ -16,6 +17,7 @@ use wasm_bindgen::JsValue;
1617
use web_sys::HtmlCanvasElement;
1718

1819
pub mod arrangement;
20+
mod controls;
1921
mod renderer;
2022

2123
/// This represents the view's content coordinate space, dynamic axes depending on the content size
@@ -72,12 +74,11 @@ const _ZOOM_MINIMUM: Scale<f32, ClipSpace, ClipSpace> = Scale::new(1.0);
7274
/// The maximum zoom level
7375
const _ZOOM_MAXIMUM: Scale<f32, ClipSpace, ClipSpace> = Scale::new(5.0);
7476

75-
// TODO(Menno 30.04.2025) Can't be flicked yet
76-
/// A 2D graph view that can be zoomed, dragged and flicked around by mouse or touch input.
7777
pub struct GraphView {
7878
_self_ref: Weak<RefCell<Self>>,
7979
frame_scheduler: FrameScheduler,
8080
_resize_observer: ResizeObserver,
81+
_controls: Rc<RefCell<Controls>>,
8182
canvas: HtmlCanvasElement,
8283
canvas_needs_size_update: bool,
8384
canvas_size: Size2D<f32, CanvasSpace>,
@@ -97,6 +98,7 @@ impl GraphView {
9798
let view = Rc::new_cyclic(|self_ref| {
9899
let self_ref_for_on_frame_cb = self_ref.clone();
99100
let self_ref_for_resize_observer_cb = self_ref.clone();
101+
let self_ref_for_mouse_event_cb = self_ref.clone();
100102

101103
RefCell::new(Self {
102104
_self_ref: self_ref.clone(),
@@ -117,6 +119,17 @@ impl GraphView {
117119
.resize(width, height);
118120
}),
119121
),
122+
_controls: Controls::new(
123+
&canvas,
124+
Box::new(move |event: ControlEvent| {
125+
self_ref_for_mouse_event_cb
126+
.upgrade()
127+
.unwrap()
128+
.borrow_mut()
129+
.handle_pointer_event(event)
130+
}),
131+
)
132+
.expect("Could not create graph controls"),
120133
canvas,
121134
canvas_needs_size_update: false,
122135
canvas_size: Size2D::zero(),
@@ -173,7 +186,17 @@ impl GraphView {
173186
self.schedule_draw();
174187
}
175188

176-
pub fn accumulate_zoom(&mut self, zoom_movement: f32, target_x: f32, target_y: f32) {
189+
fn handle_pointer_event(&mut self, event: ControlEvent) {
190+
match event {
191+
ControlEvent::Down(_coordinates) => {}
192+
ControlEvent::Move(coordinates) => {
193+
self.handle_translation(Vector2D::new(coordinates.x as f32, -coordinates.y as f32))
194+
}
195+
ControlEvent::Up() => {}
196+
}
197+
}
198+
199+
fn _accumulate_zoom(&mut self, zoom_movement: f32, target_x: f32, target_y: f32) {
177200
let target_begin = self
178201
.canvas_to_clip
179202
.transform_vector(Vector2D::new(target_x, target_y));
@@ -198,12 +221,9 @@ impl GraphView {
198221
self.schedule_draw();
199222
}
200223

201-
pub fn accumulate_translation(&mut self, delta_x: f32, delta_y: f32) {
202-
let delta_translation = self
203-
.canvas_to_clip
204-
.transform_vector(Vector2D::new(delta_x, delta_y));
224+
fn handle_translation(&mut self, translation: Vector2D<f32, CanvasSpace>) {
205225
// TODO(Menno 04.05.2025) Clamp this translation
206-
self.translation += delta_translation;
226+
self.translation += self.canvas_to_clip.transform_vector(translation);
207227
self.recalculate_view_transform();
208228
self.schedule_draw();
209229
}

src/views/mod.rs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
// SPDX-License-Identifier: MIT
33

44
mod board_view;
5-
mod controls;
65
mod frame_scheduler;
76
pub mod graph_view;
87
mod moves_view;
8+
pub mod pointer_handler;
99
mod resize_observer;
1010
mod utils;
1111

@@ -99,18 +99,6 @@ impl StatefulViews {
9999
}))
100100
}
101101

102-
pub fn accumulate_translation(&self, delta_x: f32, delta_y: f32) {
103-
self.graph_view
104-
.borrow_mut()
105-
.accumulate_translation(delta_x, delta_y);
106-
}
107-
108-
pub fn accumulate_zoom(&self, zoom_movement: f32, target_x: f32, target_y: f32) {
109-
self.graph_view
110-
.borrow_mut()
111-
.accumulate_zoom(zoom_movement, target_x, target_y);
112-
}
113-
114102
pub fn preview_move(&self, move_info: Option<MoveInfo>) {
115103
let Some(_lock) = BoolGuard::lock(&self.move_lock) else {
116104
// No preview, a move is ongoing

0 commit comments

Comments
 (0)