Skip to main content

gcrecomp_runtime/input/
controller.rs

1// Controller detection and management
2use crate::input::backends::{Backend, ControllerInfo};
3use crate::input::gamecube_mapping::GameCubeMapping;
4use crate::input::profiles::ControllerProfile;
5use anyhow::Result;
6use std::collections::HashMap;
7
8pub struct ControllerManager {
9    backends: Vec<Box<dyn Backend>>,
10    controllers: HashMap<usize, ControllerState>,
11    gamecube_mappings: HashMap<usize, GameCubeMapping>,
12    profiles: HashMap<String, ControllerProfile>,
13    next_id: usize,
14}
15
16#[derive(Debug, Clone)]
17pub struct ControllerState {
18    pub id: usize,
19    pub info: ControllerInfo,
20    pub connected: bool,
21    pub last_update: std::time::Instant,
22}
23
24impl ControllerManager {
25    pub fn new() -> Result<Self> {
26        let mut backends: Vec<Box<dyn Backend>> = Vec::new();
27
28        // Try to initialize SDL2 backend
29        if let Ok(sdl2_backend) = crate::input::backends::sdl2::SDL2Backend::new() {
30            backends.push(Box::new(sdl2_backend));
31        }
32
33        // Try to initialize gilrs backend (cross-platform gamepad)
34        if let Ok(gilrs_backend) = crate::input::backends::gilrs::GilrsBackend::new() {
35            backends.push(Box::new(gilrs_backend));
36        }
37
38        #[cfg(target_os = "windows")]
39        {
40            // Try XInput backend on Windows
41            if let Ok(xinput_backend) = crate::input::backends::xinput::XInputBackend::new() {
42                backends.push(Box::new(xinput_backend));
43            }
44        }
45
46        Ok(Self {
47            backends,
48            controllers: HashMap::new(),
49            gamecube_mappings: HashMap::new(),
50            profiles: HashMap::new(),
51            next_id: 0,
52        })
53    }
54
55    pub fn update(&mut self) -> Result<()> {
56        // Update all backends and detect new/removed controllers
57        // Collect controller IDs first to avoid borrow issues
58        let mut all_controller_infos: Vec<ControllerInfo> = Vec::new();
59
60        for backend in &mut self.backends {
61            backend.update()?;
62            all_controller_infos.extend(backend.enumerate_controllers()?);
63        }
64
65        // Check for new controllers
66        for controller in &all_controller_infos {
67            if !self.controllers.contains_key(&controller.id) {
68                let state = ControllerState {
69                    id: controller.id,
70                    info: controller.clone(),
71                    connected: true,
72                    last_update: std::time::Instant::now(),
73                };
74                self.controllers.insert(controller.id, state);
75
76                // Load default profile or create new mapping
77                self.load_default_mapping(controller.id)?;
78            }
79        }
80
81        // Check for disconnected controllers
82        let connected_ids: Vec<usize> = all_controller_infos.iter().map(|c| c.id).collect();
83
84        self.controllers.retain(|id, state| {
85            if !connected_ids.contains(id) {
86                state.connected = false;
87                false
88            } else {
89                true
90            }
91        });
92
93        Ok(())
94    }
95
96    pub fn get_controller_count(&self) -> usize {
97        self.controllers.values().filter(|c| c.connected).count()
98    }
99
100    pub fn get_controller_state(&self, id: usize) -> Option<&ControllerState> {
101        self.controllers.get(&id)
102    }
103
104    pub fn get_gamecube_input(&self, controller_id: usize) -> Option<GameCubeInput> {
105        let mapping = self.gamecube_mappings.get(&controller_id)?;
106
107        // Get raw input from backend
108        for backend in &self.backends {
109            if let Ok(input) = backend.get_input(controller_id) {
110                return Some(mapping.map_to_gamecube(&input));
111            }
112        }
113
114        None
115    }
116
117    pub fn set_mapping(&mut self, controller_id: usize, mapping: GameCubeMapping) {
118        self.gamecube_mappings.insert(controller_id, mapping);
119    }
120
121    pub fn load_profile(&mut self, controller_id: usize, profile_name: &str) -> Result<()> {
122        if let Some(profile) = self.profiles.get(profile_name) {
123            let mapping = profile.to_gamecube_mapping()?;
124            self.set_mapping(controller_id, mapping);
125        }
126        Ok(())
127    }
128
129    pub fn save_profile(&mut self, name: String, controller_id: usize) -> Result<()> {
130        if let Some(mapping) = self.gamecube_mappings.get(&controller_id) {
131            let profile = ControllerProfile::from_mapping(name, mapping.clone());
132            self.profiles.insert(profile.name.clone(), profile);
133        }
134        Ok(())
135    }
136
137    fn load_default_mapping(&mut self, controller_id: usize) -> Result<()> {
138        // Try to detect controller type and load appropriate default
139        if let Some(state) = self.controllers.get(&controller_id) {
140            let default_mapping = GameCubeMapping::default_for_controller(&state.info)?;
141            self.set_mapping(controller_id, default_mapping);
142        }
143        Ok(())
144    }
145}
146
147#[derive(Debug, Clone)]
148pub struct GameCubeInput {
149    pub buttons: GameCubeButtons,
150    pub left_stick: (f32, f32),
151    pub right_stick: (f32, f32),
152    pub left_trigger: f32,
153    pub right_trigger: f32,
154}
155
156#[derive(Debug, Clone, Default)]
157pub struct GameCubeButtons {
158    pub a: bool,
159    pub b: bool,
160    pub x: bool,
161    pub y: bool,
162    pub start: bool,
163    pub d_up: bool,
164    pub d_down: bool,
165    pub d_left: bool,
166    pub d_right: bool,
167    pub l: bool,
168    pub r: bool,
169    pub z: bool,
170}