Skip to main content

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}