use openlogi_core::binding::{ButtonId, GestureDirection, SwipeAccumulator};
use tokio::sync::mpsc;
use tracing::debug;
use super::{CapturedInput, GESTURE_SOURCE_BUTTONS};
use crate::reprog_controls::{self, RawControlEvent};
#[derive(Default)]
enum HoldState {
#[default]
Idle,
Holding {
cid: u16,
button: ButtonId,
swipe: SwipeAccumulator,
overlap: bool,
skip_first_raw_xy: bool,
},
}
fn begin_hold(cid: u16, button: ButtonId, overlap: bool, skip_first_raw_xy: bool) -> HoldState {
let mut swipe = SwipeAccumulator::default();
swipe.begin();
HoldState::Holding {
cid,
button,
swipe,
overlap,
skip_first_raw_xy,
}
}
#[derive(Default)]
pub(super) struct CaptureAccum {
hold: HoldState,
gestures_down: Vec<u16>,
dpi_down: bool,
buttons_down: Vec<u16>,
}
#[cfg(test)]
impl CaptureAccum {
pub(super) fn backdate_hold_for_test(&mut self) {
if let HoldState::Holding { swipe, .. } = &mut self.hold {
swipe.backdate_hold_for_test();
}
}
}
fn gesture_source_button(cid: u16) -> Option<ButtonId> {
GESTURE_SOURCE_BUTTONS
.into_iter()
.find(|&(c, _)| c == cid)
.map(|(_, button)| button)
}
fn captured_gesture_button(cid: u16, gesture_button_cids: &[(u16, ButtonId)]) -> Option<ButtonId> {
gesture_source_button(cid).or_else(|| {
gesture_button_cids
.iter()
.find(|&&(candidate, _)| candidate == cid)
.map(|&(_, button)| button)
})
}
impl CaptureAccum {
pub(super) fn on_event(
&mut self,
event: RawControlEvent,
gesture_cids: &[u16],
dpi_cids: &[u16],
gesture_button_cids: &[(u16, ButtonId)],
button_cids: &[(u16, ButtonId)],
sink: &mpsc::UnboundedSender<CapturedInput>,
) {
match event {
RawControlEvent::DivertedButtons(cids) => {
let held: Vec<(u16, ButtonId)> = gesture_cids
.iter()
.filter(|cid| cids.contains(cid))
.filter_map(|&cid| gesture_source_button(cid).map(|b| (cid, b)))
.chain(
gesture_button_cids
.iter()
.copied()
.filter(|(cid, _)| cids.contains(cid)),
)
.collect();
self.hold = match std::mem::take(&mut self.hold) {
HoldState::Holding {
cid,
button,
swipe,
skip_first_raw_xy,
..
} if cids.contains(&cid) => HoldState::Holding {
cid,
button,
swipe,
overlap: held.len() > 1,
skip_first_raw_xy,
},
previous => {
if let HoldState::Holding {
button, mut swipe, ..
} = previous
&& swipe.end()
{
debug!(%button, "gesture click");
let _ =
sink.send(CapturedInput::Gesture(button, GestureDirection::Click));
}
match held.first() {
Some(&(cid, button)) => begin_hold(
cid,
button,
held.len() > 1,
cid == reprog_controls::HAPTIC_PANEL_CID
&& !self.gestures_down.contains(&cid),
),
None => HoldState::Idle,
}
}
};
for &cid in &self.gestures_down {
if !held.iter().any(|(held_cid, _)| *held_cid == cid)
&& let Some(button) = captured_gesture_button(cid, gesture_button_cids)
{
let _ = sink.send(CapturedInput::ButtonUp(button));
}
}
for &(cid, button) in &held {
if !self.gestures_down.contains(&cid) {
let _ = sink.send(CapturedInput::ButtonDown(button));
}
}
self.gestures_down = held.into_iter().map(|(cid, _)| cid).collect();
let dpi_down = dpi_cids.iter().any(|cid| cids.contains(cid));
if dpi_down && !self.dpi_down {
let _ = sink.send(CapturedInput::ButtonDown(ButtonId::DpiToggle));
} else if !dpi_down && self.dpi_down {
let _ = sink.send(CapturedInput::ButtonUp(ButtonId::DpiToggle));
}
self.dpi_down = dpi_down;
for &(cid, button) in button_cids {
let down = cids.contains(&cid);
let was_down = self.buttons_down.contains(&cid);
if down && !was_down {
let _ = sink.send(CapturedInput::ButtonDown(button));
self.buttons_down.push(cid);
} else if !down && was_down {
let _ = sink.send(CapturedInput::ButtonUp(button));
self.buttons_down.retain(|&c| c != cid);
}
}
}
RawControlEvent::RawXy { dx, dy } => {
self.on_raw_xy(dx, dy, sink);
}
}
}
fn on_raw_xy(&mut self, dx: i16, dy: i16, sink: &mpsc::UnboundedSender<CapturedInput>) {
let HoldState::Holding {
button,
swipe,
overlap,
skip_first_raw_xy,
..
} = &mut self.hold
else {
return;
};
if *overlap {
return;
}
if *skip_first_raw_xy {
*skip_first_raw_xy = false;
return;
}
if let Some(direction) = swipe.accumulate(i32::from(dx), i32::from(dy)) {
debug!(?direction, %button, "gesture committed");
let _ = sink.send(CapturedInput::Gesture(*button, direction));
}
}
}
#[cfg(test)]
pub(super) fn handle_reprog(
acc: &mut CaptureAccum,
event: RawControlEvent,
gesture_cids: &[u16],
dpi_cids: &[u16],
button_cids: &[(u16, ButtonId)],
sink: &mpsc::UnboundedSender<CapturedInput>,
) {
acc.on_event(event, gesture_cids, dpi_cids, &[], button_cids, sink);
}