openlogi_core/config/gestures.rs
1//! Gesture mode: which buttons of a device dispatch a direction map, and the
2//! edits that take a button into and out of that mode.
3//!
4//! The mode is a per-button fact read from the binding shape, never a separate
5//! flag. Leaving it stashes the button's map in
6//! [`DeviceConfig::disabled_gestures`](super::DeviceConfig::disabled_gestures),
7//! so entering it again restores what the user had.
8
9use super::Config;
10use crate::binding::{
11 Action, Binding, ButtonId, GestureDirection, default_binding, default_binding_for,
12 default_gesture_binding,
13};
14
15impl Config {
16 /// Records `action` for one `direction` of `button`'s gesture binding,
17 /// creating the device entry if needed.
18 ///
19 /// A button with no binding yet is seeded from its canonical
20 /// [`default_binding_for`] — for [`ButtonId::GestureButton`] that is the full
21 /// default direction map (including a [`GestureDirection::Click`]), so the
22 /// merged map never persists a gesture binding whose click projection is a
23 /// no-op. A prior [`Binding::Single`] is upgraded to [`Binding::Gesture`],
24 /// preserving its action as the `Click` entry.
25 pub fn set_gesture_direction(
26 &mut self,
27 device_key: &str,
28 button: ButtonId,
29 direction: GestureDirection,
30 action: Action,
31 ) {
32 if let Binding::Gesture(map) = self.ensure_gesture_binding(device_key, button) {
33 map.insert(direction, action);
34 }
35 }
36
37 /// Ensure `button` on `device_key` is a [`Binding::Gesture`], creating the
38 /// device + a default binding if needed and upgrading a [`Binding::Single`]
39 /// in place (its action kept as the [`GestureDirection::Click`]). Returns the
40 /// entry so the caller can finish it — seed every direction
41 /// ([`Binding::fill_gesture_defaults`]) or set just one. Shared by
42 /// [`Self::set_gesture_mode`] and [`Self::set_gesture_direction`] so the two
43 /// promote a button into gesture mode identically.
44 fn ensure_gesture_binding(&mut self, device_key: &str, button: ButtonId) -> &mut Binding {
45 let entry = self
46 .devices
47 .entry(device_key.to_string())
48 .or_default()
49 .bindings
50 .entry(button)
51 .or_insert_with(|| default_binding_for(button));
52 entry.upgrade_to_gesture();
53 entry
54 }
55
56 /// Whether `button` on `device_key` is in gesture mode — a per-button fact
57 /// read straight from the binding shape: a stored [`Binding::Gesture`], or
58 /// no stored binding on a button whose canonical default
59 /// ([`default_binding_for`]) is gesture-shaped (the dedicated HID++ gesture
60 /// button starts in gesture mode).
61 ///
62 /// Gesture mode is not exclusive: any number of buttons may gesture at
63 /// once, each with its own direction map. This replaces the former
64 /// one-gesture-button-per-device owner lock — see [`Self::set_gesture_mode`].
65 #[must_use]
66 pub fn is_gesture_mode(&self, device_key: &str, button: ButtonId) -> bool {
67 self.devices
68 .get(device_key)
69 .and_then(|d| d.bindings.get(&button))
70 .map_or_else(
71 || default_binding_for(button).is_gesture(),
72 Binding::is_gesture,
73 )
74 }
75
76 /// Every button of `device_key` currently in gesture mode, in [`ButtonId`]
77 /// declaration order. Purely config-derived: callers cross it with the
78 /// device's actual controls (a model without the dedicated gesture button
79 /// simply never captures it).
80 #[must_use]
81 pub fn gesture_mode_buttons(&self, device_key: &str) -> Vec<ButtonId> {
82 ButtonId::ALL
83 .iter()
84 .copied()
85 .filter(|b| self.is_gesture_mode(device_key, *b))
86 .collect()
87 }
88
89 /// Turn gesture mode on or off for one button, independently of every
90 /// other button.
91 ///
92 /// On: restore the button's stashed map when one exists (see
93 /// [`DeviceConfig::disabled_gestures`]) — an off/on round trip hands back
94 /// the user's customized arms exactly. Otherwise promote the stored
95 /// binding in place ([`Binding::upgrade_to_gesture`] keeps a prior single
96 /// action as the [`GestureDirection::Click`] entry) and seed unbound
97 /// directions from [`default_gesture_binding`].
98 ///
99 /// Off: stash the live map, then demote to a [`Binding::Single`] of the
100 /// map's `Click` action, falling back to the button's canonical
101 /// [`default_binding`] when the map has no explicit `Click` — a demoted
102 /// button always keeps a meaningful press. A button gesturing only by
103 /// default (no stored binding) stashes its seeded default map and is
104 /// pinned off with an explicit `Single` at its canonical default.
105 ///
106 /// [`DeviceConfig::disabled_gestures`]: super::DeviceConfig::disabled_gestures
107 pub fn set_gesture_mode(&mut self, device_key: &str, button: ButtonId, enabled: bool) {
108 if enabled {
109 let device = self.devices.entry(device_key.to_string()).or_default();
110 if let Some(map) = device.disabled_gestures.remove(&button) {
111 device.bindings.insert(button, Binding::Gesture(map));
112 } else {
113 self.ensure_gesture_binding(device_key, button)
114 .fill_gesture_defaults();
115 }
116 return;
117 }
118 let device = self.devices.entry(device_key.to_string()).or_default();
119 match device.bindings.get_mut(&button) {
120 Some(binding) => {
121 if let Binding::Gesture(map) = binding {
122 device.disabled_gestures.insert(button, map.clone());
123 }
124 binding.demote_to_single(default_binding(button));
125 }
126 None => {
127 if default_binding_for(button).is_gesture() {
128 device.disabled_gestures.insert(
129 button,
130 GestureDirection::ALL
131 .iter()
132 .copied()
133 .map(|d| (d, default_gesture_binding(d)))
134 .collect(),
135 );
136 device
137 .bindings
138 .insert(button, Binding::Single(default_binding(button)));
139 }
140 }
141 }
142 }
143}