Skip to main content

ui/
touch.rs

1// touch -- the finger: one gesture recogniser for every host
2//
3//   touch_takes(ev) -> a raw finger event (pointer data PTR_TOUCH, process_events)
4//   touch_input(ev)                 the raw finger: the recogniser's state, events for the widgets
5//   touch_tick()                    the clock: the long press, the hover of a resting finger
6//   touch_apply()                   run_passes: what the finger and the fling moved, into the views
7//   pan_view(...) / pan_take(id)    a view with its own position rolls by finger (vlist, md_view,
8//                                   text views, the console)
9//   touch_mode() -> the pointer is a finger (no scroll bars)   touch_drag()
10//   touch_take(TF_*) a widget takes the finger or declines it   touch_fingers() -> what is down
11//   touch_two() -> two fingers on the last widget (TF_TWO): their midpoint and scale (pinch, pan)
12//   touch_hold_part(id) -> how far the press and hold on a title bar is (TF_HOLD_LONG)
13//
14// A host only forwards raw fingers: plat::finger(win, phase, id, x, y, age_ms), or jfgui_finger where
15// the windows live in one surface (the Android host: GalleryActivity.java, bridge.c; the browser's
16// pointer events: web/loader.js); scripts: swipe, flick, tap, finger, `pointer touch` (auto.rs).  Only
17// this file sees them raw.  It knows no platform: all of them behave the same and scripts test it on
18// the virtual clock.
19//
20// Every finger is in a table (touch_fingers).  The first one down drives the gesture.  A second one
21// takes nothing from it: while nothing is decided it is no tap any more (no click, no long press);
22// when the first one lifts, a pan goes on with the other one, a press ends, the rest waits for the
23// last finger to lift.  A widget with TF_TWO gets the second finger for itself: no press (one younger
24// than 200 ms is taken back: touch_two says so), and a pass whenever a finger moves, for its own pan and
25// zoom.
26//
27// A pen comes first: while it is down, hovers or left less than 500 ms ago a finger is a palm and does
28// not exist (no finger mode either); a pen that comes down ends the fingers already on the screen.
29//
30// A finger is wider than what it means: on the background (a title bar, an empty bar, no hit box at
31// all) the nearest small widget wins whose rectangle, grown to 44 DIP each way around its middle, holds
32// the point (touch_aim).  An exact hit stays what it is; a mouse sees none of this and nothing is drawn
33// differently.  A tap is a click wherever the finger lifts within the slop.
34//
35// The first decision waits until the finger left the slop (ViewConfiguration: 8 DIP) or lifted:
36//   - lifted: a tap, press and release at once (no flash when a scroll starts instead);
37//   - the widget under the down point takes this direction (grabs: a slider its axis, a colour
38//     wheel, the handle of a row, a window's title bar every direction -- those get the press at
39//     the touch itself, no slop, unless TF_SLOP or TF_HOLD): press and moves go to the widget like
40//     a mouse's;
41//   - a scroll view around the down point can move along the dominant axis: the pan.  The chain
42//     of views is fixed there (from the hit boxes of the last pass), innermost first: each moves
43//     as far as it can, the rest goes to the next.  The axis is locked (a view that scrolls both
44//     ways stays free unless the finger starts along an axis: rails);
45//   - nothing to roll: press and moves go to the widget after all (TF_DECLINE, TF_HOLD: nothing).
46// The pan moves the positions with no smoothing, by the finger's pixels; the slop's travel is
47// caught up on the next 16 DIP (half a pixel per finger pixel) or when it lifts: no jump, nothing
48// lost.  Lifted
49// with speed (least squares over the last 100 ms, as Android's VelocityTracker; 50..8000 DIP/s)
50// the fling runs along OverScroller's curve; it hands over along the chain, a new touch stops it
51// and is no click.  A finger that rests: after 100 ms it hovers (the widget shows it is meant, a
52// tooltip follows), after 400 ms the right button (a context menu; plat::touch_long_press lets the
53// host give feedback; TF_HOLD: the left button, the drag starts, TF_HOLD_LONG after a second; TF_REST:
54// the left button, no context menu: a canvas); if it moves after that, the same decision.  After the
55// finger left, the hover goes.
56//
57// With a finger (and at the start on Android, and in a phone's browser) there are no scroll bars:
58// no pixels and no hit box in sbar_core, the room they would have stays, so nothing jumps; a mouse
59// brings them back.  ui.touch_drag() marks a rectangle whose hit box is someone else's (the handle
60// inside a list row); widgets that need the finger say so themselves (HB_GRAB_*, the shape of their
61// cursor, touch_grab).
62
63use alloc::collections::VecDeque;
64use alloc::vec::Vec;
65
66use super::consts::*;
67use super::ctx::{Hit, W};
68use super::layout::px2dip_at;
69use super::Ui;
70use plat::Event;
71
72/// the axes of a grab or a view
73pub(crate) const AX_V: u8 = TF_V as u8;
74pub(crate) const AX_H: u8 = TF_H as u8;
75pub(crate) const AX_ALL: u8 = TF_BOTH as u8;
76const G_HOLD: u8 = TF_HOLD as u8;
77const G_TWO: u8 = TF_TWO as u8;
78const G_SLOP: u8 = TF_SLOP as u8;
79const G_DECLINE: u8 = TF_DECLINE as u8;
80const G_REST: u8 = TF_REST as u8;
81const G_HOLD_LONG: u8 = TF_HOLD_LONG as u8;
82
83// ViewConfiguration's defaults (GalleryActivity overrides them with the device's: jfgui_TOUCH)
84const SLOP_DIP: i32 = 8;
85const LONG_PRESS_MS: u32 = 400;
86const FLING_MIN_DIP: i32 = 50;
87const FLING_MAX_DIP: i32 = 8000;
88/// the resting finger shows on the widget (ViewConfiguration's tap timeout)
89const HOVER_MS: u32 = 100;
90/// after the finger left the hover stays this long: a quick tap shows on the widget too
91const LEAVE_MS: u32 = 120;
92const TIP_LEAVE_MS: u32 = 1500;
93/// the velocity: samples of the last 100 ms; a finger that rested this long before it lifted has
94/// none (VelocityTracker's horizon and ASSUME_POINTER_STOPPED_TIME)
95const VEL_HORIZON_MS: u32 = 100;
96const VEL_STOPPED_MS: u32 = 40;
97const SAMPLES: usize = 20;
98/// OverScroller's fling: ViewConfiguration.getScrollFriction, the deceleration ln 0.78 / ln 0.9, the
99/// inflexion, g in inches, its physical coefficient's 0.84 (ppi is scale * 160)
100const FRICTION: f64 = 0.015;
101const DECEL: f64 = 2.358_201_815_154_572;
102const INFLEXION: f64 = 0.35;
103const G_INCH: f64 = 9.80665 * 39.37 * 0.84;
104/// with a finger a window's title bar is at least this high, its close button this wide (DIP); not in
105/// a window narrower than CAPTION_MIN_W: the bar has to stay something to hold on to
106pub(crate) const TITLE_DIP: i32 = 40;
107pub(crate) const CAPTION_DIP: i32 = 44;
108pub(crate) const CAPTION_MIN_W: i32 = 120;
109/// where a cancelled press is released: outside everything, so it cannot click
110const OUTSIDE: i32 = -10000;
111/// TF_HOLD_LONG: a panel's title bar moves after resting this long; nothing shows before HOLD_SHOW_MS
112/// (a tap shows nothing)
113const HOLD_LONG_MS: u32 = 600;
114const HOLD_SHOW_MS: u32 = 120;
115/// a finger within this time after the pen's last sign of life is a palm
116const PALM_MS: u32 = 500;
117/// a second finger takes a press of a TF_TWO widget back that is younger than this
118const TAKE_BACK_MS: u32 = 200;
119/// a small widget's rectangle for a finger (touch_aim, edge_grips)
120pub(crate) const TARGET_DIP: i32 = 44;
121
122#[derive(Clone, Copy, PartialEq, Eq, Default)]
123enum Mode {
124    #[default]
125    Idle,
126    /// down, nothing decided
127    Wait,
128    Pan,
129    /// the press and the moves go to the widget
130    Widget,
131    /// ignored until it lifts (a long press opened something under it)
132    Gone,
133}
134
135/// a view the finger can roll, in the pass that built it: its hit boxes [hit0, hit1) of its layer
136#[derive(Clone, Copy)]
137pub(crate) struct PanView {
138    id: u32,
139    hit0: u32,
140    hit1: u32,
141    layer: u8,
142    /// the widget keeps the position itself (pan_take), else a scroll view's slot
143    own: bool,
144    axes: u8,
145    /// x, y (px)
146    pos: [i32; 2],
147    max: [i32; 2],
148}
149
150#[derive(Clone, Copy, Default)]
151struct Fling {
152    t0: u32,
153    /// duration in ms, the way (px, in the finger's direction), what of it was delivered
154    dur: f64,
155    dist: [f64; 2],
156    done: [f64; 2],
157}
158
159/// a finger that is down (ui.touch_fingers): px of the window, logical in a mirrored one
160#[derive(Clone, Copy, Debug, PartialEq, Eq)]
161pub struct Finger {
162    /// the host's number for it, while it is down
163    pub id: u8,
164    pub x: i32,
165    pub y: i32,
166    /// where it came down
167    pub down_x: i32,
168    pub down_y: i32,
169    /// its last sample (ms, as Ui::now)
170    pub time_ms: u32,
171    win: u8,
172}
173
174/// two fingers on a widget that said TF_TWO (ui.touch_two): px of the window, logical in a mirrored one
175#[derive(Clone, Copy, Debug, PartialEq)]
176pub struct TwoFingers {
177    /// the point between them
178    pub x: i32,
179    pub y: i32,
180    /// where it was when this pair came down
181    pub start_x: i32,
182    pub start_y: i32,
183    /// their distance against the one at the start (1.0 = the same)
184    pub scale: f64,
185    /// the first read of this pair: take the anchor now
186    pub began: bool,
187    /// the first finger's press went out less than 200 ms ago and was taken back for this pair: its
188    /// release comes through as usual, undo what the press began
189    pub taken_back: bool,
190}
191
192// the pair of fingers ui.touch_two reports
193#[derive(Clone, Copy)]
194struct Two {
195    ids: [u8; 2],
196    start: [i32; 2],
197    dist: f64,
198}
199
200/// shared state (Ui::m.touch)
201pub(crate) struct S {
202    /// the pointer is a finger
203    pub(crate) on: bool,
204    mode: Mode,
205    /// the fingers down in the order they came, the one that drives the gesture
206    fingers: Vec<Finger>,
207    primary: u8,
208    /// what the widget under the down point takes (TF_*)
209    grab: u8,
210    win: u32,
211    /// raw px of the window (before the mirror of a right-to-left window)
212    down: [i32; 2],
213    cur: [i32; 2],
214    /// what the finger means minus where it is (touch_aim): the widget sees that much further
215    off: [i32; 2],
216    down_t: u32,
217    /// when the press went out
218    press_t: u32,
219    /// the widget under the down point: its id (TF_HOLD_LONG shows on it, and the press finds it again)
220    down_id: u32,
221    last_t: u32,
222    /// the down only stopped a fling: lifted without a move it does nothing
223    no_click: bool,
224    hovered: bool,
225    long_done: bool,
226    /// the widget under the finger when the long press fired
227    long_id: u32,
228    data: u32,
229    mods: u8,
230    leave_at: Option<u32>,
231    axes: u8,
232    /// inner to outer
233    chain: Vec<u32>,
234    /// the finger's way since the last frame (px)
235    pend: [i32; 2],
236    /// the scroll bars come or go
237    repaint: bool,
238    /// the slop's way to catch up (px, signed)
239    catchup: [i32; 2],
240    samples: [(u32, i32, i32); SAMPLES],
241    n: usize,
242    fling: Option<Fling>,
243    /// what the widgets get next, before the platform's next event
244    out: VecDeque<Event>,
245    /// this frame's way for the views with their own position
246    take: Vec<(u32, i32, i32)>,
247    spline: Vec<f64>,
248    /// slop, min and max velocity (DIP, DIP/s), long press (ms): the device's or the defaults
249    conf: [i32; 4],
250    conf_read: bool,
251    /// (a script) the place of the window being moved by a finger when the press came
252    pub(crate) win_base: [i32; 2],
253    /// the pair ui.touch_two reports, a press taken back for it
254    two: Option<Two>,
255    took_back: bool,
256    /// the pen: down, its last sign of life (ms)
257    pen_down: bool,
258    pen_t: Option<u32>,
259    /// a finger's press is on its way (a text field may get the focus from it); the on-screen
260    /// keyboard is up (asked for), and in which UI window
261    kb_ask: bool,
262    pub(crate) kb_on: bool,
263    kb_win: u32,
264}
265
266impl Default for S {
267    fn default() -> Self {
268        S {
269            on: plat::touch_primary(),
270            mode: Mode::Idle,
271            fingers: Vec::new(),
272            primary: 0,
273            grab: 0,
274            win: 0,
275            down: [0; 2],
276            cur: [0; 2],
277            off: [0; 2],
278            down_t: 0,
279            press_t: 0,
280            down_id: 0,
281            last_t: 0,
282            no_click: false,
283            hovered: false,
284            long_done: false,
285            long_id: 0,
286            data: 0,
287            mods: 0,
288            leave_at: None,
289            axes: 0,
290            chain: Vec::new(),
291            pend: [0; 2],
292            repaint: false,
293            catchup: [0; 2],
294            samples: [(0, 0, 0); SAMPLES],
295            n: 0,
296            fling: None,
297            out: VecDeque::new(),
298            take: Vec::new(),
299            spline: Vec::new(),
300            conf: [SLOP_DIP, FLING_MIN_DIP, FLING_MAX_DIP, LONG_PRESS_MS as i32],
301            conf_read: false,
302            win_base: [0; 2],
303            two: None,
304            took_back: false,
305            pen_down: false,
306            pen_t: None,
307            kb_ask: false,
308            kb_on: false,
309            kb_win: 0,
310        }
311    }
312}
313
314/// the grabs a hit box says by itself: its HB_GRAB_* bits and the shape of its cursor
315pub(crate) fn hit_grab(hbflags: u32) -> u8 {
316    let mut g = 0;
317    if hbflags & HB_GRAB_H != 0 {
318        g |= AX_H;
319    }
320    if hbflags & HB_GRAB_V != 0 {
321        g |= AX_V;
322    }
323    if hbflags & HB_DRAG != 0 {
324        g |= AX_ALL;
325    }
326    g | match (hbflags >> HB_CURSOR_SHIFT) & 15 {
327        CUR_SIZE_WE | CUR_IBEAM => AX_H,
328        CUR_SIZE_NS => AX_V,
329        CUR_SIZE_NWSE | CUR_SIZE_NESW | CUR_MOVE | CUR_CROSS => AX_ALL,
330        _ => 0,
331    }
332}
333
334// the topmost hit box at a point of the last pass (hit_find_list's order): its index
335fn hit_index(list: &[Hit], modal: u32, x: i32, y: i32) -> Option<usize> {
336    let mut layer = super::consts::L_COUNT as i32 - 1;
337    while layer >= modal as i32 {
338        for (i, h) in list.iter().enumerate().rev() {
339            if h.layer as i32 == layer && x >= h.x0 && x < h.x1 && y >= h.y0 && y < h.y1 {
340                return Some(i);
341            }
342        }
343        layer -= 1;
344    }
345    None
346}
347
348// OverScroller's curve: the part of the way done after each hundredth of the time
349fn spline_table() -> Vec<f64> {
350    const P1: f64 = 0.5 * INFLEXION;
351    const P2: f64 = 1.0 - (1.0 - INFLEXION);
352    let mut table = Vec::with_capacity(101);
353    let mut x_min = 0.0f64;
354    for i in 0..100 {
355        let alpha = i as f64 / 100.0;
356        let mut x_max = 1.0f64;
357        let (mut x, mut coef);
358        loop {
359            x = x_min + (x_max - x_min) / 2.0;
360            coef = 3.0 * x * (1.0 - x);
361            let tx = coef * ((1.0 - x) * P1 + x * P2) + x * x * x;
362            let d = tx - alpha;
363            if (if d < 0.0 { -d } else { d }) < 1e-5 {
364                break;
365            }
366            if tx > alpha {
367                x_max = x;
368            } else {
369                x_min = x;
370            }
371        }
372        table.push(coef * ((1.0 - x) * 0.5 + x) + x * x * x);
373    }
374    table.push(1.0);
375    table
376}
377
378fn sqrt(v: f64) -> f64 {
379    rt::x87::pow(v, 0.5)
380}
381
382impl Ui {
383    /// ui_touch_mode: the pointer is a finger (the last pointer event came from one; on Android and
384    /// in a phone's browser from the start): the library shows no scroll bars.  A mouse ends it
385    pub fn touch_mode(&self) -> bool {
386        self.m.touch.on
387    }
388
389    /// ui_touch_drag: a finger that starts on the last widget's rectangle drags in every direction
390    /// instead of rolling what is around it: for a part of a widget that has no hit box of its own
391    /// (the handle of a list's row).  Sliders, colour wheels, splitters and the like take the finger
392    /// by themselves.  Call it after the widget, every frame
393    pub fn touch_drag(&mut self) {
394        let c = &mut self.wp.ctx;
395        let [x, y, w, h] = c.last_rect;
396        let k = c.uiclip;
397        let r = [x.max(k[0]), y.max(k[1]), (x + w).min(k[2]), (y + h).min(k[3])];
398        let cur = c.hit_cur;
399        if r[2] > r[0] && r[3] > r[1] && c.grabs[cur].len() < 256 {
400            c.grabs[cur].push(r);
401        }
402    }
403
404    /// ui_touch_take(flags): what the last widget (all hit boxes in its rectangle) wants of a finger,
405    /// TF_*, instead of what its hit boxes say by themselves.  Call it after the widget, every frame
406    pub fn touch_take(&mut self, flags: u32) {
407        let c = &mut self.wp.ctx;
408        let [x, y, w, h] = c.last_rect;
409        let k = c.uiclip;
410        let r = [x.max(k[0]), y.max(k[1]), (x + w).min(k[2]), (y + h).min(k[3])];
411        let cur = c.hit_cur;
412        for (i, hit) in c.hits[cur].iter_mut().rev().enumerate() {
413            if i != 0 && !(hit.x0 >= r[0] && hit.y0 >= r[1] && hit.x1 <= r[2] && hit.y1 <= r[3]) {
414                break;
415            }
416            hit.grab = flags as u8;
417        }
418    }
419
420    /// ui_touch_fingers: the fingers down on the live window, the first one first (a widget with
421    /// TF_TWO gets a pass whenever one moves)
422    pub fn touch_fingers(&self) -> Vec<Finger> {
423        let win = self.uiw_cur as u8;
424        let fb = self.wp.rt.fb.w.wrapping_sub(1);
425        let rtl = self.rtl;
426        let m = |x: i32| if rtl { fb.wrapping_sub(x) } else { x };
427        self.m.touch.fingers.iter().filter(|f| f.win == win).map(|f| Finger { x: m(f.x), down_x: m(f.down_x), ..*f }).collect()
428    }
429
430    /// ui_touch_two: two fingers on the last widget (it said TF_TWO; call it after the widget, every
431    /// frame): where they are and how far apart.  None while the first finger drags the widget: a
432    /// stroke older than 200 ms goes on, the second finger waits
433    pub fn touch_two(&mut self) -> Option<TwoFingers> {
434        let win = self.uiw_cur;
435        let [x0, y0, w, h] = self.wp.ctx.last_rect;
436        let s = &self.m.touch;
437        if s.grab & G_TWO == 0 || s.win != win || s.mode == Mode::Widget {
438            return None;
439        }
440        let mut it = s.fingers.iter().filter(|f| f.win as u32 == win);
441        let (a, b) = (*it.next()?, *it.next()?);
442        // (the first one came down on this widget)
443        let d = self.touch_logical([a.down_x, a.down_y]);
444        if d[0] < x0 || d[1] < y0 || d[0] >= x0.wrapping_add(w) || d[1] >= y0.wrapping_add(h) {
445            return None;
446        }
447        let m = self.touch_logical([a.x.wrapping_add(b.x) >> 1, a.y.wrapping_add(b.y) >> 1]);
448        let (dx, dy) = (a.x.wrapping_sub(b.x) as f64, a.y.wrapping_sub(b.y) as f64);
449        let dist = sqrt(dx * dx + dy * dy).max(1.0);
450        let s = &mut self.m.touch;
451        let began = s.two.is_none_or(|t| t.ids != [a.id, b.id]);
452        if began {
453            s.two = Some(Two { ids: [a.id, b.id], start: m, dist });
454        }
455        let t = s.two?;
456        let taken_back = began && core::mem::take(&mut s.took_back);
457        Some(TwoFingers { x: m[0], y: m[1], start_x: t.start[0], start_y: t.start[1], scale: dist / t.dist, began, taken_back })
458    }
459
460    /// ui_touch_hold_part(id) -> how far a finger's press and hold on widget id of this window is, in
461    /// 1/1000 (TF_HOLD_LONG): None without one, 1000 once the drag runs.  Show it: it is long without
462    /// a sign
463    pub fn touch_hold_part(&self, id: u32) -> Option<u32> {
464        let s = &self.m.touch;
465        if s.grab & G_HOLD_LONG == 0 || s.win != self.uiw_cur || s.down_id != id || id == 0 {
466            return None;
467        }
468        match s.mode {
469            Mode::Wait if !s.no_click => {
470                let held = self.now_ms.wrapping_sub(s.down_t);
471                (held >= HOLD_SHOW_MS).then(|| ((held - HOLD_SHOW_MS) * 1000 / (HOLD_LONG_MS - HOLD_SHOW_MS)).min(1000))
472            }
473            Mode::Widget => Some(1000),
474            _ => None,
475        }
476    }
477
478    /// the widget that just called behave takes the finger along these axes (AX_*)
479    pub(crate) fn touch_grab(&mut self, axes: u8) {
480        let c = &mut self.wp.ctx;
481        let cur = c.hit_cur;
482        if let Some(h) = c.hits[cur].last_mut() {
483            h.grab |= axes;
484        }
485    }
486
487    /// a view with its own position, at the end of its hit boxes (from hit0): the finger may roll it
488    /// (AX_* along axes; x, y px).  Read what it moved with pan_take before drawing
489    pub(crate) fn pan_view(&mut self, id: u32, hit0: usize, axes: u8, pos: [i32; 2], max: [i32; 2]) {
490        let layer = self.wp.ctx.cmd_layer_ui as u8;
491        self.pan_push(id, hit0, layer, true, axes, pos, max);
492    }
493
494    // one more view of this pass
495    #[allow(clippy::too_many_arguments)]
496    pub(crate) fn pan_push(&mut self, id: u32, hit0: usize, layer: u8, own: bool, axes: u8, pos: [i32; 2], max: [i32; 2]) {
497        let c = &mut self.wp.ctx;
498        let cur = c.hit_cur;
499        if c.pans[cur].len() < 64 {
500            let hit1 = c.hits[cur].len() as u32;
501            c.pans[cur].push(PanView { id, hit0: hit0 as u32, hit1, layer, own, axes, pos, max });
502        }
503    }
504
505    /// pan_take(id) -> (dx, dy): how far the view's position moves in this frame (px)
506    pub(crate) fn pan_take(&mut self, id: u32) -> (i32, i32) {
507        let take = &mut self.m.touch.take;
508        match take.iter().position(|t| t.0 == id) {
509            Some(i) => {
510                let t = take.swap_remove(i);
511                (t.1, t.2)
512            }
513            None => (0, 0),
514        }
515    }
516
517    // the device's numbers (GalleryActivity: jfgui_TOUCH = slop, min velocity, max velocity in DIP,
518    // long press in ms)
519    fn touch_conf(&mut self) {
520        if core::mem::replace(&mut self.m.touch.conf_read, true) {
521            return;
522        }
523        let Some(v) = plat::env("jfgui_TOUCH") else { return };
524        let mut r = v.as_bytes();
525        for k in 0..4 {
526            let (n, rest) = super::runtime::parse_int(r);
527            if n > 0 {
528                self.m.touch.conf[k] = n;
529            }
530            r = rest;
531        }
532    }
533
534    /// a raw finger event of the platform or a script
535    pub(crate) fn touch_takes(&self, ev: &Event) -> bool {
536        matches!(ev.typ, plat::E_MOVE | plat::E_DOWN | plat::E_UP) && (ev.data >> plat::PD_TYPE_SHIFT) & 3 == PTR_TOUCH
537    }
538
539    /// process_events: what the pointer is, from every raw pointer event
540    pub(crate) fn touch_kind(&mut self, ev: &Event) {
541        let kind = (ev.data >> plat::PD_TYPE_SHIFT) & 3;
542        if matches!(ev.typ, plat::E_MOVE | plat::E_DOWN | plat::E_UP) && kind != PTR_TOUCH {
543            self.touch_pen(ev, kind);
544        }
545        if !matches!(ev.typ, plat::E_MOVE | plat::E_DOWN) {
546            return;
547        }
548        let finger = kind == PTR_TOUCH;
549        if finger && self.touch_palm() {
550            return; // (no finger mode for a palm)
551        }
552        if self.m.touch.on != finger {
553            self.touch_mode_set(finger);
554        }
555        if !finger {
556            self.m.touch.fling = None; // a mouse took over
557        }
558    }
559
560    // the pen shows itself (a press ends the fingers: they are the hand that holds it)
561    fn touch_pen(&mut self, ev: &Event, kind: u32) {
562        let now = self.now_ms;
563        let s = &mut self.m.touch;
564        if kind == PTR_MOUSE {
565            if ev.typ == plat::E_UP {
566                s.pen_down = false; // (the pen's release after a lost capture has no pen data)
567            }
568            return;
569        }
570        s.pen_t = Some(now);
571        match ev.typ {
572            plat::E_DOWN => {
573                s.pen_down = true;
574                self.touch_end_all();
575            }
576            plat::E_UP => s.pen_down = false,
577            _ => {}
578        }
579    }
580
581    // a finger now would be a palm
582    fn touch_palm(&self) -> bool {
583        let s = &self.m.touch;
584        s.pen_down || s.pen_t.is_some_and(|t| self.now_ms.wrapping_sub(t) < PALM_MS)
585    }
586
587    // the fingers on the screen do not count any more: a press is released outside everything, no
588    // click, no fling; what they do from now on is nobody's (their ids are not in the table)
589    fn touch_end_all(&mut self) {
590        let s = &mut self.m.touch;
591        if s.fingers.is_empty() {
592            return;
593        }
594        s.fingers.clear();
595        s.two = None;
596        s.took_back = false;
597        s.repaint = true;
598        let mode = core::mem::replace(&mut s.mode, Mode::Idle);
599        if mode == Mode::Widget {
600            self.touch_out(plat::E_UP, BTN_LEFT as u8, OUTSIDE, OUTSIDE);
601        }
602        if self.m.touch.hovered {
603            self.m.touch.leave_at = Some(self.now_ms.wrapping_add(LEAVE_MS));
604        }
605    }
606
607    /// a finger or a mouse from now on: the scroll bars come or go in the next frame
608    pub(crate) fn touch_mode_set(&mut self, finger: bool) {
609        self.m.touch.on = finger;
610        self.m.touch.repaint = true;
611    }
612
613    // the next event for the widgets from the recogniser
614    pub(crate) fn touch_next(&mut self) -> Option<Event> {
615        self.m.touch.out.pop_front()
616    }
617
618    // an event for the widgets
619    fn touch_out(&mut self, typ: u8, btn: u8, x: i32, y: i32) {
620        let t = &mut self.m.touch;
621        t.kb_ask |= typ == plat::E_DOWN;
622        let data = (t.data & plat::PD_PRESSURE) | PTR_TOUCH << plat::PD_TYPE_SHIFT;
623        let (win, mods) = (t.win as u8, t.mods);
624        t.out.push_back(Event { typ, btn, mods, win, x, y, data });
625    }
626
627    // the sample's time on the UI's clock: as old as the host said when it pushed it
628    fn touch_time(&self, ev: &Event) -> u32 {
629        self.now_ms.wrapping_sub((ev.data >> plat::PD_AGE_SHIFT) & plat::PD_AGE_MASK)
630    }
631
632    fn touch_sample(&mut self, t: u32, x: i32, y: i32) {
633        let s = &mut self.m.touch;
634        if s.n == SAMPLES {
635            s.samples.copy_within(1.., 0);
636            s.n -= 1;
637        }
638        s.samples[s.n] = (t, x, y);
639        s.n += 1;
640    }
641
642    // the point in the window's own coordinates (a mirrored window: logical)
643    fn touch_logical(&self, p: [i32; 2]) -> [i32; 2] {
644        if self.rtl { [self.wp.rt.fb.w.wrapping_sub(1).wrapping_sub(p[0]), p[1]] } else { p }
645    }
646
647    /// the raw finger: never reaches a widget as it is
648    pub(crate) fn touch_input(&mut self, ev: &Event) {
649        self.touch_conf();
650        let t = self.touch_time(ev);
651        let id = ev.btn;
652        let s = &mut self.m.touch;
653        match ev.typ {
654            plat::E_DOWN => {
655                s.fingers.retain(|f| f.id != id); // (it never lifted)
656                s.two = None;
657                if self.touch_palm() {
658                    return; // never in the table: its moves and its lift are nobody's
659                }
660                let s = &mut self.m.touch;
661                let first = s.fingers.is_empty();
662                s.fingers.push(Finger { id, x: ev.x, y: ev.y, down_x: ev.x, down_y: ev.y, time_ms: t, win: ev.win });
663                if first {
664                    s.primary = id;
665                    self.touch_down(ev, t);
666                } else {
667                    self.touch_second(ev, t);
668                }
669            }
670            plat::E_MOVE => {
671                let Some(f) = s.fingers.iter_mut().find(|f| f.id == id) else { return };
672                f.x = ev.x;
673                f.y = ev.y;
674                f.time_ms = t;
675                if s.grab & G_TWO != 0 {
676                    s.repaint = true;
677                }
678                if id == s.primary && s.mode != Mode::Idle && ev.win as u32 == s.win {
679                    self.touch_move(ev, t);
680                }
681            }
682            _ => {
683                let Some(k) = s.fingers.iter().position(|f| f.id == id) else { return };
684                s.fingers.remove(k);
685                s.two = None;
686                if s.grab & G_TWO != 0 {
687                    s.repaint = true;
688                }
689                if id != s.primary {
690                    return;
691                }
692                match s.fingers.first().copied() {
693                    Some(next) => self.touch_handover(ev, next),
694                    None if s.mode != Mode::Idle => self.touch_up(ev, t),
695                    None => {}
696                }
697            }
698        }
699    }
700
701    // a second finger came down
702    fn touch_second(&mut self, ev: &Event, t: u32) {
703        let s = &mut self.m.touch;
704        if s.grab & G_TWO != 0 && ev.win as u32 == s.win {
705            s.repaint = true;
706            match s.mode {
707                Mode::Wait => s.mode = Mode::Gone, // the two are the widget's: no press, no click
708                Mode::Widget if t.wrapping_sub(s.press_t) < TAKE_BACK_MS => {
709                    // the first finger had only just left the slop: that was the start of a
710                    // two-finger gesture
711                    s.mode = Mode::Gone;
712                    s.took_back = true;
713                    let [x, y] = [s.cur[0].wrapping_add(s.off[0]), s.cur[1].wrapping_add(s.off[1])];
714                    self.touch_out(plat::E_UP, BTN_LEFT as u8, x, y);
715                }
716                _ => {}
717            }
718        } else if s.mode == Mode::Wait {
719            s.no_click = true; // two fingers are no tap and no long press
720        }
721    }
722
723    // the first finger lifted, another one is still down
724    fn touch_handover(&mut self, ev: &Event, next: Finger) {
725        let cancel = ev.data & plat::PD_CANCEL != 0;
726        let s = &mut self.m.touch;
727        s.primary = next.id;
728        match s.mode {
729            Mode::Pan => {
730                // the pan goes on with the other one, from where it is
731                s.cur = [next.x, next.y];
732                s.n = 0;
733                self.touch_sample(next.time_ms, next.x, next.y);
734            }
735            Mode::Widget => {
736                s.mode = Mode::Gone;
737                let [ax, ay] = self.touch_aimed([ev.x, ev.y]);
738                let (x, y) = if cancel { (OUTSIDE, OUTSIDE) } else { (ax, ay) };
739                self.touch_out(plat::E_UP, BTN_LEFT as u8, x, y);
740            }
741            Mode::Wait => s.mode = Mode::Gone,
742            _ => {}
743        }
744    }
745
746    fn touch_down(&mut self, ev: &Event, t: u32) {
747        let win = ev.win as u32;
748        if win as usize >= WIN_MAX || (win != 0 && !self.uiw_drawable(win)) {
749            return;
750        }
751        // (the last finger never lifted: its press ends here)
752        if self.m.touch.mode == Mode::Widget {
753            self.touch_out(plat::E_UP, BTN_LEFT as u8, OUTSIDE, OUTSIDE);
754        }
755        let stopped = self.m.touch.fling.take().is_some();
756        let s = &mut self.m.touch;
757        s.mode = Mode::Wait;
758        s.grab = 0;
759        s.win = win;
760        s.down = [ev.x, ev.y];
761        s.cur = s.down;
762        s.off = [0; 2];
763        s.took_back = false;
764        s.down_t = t;
765        s.last_t = t;
766        s.no_click = stopped;
767        s.hovered = false;
768        s.long_done = false;
769        s.data = ev.data;
770        s.mods = ev.mods;
771        s.leave_at = None;
772        s.pend = [0; 2];
773        s.catchup = [0; 2];
774        s.n = 0;
775        self.touch_sample(t, ev.x, ev.y);
776        plat::touch_reached(false);
777        self.uiw_use(win);
778        self.touch_aim();
779        let (hit, grab) = self.touch_under();
780        let c = &self.wp.ctx;
781        self.m.touch.down_id = hit.map_or(0, |i| c.hits[c.hit_cur ^ 1][i].id);
782        self.m.touch.grab = grab;
783        if stopped {
784            return;
785        }
786        // a widget that takes every direction has the finger from the start
787        if grab & AX_ALL == AX_ALL && grab & (G_HOLD | G_SLOP | G_DECLINE) == 0 {
788            self.touch_press();
789        }
790    }
791
792    // what the finger means, in the live window: on the background the nearest small widget whose
793    // rectangle, grown to the target size around its middle, holds the point.  Only one that is on top
794    // where the finger would touch it, and an exact hit is left alone; a surface with gestures of its own
795    // (TF_TWO) counts as background: a finger just above the status bar means the slider in it
796    fn touch_aim(&mut self) {
797        if self.uiw_inert_now() {
798            return;
799        }
800        let t = self.dp(TARGET_DIP);
801        let c = &self.wp.ctx;
802        let p = self.touch_logical(self.m.touch.down);
803        let list = &c.hits[c.hit_cur ^ 1];
804        // (a disabled button is what it hit, not its neighbour)
805        let real = |h: &Hit| h.id != 0 && h.flags as u32 & (HB_BLOCK | HB_DRAG) == 0;
806        // (grips and splitters have their own width for a finger, and a title bar next to them is one)
807        let grip = |h: &Hit| matches!((h.flags as u32 >> HB_CURSOR_SHIFT) & 15, CUR_SIZE_WE | CUR_SIZE_NS | CUR_SIZE_NWSE | CUR_SIZE_NESW);
808        let usable = |h: &Hit| real(h) && h.flags as u32 & HB_DISABLED == 0 && !grip(h);
809        let exact = hit_index(list, c.modal_prev, p[0], p[1]);
810        if exact.is_some_and(|i| real(&list[i]) && list[i].grab & G_TWO == 0) {
811            return;
812        }
813        let floor = exact.map_or(c.modal_prev as i32, |i| list[i].layer as i32).max(c.modal_prev as i32);
814        let mut best: Option<(i32, [i32; 2])> = None;
815        for (i, h) in list.iter().enumerate() {
816            if !usable(h) || (h.layer as i32) < floor {
817                continue;
818            }
819            let (ex, ey) = (((t - (h.x1 - h.x0)) / 2).max(0), ((t - (h.y1 - h.y0)) / 2).max(0));
820            if ex == 0 && ey == 0 || p[0] < h.x0 - ex || p[0] >= h.x1 + ex || p[1] < h.y0 - ey || p[1] >= h.y1 + ey {
821                continue;
822            }
823            // the nearest point of the widget
824            let q = [p[0].clamp(h.x0, h.x1 - 1), p[1].clamp(h.y0, h.y1 - 1)];
825            let d = (q[0] - p[0]).abs().max((q[1] - p[1]).abs());
826            if best.is_none_or(|b| d < b.0) && hit_index(list, c.modal_prev, q[0], q[1]) == Some(i) {
827                best = Some((d, q));
828            }
829        }
830        if let Some((_, q)) = best {
831            let raw = self.touch_logical(q); // (its own inverse)
832            let s = &mut self.m.touch;
833            s.off = [raw[0] - s.down[0], raw[1] - s.down[1]];
834        }
835    }
836
837    // the press is about to go out: the layout may have moved since the finger came down (the first
838    // finger of a session: a tool window's title bar grows, what is below it moves), and what the finger
839    // came down on is what it means
840    fn touch_find_again(&mut self) {
841        let id = self.m.touch.down_id;
842        if id == 0 || self.uiw_inert_now() {
843            return;
844        }
845        let c = &self.wp.ctx;
846        let list = &c.hits[c.hit_cur ^ 1];
847        let p = self.touch_logical(self.touch_aimed(self.m.touch.down));
848        if hit_index(list, c.modal_prev, p[0], p[1]).is_some_and(|i| list[i].id == id) {
849            return;
850        }
851        let Some(h) = list.iter().rev().find(|h| h.id == id && h.x1 > h.x0 && h.y1 > h.y0) else { return };
852        let q = [p[0].clamp(h.x0, h.x1 - 1), p[1].clamp(h.y0, h.y1 - 1)];
853        if hit_index(list, c.modal_prev, q[0], q[1]).is_some_and(|i| list[i].id == id) {
854            let raw = self.touch_logical(q);
855            let s = &mut self.m.touch;
856            s.off = [raw[0] - s.down[0], raw[1] - s.down[1]];
857        }
858    }
859
860    // where the widget sees the finger now
861    fn touch_aimed(&self, p: [i32; 2]) -> [i32; 2] {
862        let o = self.m.touch.off;
863        [p[0].wrapping_add(o[0]), p[1].wrapping_add(o[1])]
864    }
865
866    // the widget under the down point: (its hit box, the axes it takes)
867    fn touch_under(&self) -> (Option<usize>, u8) {
868        if self.uiw_inert_now() {
869            return (None, 0);
870        }
871        let c = &self.wp.ctx;
872        let p = self.touch_logical(self.touch_aimed(self.m.touch.down));
873        let list = &c.hits[c.hit_cur ^ 1];
874        let i = hit_index(list, c.modal_prev, p[0], p[1]);
875        let mut grab = i.map_or(0, |i| if list[i].flags as u32 & HB_DISABLED != 0 { 0 } else { list[i].grab });
876        if c.grabs[c.hit_cur ^ 1].iter().any(|r| p[0] >= r[0] && p[1] >= r[1] && p[0] < r[2] && p[1] < r[3]) {
877            grab = AX_ALL;
878        }
879        (i, grab)
880    }
881
882    // the press at the down point: from here on the widget has the finger
883    fn touch_press(&mut self) {
884        self.uiw_use(self.m.touch.win);
885        self.touch_find_again();
886        let [x, y] = self.touch_aimed(self.m.touch.down);
887        let now = self.now_ms;
888        let s = &mut self.m.touch;
889        s.mode = Mode::Widget;
890        s.press_t = now;
891        self.touch_out(plat::E_MOVE, 0, x, y);
892        self.touch_out(plat::E_DOWN, BTN_LEFT as u8, x, y);
893        plat::touch_reached(true);
894    }
895
896    fn touch_move(&mut self, ev: &Event, t: u32) {
897        let (ox, oy) = (self.m.touch.cur[0], self.m.touch.cur[1]);
898        self.m.touch.cur = [ev.x, ev.y];
899        self.m.touch.last_t = t;
900        self.m.touch.data = ev.data;
901        self.touch_sample(t, ev.x, ev.y);
902        match self.m.touch.mode {
903            Mode::Wait => {
904                let s = &self.m.touch;
905                let (dx, dy) = (ev.x.wrapping_sub(s.down[0]), ev.y.wrapping_sub(s.down[1]));
906                let slop = self.dp(s.conf[0]);
907                if (dx as i64) * (dx as i64) + (dy as i64) * (dy as i64) > (slop as i64) * (slop as i64) {
908                    self.uiw_use(self.m.touch.win);
909                    self.touch_decide(dx, dy);
910                    self.touch_after_decide();
911                }
912            }
913            Mode::Pan => self.touch_pan(ev.x.wrapping_sub(ox), ev.y.wrapping_sub(oy)),
914            Mode::Widget => {
915                let [x, y] = self.touch_aimed([ev.x, ev.y]);
916                self.touch_out(plat::E_MOVE, 0, x, y);
917            }
918            _ => {}
919        }
920    }
921
922    // after the decision: the widget sees where the finger is by now
923    fn touch_after_decide(&mut self) {
924        if self.m.touch.mode == Mode::Widget {
925            let [x, y] = self.touch_aimed(self.m.touch.cur);
926            self.touch_out(plat::E_MOVE, 0, x, y);
927        }
928    }
929
930    // the finger left the slop by (dx, dy)
931    fn touch_decide(&mut self, dx: i32, dy: i32) {
932        let (hit, grab) = self.touch_under();
933        let s = &self.m.touch;
934        if s.long_done {
935            // the long press changed what is under the finger (a menu opened): the rest is not
936            // for that
937            let c = &self.wp.ctx;
938            let id = hit.map_or(0, |i| c.hits[c.hit_cur ^ 1][i].id);
939            if id != s.long_id {
940                self.m.touch.mode = Mode::Gone;
941                return;
942            }
943        }
944        let (adx, ady) = (dx.wrapping_abs(), dy.wrapping_abs());
945        let axis = if adx > ady { AX_H } else { AX_V };
946        let waits = grab & (G_HOLD | G_DECLINE) != 0; // no drag now: what is around it, or nothing
947        if grab & axis != 0 && !waits {
948            self.touch_press();
949            return;
950        }
951        // the views around the widget, inner to outer
952        let mut chain = core::mem::take(&mut self.m.touch.chain);
953        chain.clear();
954        let mut can = 0u8; // the axes something can roll along
955        let mut both = false;
956        {
957            let c = &self.wp.ctx;
958            if let Some(i) = hit {
959                let layer = c.hits[c.hit_cur ^ 1][i].layer;
960                for v in c.pans[c.hit_cur ^ 1].iter() {
961                    if v.layer == layer && i as u32 >= v.hit0 && (i as u32) < v.hit1 {
962                        chain.push(v.id);
963                        let mut a = 0;
964                        if v.axes & AX_H != 0 && v.max[0] > 0 {
965                            a |= AX_H;
966                        }
967                        if v.axes & AX_V != 0 && v.max[1] > 0 {
968                            a |= AX_V;
969                        }
970                        // the first view that takes this axis scrolls both ways: the other is free too
971                        if can & axis == 0 && a & axis != 0 && a == AX_ALL {
972                            both = true;
973                        }
974                        can |= a;
975                    }
976                }
977            }
978        }
979        self.m.touch.chain = chain;
980        if can & axis == 0 {
981            if waits {
982                self.m.touch.mode = Mode::Gone;
983            } else {
984                self.touch_press();
985            }
986            return;
987        }
988        // rails: a finger along an axis stays on it
989        let free = both && adx.min(ady).wrapping_mul(2) >= adx.max(ady);
990        let hovered = self.m.touch.hovered;
991        let slop = self.dp(self.m.touch.conf[0]);
992        let s = &mut self.m.touch;
993        s.mode = Mode::Pan;
994        s.axes = if free { AX_ALL } else { axis };
995        // what lies beyond the slop moves now (a slow frame's long first step), the slop is caught up
996        for (k, d, ax) in [(0, dx, AX_H), (1, dy, AX_V)] {
997            if s.axes & ax != 0 {
998                let c = d.clamp(-slop, slop);
999                s.catchup[k] = c;
1000                s.pend[k] = s.pend[k].wrapping_add(d.wrapping_sub(c));
1001            }
1002        }
1003        if hovered {
1004            let (x, y) = (-1, -1);
1005            self.touch_out(plat::E_LEAVE, 0, x, y);
1006        }
1007    }
1008
1009    // the finger moved on (px): along the locked axes, the slop's way caught up on the way
1010    fn touch_pan(&mut self, dx: i32, dy: i32) {
1011        let s = &mut self.m.touch;
1012        for (k, d, ax) in [(0, dx, AX_H), (1, dy, AX_V)] {
1013            if s.axes & ax == 0 {
1014                continue;
1015            }
1016            let c = s.catchup[k];
1017            let extra = (d.wrapping_abs() / 2).min(c.wrapping_abs());
1018            let extra = if d != 0 && extra == 0 && c != 0 { 1 } else { extra };
1019            let extra = if c < 0 { -extra } else { extra };
1020            s.catchup[k] = c - extra;
1021            s.pend[k] = s.pend[k].wrapping_add(d).wrapping_add(extra);
1022        }
1023    }
1024
1025    fn touch_up(&mut self, ev: &Event, t: u32) {
1026        let cancel = ev.data & plat::PD_CANCEL != 0;
1027        let mode = self.m.touch.mode;
1028        self.m.touch.mode = Mode::Idle;
1029        match mode {
1030            Mode::Wait => {
1031                if !cancel && !self.m.touch.no_click && !self.m.touch.long_done {
1032                    // a click where it came down: a finger that rolled a little within the slop
1033                    // still means that widget
1034                    self.uiw_use(self.m.touch.win);
1035                    self.touch_find_again();
1036                    let [x, y] = self.touch_aimed(self.m.touch.down);
1037                    self.touch_out(plat::E_MOVE, 0, x, y);
1038                    self.touch_out(plat::E_DOWN, BTN_LEFT as u8, x, y);
1039                    self.touch_out(plat::E_UP, BTN_LEFT as u8, x, y);
1040                    plat::touch_reached(true);
1041                }
1042                if self.m.touch.hovered || !self.m.touch.no_click {
1043                    // (a tooltip the resting finger brought stays long enough to read it)
1044                    self.uiw_use(self.m.touch.win);
1045                    let ms = if self.tip_showing() { TIP_LEAVE_MS } else { LEAVE_MS };
1046                    self.m.touch.leave_at = Some(t.wrapping_add(ms));
1047                }
1048            }
1049            Mode::Widget => {
1050                let [ax, ay] = self.touch_aimed([ev.x, ev.y]);
1051                let (x, y) = if cancel { (OUTSIDE, OUTSIDE) } else { (ax, ay) };
1052                self.touch_out(plat::E_UP, BTN_LEFT as u8, x, y);
1053                self.m.touch.leave_at = Some(t.wrapping_add(LEAVE_MS));
1054            }
1055            Mode::Pan if !cancel => {
1056                self.uiw_use(self.m.touch.win);
1057                self.touch_fling(t);
1058                // no fling: the rest of the slop's way now, nothing is lost
1059                let s = &mut self.m.touch;
1060                if s.fling.is_none() {
1061                    s.pend[0] = s.pend[0].wrapping_add(core::mem::take(&mut s.catchup[0]));
1062                    s.pend[1] = s.pend[1].wrapping_add(core::mem::take(&mut s.catchup[1]));
1063                }
1064            }
1065            Mode::Gone if self.m.touch.hovered => self.m.touch.leave_at = Some(t.wrapping_add(LEAVE_MS)),
1066            _ => {}
1067        }
1068    }
1069
1070    // the velocity along one axis at the end of the samples (px/s): a least squares parabola over
1071    // the last 100 ms, as VelocityTracker's LSQ2; 0 when the finger had stopped
1072    fn touch_velocity(&self, k: usize, up: u32) -> f64 {
1073        let s = &self.m.touch;
1074        if s.n < 2 || up.wrapping_sub(s.samples[s.n - 1].0) > VEL_STOPPED_MS {
1075            return 0.0;
1076        }
1077        let last = s.samples[s.n - 1];
1078        let at = |e: &(u32, i32, i32)| if k == 0 { e.1 } else { e.2 };
1079        let (mut n, mut st, mut st2, mut st3, mut st4, mut sx, mut stx, mut st2x) = (0.0f64, 0.0f64, 0.0f64, 0.0f64, 0.0f64, 0.0f64, 0.0f64, 0.0f64);
1080        let mut first = 0.0f64;
1081        let mut stamps = 0;
1082        let mut prev = u32::MAX;
1083        for e in s.samples[..s.n].iter().rev() {
1084            let age = last.0.wrapping_sub(e.0);
1085            if age > VEL_HORIZON_MS {
1086                break;
1087            }
1088            let t = -(age as f64) / 1000.0;
1089            let x = (at(e) - at(&last)) as f64;
1090            first = t;
1091            if age != prev {
1092                stamps += 1;
1093                prev = age;
1094            }
1095            n += 1.0;
1096            st += t;
1097            st2 += t * t;
1098            st3 += t * t * t;
1099            st4 += t * t * t * t;
1100            sx += x;
1101            stx += t * x;
1102            st2x += t * t * x;
1103        }
1104        if stamps < 2 || first == 0.0 {
1105            return 0.0;
1106        }
1107        if stamps >= 3 {
1108            // the normal equations of x = a + b t + c t^2 (Cramer): b
1109            let det = n * (st2 * st4 - st3 * st3) - st * (st * st4 - st3 * st2) + st2 * (st * st3 - st2 * st2);
1110            let d = if det < 0.0 { -det } else { det };
1111            if d > 1e-18 {
1112                let db = n * (stx * st4 - st3 * st2x) - sx * (st * st4 - st3 * st2) + st2 * (st * st2x - st2 * stx);
1113                return db / det;
1114            }
1115        }
1116        // two instants: the line
1117        let det = n * st2 - st * st;
1118        if det == 0.0 { 0.0 } else { (n * stx - st * sx) / det }
1119    }
1120
1121    // the finger lifted from a pan
1122    fn touch_fling(&mut self, up: u32) {
1123        let (vx, vy) = (
1124            if self.m.touch.axes & AX_H != 0 { self.touch_velocity(0, up) } else { 0.0 },
1125            if self.m.touch.axes & AX_V != 0 { self.touch_velocity(1, up) } else { 0.0 },
1126        );
1127        let mut speed = sqrt(vx * vx + vy * vy);
1128        let (lo, hi) = (self.dp(self.m.touch.conf[1]) as f64, self.dp(self.m.touch.conf[2]) as f64);
1129        if !(speed >= lo) {
1130            return;
1131        }
1132        let (ux, uy) = (vx / speed, vy / speed);
1133        if speed > hi {
1134            speed = hi;
1135        }
1136        // OverScroller: the distance and the time from the speed
1137        let ppi = self.wp.rt.dpi as f64 * 160.0 / 96.0;
1138        let phys = G_INCH * ppi;
1139        let r = INFLEXION * speed / (FRICTION * phys);
1140        let dur = 1000.0 * rt::x87::pow(r, 1.0 / (DECEL - 1.0));
1141        let way = FRICTION * phys * rt::x87::pow(r, DECEL / (DECEL - 1.0));
1142        if self.m.touch.spline.is_empty() {
1143            self.m.touch.spline = spline_table();
1144        }
1145        let s = &mut self.m.touch;
1146        s.fling = Some(Fling {
1147            t0: up,
1148            dur,
1149            // (what the slop still owes goes along)
1150            dist: [way * ux + s.catchup[0] as f64, way * uy + s.catchup[1] as f64],
1151            done: [0.0; 2],
1152        });
1153        self.animating = true;
1154    }
1155
1156    /// the clock: the resting finger, the hover after it lifted
1157    pub(crate) fn touch_tick(&mut self) {
1158        let now = self.now_ms;
1159        let s = &self.m.touch;
1160        if s.mode == Mode::Wait && !s.no_click && !s.long_done {
1161            let held = now.wrapping_sub(s.down_t);
1162            let [x, y] = self.touch_aimed(s.down);
1163            let s = &mut self.m.touch;
1164            if s.grab & G_HOLD_LONG != 0 {
1165                s.repaint = true; // (it shows how far the hold is: touch_hold_part)
1166            }
1167            if held >= HOVER_MS && !s.hovered {
1168                self.m.touch.hovered = true;
1169                self.touch_out(plat::E_MOVE, 0, x, y);
1170            }
1171            let long = if self.m.touch.grab & G_HOLD_LONG != 0 { HOLD_LONG_MS } else { self.m.touch.conf[3] as u32 };
1172            if held >= long {
1173                self.uiw_use(self.m.touch.win);
1174                let (hit, grab) = self.touch_under();
1175                if grab & (G_HOLD | G_REST) != 0 {
1176                    // it wanted a press and hold: its drag starts here (a canvas: the press)
1177                    if grab & G_HOLD != 0 {
1178                        plat::touch_long_press();
1179                    }
1180                    self.touch_press();
1181                    return;
1182                }
1183                let c = &self.wp.ctx;
1184                let id = hit.map_or(0, |i| c.hits[c.hit_cur ^ 1][i].id);
1185                self.m.touch.long_id = id;
1186                self.m.touch.long_done = true;
1187                self.touch_out(plat::E_MOVE, 0, x, y);
1188                self.touch_out(plat::E_DOWN, BTN_RIGHT as u8, x, y);
1189                self.touch_out(plat::E_UP, BTN_RIGHT as u8, x, y);
1190                plat::touch_long_press();
1191            }
1192        } else if self.m.touch.mode == Mode::Idle {
1193            if let Some(at) = self.m.touch.leave_at {
1194                if (now.wrapping_sub(at) as i32) >= 0 {
1195                    self.m.touch.leave_at = None;
1196                    self.touch_out(plat::E_LEAVE, 0, -1, -1);
1197                }
1198            }
1199        }
1200    }
1201
1202    /// the host keeps drawing (and a script's clock looks here): a finger is down, a fling runs,
1203    /// the hover waits to go
1204    pub(crate) fn touch_busy(&self) -> bool {
1205        let s = &self.m.touch;
1206        s.mode != Mode::Idle || s.fling.is_some() || s.leave_at.is_some() || !s.out.is_empty() || s.pend != [0; 2] || s.repaint
1207    }
1208
1209    /// the finger moved some way since the last frame (or the scroll bars changed): a frame now
1210    pub(crate) fn touch_moved(&self) -> bool {
1211        self.m.touch.pend != [0; 2] || self.m.touch.repaint
1212    }
1213
1214    /// (run_passes, before the first pass) the way of the finger and of the fling since the last
1215    /// frame into the views: this frame shows it
1216    pub(crate) fn touch_apply(&mut self) {
1217        let s = &mut self.m.touch;
1218        s.take.clear();
1219        s.repaint = false;
1220        let mut d = core::mem::take(&mut s.pend);
1221        let mut flinging = false;
1222        if let Some(f) = s.fling.as_mut() {
1223            flinging = true;
1224            let el = self.now_ms.wrapping_sub(f.t0) as f64;
1225            let part = if el >= f.dur {
1226                1.0
1227            } else {
1228                let p = el / f.dur * 100.0;
1229                let k = p as usize;
1230                s.spline[k] + (p - k as f64) * (s.spline[k + 1] - s.spline[k])
1231            };
1232            for k in 0..2 {
1233                let step = (f.dist[k] * part - f.done[k]) as i32; // whole pixels
1234                f.done[k] += step as f64;
1235                d[k] = d[k].wrapping_add(step);
1236            }
1237            if part >= 1.0 {
1238                s.fling = None;
1239            }
1240        }
1241        if d == [0; 2] {
1242            return;
1243        }
1244        let win = s.win;
1245        if win as usize >= WIN_MAX || (win != 0 && !self.uiw_drawable(win)) {
1246            self.m.touch.fling = None;
1247            return;
1248        }
1249        self.uiw_use(win);
1250        if self.rtl {
1251            d[0] = d[0].wrapping_neg(); // the content moves in logical coordinates
1252        }
1253        let mut moved = false;
1254        let Ui { wp, m, slots, .. } = self;
1255        let W { pans, hit_cur, .. } = &wp.ctx;
1256        let views = &pans[*hit_cur ^ 1];
1257        let t = &mut m.touch;
1258        for &id in t.chain.iter() {
1259            let Some(v) = views.iter().find(|v| v.id == id) else { continue };
1260            let mut np = v.pos;
1261            for (k, ax) in [(0, AX_H), (1, AX_V)] {
1262                if v.axes & ax != 0 && d[k] != 0 {
1263                    np[k] = v.pos[k].wrapping_sub(d[k]).clamp(0, v.max[k].max(0));
1264                    d[k] = d[k].wrapping_sub(v.pos[k].wrapping_sub(np[k]));
1265                }
1266            }
1267            if np == v.pos {
1268                continue;
1269            }
1270            moved = true;
1271            if v.own {
1272                if t.take.len() < 16 {
1273                    t.take.push((id, np[0].wrapping_sub(v.pos[0]), np[1].wrapping_sub(v.pos[1])));
1274                }
1275            } else if let Some(sl) = slots.get_mut(&id) {
1276                if v.axes & AX_H != 0 {
1277                    sl.scrollx = np[0];
1278                    sl.at0 = np[0] as u32;
1279                }
1280                if v.axes & AX_V != 0 {
1281                    sl.scrolly = np[1];
1282                    sl.adur = np[1] as u32;
1283                }
1284            }
1285        }
1286        // everything at its limit: the fling is over
1287        if flinging && !moved {
1288            t.fling = None;
1289        }
1290        self.uiw_use(0);
1291    }
1292
1293    /// (run_passes, after the passes) a fling goes on; the on-screen keyboard comes when a finger's
1294    /// press gave a text field the focus (not for a scroll, not for a mouse) and goes with that focus
1295    pub(crate) fn touch_after_passes(&mut self) {
1296        if self.m.touch.fling.is_some() {
1297            self.animating = true;
1298        }
1299        let ask = core::mem::take(&mut self.m.touch.kb_ask);
1300        if !ask && !self.m.touch.kb_on {
1301            return;
1302        }
1303        let win = self.m.win.active;
1304        self.uiw_use(win);
1305        let c = &self.wp.ctx;
1306        let text = c.focus != 0 && c.hits[c.hit_cur ^ 1].iter().any(|h| h.id == c.focus && h.flags as u32 & HB_TEXT != 0);
1307        let s = &mut self.m.touch;
1308        let want = text && (s.kb_on || (ask && s.on));
1309        if want != s.kb_on {
1310            s.kb_on = want;
1311            s.kb_win = win;
1312            if !self.headless {
1313                plat::touch_keyboard(self.uiw_pw(self.m.touch.kb_win), want);
1314            }
1315        }
1316        self.uiw_use(0);
1317    }
1318
1319    /// (chrome) how far a press and hold on this title bar is: a line along its lower edge that grows
1320    /// until the window comes loose, and stays while it moves
1321    pub(crate) fn touch_hold_show(&mut self, id: u32) {
1322        let Some(part) = self.touch_hold_part(id) else { return };
1323        let [x, y, w, h] = self.wp.ctx.last_rect;
1324        let t = self.dp(3);
1325        let c = self.color(T_ACCENT);
1326        self.draw_color(c);
1327        self.draw_rect(x, y + h - t, (w as i64 * part as i64 / 1000) as i32, t);
1328    }
1329
1330    /// (a script: the platform's job elsewhere) a finger on a window's title bar moves the window,
1331    /// and not out of reach
1332    pub(crate) fn touch_window_drag(&mut self, i: u32, pressed: bool) {
1333        let c = &self.wp.ctx;
1334        let (dx, dy) = (c.mx.wrapping_sub(c.press_x), c.my.wrapping_sub(c.press_y));
1335        let dpi = self.wp.rt.dpi;
1336        let (dx, dy) = (px2dip_at(dx, dpi), px2dip_at(dy, dpi));
1337        let iu = (i as usize) & (WIN_MAX - 1);
1338        let rc = self.m.win.recs[iu].rect.get();
1339        if pressed {
1340            self.m.touch.win_base = [rc.x, rc.y];
1341        }
1342        let [bx, by] = self.m.touch.win_base;
1343        let fb = self.win_fb(0);
1344        let mdpi = self.win_dpi(0);
1345        let (sw, sh) = (px2dip_at(fb.w, mdpi), px2dip_at(fb.h, mdpi));
1346        let wfb = self.win_fb(i);
1347        let w = px2dip_at(wfb.w, self.win_dpi(i));
1348        let (sx, sy) = self.uiw_snap_dip(i, bx.wrapping_add(dx), by.wrapping_add(dy));
1349        let [x, y] = plat::reach_clamp([sx, sy], w, [sw, sh], 96);
1350        if x != rc.x || y != rc.y {
1351            self.uiw_user_rect(i, x, y, 0, 0);
1352        }
1353    }
1354}