Skip to main content

pfx_input/
state.rs

1use std::collections::{BTreeMap, BTreeSet};
2
3use pfx_gpu::window::Viewport;
4use serde::{Deserialize, Serialize};
5
6use crate::action::{ActionId, ActionKind, Actions, Binding, BindingMap, Set, Source};
7use crate::backend::Backend;
8use crate::device::{
9    Axis, Button, Control, DeviceKind, Key, Motors, MouseButton, PadId, PadInfo, Sign, Wheel,
10};
11use crate::event::InputEvent;
12use crate::family::{Family, GlyphStyle};
13use crate::record::{Recording, Start};
14use crate::rumble::{MotorCommand, Rumble, Rumbler};
15
16pub const PRESS: f32 = 0.5;
17pub const POINTER_SWITCH: f32 = 4.0;
18
19#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
20pub struct ActionState {
21    pub held: bool,
22    pub pressed: bool,
23    pub released: bool,
24    pub repeated: bool,
25    pub held_steps: u32,
26    pub value: f32,
27    pub vector: [f32; 2],
28}
29
30impl ActionState {
31    pub fn fired(&self) -> bool {
32        self.pressed || self.repeated
33    }
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
37pub struct Active {
38    pub kind: DeviceKind,
39    pub pad: Option<PadId>,
40    pub family: Option<Family>,
41}
42
43impl Active {
44    pub fn keyboard() -> Self {
45        Self {
46            kind: DeviceKind::Keyboard,
47            pad: None,
48            family: None,
49        }
50    }
51
52    fn mouse() -> Self {
53        Self {
54            kind: DeviceKind::Mouse,
55            pad: None,
56            family: None,
57        }
58    }
59
60    pub fn set(&self) -> Set {
61        if self.kind.is_gamepad() {
62            Set::Gamepad
63        } else {
64            Set::Keyboard
65        }
66    }
67}
68
69#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
70pub struct Pointer {
71    pub layout: Option<[f32; 2]>,
72    pub inside: bool,
73}
74
75#[derive(Clone, Debug, PartialEq)]
76pub struct Step {
77    pub index: u64,
78    pub changed: Option<Active>,
79    pub style: Option<GlyphStyle>,
80    pub motors: Vec<MotorCommand>,
81}
82
83#[derive(Clone, Copy, Debug, Default, PartialEq)]
84struct Level {
85    value: f32,
86    tapped: bool,
87}
88
89impl Level {
90    fn tap(&self) -> bool {
91        self.tapped && self.value < PRESS
92    }
93
94    fn set(&mut self, value: f32) -> bool {
95        let rising = self.value < PRESS && value >= PRESS;
96        self.value = value;
97        if rising {
98            self.tapped = true;
99        }
100        rising
101    }
102}
103
104#[derive(Clone, Debug)]
105struct Pad {
106    info: PadInfo,
107    buttons: BTreeMap<Button, Level>,
108    axes: BTreeMap<Axis, f32>,
109    actions: BTreeMap<String, [f32; 2]>,
110    origins: BTreeMap<String, Vec<Control>>,
111}
112
113impl Pad {
114    fn new(info: PadInfo) -> Self {
115        Self {
116            info,
117            buttons: BTreeMap::new(),
118            axes: BTreeMap::new(),
119            actions: BTreeMap::new(),
120            origins: BTreeMap::new(),
121        }
122    }
123
124    fn source(&self, source: Source) -> Level {
125        match source {
126            Source::Button(button) => self.buttons.get(&button).copied().unwrap_or_default(),
127            Source::Axis(axis, sign) => {
128                let value = self.axes.get(&axis).copied().unwrap_or(0.0);
129                let value = match sign {
130                    Sign::Positive => value,
131                    Sign::Negative => -value,
132                };
133                Level {
134                    value: value.max(0.0),
135                    tapped: false,
136                }
137            }
138            _ => Level::default(),
139        }
140    }
141
142    fn axis(&self, axis: Axis) -> f32 {
143        self.axes.get(&axis).copied().unwrap_or(0.0)
144    }
145}
146
147pub struct Input {
148    actions: Actions,
149    map: BindingMap,
150    resolved: Vec<[Vec<Binding>; 2]>,
151    keys: BTreeMap<Source, Level>,
152    wheel_pending: [f32; 2],
153    wheel: [f32; 2],
154    pads: BTreeMap<PadId, Pad>,
155    states: Vec<ActionState>,
156    pointer: Pointer,
157    outside: BTreeSet<MouseButton>,
158    anchor: Option<[f32; 2]>,
159    viewport: Option<Viewport>,
160    active: Active,
161    reported: Active,
162    reported_style: GlyphStyle,
163    last_pad: Option<PadId>,
164    step: u64,
165    step_us: u32,
166    rumbler: Rumbler,
167    recording: Option<Recording>,
168    pending: Vec<InputEvent>,
169}
170
171impl Input {
172    pub fn new(actions: Actions, step_us: u32) -> Self {
173        let map = BindingMap::defaults(&actions);
174        let states = vec![ActionState::default(); actions.len()];
175        let mut input = Self {
176            actions,
177            map,
178            resolved: Vec::new(),
179            keys: BTreeMap::new(),
180            wheel_pending: [0.0; 2],
181            wheel: [0.0; 2],
182            pads: BTreeMap::new(),
183            states,
184            pointer: Pointer::default(),
185            outside: BTreeSet::new(),
186            anchor: None,
187            viewport: None,
188            active: Active::keyboard(),
189            reported: Active::keyboard(),
190            reported_style: GlyphStyle::Standard,
191            last_pad: None,
192            step: 0,
193            step_us: step_us.max(1),
194            rumbler: Rumbler::new(step_us, 100),
195            recording: None,
196            pending: Vec::new(),
197        };
198        input.resolve();
199        input
200    }
201
202    pub fn actions(&self) -> &Actions {
203        &self.actions
204    }
205
206    pub fn bindings(&self) -> &BindingMap {
207        &self.map
208    }
209
210    pub fn set_bindings(&mut self, mut map: BindingMap) {
211        map.fit(&self.actions);
212        self.map = map;
213        self.resolve();
214    }
215
216    pub fn edit_bindings<R>(&mut self, edit: impl FnOnce(&mut BindingMap, &Actions) -> R) -> R {
217        let result = edit(&mut self.map, &self.actions);
218        self.map.fit(&self.actions);
219        self.resolve();
220        result
221    }
222
223    fn resolve(&mut self) {
224        self.resolved = self
225            .actions
226            .iter()
227            .map(|(_, spec)| {
228                [
229                    self.map.bindings(Set::Keyboard, &spec.name).to_vec(),
230                    self.map.bindings(Set::Gamepad, &spec.name).to_vec(),
231                ]
232            })
233            .collect();
234    }
235
236    pub fn set_viewport(&mut self, viewport: Option<Viewport>) {
237        self.viewport = viewport;
238    }
239
240    pub fn step_index(&self) -> u64 {
241        self.step
242    }
243
244    pub fn step_us(&self) -> u32 {
245        self.step_us
246    }
247
248    pub fn state(&self, id: ActionId) -> ActionState {
249        self.states[usize::from(id.0)]
250    }
251
252    pub fn action(&self, name: &str) -> ActionState {
253        self.actions
254            .id(name)
255            .map_or_else(ActionState::default, |id| self.state(id))
256    }
257
258    pub fn states(&self) -> &[ActionState] {
259        &self.states
260    }
261
262    pub fn pointer(&self) -> Pointer {
263        self.pointer
264    }
265
266    pub fn wheel(&self) -> [f32; 2] {
267        self.wheel
268    }
269
270    pub fn active(&self) -> Active {
271        self.active
272    }
273
274    pub fn last_family(&self) -> Option<Family> {
275        self.last_pad
276            .and_then(|pad| self.pads.get(&pad))
277            .map(|pad| pad.info.family)
278    }
279
280    pub fn pads(&self) -> impl Iterator<Item = (PadId, &PadInfo)> {
281        self.pads.iter().map(|(id, pad)| (*id, &pad.info))
282    }
283
284    pub fn key_held(&self, key: Key) -> bool {
285        self.keys
286            .get(&Source::Key(key))
287            .is_some_and(|level| level.value >= PRESS)
288    }
289
290    pub fn mouse_held(&self, button: MouseButton) -> bool {
291        self.keys
292            .get(&Source::Mouse(button))
293            .is_some_and(|level| level.value >= PRESS)
294    }
295
296    pub fn mouse_outside(&self, button: MouseButton) -> bool {
297        self.outside.contains(&button)
298    }
299
300    pub fn feed(&mut self, event: InputEvent) {
301        let event = self.normalize(event);
302        self.apply(&event);
303        if self.recording.is_some() {
304            self.pending.push(event);
305        }
306    }
307
308    fn apply(&mut self, event: &InputEvent) {
309        match event {
310            InputEvent::Key { key, pressed } => {
311                let rising = self
312                    .keys
313                    .entry(Source::Key(*key))
314                    .or_default()
315                    .set(if *pressed { 1.0 } else { 0.0 });
316                if rising {
317                    self.active = Active::keyboard();
318                }
319            }
320            InputEvent::MouseButton { button, pressed } => {
321                if *pressed && self.pointer.layout.is_some() && !self.pointer.inside {
322                    self.outside.insert(*button);
323                } else {
324                    self.outside.remove(button);
325                }
326                let rising = self
327                    .keys
328                    .entry(Source::Mouse(*button))
329                    .or_default()
330                    .set(if *pressed { 1.0 } else { 0.0 });
331                if rising {
332                    self.active = Active::mouse();
333                }
334            }
335            InputEvent::Wheel { x, y } => {
336                if x.is_finite() && y.is_finite() {
337                    self.wheel_pending[0] += x;
338                    self.wheel_pending[1] += y;
339                    for (wheel, on) in [
340                        (Wheel::Up, *y > 0.0),
341                        (Wheel::Down, *y < 0.0),
342                        (Wheel::Left, *x < 0.0),
343                        (Wheel::Right, *x > 0.0),
344                    ] {
345                        if on {
346                            self.keys.entry(Source::Wheel(wheel)).or_default().tapped = true;
347                            self.active = Active::mouse();
348                        }
349                    }
350                }
351            }
352            InputEvent::PointerMoved { .. } => {}
353            InputEvent::PointerAt { layout, inside } => {
354                match self.anchor {
355                    Some(anchor) if self.active.kind != DeviceKind::Mouse => {
356                        let dx = layout[0] - anchor[0];
357                        let dy = layout[1] - anchor[1];
358                        if dx * dx + dy * dy >= POINTER_SWITCH * POINTER_SWITCH {
359                            self.active = Active::mouse();
360                            self.anchor = Some(*layout);
361                        }
362                    }
363                    _ => self.anchor = Some(*layout),
364                }
365                self.pointer = Pointer {
366                    layout: Some(*layout),
367                    inside: *inside,
368                };
369            }
370            InputEvent::PointerLeft => {
371                self.pointer = Pointer::default();
372                self.anchor = None;
373            }
374            InputEvent::FocusLost => {
375                for level in self.keys.values_mut() {
376                    level.value = 0.0;
377                }
378                self.outside.clear();
379            }
380            InputEvent::PadConnected { pad, info } => {
381                self.pads.insert(*pad, Pad::new(info.clone()));
382                if self.active.pad == Some(*pad) {
383                    self.active.family = Some(info.family);
384                }
385            }
386            InputEvent::PadDisconnected { pad } => {
387                self.pads.remove(pad);
388                self.rumbler.forget(*pad);
389                if self.last_pad == Some(*pad) {
390                    self.last_pad = None;
391                }
392                if self.active.pad == Some(*pad) {
393                    self.active = Active::keyboard();
394                }
395            }
396            InputEvent::PadButton { pad, button, value } => {
397                let value = finite_unit(*value);
398                let rising = self
399                    .pad_mut(*pad)
400                    .buttons
401                    .entry(*button)
402                    .or_default()
403                    .set(value);
404                if rising {
405                    self.touch_pad(*pad);
406                }
407            }
408            InputEvent::PadAxis { pad, axis, value } => {
409                let value = if value.is_finite() {
410                    value.clamp(-1.0, 1.0)
411                } else {
412                    0.0
413                };
414                let entry = self.pad_mut(*pad);
415                let before = entry.axis(*axis).abs();
416                entry.axes.insert(*axis, value);
417                if before < PRESS && value.abs() >= PRESS {
418                    self.touch_pad(*pad);
419                }
420            }
421            InputEvent::PadAction { pad, action, value } => {
422                let value = [finite_signed(value[0]), finite_signed(value[1])];
423                let entry = self.pad_mut(*pad);
424                let before = entry.actions.get(action).copied().unwrap_or([0.0; 2]);
425                entry.actions.insert(action.clone(), value);
426                if magnitude(before) < PRESS && magnitude(value) >= PRESS {
427                    self.touch_pad(*pad);
428                }
429            }
430            InputEvent::PadOrigins {
431                pad,
432                action,
433                controls,
434            } => {
435                self.pad_mut(*pad)
436                    .origins
437                    .insert(action.clone(), controls.clone());
438            }
439        }
440    }
441
442    fn normalize(&self, event: InputEvent) -> InputEvent {
443        match event {
444            InputEvent::PointerMoved { window } => match self.viewport {
445                Some(viewport) => InputEvent::PointerAt {
446                    layout: viewport.pointer_to_layout(window[0], window[1]),
447                    inside: viewport.pointer_inside(window[0], window[1]),
448                },
449                None => InputEvent::PointerAt {
450                    layout: window,
451                    inside: true,
452                },
453            },
454            InputEvent::Key { .. }
455            | InputEvent::MouseButton { .. }
456            | InputEvent::Wheel { .. }
457            | InputEvent::PointerAt { .. }
458            | InputEvent::PointerLeft
459            | InputEvent::FocusLost
460            | InputEvent::PadConnected { .. }
461            | InputEvent::PadDisconnected { .. }
462            | InputEvent::PadButton { .. }
463            | InputEvent::PadAxis { .. }
464            | InputEvent::PadAction { .. }
465            | InputEvent::PadOrigins { .. } => event,
466        }
467    }
468
469    fn pad_mut(&mut self, pad: PadId) -> &mut Pad {
470        self.pads.entry(pad).or_insert_with(|| {
471            Pad::new(PadInfo {
472                name: String::new(),
473                vendor: None,
474                product: None,
475                family: Family::Generic,
476                motors: Motors::None,
477                resolves_actions: false,
478            })
479        })
480    }
481
482    fn touch_pad(&mut self, pad: PadId) {
483        let family = self.pads.get(&pad).map(|pad| pad.info.family);
484        self.last_pad = Some(pad);
485        self.active = Active {
486            kind: DeviceKind::Gamepad,
487            pad: Some(pad),
488            family,
489        };
490    }
491
492    pub fn step(&mut self) -> Step {
493        for index in 0..self.states.len() {
494            let id = ActionId(index as u16);
495            let (held, value, vector) = self.evaluate(id);
496            let spec = self.actions.spec(id);
497            let repeat = spec.repeat;
498            let state = &mut self.states[index];
499            let was = state.held;
500            state.pressed = held && !was;
501            state.released = !held && was;
502            state.held_steps = if held && was {
503                state.held_steps.saturating_add(1)
504            } else {
505                0
506            };
507            state.repeated = held
508                && was
509                && repeat.is_some_and(|repeat| {
510                    state.held_steps >= repeat.delay
511                        && (state.held_steps - repeat.delay).is_multiple_of(repeat.interval)
512                });
513            state.held = held;
514            state.value = value;
515            state.vector = vector;
516        }
517        for level in self.keys.values_mut() {
518            level.tapped = false;
519        }
520        for pad in self.pads.values_mut() {
521            for level in pad.buttons.values_mut() {
522                level.tapped = false;
523            }
524        }
525        self.wheel = self.wheel_pending;
526        self.wheel_pending = [0.0; 2];
527        let changed = (self.active != self.reported).then_some(self.active);
528        self.reported = self.active;
529        let style = self.glyph_style();
530        let style_changed = (style != self.reported_style).then_some(style);
531        self.reported_style = style;
532        let motors = self.rumbler.step();
533        if let Some(recording) = &mut self.recording {
534            recording.steps.push(std::mem::take(&mut self.pending));
535        }
536        let index = self.step;
537        self.step += 1;
538        Step {
539            index,
540            changed,
541            style: style_changed,
542            motors,
543        }
544    }
545
546    pub fn update(&mut self, backend: &mut impl Backend) -> Step {
547        let mut events = Vec::new();
548        backend.poll(&mut events);
549        for event in events {
550            self.feed(event);
551        }
552        let step = self.step();
553        for command in &step.motors {
554            backend.set_motors(command);
555        }
556        step
557    }
558
559    fn level(&self, source: Source, set: Set) -> Level {
560        match set {
561            Set::Keyboard => match source {
562                Source::Mouse(button) if self.outside.contains(&button) => Level::default(),
563                _ => self.keys.get(&source).copied().unwrap_or_default(),
564            },
565            Set::Gamepad => {
566                let mut best = Level::default();
567                for pad in self.pads.values() {
568                    if pad.info.resolves_actions {
569                        continue;
570                    }
571                    let level = pad.source(source);
572                    best.value = best.value.max(level.value);
573                    best.tapped |= level.tapped;
574                }
575                best
576            }
577        }
578    }
579
580    fn stick_axes(&self, x: Axis, y: Axis) -> [f32; 2] {
581        let mut best = [0.0f32; 2];
582        for pad in self.pads.values() {
583            if pad.info.resolves_actions {
584                continue;
585            }
586            let v = [pad.axis(x), pad.axis(y)];
587            if magnitude(v) > magnitude(best) {
588                best = v;
589            }
590        }
591        best
592    }
593
594    fn evaluate(&self, id: ActionId) -> (bool, f32, [f32; 2]) {
595        let spec = self.actions.spec(id);
596        let dead_zone = spec.dead_zone;
597        let mut held = false;
598        let mut best = [0.0f32; 2];
599        for (set, bindings) in [Set::Keyboard, Set::Gamepad]
600            .into_iter()
601            .zip(&self.resolved[usize::from(id.0)])
602        {
603            for binding in bindings {
604                let (tapped, vector) = self.binding_value(binding, set, dead_zone);
605                held |= tapped;
606                if magnitude(vector) > magnitude(best) {
607                    best = vector;
608                }
609            }
610        }
611        for pad in self.pads.values() {
612            if !pad.info.resolves_actions {
613                continue;
614            }
615            if let Some(value) = pad.actions.get(&spec.name) {
616                let vector = match spec.kind {
617                    ActionKind::Digital | ActionKind::Axis => [value[0], 0.0],
618                    ActionKind::Stick => radial(*value, dead_zone),
619                };
620                if magnitude(vector) > magnitude(best) {
621                    best = vector;
622                }
623            }
624        }
625        held |= magnitude(best) >= PRESS;
626        match spec.kind {
627            ActionKind::Digital => {
628                let value = magnitude(best).min(1.0);
629                (held, value, [value, 0.0])
630            }
631            ActionKind::Axis => (held, best[0], [best[0], 0.0]),
632            ActionKind::Stick => (held, magnitude(best), best),
633        }
634    }
635
636    fn binding_value(&self, binding: &Binding, set: Set, dead_zone: f32) -> (bool, [f32; 2]) {
637        match *binding {
638            Binding::Press(source) => {
639                let level = self.level(source, set);
640                (level.tap(), [axial(level.value, dead_zone), 0.0])
641            }
642            Binding::Axis { negative, positive } => {
643                let negative = self.level(negative, set);
644                let positive = self.level(positive, set);
645                (
646                    negative.tap() || positive.tap(),
647                    [axial(positive.value - negative.value, dead_zone), 0.0],
648                )
649            }
650            Binding::PadAxis(axis) => {
651                if set != Set::Gamepad {
652                    return (false, [0.0; 2]);
653                }
654                let (x, y) = axis.stick().axes();
655                let v = self.stick_axes(x, y);
656                let value = if axis == x { v[0] } else { v[1] };
657                let vector = match axis {
658                    Axis::LeftX | Axis::RightX => [axial(value, dead_zone), 0.0],
659                    Axis::LeftY | Axis::RightY => [0.0, axial(value, dead_zone)],
660                };
661                (false, vector)
662            }
663            Binding::Stick {
664                up,
665                down,
666                left,
667                right,
668            } => {
669                let up = self.level(up, set);
670                let down = self.level(down, set);
671                let left = self.level(left, set);
672                let right = self.level(right, set);
673                let vector = radial([right.value - left.value, up.value - down.value], dead_zone);
674                (up.tap() || down.tap() || left.tap() || right.tap(), vector)
675            }
676            Binding::PadStick(stick) => {
677                if set != Set::Gamepad {
678                    return (false, [0.0; 2]);
679                }
680                let (x, y) = stick.axes();
681                (false, radial(self.stick_axes(x, y), dead_zone))
682            }
683        }
684    }
685
686    pub fn prompt(&self, name: &str) -> Vec<Control> {
687        let set = self.active.set();
688        if set == Set::Gamepad
689            && let Some(pad) = self.active.pad.and_then(|pad| self.pads.get(&pad))
690            && pad.info.resolves_actions
691        {
692            return pad.origins.get(name).cloned().unwrap_or_default();
693        }
694        let bindings = self.map.bindings(set, name);
695        let chosen = match self.active.kind {
696            DeviceKind::Mouse => bindings
697                .iter()
698                .find(|binding| binding.sources().iter().any(|source| source.is_mouse()))
699                .or_else(|| bindings.first()),
700            DeviceKind::Keyboard => bindings
701                .iter()
702                .find(|binding| binding.sources().iter().all(|source| source.is_keyboard()))
703                .or_else(|| bindings.first()),
704            DeviceKind::Gamepad => bindings.first(),
705        };
706        chosen.map(Binding::controls).unwrap_or_default()
707    }
708
709    pub fn prompt_family(&self) -> Family {
710        self.active
711            .family
712            .or_else(|| self.last_family())
713            .unwrap_or(Family::Generic)
714    }
715
716    pub fn glyph_style(&self) -> GlyphStyle {
717        self.prompt_family().style()
718    }
719
720    pub fn rumble_intensity(&self) -> u8 {
721        self.rumbler.intensity()
722    }
723
724    pub fn set_rumble_intensity(&mut self, percent: u8) {
725        self.rumbler.set_intensity(percent);
726    }
727
728    pub fn rumble(&mut self, rumble: Rumble) -> bool {
729        match (self.active.kind, self.active.pad) {
730            (DeviceKind::Gamepad, Some(pad)) => self.rumble_pad(pad, rumble),
731            _ => false,
732        }
733    }
734
735    pub fn rumble_pad(&mut self, pad: PadId, rumble: Rumble) -> bool {
736        let Some(motors) = self.pads.get(&pad).map(|pad| pad.info.motors) else {
737            return false;
738        };
739        self.rumbler.play(pad, motors, rumble)
740    }
741
742    pub fn start_recording(&mut self) {
743        let mut prelude = Vec::new();
744        for (pad, state) in &self.pads {
745            prelude.push(InputEvent::PadConnected {
746                pad: *pad,
747                info: state.info.clone(),
748            });
749            for (button, level) in &state.buttons {
750                if level.value != 0.0 {
751                    prelude.push(InputEvent::PadButton {
752                        pad: *pad,
753                        button: *button,
754                        value: level.value,
755                    });
756                }
757            }
758            for (axis, value) in &state.axes {
759                if *value != 0.0 {
760                    prelude.push(InputEvent::PadAxis {
761                        pad: *pad,
762                        axis: *axis,
763                        value: *value,
764                    });
765                }
766            }
767            for (action, value) in &state.actions {
768                prelude.push(InputEvent::PadAction {
769                    pad: *pad,
770                    action: action.clone(),
771                    value: *value,
772                });
773            }
774            for (action, controls) in &state.origins {
775                prelude.push(InputEvent::PadOrigins {
776                    pad: *pad,
777                    action: action.clone(),
778                    controls: controls.clone(),
779                });
780            }
781        }
782        for (source, level) in &self.keys {
783            if level.value < PRESS {
784                continue;
785            }
786            match source {
787                Source::Key(key) => prelude.push(InputEvent::Key {
788                    key: *key,
789                    pressed: true,
790                }),
791                Source::Mouse(button) => prelude.push(InputEvent::MouseButton {
792                    button: *button,
793                    pressed: true,
794                }),
795                Source::Wheel(_) | Source::Button(_) | Source::Axis(..) => {}
796            }
797        }
798        if let Some(layout) = self.pointer.layout {
799            prelude.push(InputEvent::PointerAt {
800                layout,
801                inside: self.pointer.inside,
802            });
803        }
804        self.pending.clear();
805        self.recording = Some(Recording::new(
806            self.step_us,
807            Start {
808                events: prelude,
809                states: self.states.clone(),
810                active: self.active,
811                last_pad: self.last_pad,
812                anchor: self.anchor,
813                outside: self.outside.iter().copied().collect(),
814            },
815        ));
816    }
817
818    pub fn begin_replay(&mut self, recording: &Recording) {
819        let start = &recording.start;
820        for event in &start.events {
821            self.apply(event);
822        }
823        if start.states.len() == self.states.len() {
824            self.states.clone_from(&start.states);
825        }
826        self.active = start.active;
827        self.reported = start.active;
828        self.last_pad = start.last_pad;
829        self.reported_style = self.glyph_style();
830        self.anchor = start.anchor;
831        self.outside = start.outside.iter().copied().collect();
832        for level in self.keys.values_mut() {
833            level.tapped = false;
834        }
835        for pad in self.pads.values_mut() {
836            for level in pad.buttons.values_mut() {
837                level.tapped = false;
838            }
839        }
840    }
841
842    pub fn stop_recording(&mut self) -> Option<Recording> {
843        self.pending.clear();
844        self.recording.take()
845    }
846
847    pub fn recording(&self) -> Option<&Recording> {
848        self.recording.as_ref()
849    }
850
851    pub fn is_recording(&self) -> bool {
852        self.recording.is_some()
853    }
854}
855
856fn finite_unit(value: f32) -> f32 {
857    if value.is_finite() {
858        value.clamp(0.0, 1.0)
859    } else {
860        0.0
861    }
862}
863
864fn finite_signed(value: f32) -> f32 {
865    if value.is_finite() {
866        value.clamp(-1.0, 1.0)
867    } else {
868        0.0
869    }
870}
871
872fn magnitude(v: [f32; 2]) -> f32 {
873    v[0].hypot(v[1])
874}
875
876pub fn axial(value: f32, dead_zone: f32) -> f32 {
877    let value = finite_signed(value);
878    let dead_zone = dead_zone.clamp(0.0, 0.95);
879    let size = value.abs();
880    if size <= dead_zone {
881        return 0.0;
882    }
883    value.signum() * ((size - dead_zone) / (1.0 - dead_zone)).min(1.0)
884}
885
886pub fn radial(v: [f32; 2], dead_zone: f32) -> [f32; 2] {
887    let v = [finite_signed(v[0]), finite_signed(v[1])];
888    let dead_zone = dead_zone.clamp(0.0, 0.95);
889    let size = magnitude(v);
890    if size <= dead_zone {
891        return [0.0; 2];
892    }
893    let scaled = ((size - dead_zone) / (1.0 - dead_zone)).min(1.0);
894    [v[0] / size * scaled, v[1] / size * scaled]
895}