use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
const MAX_QUEUED_CONTROLLER_ACTIONS: usize = 64;
const MAX_DRAINED_CONTROLLER_ACTIONS: usize = 32;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(in crate::gui) enum ControllerAction {
Up,
Down,
Left,
Right,
Accept,
Cancel,
ClearCurrent,
Secondary,
Space,
SelectCurrent,
ToggleHiddenDirectories,
PagePreviewDown,
ScrollPreviewLeft,
ScrollPreviewRight,
ScrollPreviewUp,
ScrollPreviewDown,
}
#[cfg(target_os = "linux")]
impl ControllerAction {
pub(in crate::gui) const fn is_repeatable(self) -> bool {
matches!(
self,
Self::Up
| Self::Down
| Self::Left
| Self::Right
| Self::ScrollPreviewLeft
| Self::ScrollPreviewRight
| Self::ScrollPreviewUp
| Self::ScrollPreviewDown
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::gui) enum ControllerEvent {
Action(ControllerAction),
RepeatAction(ControllerAction),
Available(bool),
PurgeQueuedActions,
}
#[derive(Clone, Debug)]
pub(super) struct ControllerEventSender {
queue: Arc<ControllerEventQueue>,
}
impl ControllerEventSender {
fn new(queue: Arc<ControllerEventQueue>) -> Self {
Self { queue }
}
pub(super) fn send(&self, event: ControllerEvent) -> bool {
self.queue.push(event)
}
pub(super) fn purge_actions(&self) {
self.queue.purge_actions();
}
}
#[derive(Debug, Default)]
struct ControllerEventQueue {
events: Mutex<VecDeque<ControllerEvent>>,
}
impl ControllerEventQueue {
fn push(&self, event: ControllerEvent) -> bool {
let mut events = self.events.lock().expect("controller event queue poisoned");
match event {
ControllerEvent::Action(_) | ControllerEvent::RepeatAction(_) => {
if events.len() >= MAX_QUEUED_CONTROLLER_ACTIONS {
return false;
}
}
ControllerEvent::Available(_) => {
events.retain(|queued| !matches!(queued, ControllerEvent::Available(_)));
if events.len() >= MAX_QUEUED_CONTROLLER_ACTIONS {
events.pop_front();
}
}
ControllerEvent::PurgeQueuedActions => {
events.retain(|queued| !matches!(queued, ControllerEvent::RepeatAction(_)));
events.retain(|queued| !matches!(queued, ControllerEvent::PurgeQueuedActions));
if events.len() >= MAX_QUEUED_CONTROLLER_ACTIONS {
events.pop_front();
}
}
}
events.push_back(event);
true
}
fn purge_actions(&self) {
let mut events = self.events.lock().expect("controller event queue poisoned");
events.retain(|event| !matches!(event, ControllerEvent::RepeatAction(_)));
}
fn drain(&self) -> Vec<ControllerEvent> {
let mut events = self.events.lock().expect("controller event queue poisoned");
let drain_count = events.len().min(MAX_DRAINED_CONTROLLER_ACTIONS);
events.drain(..drain_count).collect()
}
}
#[derive(Debug)]
pub(in crate::gui) struct Controller {
queue: Arc<ControllerEventQueue>,
worker: Option<backend::ControllerWorker>,
}
impl Controller {
pub(in crate::gui) fn new(context: egui::Context) -> Self {
let queue = Arc::new(ControllerEventQueue::default());
let sender = ControllerEventSender::new(Arc::clone(&queue));
Self {
queue,
worker: backend::ControllerWorker::spawn(sender, context),
}
}
pub(in crate::gui) fn drain_events(&mut self) -> Vec<ControllerEvent> {
self.queue.drain()
}
#[cfg(test)]
pub(super) fn with_test_sender() -> (Self, ControllerEventSender) {
let queue = Arc::new(ControllerEventQueue::default());
let sender = ControllerEventSender::new(Arc::clone(&queue));
(
Self {
queue,
worker: None,
},
sender,
)
}
}
impl Default for Controller {
fn default() -> Self {
Self {
queue: Arc::new(ControllerEventQueue::default()),
worker: None,
}
}
}
impl Drop for Controller {
fn drop(&mut self) {
drop(self.worker.take());
}
}
#[cfg(target_os = "linux")]
mod linux;
mod common;
#[cfg(any(target_os = "windows", target_os = "macos"))]
mod gilrs_backend;
#[cfg(target_os = "linux")]
mod backend {
pub(super) use super::linux::ControllerWorker;
}
#[cfg(any(target_os = "windows", target_os = "macos"))]
mod backend {
pub(super) use super::gilrs_backend::ControllerWorker;
}
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
mod backend;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_controller_starts_quiet() {
assert!(Controller::default().drain_events().is_empty());
}
#[test]
fn purge_event_keeps_initial_action_and_drops_repeat_actions() {
let (mut controller, sender) = Controller::with_test_sender();
assert!(sender.send(ControllerEvent::Action(ControllerAction::Down)));
assert!(sender.send(ControllerEvent::RepeatAction(ControllerAction::Down)));
sender.purge_actions();
assert!(sender.send(ControllerEvent::PurgeQueuedActions));
assert_eq!(
controller.drain_events(),
vec![
ControllerEvent::Action(ControllerAction::Down),
ControllerEvent::PurgeQueuedActions,
]
);
}
#[test]
fn purge_events_preserve_multiple_initial_taps() {
let (mut controller, sender) = Controller::with_test_sender();
assert!(sender.send(ControllerEvent::Action(ControllerAction::Down)));
assert!(sender.send(ControllerEvent::PurgeQueuedActions));
assert!(sender.send(ControllerEvent::Action(ControllerAction::Down)));
assert!(sender.send(ControllerEvent::PurgeQueuedActions));
assert_eq!(
controller.drain_events(),
vec![
ControllerEvent::Action(ControllerAction::Down),
ControllerEvent::Action(ControllerAction::Down),
ControllerEvent::PurgeQueuedActions,
]
);
}
#[test]
fn action_queue_refuses_events_at_capacity() {
let (mut controller, sender) = Controller::with_test_sender();
for _ in 0..MAX_QUEUED_CONTROLLER_ACTIONS {
assert!(sender.send(ControllerEvent::Action(ControllerAction::Down)));
}
assert!(!sender.send(ControllerEvent::Action(ControllerAction::Up)));
assert_eq!(
controller.drain_events().len(),
MAX_DRAINED_CONTROLLER_ACTIONS
);
}
#[test]
fn availability_events_coalesce_to_latest_state() {
let (mut controller, sender) = Controller::with_test_sender();
assert!(sender.send(ControllerEvent::Action(ControllerAction::Down)));
assert!(sender.send(ControllerEvent::Available(true)));
assert!(sender.send(ControllerEvent::Available(false)));
assert_eq!(
controller.drain_events(),
vec![
ControllerEvent::Action(ControllerAction::Down),
ControllerEvent::Available(false),
]
);
}
#[test]
fn purge_event_preserves_non_action_events() {
let (mut controller, sender) = Controller::with_test_sender();
assert!(sender.send(ControllerEvent::Available(true)));
assert!(sender.send(ControllerEvent::Action(ControllerAction::Down)));
assert!(sender.send(ControllerEvent::RepeatAction(ControllerAction::Down)));
assert!(sender.send(ControllerEvent::Action(ControllerAction::Accept)));
assert!(sender.send(ControllerEvent::Action(ControllerAction::Up)));
assert!(sender.send(ControllerEvent::RepeatAction(ControllerAction::Up)));
assert!(sender.send(ControllerEvent::PurgeQueuedActions));
assert!(sender.send(ControllerEvent::PurgeQueuedActions));
assert_eq!(
controller.drain_events(),
vec![
ControllerEvent::Available(true),
ControllerEvent::Action(ControllerAction::Down),
ControllerEvent::Action(ControllerAction::Accept),
ControllerEvent::Action(ControllerAction::Up),
ControllerEvent::PurgeQueuedActions,
]
);
}
}