1use web_time::Duration;
4
5use repose_app::{ReposeRuntime, TouchGestureState};
6use repose_core::Vec2;
7use repose_core::input::PointerButton;
8use repose_core::shortcuts::{Action, Gesture};
9use winit::event::{ElementState, MouseScrollDelta, Touch};
10use winit::keyboard::{KeyCode, PhysicalKey};
11
12use crate::common::{map_key, winit_key_to_repose};
13
14pub const MAX_BACKEND_INIT_ATTEMPTS: u32 = 3;
18
19const BASE_BACKEND_RETRY_DELAY: Duration = Duration::from_millis(250);
20
21#[derive(Clone, Copy, PartialEq, Eq, Debug)]
22pub enum BackendState {
23 Pending,
24 Ready,
25 Failed,
26 Unavailable,
28}
29
30pub enum BackendRetry {
31 Ready,
32 RetryAfter(Duration),
33 Unavailable,
34}
35
36#[derive(Default)]
41pub struct BackendLifecycle {
42 state: Option<BackendState>,
43 attempts: u32,
44}
45
46impl BackendLifecycle {
47 pub fn new() -> Self {
48 Self::default()
49 }
50
51 pub fn state(&self) -> BackendState {
52 self.state.unwrap_or(BackendState::Pending)
53 }
54
55 pub fn begin(&mut self) {
57 self.state = Some(BackendState::Pending);
58 }
59
60 pub fn ready(&mut self) {
61 self.state = Some(BackendState::Ready);
62 self.attempts = 0;
63 }
64
65 pub fn failed(&mut self) -> BackendRetry {
67 self.attempts += 1;
68 if self.attempts >= MAX_BACKEND_INIT_ATTEMPTS {
69 self.state = Some(BackendState::Unavailable);
70 BackendRetry::Unavailable
71 } else {
72 self.state = Some(BackendState::Failed);
73 BackendRetry::RetryAfter(BASE_BACKEND_RETRY_DELAY * (1 << (self.attempts - 1).min(4)))
74 }
75 }
76}
77
78pub fn on_modifiers_changed(rt: &mut ReposeRuntime, state: &winit::keyboard::ModifiersState) {
80 crate::common::update_modifiers(&mut rt.modifiers, state);
81}
82
83pub fn on_cursor_moved(
85 rt: &mut ReposeRuntime,
86 pos: Vec2,
87 inspector: &mut Option<repose_devtools::Inspector>,
88) -> Option<repose_core::CursorIcon> {
89 let result = rt.handle_pointer_move(pos);
90 if let (Some(inspector), Some(f)) = (inspector, &rt.frame_cache)
91 && inspector.hud.inspector_enabled
92 {
93 let hit = f.hit_regions.iter().find(|h| h.rect.contains(pos));
94 let hover_rect = hit.map(|h| h.rect);
95 let hover_info = hit.and_then(|h| {
96 f.semantics_nodes
97 .iter()
98 .find(|s| s.id == h.id)
99 .map(|s| repose_devtools::HoveredInfo {
100 id: s.id,
101 role: format!("{:?}", s.role),
102 label: s.label.clone(),
103 })
104 });
105 inspector.hud.set_hovered(hover_rect, hover_info);
106 }
107 result.cursor
108}
109
110pub fn on_mouse_wheel(rt: &mut ReposeRuntime, delta: MouseScrollDelta, scale: f32) -> bool {
112 let (dx_px, dy_px) = match delta {
113 MouseScrollDelta::LineDelta(x, y) => {
114 let _ = scale;
115 let unit_px = repose_core::Dp(60.0).to_px().0;
116 (-(x * unit_px), -(y * unit_px))
117 }
118 MouseScrollDelta::PixelDelta(p) => (-(p.x as f32), -(p.y as f32)),
119 };
120 rt.handle_scroll(Vec2 { x: dx_px, y: dy_px })
121}
122
123pub fn map_mouse_button(btn: winit::event::MouseButton) -> Option<PointerButton> {
125 match btn {
126 winit::event::MouseButton::Left => Some(PointerButton::Primary),
127 winit::event::MouseButton::Right => Some(PointerButton::Secondary),
128 winit::event::MouseButton::Middle => Some(PointerButton::Tertiary),
129 _ => None,
130 }
131}
132
133pub struct TouchResult {
135 pub dirty: bool,
136 pub pinch: Option<(f32, Vec2)>,
137 pub pan: Option<(Vec2, Vec2)>,
138 pub rotation: Option<(f32, Vec2)>,
139 pub swipe_right: Option<bool>,
140 pub press: Option<Option<u64>>,
144}
145
146pub fn handle_touch_raw(
147 rt: &mut ReposeRuntime,
148 touch_gestures: &mut TouchGestureState,
149 t: &Touch,
150 scale: f32,
151) -> TouchResult {
152 let pos_px = (t.location.x as f32, t.location.y as f32);
153 let tid = t.id;
154 match t.phase {
155 winit::event::TouchPhase::Started => {
156 touch_gestures.contact_down(tid, pos_px);
161 touch_gestures.touch_started(rt, tid, pos_px);
162 sync_touch_points(rt, touch_gestures);
163 TouchResult {
164 dirty: true,
165 pinch: None,
166 pan: None,
167 rotation: None,
168 swipe_right: None,
169 press: None,
170 }
171 }
172 winit::event::TouchPhase::Moved => {
173 touch_gestures.contact_down(tid, pos_px);
174 let (dirty, pinch, pan, rotation) = touch_gestures.touch_moved(rt, tid, pos_px, scale);
175 sync_touch_points(rt, touch_gestures);
176 TouchResult {
177 dirty,
178 pinch,
179 pan,
180 rotation,
181 swipe_right: None,
182 press: None,
183 }
184 }
185 winit::event::TouchPhase::Ended | winit::event::TouchPhase::Cancelled => {
186 let cancelled = t.phase == winit::event::TouchPhase::Cancelled;
187 let ended = touch_gestures.touch_ended(rt, tid, pos_px, cancelled);
191 touch_gestures.contact_up(tid);
192 sync_touch_points(rt, touch_gestures);
193 TouchResult {
194 dirty: false,
195 pinch: None,
196 pan: None,
197 rotation: None,
198 swipe_right: ended.swipe_right,
199 press: ended.press,
200 }
201 }
202 }
203}
204
205pub fn sync_touch_points(rt: &mut ReposeRuntime, touch_gestures: &TouchGestureState) {
212 rt.sched.touch_points.clear();
213 rt.sched.touch_points.extend(
214 touch_gestures
215 .active_touches()
216 .iter()
217 .map(|(id, (x, y))| (*id, *x, *y)),
218 );
219}
220
221pub fn on_touch(
224 rt: &mut ReposeRuntime,
225 touch_gestures: &mut TouchGestureState,
226 t: &Touch,
227 scale: f32,
228 mut dispatch: impl FnMut(Action) -> bool,
229) -> bool {
230 let r = handle_touch_raw(rt, touch_gestures, t, scale);
231 let mut dirty = r.dirty;
232 if let Some((delta_scale, center)) = r.pinch
233 && dispatch(Action::Gesture(Gesture::PinchWithCenter {
234 delta_scale,
235 center,
236 }))
237 {
238 dirty = true;
239 }
240 if let Some((delta, center)) = r.pan
241 && dispatch(Action::Gesture(Gesture::Pan { delta, center }))
242 {
243 dirty = true;
244 }
245 if let Some((delta_rotation, center)) = r.rotation
246 && dispatch(Action::Gesture(Gesture::Rotate {
247 delta_rotation,
248 center,
249 }))
250 {
251 dirty = true;
252 }
253 if let Some(right) = r.swipe_right {
254 let g = if right {
255 Gesture::SwipeRight
256 } else {
257 Gesture::SwipeLeft
258 };
259 if dispatch(Action::Gesture(g)) {
260 dirty = true;
261 }
262 }
263 dirty
264}
265
266pub fn on_touch_with_ime(
269 rt: &mut ReposeRuntime,
270 touch_gestures: &mut TouchGestureState,
271 t: &Touch,
272 scale: f32,
273 _window: &winit::window::Window,
274 dispatch: impl FnMut(Action) -> bool,
275) -> bool {
276 let pos_px = (t.location.x as f32, t.location.y as f32);
277 let tid = t.id;
278 if t.phase == winit::event::TouchPhase::Started {
279 touch_gestures.contact_down(tid, pos_px);
280 let focused = touch_gestures.touch_started(rt, tid, pos_px);
281 sync_touch_points(rt, touch_gestures);
282 #[cfg(not(target_arch = "wasm32"))]
283 if let Some(fid) = focused {
284 let editable = rt
285 .frame_cache
286 .as_ref()
287 .is_some_and(|frame| crate::common::is_editable_textfield_hit(frame, fid));
288 if rt.sched.window_focused && editable {
289 let (purpose, ac, cap) = rt.focused_keyboard_hints();
290 crate::common::set_ime_for_textfield_ex(_window, true, purpose, ac, cap);
291 } else {
292 crate::common::set_ime_for_textfield(_window, false);
293 }
294 } else {
295 crate::common::set_ime_for_textfield(_window, false);
296 }
297 #[cfg(target_arch = "wasm32")]
298 let _ = focused;
299 return true;
300 }
301 on_touch(rt, touch_gestures, t, scale, dispatch)
302}
303
304pub fn on_keyboard_input(
310 rt: &mut ReposeRuntime,
311 key_event: &winit::event::KeyEvent,
312 inspector: &mut Option<repose_devtools::Inspector>,
313) -> bool {
314 on_key_parts(
315 rt,
316 key_event.physical_key,
317 key_event.state == ElementState::Pressed,
318 key_event.repeat,
319 key_event.text.as_deref(),
320 inspector,
321 )
322}
323
324pub fn on_key_parts(
328 rt: &mut ReposeRuntime,
329 physical_key: PhysicalKey,
330 pressed: bool,
331 repeat: bool,
332 text: Option<&str>,
333 inspector: &mut Option<repose_devtools::Inspector>,
334) -> bool {
335 let key_event = KeyParts {
336 physical_key,
337 pressed,
338 repeat,
339 };
340 rt.set_physical_key(
341 &physical_key_name(key_event.physical_key),
342 key_event.pressed,
343 );
344 {
349 let key = map_physical_key(key_event.physical_key);
350 let sched = &mut rt.sched;
351 if key_event.pressed {
352 sched.held_keys.insert(key);
353 } else {
354 sched.held_keys.remove(&key);
355 }
356 }
357 if key_event.pressed
358 && !key_event.repeat
359 && rt.modifiers.ctrl
360 && rt.modifiers.shift
361 && key_event.physical_key == PhysicalKey::Code(KeyCode::KeyI)
362 && let Some(inspector) = inspector
363 {
364 inspector.hud.toggle_inspector();
365 return true;
366 }
367 let mapped = if crate::common::is_back_key_physical(key_event.physical_key) {
368 repose_core::input::Key::Escape
369 } else {
370 map_key(key_event.physical_key, &rt.modifiers)
371 };
372 let ke = winit_key_to_repose(
373 key_event.physical_key,
374 key_event.pressed,
375 key_event.repeat,
376 &mapped,
377 &rt.modifiers,
378 );
379 rt.handle_key_with_text(&ke, text)
380}
381
382struct KeyParts {
383 physical_key: PhysicalKey,
384 pressed: bool,
385 repeat: bool,
386}
387
388pub fn physical_key_name(key: PhysicalKey) -> String {
393 match key {
394 PhysicalKey::Code(code) => format!("{code:?}"),
395 PhysicalKey::Unidentified(native) => format!("Unidentified({native:?})"),
396 }
397}
398
399pub fn map_physical_key(key: PhysicalKey) -> repose_core::input::PhysicalKey {
404 repose_core::input::PhysicalKey::from_name(&physical_key_name(key))
405}
406
407pub fn on_ime(rt: &mut ReposeRuntime, ime: &winit::event::Ime) {
409 use winit::event::Ime;
410 let ev = match ime {
411 Ime::Enabled => repose_core::input::ImeEvent::Start,
412 Ime::Preedit(text, cursor) => repose_core::input::ImeEvent::Update {
413 text: text.clone(),
414 cursor: cursor.map(|(a, b)| (a, b)),
415 },
416 Ime::Commit(text) => repose_core::input::ImeEvent::Commit(text.clone()),
417 Ime::Disabled => repose_core::input::ImeEvent::Cancel,
418 };
419 rt.handle_ime(&ev);
420}