input.rsannotatedinput.rssource1462 lines · 55.0 KB · raw
1mod helptext;
2
3use crate::app::inputcollect::InputCollectionState;
4use crate::app::widgets::NumericTextEdit;
5use crate::app::{App, OpenWindow};
6use egui::{Button, Color32, ComboBox, Context, Grid, ScrollArea, Slider, Ui, Window};
7use gb_config::GameBoyButton;
8use gba_config::GbaButton;
9use genesis_config::{GenesisButton, GenesisControllerType};
10use jgenesis_common::input::Player;
11use jgenesis_native_config::genesis::GenesisAppConfig;
12use jgenesis_native_config::input::InputAppConfig;
13use jgenesis_native_config::input::mappings::{
14    GameBoyInputMapping, GbaInputMapping, GbaJoypadMapping, GbaSolarMapping,
15    GenesisControllerMapping, GenesisInputMapping, HotkeyMapping, NesControllerMapping,
16    NesControllerType, NesInputMapping, NesZapperMapping, SmsGgControllerMapping,
17    SmsGgInputMapping, SnesControllerMapping, SnesControllerType, SnesInputMapping,
18};
19use jgenesis_native_config::input::mappings::{PceInputMapping, PceJoypadMapping};
20use jgenesis_native_config::input::{GenericInput, Hotkey};
21use jgenesis_native_config::snes::SnesAppConfig;
22use nes_config::NesButton;
23use pce_config::{PceButton, PceInputDevice};
24use polonius_the_crab::{polonius, polonius_return};
25use smsgg_config::SmsGgButton;
26use snes_config::SnesButton;
27use std::mem;
28use std::sync::LazyLock;
29
30const ALLOW_OPPOSING_DIRECTIONS_LABEL: &str = "Allow simultaneous opposing gamepad directions";
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
33pub enum InputMappingSet {
34    #[default]
35    One,
36    Two,
37}
38
39impl InputMappingSet {
40    fn label(self) -> &'static str {
41        match self {
42            Self::One => "Input Mapping #1",
43            Self::Two => "Input Mapping #2",
44        }
45    }
46
47    fn smsgg(self, config: &mut InputAppConfig) -> &mut SmsGgInputMapping {
48        match self {
49            Self::One => &mut config.smsgg.mapping_1,
50            Self::Two => &mut config.smsgg.mapping_2,
51        }
52    }
53
54    fn genesis(self, config: &mut InputAppConfig) -> &mut GenesisInputMapping {
55        match self {
56            Self::One => &mut config.genesis.mapping_1,
57            Self::Two => &mut config.genesis.mapping_2,
58        }
59    }
60
61    fn nes(self, config: &mut InputAppConfig) -> &mut NesInputMapping {
62        match self {
63            Self::One => &mut config.nes.mapping_1,
64            Self::Two => &mut config.nes.mapping_2,
65        }
66    }
67
68    fn snes(self, config: &mut InputAppConfig) -> &mut SnesInputMapping {
69        match self {
70            Self::One => &mut config.snes.mapping_1,
71            Self::Two => &mut config.snes.mapping_2,
72        }
73    }
74
75    fn gb(self, config: &mut InputAppConfig, turbo: bool) -> &mut GameBoyInputMapping {
76        match (self, turbo) {
77            (Self::One, false) => &mut config.game_boy.mapping_1,
78            (Self::One, true) => &mut config.game_boy.mapping_1_turbo,
79            (Self::Two, false) => &mut config.game_boy.mapping_2,
80            (Self::Two, true) => &mut config.game_boy.mapping_2_turbo,
81        }
82    }
83
84    fn gba(self, config: &mut InputAppConfig, turbo: bool) -> &mut GbaInputMapping {
85        match (self, turbo) {
86            (Self::One, false) => &mut config.game_boy_advance.mapping_1,
87            (Self::One, true) => &mut config.game_boy_advance.mapping_1_turbo,
88            (Self::Two, false) => &mut config.game_boy_advance.mapping_2,
89            (Self::Two, true) => &mut config.game_boy_advance.mapping_2_turbo,
90        }
91    }
92
93    fn pce(self, config: &mut InputAppConfig) -> &mut PceInputMapping {
94        match self {
95            Self::One => &mut config.pc_engine.mapping_1,
96            Self::Two => &mut config.pc_engine.mapping_2,
97        }
98    }
99
100    fn hotkey(self, config: &mut InputAppConfig) -> &mut HotkeyMapping {
101        match self {
102            Self::One => &mut config.hotkeys.mapping_1,
103            Self::Two => &mut config.hotkeys.mapping_2,
104        }
105    }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub enum GenericButton {
110    SmsGg(SmsGgButton, Player),
111    Genesis(GenesisButton, Player),
112    Nes(NesButton, Player),
113    Snes(SnesButton, Player),
114    GameBoy(GameBoyButton),
115    Gba(GbaButton),
116    Pce(PceButton, Player),
117    Hotkey(Hotkey),
118}
119
120impl GenericButton {
121    pub fn label(self) -> &'static str {
122        match self {
123            Self::SmsGg(button, _) => button.label(),
124            Self::Genesis(button, _) => button.label(),
125            Self::Nes(button, _) => button.label(),
126            Self::Snes(button, _) => button.label(),
127            Self::GameBoy(button) => button.label(),
128            Self::Gba(button) => button.label(),
129            Self::Pce(button, _) => button.label(),
130            Self::Hotkey(hotkey) => hotkey.label(),
131        }
132    }
133
134    pub fn access_value(
135        self,
136        mapping: InputMappingSet,
137        config: &mut InputAppConfig,
138    ) -> Option<&mut Option<Vec<GenericInput>>> {
139        match self {
140            Self::SmsGg(button, player) => {
141                access_smsgg_value(mapping, button, player, false, config)
142            }
143            Self::Genesis(button, player) => {
144                access_genesis_value(mapping, button, player, false, config)
145            }
146            Self::Nes(button, player) => access_nes_value(mapping, button, player, false, config),
147            Self::Snes(button, player) => access_snes_value(mapping, button, player, false, config),
148            Self::GameBoy(button) => access_gb_value(mapping, button, false, config),
149            Self::Gba(button) => access_gba_value(mapping, button, false, config),
150            Self::Pce(button, player) => access_pce_value(mapping, button, player, false, config),
151            Self::Hotkey(hotkey) => Some(access_hotkey(mapping, hotkey, config)),
152        }
153    }
154
155    pub fn access_value_turbo(
156        self,
157        mapping: InputMappingSet,
158        config: &mut InputAppConfig,
159    ) -> Option<&mut Option<Vec<GenericInput>>> {
160        match self {
161            Self::SmsGg(button @ (SmsGgButton::Button1 | SmsGgButton::Button2), player) => {
162                access_smsgg_value(mapping, button, player, true, config)
163            }
164            Self::Genesis(
165                button @ (GenesisButton::A
166                | GenesisButton::B
167                | GenesisButton::C
168                | GenesisButton::X
169                | GenesisButton::Y
170                | GenesisButton::Z
171                | GenesisButton::Xe1apA
172                | GenesisButton::Xe1apB
173                | GenesisButton::Xe1apC
174                | GenesisButton::Xe1apD
175                | GenesisButton::Xe1apE1
176                | GenesisButton::Xe1apE2
177                | GenesisButton::Xe1apAp
178                | GenesisButton::Xe1apBp),
179                player,
180            ) => access_genesis_value(mapping, button, player, true, config),
181            Self::Nes(button @ (NesButton::A | NesButton::B), player) => {
182                access_nes_value(mapping, button, player, true, config)
183            }
184            Self::Snes(
185                button @ (SnesButton::A
186                | SnesButton::B
187                | SnesButton::X
188                | SnesButton::Y
189                | SnesButton::L
190                | SnesButton::R),
191                player,
192            ) => access_snes_value(mapping, button, player, true, config),
193            Self::GameBoy(button @ (GameBoyButton::A | GameBoyButton::B)) => {
194                access_gb_value(mapping, button, true, config)
195            }
196            Self::Gba(button @ (GbaButton::A | GbaButton::B | GbaButton::L | GbaButton::R)) => {
197                access_gba_value(mapping, button, true, config)
198            }
199            Self::Pce(button @ (PceButton::Button1 | PceButton::Button2), player) => {
200                access_pce_value(mapping, button, player, true, config)
201            }
202            _ => None,
203        }
204    }
205
206    pub fn access_value_maybe_turbo(
207        self,
208        mapping: InputMappingSet,
209        config: &mut InputAppConfig,
210        turbo: bool,
211    ) -> Option<&mut Option<Vec<GenericInput>>> {
212        if turbo {
213            self.access_value_turbo(mapping, config)
214        } else {
215            self.access_value(mapping, config)
216        }
217    }
218}
219
220fn access_smsgg_value(
221    mapping: InputMappingSet,
222    button: SmsGgButton,
223    player: Player,
224    turbo: bool,
225    config: &mut InputAppConfig,
226) -> Option<&mut Option<Vec<GenericInput>>> {
227    let mapping_config = mapping.smsgg(config);
228
229    if button == SmsGgButton::Pause {
230        return Some(&mut mapping_config.pause);
231    }
232
233    let player_config = mapping_config.player_mapping(player, turbo)?;
234    player_config.access_value(button)
235}
236
237fn access_genesis_value(
238    mapping: InputMappingSet,
239    button: GenesisButton,
240    player: Player,
241    turbo: bool,
242    config: &mut InputAppConfig,
243) -> Option<&mut Option<Vec<GenericInput>>> {
244    let mapping_config = mapping.genesis(config);
245    let player_config = mapping_config.player_mapping(player, turbo)?;
246    player_config.access_value(button)
247}
248
249fn access_nes_value(
250    mapping: InputMappingSet,
251    button: NesButton,
252    player: Player,
253    turbo: bool,
254    config: &mut InputAppConfig,
255) -> Option<&mut Option<Vec<GenericInput>>> {
256    let mut mapping_config = mapping.nes(config);
257
258    polonius!(|mapping_config| -> Option<&'polonius mut Option<Vec<GenericInput>>> {
259        if let Some(value) = mapping_config.zapper.access_value(button) {
260            polonius_return!(Some(value));
261        }
262    });
263
264    let player_config = mapping_config.player_mapping(player, turbo)?;
265    player_config.access_value(button)
266}
267
268fn access_snes_value(
269    mapping: InputMappingSet,
270    button: SnesButton,
271    player: Player,
272    turbo: bool,
273    config: &mut InputAppConfig,
274) -> Option<&mut Option<Vec<GenericInput>>> {
275    let mapping_config = mapping.snes(config);
276    let player_config = mapping_config.player_mapping(player, turbo)?;
277    player_config.access_value(button)
278}
279
280fn access_gb_value(
281    mapping: InputMappingSet,
282    button: GameBoyButton,
283    turbo: bool,
284    config: &mut InputAppConfig,
285) -> Option<&mut Option<Vec<GenericInput>>> {
286    let mapping_config = mapping.gb(config, turbo);
287    mapping_config.access_value(button)
288}
289
290fn access_gba_value(
291    mapping: InputMappingSet,
292    button: GbaButton,
293    turbo: bool,
294    config: &mut InputAppConfig,
295) -> Option<&mut Option<Vec<GenericInput>>> {
296    let mapping_config = mapping.gba(config, turbo);
297    mapping_config.joypad.access_value(button).or_else(|| mapping_config.solar.access_value(button))
298}
299
300fn access_pce_value(
301    mapping: InputMappingSet,
302    button: PceButton,
303    player: Player,
304    turbo: bool,
305    config: &mut InputAppConfig,
306) -> Option<&mut Option<Vec<GenericInput>>> {
307    let mapping_config = mapping.pce(config);
308    let player_config = mapping_config.player_mapping(player, turbo)?;
309    player_config.access_value(button)
310}
311
312fn access_hotkey(
313    mapping: InputMappingSet,
314    hotkey: Hotkey,
315    config: &mut InputAppConfig,
316) -> &mut Option<Vec<GenericInput>> {
317    let mapping_config = mapping.hotkey(config);
318    mapping_config.access_value(hotkey)
319}
320
321impl App {
322    pub(super) fn render_general_input_settings(&mut self, ctx: &Context) {
323        let mut open = true;
324        Window::new(OpenWindow::GeneralInput.title()).open(&mut open).show(ctx, |ui| {
325            ui.horizontal(|ui| {
326                ui.label("Gamepad joystick axis deadzone:");
327                ui.add(Slider::new(&mut self.config.input.axis_deadzone, 0..=i16::MAX));
328
329                if ui.button("Default").clicked() {
330                    self.config.input.axis_deadzone = InputAppConfig::default().axis_deadzone;
331                }
332            });
333        });
334        if !open {
335            self.state.open_windows.remove(&OpenWindow::GeneralInput);
336        }
337    }
338
339    pub(super) fn render_smsgg_input_settings(&mut self, ctx: &Context) {
340        static P1_BUTTONS: LazyLock<Vec<GenericButton>> = LazyLock::new(|| {
341            SmsGgButton::ALL
342                .into_iter()
343                .filter_map(|button| {
344                    (button != SmsGgButton::Pause)
345                        .then_some(GenericButton::SmsGg(button, Player::One))
346                })
347                .collect()
348        });
349        static P2_BUTTONS: LazyLock<Vec<GenericButton>> = LazyLock::new(|| {
350            SmsGgButton::ALL
351                .into_iter()
352                .filter_map(|button| {
353                    (button != SmsGgButton::Pause)
354                        .then_some(GenericButton::SmsGg(button, Player::Two))
355                })
356                .collect()
357        });
358
359        let mut open = true;
360        Window::new(OpenWindow::SmsGgInput.title()).open(&mut open).show(ctx, |ui| {
361            self.disable_if_waiting_for_input(ui);
362
363            ui.checkbox(
364                &mut self.config.smsgg.allow_opposing_joypad_directions,
365                ALLOW_OPPOSING_DIRECTIONS_LABEL,
366            );
367            ui.separator();
368
369            let mapping = self.render_mapping_set_selector(OpenWindow::SmsGgInput, ui);
370            ui.separator();
371
372            Grid::new("smsgg_inputs").spacing([50.0, 5.0]).show(ui, |ui| {
373                ui.heading("Player 1");
374                ui.heading("Player 2");
375                ui.end_row();
376
377                self.render_input_buttons("smsgg_p1_input_settings", mapping, &P1_BUTTONS, ui);
378                self.render_input_buttons("smsgg_p2_input_settings", mapping, &P2_BUTTONS, ui);
379                ui.end_row();
380
381                self.render_configure_all_button(&P1_BUTTONS, mapping, ui);
382                self.render_configure_all_button(&P2_BUTTONS, mapping, ui);
383                ui.end_row();
384            });
385
386            ui.add_space(15.0);
387
388            self.render_input_buttons(
389                "smsgg_pause_input",
390                mapping,
391                &[GenericButton::SmsGg(SmsGgButton::Pause, Player::One)],
392                ui,
393            );
394
395            ui.add_space(15.0);
396
397            let mapping_config = mapping.smsgg(&mut self.config.input);
398            ui.horizontal(|ui| {
399                ComboBox::new("smsgg_presets", "").selected_text("Apply preset...").show_ui(
400                    ui,
401                    |ui| {
402                        if ui.selectable_label(false, "Keyboard - Arrow movement").clicked() {
403                            mapping_config.p1 = SmsGgControllerMapping::keyboard_arrows();
404                            mapping_config.p1_turbo = SmsGgControllerMapping::default();
405                            mapping_config.pause = SmsGgControllerMapping::keyboard_pause();
406                        }
407
408                        if ui.selectable_label(false, "Keyboard - WASD movement").clicked() {
409                            mapping_config.p1 = SmsGgControllerMapping::keyboard_wasd();
410                            mapping_config.p1_turbo = SmsGgControllerMapping::default();
411                            mapping_config.pause = SmsGgControllerMapping::keyboard_pause();
412                        }
413                    },
414                );
415
416                if ui.button("Clear All P1").clicked() {
417                    mapping_config.p1 = SmsGgControllerMapping::default();
418                    mapping_config.p1_turbo = SmsGgControllerMapping::default();
419                }
420
421                if ui.button("Clear All P2").clicked() {
422                    mapping_config.p2 = SmsGgControllerMapping::default();
423                    mapping_config.p2_turbo = SmsGgControllerMapping::default();
424                }
425            });
426        });
427        if !open {
428            self.state.open_windows.remove(&OpenWindow::SmsGgInput);
429        }
430    }
431
432    pub(super) fn render_genesis_input_settings(&mut self, ctx: &Context) {
433        fn genesis_buttons(
434            player: Player,
435            filter: impl Fn(GenesisButton) -> bool,
436        ) -> Vec<GenericButton> {
437            GenesisButton::ALL
438                .into_iter()
439                .filter_map(|button| {
440                    filter(button).then_some(GenericButton::Genesis(button, player))
441                })
442                .collect()
443        }
444
445        static P1_GAMEPAD: LazyLock<Vec<GenericButton>> =
446            LazyLock::new(|| genesis_buttons(Player::One, GenesisButton::is_gamepad));
447        static P2_GAMEPAD: LazyLock<Vec<GenericButton>> =
448            LazyLock::new(|| genesis_buttons(Player::Two, GenesisButton::is_gamepad));
449        static P1_MOUSE: LazyLock<Vec<GenericButton>> =
450            LazyLock::new(|| genesis_buttons(Player::One, GenesisButton::is_mouse));
451        static P2_MOUSE: LazyLock<Vec<GenericButton>> =
452            LazyLock::new(|| genesis_buttons(Player::Two, GenesisButton::is_mouse));
453        static P1_XE1AP: LazyLock<Vec<GenericButton>> =
454            LazyLock::new(|| genesis_buttons(Player::One, GenesisButton::is_xe1ap));
455        static P2_XE1AP: LazyLock<Vec<GenericButton>> =
456            LazyLock::new(|| genesis_buttons(Player::Two, GenesisButton::is_xe1ap));
457
458        let mut open = true;
459        Window::new(OpenWindow::GenesisInput.title()).open(&mut open).show(ctx, |ui| {
460            self.disable_if_waiting_for_input(ui);
461
462            ui.horizontal(|ui| {
463                for (label, controller_type_field) in [
464                    ("Player 1 controller type", &mut self.config.input.genesis.p1_type),
465                    ("Player 2 controller type", &mut self.config.input.genesis.p2_type),
466                ] {
467                    ui.vertical(|ui| {
468                        ui.group(|ui| {
469                            ui.label(label);
470
471                            ui.horizontal(|ui| {
472                                for (value, label) in [
473                                    (GenesisControllerType::ThreeButton, "3-button"),
474                                    (GenesisControllerType::SixButton, "6-button"),
475                                    (GenesisControllerType::MegaMouse, "Mega Mouse"),
476                                    (GenesisControllerType::Xe1ap, "XE-1 AP"),
477                                    (GenesisControllerType::None, "None"),
478                                ] {
479                                    ui.radio_value(controller_type_field, value, label);
480                                }
481                            });
482                        });
483                    });
484                }
485            });
486
487            ui.add_space(3.0);
488
489            ui.checkbox(
490                &mut self.config.genesis.allow_opposing_joypad_directions,
491                ALLOW_OPPOSING_DIRECTIONS_LABEL,
492            );
493            ui.checkbox(
494                &mut self.config.genesis.auto_3_button_mode,
495                "Automatically force 3-button mode in 6-button-incompatible games",
496            );
497
498            ui.add_space(3.0);
499
500            ui.horizontal(|ui| {
501                ui.label("Mega Mouse sensitivity:");
502                ui.add(Slider::new(&mut self.config.genesis.mega_mouse_sensitivity, 0.0..=10.0));
503
504                if ui.button("Default").clicked() {
505                    self.config.genesis.mega_mouse_sensitivity =
506                        GenesisAppConfig::default().mega_mouse_sensitivity;
507                }
508            });
509
510            ui.separator();
511
512            let mapping = self.render_mapping_set_selector(OpenWindow::GenesisInput, ui);
513            ui.separator();
514
515            let p1_type = self.config.input.genesis.p1_type;
516            let p2_type = self.config.input.genesis.p2_type;
517
518            Grid::new("genesis_inputs").spacing([50.0, 5.0]).show(ui, |ui| {
519                let (p1_heading, p1_buttons) = match p1_type {
520                    GenesisControllerType::ThreeButton
521                    | GenesisControllerType::SixButton
522                    | GenesisControllerType::None => ("Player 1 - Gamepad", &P1_GAMEPAD),
523                    GenesisControllerType::MegaMouse => ("Player 1 - Mega Mouse", &P1_MOUSE),
524                    GenesisControllerType::Xe1ap => ("Player 1 - XE-1 AP", &P1_XE1AP),
525                };
526                let (p2_heading, p2_buttons) = match p2_type {
527                    GenesisControllerType::ThreeButton
528                    | GenesisControllerType::SixButton
529                    | GenesisControllerType::None => ("Player 2 - Gamepad", &P2_GAMEPAD),
530                    GenesisControllerType::MegaMouse => ("Player 2 - Mega Mouse", &P2_MOUSE),
531                    GenesisControllerType::Xe1ap => ("Player 2 - XE-1 AP", &P2_XE1AP),
532                };
533
534                ui.heading(p1_heading);
535                ui.heading(p2_heading);
536                ui.end_row();
537
538                self.render_input_buttons("genesis_p1_input_settings", mapping, p1_buttons, ui);
539                self.render_input_buttons("genesis_p2_input_settings", mapping, p2_buttons, ui);
540                ui.end_row();
541
542                self.render_configure_all_button(p1_buttons, mapping, ui);
543                self.render_configure_all_button(p2_buttons, mapping, ui);
544                ui.end_row();
545            });
546
547            ui.add_space(15.0);
548
549            let mapping_config = mapping.genesis(&mut self.config.input);
550            ui.horizontal(|ui| {
551                ui.add_enabled_ui(p1_type != GenesisControllerType::Xe1ap, |ui| {
552                    ComboBox::new("genesis_presets", "").selected_text("Apply preset...").show_ui(
553                        ui,
554                        |ui| {
555                            if ui.selectable_label(false, "Keyboard - Arrow movement").clicked() {
556                                mapping_config.p1.clone_from_type(
557                                    &GenesisControllerMapping::keyboard_arrows(),
558                                    p1_type,
559                                );
560                                mapping_config
561                                    .p1_turbo
562                                    .clone_from_type(&GenesisControllerMapping::default(), p1_type);
563                            }
564
565                            if ui.selectable_label(false, "Keyboard - WASD movement").clicked() {
566                                mapping_config.p1.clone_from_type(
567                                    &GenesisControllerMapping::keyboard_wasd(),
568                                    p1_type,
569                                );
570                                mapping_config
571                                    .p1_turbo
572                                    .clone_from_type(&GenesisControllerMapping::default(), p1_type);
573                            }
574                        },
575                    );
576                });
577
578                if ui.button("Clear All P1").clicked() {
579                    mapping_config
580                        .p1
581                        .clone_from_type(&GenesisControllerMapping::default(), p1_type);
582                    mapping_config
583                        .p1_turbo
584                        .clone_from_type(&GenesisControllerMapping::default(), p1_type);
585                }
586
587                if ui.button("Clear All P2").clicked() {
588                    mapping_config
589                        .p2
590                        .clone_from_type(&GenesisControllerMapping::default(), p2_type);
591                    mapping_config
592                        .p2_turbo
593                        .clone_from_type(&GenesisControllerMapping::default(), p2_type);
594                }
595            });
596        });
597        if !open {
598            self.state.open_windows.remove(&OpenWindow::GenesisInput);
599        }
600    }
601
602    pub(super) fn render_nes_input_settings(&mut self, ctx: &Context) {
603        static P1_BUTTONS: LazyLock<Vec<GenericButton>> = LazyLock::new(|| {
604            NesButton::ALL
605                .into_iter()
606                .filter_map(|button| {
607                    (!button.is_zapper()).then_some(GenericButton::Nes(button, Player::One))
608                })
609                .collect()
610        });
611        static P2_BUTTONS: LazyLock<Vec<GenericButton>> = LazyLock::new(|| {
612            NesButton::ALL
613                .into_iter()
614                .filter_map(|button| {
615                    (!button.is_zapper()).then_some(GenericButton::Nes(button, Player::Two))
616                })
617                .collect()
618        });
619
620        let mut open = true;
621        Window::new(OpenWindow::NesInput.title()).open(&mut open).show(ctx, |ui| {
622            self.disable_if_waiting_for_input(ui);
623
624            let rect = ui
625                .checkbox(
626                    &mut self.config.nes.allow_opposing_joypad_directions,
627                    ALLOW_OPPOSING_DIRECTIONS_LABEL,
628                )
629                .interact_rect;
630            if ui.rect_contains_pointer(rect) {
631                self.state
632                    .help_text
633                    .insert(OpenWindow::NesInput, helptext::NES_OPPOSING_JOYPAD_DIRECTIONS);
634            }
635            ui.separator();
636
637            let mapping = self.render_mapping_set_selector(OpenWindow::NesInput, ui);
638            ui.separator();
639
640            Grid::new("nes_inputs").spacing([50.0, 5.0]).show(ui, |ui| {
641                ui.heading("Player 1");
642                ui.heading("Player 2");
643                ui.end_row();
644
645                self.render_input_buttons("nes_p1_inputs", mapping, &P1_BUTTONS, ui);
646                self.render_input_buttons("nes_p2_inputs", mapping, &P2_BUTTONS, ui);
647                ui.end_row();
648
649                self.render_configure_all_button(&P1_BUTTONS, mapping, ui);
650                self.render_configure_all_button(&P2_BUTTONS, mapping, ui);
651                ui.end_row();
652            });
653
654            ui.add_space(15.0);
655
656            let mapping_config = mapping.nes(&mut self.config.input);
657            ui.horizontal(|ui| {
658                ComboBox::new("nes_presets", "").selected_text("Apply preset...").show_ui(
659                    ui,
660                    |ui| {
661                        if ui.selectable_label(false, "Keyboard - Arrow movement").clicked() {
662                            mapping_config.p1 = NesControllerMapping::keyboard_arrows();
663                            mapping_config.p1_turbo = NesControllerMapping::default();
664                        }
665
666                        if ui.selectable_label(false, "Keyboard - WASD movement").clicked() {
667                            mapping_config.p1 = NesControllerMapping::keyboard_wasd();
668                            mapping_config.p1_turbo = NesControllerMapping::default();
669                        }
670                    },
671                );
672
673                if ui.button("Clear All P1").clicked() {
674                    mapping_config.p1 = NesControllerMapping::default();
675                    mapping_config.p1_turbo = NesControllerMapping::default();
676                }
677
678                if ui.button("Clear All P2").clicked() {
679                    mapping_config.p2 = NesControllerMapping::default();
680                    mapping_config.p2_turbo = NesControllerMapping::default();
681                }
682            });
683
684            self.render_help_text(ui, OpenWindow::NesInput);
685        });
686        if !open {
687            self.state.open_windows.remove(&OpenWindow::NesInput);
688        }
689    }
690
691    pub(super) fn render_nes_peripheral_settings(&mut self, ctx: &Context) {
692        static ZAPPER_BUTTONS: LazyLock<Vec<GenericButton>> = LazyLock::new(|| {
693            NesButton::ALL
694                .into_iter()
695                .filter_map(|button| {
696                    button.is_zapper().then_some(GenericButton::Nes(button, Player::One))
697                })
698                .collect()
699        });
700
701        let mut open = true;
702        Window::new(OpenWindow::NesPeripherals.title()).open(&mut open).show(ctx, |ui| {
703            self.disable_if_waiting_for_input(ui);
704
705            ui.group(|ui| {
706                ui.label("Player 2 device");
707
708                ui.horizontal(|ui| {
709                    ui.radio_value(
710                        &mut self.config.input.nes.p2_type,
711                        NesControllerType::Gamepad,
712                        "Gamepad",
713                    );
714                    ui.radio_value(
715                        &mut self.config.input.nes.p2_type,
716                        NesControllerType::Zapper,
717                        "Zapper",
718                    );
719                });
720            });
721
722            ui.separator();
723            let mapping = self.render_mapping_set_selector(OpenWindow::NesPeripherals, ui);
724            ui.separator();
725
726            ui.heading("Zapper");
727
728            ui.add_space(5.0);
729
730            self.render_input_buttons("nes_zapper_inputs", mapping, &ZAPPER_BUTTONS, ui);
731
732            ui.add_space(15.0);
733
734            let mapping_config = mapping.nes(&mut self.config.input);
735            ui.horizontal(|ui| {
736                if ui.button("Restore Defaults").clicked() {
737                    mapping_config.zapper = NesZapperMapping::mouse();
738                }
739
740                if ui.button("Clear All").clicked() {
741                    mapping_config.zapper = NesZapperMapping::default();
742                }
743            });
744        });
745        if !open {
746            self.state.open_windows.remove(&OpenWindow::NesPeripherals);
747        }
748    }
749
750    pub(super) fn render_snes_input_settings(&mut self, ctx: &Context) {
751        fn snes_buttons(player: Player, filter: fn(SnesButton) -> bool) -> Vec<GenericButton> {
752            SnesButton::ALL
753                .into_iter()
754                .filter_map(|button| filter(button).then_some(GenericButton::Snes(button, player)))
755                .collect()
756        }
757
758        static P1_GAMEPAD_BUTTONS: LazyLock<Vec<GenericButton>> =
759            LazyLock::new(|| snes_buttons(Player::One, SnesButton::is_gamepad));
760        static P2_GAMEPAD_BUTTONS: LazyLock<Vec<GenericButton>> =
761            LazyLock::new(|| snes_buttons(Player::Two, SnesButton::is_gamepad));
762        static P1_MOUSE_BUTTONS: LazyLock<Vec<GenericButton>> =
763            LazyLock::new(|| snes_buttons(Player::One, SnesButton::is_mouse));
764        static P2_MOUSE_BUTTONS: LazyLock<Vec<GenericButton>> =
765            LazyLock::new(|| snes_buttons(Player::Two, SnesButton::is_mouse));
766        static P1_SUPER_SCOPE_BUTTONS: LazyLock<Vec<GenericButton>> =
767            LazyLock::new(|| snes_buttons(Player::One, SnesButton::is_super_scope));
768        static P2_SUPER_SCOPE_BUTTONS: LazyLock<Vec<GenericButton>> =
769            LazyLock::new(|| snes_buttons(Player::Two, SnesButton::is_super_scope));
770
771        let mut open = true;
772        Window::new(OpenWindow::SnesInput.title()).open(&mut open).show(ctx, |ui| {
773            self.disable_if_waiting_for_input(ui);
774
775            ui.horizontal(|ui| {
776                for (heading, field) in [
777                    ("Player 1 controller type", &mut self.config.input.snes.p1_type),
778                    ("Player 2 controller type", &mut self.config.input.snes.p2_type),
779                ] {
780                    ui.group(|ui| {
781                        ui.vertical(|ui| {
782                            ui.label(heading);
783
784                            ui.horizontal(|ui| {
785                                for (label, value) in [
786                                    ("Gamepad", SnesControllerType::Gamepad),
787                                    ("Mouse", SnesControllerType::Mouse),
788                                    ("Super Scope", SnesControllerType::SuperScope),
789                                    ("None", SnesControllerType::None),
790                                ] {
791                                    ui.radio_value(field, value, label);
792                                }
793                            });
794                        });
795                    });
796                }
797            });
798
799            if self.config.input.snes.p1_type == SnesControllerType::SuperScope {
800                ui.colored_label(
801                    Color32::RED,
802                    "Super Scope only works properly in controller port 2",
803                );
804            }
805
806            ui.add_space(3.0);
807
808            ui.checkbox(
809                &mut self.config.snes.allow_opposing_joypad_directions,
810                ALLOW_OPPOSING_DIRECTIONS_LABEL,
811            );
812
813            ui.add_space(3.0);
814
815            ui.horizontal(|ui| {
816                ui.label("Mouse sensitivity:");
817                ui.add(Slider::new(&mut self.config.snes.mouse_sensitivity, 0.0..=10.0));
818
819                if ui.button("Default").clicked() {
820                    self.config.snes.mouse_sensitivity = SnesAppConfig::default().mouse_sensitivity;
821                }
822            });
823
824            ui.separator();
825
826            let mapping = self.render_mapping_set_selector(OpenWindow::SnesInput, ui);
827            ui.separator();
828
829            let (p1_heading, p1_buttons) = match self.config.input.snes.p1_type {
830                SnesControllerType::Gamepad | SnesControllerType::None => {
831                    ("Player 1 - Gamepad", &P1_GAMEPAD_BUTTONS)
832                }
833                SnesControllerType::Mouse => ("Player 1 - Mouse", &P1_MOUSE_BUTTONS),
834                SnesControllerType::SuperScope => {
835                    ("Player 1 - Super Scope", &P1_SUPER_SCOPE_BUTTONS)
836                }
837            };
838            let (p2_heading, p2_buttons) = match self.config.input.snes.p2_type {
839                SnesControllerType::Gamepad | SnesControllerType::None => {
840                    ("Player 2 - Gamepad", &P2_GAMEPAD_BUTTONS)
841                }
842                SnesControllerType::Mouse => ("Player 2 - Mouse", &P2_MOUSE_BUTTONS),
843                SnesControllerType::SuperScope => {
844                    ("Player 2 - Super Scope", &P2_SUPER_SCOPE_BUTTONS)
845                }
846            };
847
848            Grid::new("snes_inputs").spacing([50.0, 5.0]).show(ui, |ui| {
849                ui.heading(p1_heading);
850                ui.heading(p2_heading);
851                ui.end_row();
852
853                self.render_input_buttons("snes_p1_inputs", mapping, p1_buttons, ui);
854                self.render_input_buttons("snes_p2_inputs", mapping, p2_buttons, ui);
855                ui.end_row();
856
857                self.render_configure_all_button(p1_buttons, mapping, ui);
858                self.render_configure_all_button(p2_buttons, mapping, ui);
859                ui.end_row();
860            });
861
862            ui.add_space(15.0);
863
864            let mapping_config = mapping.snes(&mut self.config.input);
865            ui.horizontal(|ui| {
866                ComboBox::new("snes_presets", "").selected_text("Apply preset...").show_ui(
867                    ui,
868                    |ui| {
869                        if ui.selectable_label(false, "Keyboard - Arrow movement").clicked() {
870                            mapping_config.p1 = SnesControllerMapping::keyboard_arrows();
871                            mapping_config.p1_turbo = SnesControllerMapping::default();
872                        }
873
874                        if ui.selectable_label(false, "Keyboard - WASD movement").clicked() {
875                            mapping_config.p1 = SnesControllerMapping::keyboard_wasd();
876                            mapping_config.p1_turbo = SnesControllerMapping::default();
877                        }
878                    },
879                );
880
881                if ui.button("Clear All P1").clicked() {
882                    mapping_config.p1 = SnesControllerMapping::default();
883                    mapping_config.p1_turbo = SnesControllerMapping::default();
884                }
885
886                if ui.button("Clear All P2").clicked() {
887                    mapping_config.p2 = SnesControllerMapping::default();
888                    mapping_config.p2_turbo = SnesControllerMapping::default();
889                }
890            });
891        });
892        if !open {
893            self.state.open_windows.remove(&OpenWindow::SnesInput);
894        }
895    }
896
897    pub(super) fn render_gb_input_settings(&mut self, ctx: &Context) {
898        static BUTTONS: LazyLock<Vec<GenericButton>> =
899            LazyLock::new(|| GameBoyButton::ALL.into_iter().map(GenericButton::GameBoy).collect());
900
901        let mut open = true;
902        Window::new(OpenWindow::GameBoyInput.title()).open(&mut open).show(ctx, |ui| {
903            self.disable_if_waiting_for_input(ui);
904
905            ui.checkbox(
906                &mut self.config.game_boy.allow_opposing_joypad_directions,
907                ALLOW_OPPOSING_DIRECTIONS_LABEL,
908            );
909            ui.separator();
910
911            let mapping = self.render_mapping_set_selector(OpenWindow::GameBoyInput, ui);
912            ui.separator();
913
914            self.render_input_buttons("gb_inputs", mapping, &BUTTONS, ui);
915
916            self.render_configure_all_button(&BUTTONS, mapping, ui);
917
918            ui.add_space(15.0);
919
920            ui.horizontal(|ui| {
921                ComboBox::new("gb_presets", "").selected_text("Apply preset...").show_ui(
922                    ui,
923                    |ui| {
924                        if ui.selectable_label(false, "Keyboard - Arrow movement").clicked() {
925                            *mapping.gb(&mut self.config.input, false) =
926                                GameBoyInputMapping::keyboard_arrows();
927                            *mapping.gb(&mut self.config.input, true) =
928                                GameBoyInputMapping::default();
929                        }
930
931                        if ui.selectable_label(false, "Keyboard - WASD movement").clicked() {
932                            *mapping.gb(&mut self.config.input, false) =
933                                GameBoyInputMapping::keyboard_wasd();
934                            *mapping.gb(&mut self.config.input, true) =
935                                GameBoyInputMapping::default();
936                        }
937                    },
938                );
939
940                if ui.button("Clear All").clicked() {
941                    *mapping.gb(&mut self.config.input, false) = GameBoyInputMapping::default();
942                    *mapping.gb(&mut self.config.input, true) = GameBoyInputMapping::default();
943                }
944            });
945        });
946        if !open {
947            self.state.open_windows.remove(&OpenWindow::GameBoyInput);
948        }
949    }
950
951    pub(super) fn render_gba_input_settings(&mut self, ctx: &Context) {
952        static BUTTONS: LazyLock<Vec<GenericButton>> = LazyLock::new(|| {
953            GbaButton::ALL
954                .into_iter()
955                .filter_map(|button| button.is_joypad().then_some(GenericButton::Gba(button)))
956                .collect()
957        });
958
959        let mut open = true;
960        Window::new(OpenWindow::GbaInput.title()).open(&mut open).show(ctx, |ui| {
961            self.disable_if_waiting_for_input(ui);
962
963            ui.checkbox(
964                &mut self.config.game_boy_advance.allow_opposing_joypad_directions,
965                ALLOW_OPPOSING_DIRECTIONS_LABEL,
966            );
967            ui.separator();
968
969            let mapping = self.render_mapping_set_selector(OpenWindow::GbaInput, ui);
970            ui.separator();
971
972            self.render_input_buttons("gba_inputs", mapping, &BUTTONS, ui);
973
974            self.render_configure_all_button(&BUTTONS, mapping, ui);
975
976            ui.add_space(15.0);
977
978            ui.horizontal(|ui| {
979                ComboBox::new("gba_presets", "").selected_text("Apply preset...").show_ui(
980                    ui,
981                    |ui| {
982                        if ui.selectable_label(false, "Keyboard - Arrow movement").clicked() {
983                            mapping.gba(&mut self.config.input, false).joypad =
984                                GbaJoypadMapping::keyboard_arrows();
985                            mapping.gba(&mut self.config.input, true).joypad =
986                                GbaJoypadMapping::default();
987                        }
988
989                        if ui.selectable_label(false, "Keyboard - WASD movement").clicked() {
990                            mapping.gba(&mut self.config.input, false).joypad =
991                                GbaJoypadMapping::keyboard_wasd();
992                            mapping.gba(&mut self.config.input, true).joypad =
993                                GbaJoypadMapping::default();
994                        }
995                    },
996                );
997
998                if ui.button("Clear All").clicked() {
999                    mapping.gba(&mut self.config.input, false).joypad = GbaJoypadMapping::default();
1000                    mapping.gba(&mut self.config.input, true).joypad = GbaJoypadMapping::default();
1001                }
1002            });
1003        });
1004        if !open {
1005            self.state.open_windows.remove(&OpenWindow::GbaInput);
1006        }
1007    }
1008
1009    pub(super) fn render_gba_peripheral_settings(&mut self, ctx: &Context) {
1010        const WINDOW: OpenWindow = OpenWindow::GbaPeripherals;
1011
1012        static SOLAR_BUTTONS: LazyLock<Vec<GenericButton>> = LazyLock::new(|| {
1013            GbaButton::ALL
1014                .into_iter()
1015                .filter_map(|button| button.is_solar_sensor().then_some(GenericButton::Gba(button)))
1016                .collect()
1017        });
1018
1019        let mut open = true;
1020        Window::new(OpenWindow::GbaPeripherals.title()).open(&mut open).show(ctx, |ui| {
1021            self.disable_if_waiting_for_input(ui);
1022
1023            let mapping = self.render_mapping_set_selector(WINDOW, ui);
1024            ui.separator();
1025
1026            ui.heading("Solar Sensor");
1027
1028            self.render_input_buttons("solar_sensor_inputs", mapping, &SOLAR_BUTTONS, ui);
1029
1030            ui.add_space(15.0);
1031
1032            let mapping_config = mapping.gba(&mut self.config.input, false);
1033            if ui.button("Clear All").clicked() {
1034                mapping_config.solar = GbaSolarMapping::default();
1035            }
1036
1037            ui.add_space(15.0);
1038
1039            let prev_slider_width = mem::replace(&mut ui.style_mut().spacing.slider_width, 200.0);
1040
1041            Grid::new("gba_solar_grid").show(ui, |ui| {
1042                ui.label("Brightness step");
1043
1044                ui.add(Slider::new(
1045                    &mut self.config.game_boy_advance.solar_brightness_step,
1046                    1..=255,
1047                ));
1048
1049                if ui.button("Default").clicked() {
1050                    self.config.game_boy_advance.solar_brightness_step =
1051                        gba_config::DEFAULT_SOLAR_BRIGHTNESS_STEP;
1052                }
1053
1054                ui.end_row();
1055
1056                ui.label("Minimum brightness");
1057
1058                ui.add(Slider::new(
1059                    &mut self.config.game_boy_advance.solar_min_brightness,
1060                    0..=255,
1061                ));
1062
1063                if ui.button("Default").clicked() {
1064                    self.config.game_boy_advance.solar_min_brightness =
1065                        gba_config::DEFAULT_SOLAR_MIN_BRIGHTNESS;
1066                }
1067
1068                ui.end_row();
1069
1070                ui.label("Maximum brightness");
1071
1072                ui.add(Slider::new(
1073                    &mut self.config.game_boy_advance.solar_max_brightness,
1074                    0..=255,
1075                ));
1076
1077                if ui.button("Default").clicked() {
1078                    self.config.game_boy_advance.solar_max_brightness =
1079                        gba_config::DEFAULT_SOLAR_MAX_BRIGHTNESS;
1080                }
1081
1082                ui.end_row();
1083            });
1084
1085            ui.style_mut().spacing.slider_width = prev_slider_width;
1086        });
1087
1088        if !open {
1089            self.state.open_windows.remove(&OpenWindow::GbaPeripherals);
1090        }
1091    }
1092
1093    pub(super) fn render_pce_input_settings(&mut self, ctx: &Context) {
1094        let mut open = true;
1095        Window::new(OpenWindow::PceInput.title()).open(&mut open).show(ctx, |ui| {
1096            self.disable_if_waiting_for_input(ui);
1097
1098            ui.group(|ui| {
1099                ui.label("Controller port");
1100
1101                ui.horizontal(|ui| {
1102                    for (value, label) in [
1103                        (PceInputDevice::TwoButtonGamepad, "Gamepad"),
1104                        (PceInputDevice::TurboTap, "Turbo Tap"),
1105                    ] {
1106                        ui.radio_value(&mut self.config.pc_engine.input_device, value, label);
1107                    }
1108                });
1109
1110                ui.horizontal(|ui| {
1111                    ui.add_enabled_ui(
1112                        self.config.pc_engine.input_device == PceInputDevice::TurboTap,
1113                        |ui| {
1114                            ui.label("Turbo Tap gamepads:");
1115
1116                            for (idx, connected) in
1117                                self.config.pc_engine.turbo_tap_connected.iter_mut().enumerate()
1118                            {
1119                                ui.checkbox(connected, (idx + 1).to_string());
1120                            }
1121                        },
1122                    );
1123                });
1124            });
1125
1126            let rect = ui
1127                .checkbox(
1128                    &mut self.config.pc_engine.allow_opposing_joypad_directions,
1129                    ALLOW_OPPOSING_DIRECTIONS_LABEL,
1130                )
1131                .interact_rect;
1132            if ui.rect_contains_pointer(rect) {
1133                self.state
1134                    .help_text
1135                    .insert(OpenWindow::PceInput, helptext::OPPOSING_JOYPAD_DIRECTIONS);
1136            }
1137
1138            let rect = ui
1139                .checkbox(
1140                    &mut self.config.pc_engine.allow_simultaneous_run_select,
1141                    "Allow pressing Run+Select simultaneously",
1142                )
1143                .interact_rect;
1144            if ui.rect_contains_pointer(rect) {
1145                self.state
1146                    .help_text
1147                    .insert(OpenWindow::PceInput, helptext::PCE_SIMULTANEOUS_RUN_SELECT);
1148            }
1149
1150            ui.separator();
1151
1152            let mapping = self.render_mapping_set_selector(OpenWindow::PceInput, ui);
1153            ui.separator();
1154
1155            let using_turbo_tap = self.config.pc_engine.input_device == PceInputDevice::TurboTap;
1156            if !using_turbo_tap {
1157                self.state.pce_selected_player_idx = 0;
1158            }
1159
1160            ui.add_enabled_ui(using_turbo_tap, |ui| {
1161                ComboBox::new("pce_player_combo", "").show_index(
1162                    ui,
1163                    &mut self.state.pce_selected_player_idx,
1164                    5,
1165                    |player_idx| format!("Player {}", player_idx + 1),
1166                );
1167            });
1168            ui.add_space(5.0);
1169
1170            let player = [Player::One, Player::Two, Player::Three, Player::Four, Player::Five]
1171                [self.state.pce_selected_player_idx];
1172
1173            Grid::new("pce_inputs").spacing([50.0, 5.0]).show(ui, |ui| {
1174                let buttons = PceButton::ALL
1175                    .into_iter()
1176                    .map(|button| GenericButton::Pce(button, player))
1177                    .collect::<Vec<_>>();
1178
1179                self.render_input_buttons("pce_p1_inputs", mapping, &buttons, ui);
1180                ui.end_row();
1181
1182                self.render_configure_all_button(&buttons, mapping, ui);
1183                ui.end_row();
1184            });
1185
1186            ui.add_space(15.0);
1187
1188            let mapping_config = mapping.pce(&mut self.config.input);
1189            let (mapping, turbo_mapping) = match player {
1190                Player::One => (&mut mapping_config.p1, &mut mapping_config.p1_turbo),
1191                Player::Two => (&mut mapping_config.p2, &mut mapping_config.p2_turbo),
1192                Player::Three => (&mut mapping_config.p3, &mut mapping_config.p3_turbo),
1193                Player::Four => (&mut mapping_config.p4, &mut mapping_config.p4_turbo),
1194                Player::Five => (&mut mapping_config.p5, &mut mapping_config.p5_turbo),
1195                _ => unreachable!("player is always 1-5"),
1196            };
1197
1198            ui.horizontal(|ui| {
1199                ComboBox::new("pce_presets", "").selected_text("Apply preset...").show_ui(
1200                    ui,
1201                    |ui| {
1202                        if ui.selectable_label(false, "Keyboard - Arrow movement").clicked() {
1203                            *mapping = PceJoypadMapping::keyboard_arrows();
1204                            *turbo_mapping = PceJoypadMapping::default();
1205                        }
1206
1207                        if ui.selectable_label(false, "Keyboard - WASD movement").clicked() {
1208                            *mapping = PceJoypadMapping::keyboard_wasd();
1209                            *turbo_mapping = PceJoypadMapping::default();
1210                        }
1211                    },
1212                );
1213
1214                if ui.button("Clear All").clicked() {
1215                    *mapping = PceJoypadMapping::default();
1216                    *turbo_mapping = PceJoypadMapping::default();
1217                }
1218            });
1219
1220            self.render_help_text(ui, OpenWindow::PceInput);
1221        });
1222        if !open {
1223            self.state.open_windows.remove(&OpenWindow::PceInput);
1224        }
1225    }
1226
1227    pub(super) fn render_hotkey_settings(&mut self, ctx: &Context) {
1228        static GENERAL_HOTKEYS: LazyLock<Vec<GenericButton>> =
1229            LazyLock::new(|| hotkey_vec(HotkeyCategory::General));
1230        static STATE_HOTKEYS: LazyLock<Vec<GenericButton>> =
1231            LazyLock::new(|| hotkey_vec(HotkeyCategory::SaveState));
1232
1233        let mut open = true;
1234        Window::new(OpenWindow::Hotkeys.title()).open(&mut open).show(ctx, |ui| {
1235            self.disable_if_waiting_for_input(ui);
1236
1237            let mapping = self.render_mapping_set_selector(OpenWindow::Hotkeys, ui);
1238            ui.separator();
1239
1240            ScrollArea::vertical()
1241                .auto_shrink([false, true])
1242                .max_height(ctx.content_rect().height() * 0.5)
1243                .show(ui, |ui| {
1244                    ui.heading("General");
1245                    self.render_input_buttons("general_hotkeys", mapping, &GENERAL_HOTKEYS, ui);
1246
1247                    ui.separator();
1248
1249                    ui.heading("Save States");
1250                    self.render_input_buttons("state_hotkeys", mapping, &STATE_HOTKEYS, ui);
1251                });
1252
1253            ui.add_space(15.0);
1254
1255            let mapping_config = mapping.hotkey(&mut self.config.input);
1256            ui.horizontal(|ui| {
1257                if ui.button("Restore Defaults").clicked() {
1258                    *mapping_config = HotkeyMapping::default_keyboard();
1259                }
1260
1261                if ui.button("Clear All").clicked() {
1262                    *mapping_config = HotkeyMapping::default();
1263                }
1264            });
1265
1266            ui.separator();
1267
1268            ui.horizontal(|ui| {
1269                ui.add(
1270                    NumericTextEdit::new(
1271                        &mut self.state.ff_multiplier_text,
1272                        &mut self.config.common.fast_forward_multiplier,
1273                        &mut self.state.ff_multiplier_invalid,
1274                    )
1275                    .with_validation(|value| value != 0)
1276                    .desired_width(30.0),
1277                );
1278
1279                ui.label("Fast forward multiplier");
1280            });
1281            if self.state.ff_multiplier_invalid {
1282                ui.colored_label(
1283                    Color32::RED,
1284                    "Fast forward multiplier must be a positive integer",
1285                );
1286            }
1287
1288            ui.horizontal(|ui| {
1289                ui.add(
1290                    NumericTextEdit::new(
1291                        &mut self.state.rewind_buffer_len_text,
1292                        &mut self.config.common.rewind_buffer_length_seconds,
1293                        &mut self.state.rewind_buffer_len_invalid,
1294                    )
1295                    .desired_width(30.0),
1296                );
1297
1298                ui.label("Rewind buffer length in seconds");
1299            });
1300            if self.state.rewind_buffer_len_invalid {
1301                ui.colored_label(
1302                    Color32::RED,
1303                    "Rewind buffer length must be a non-negative integer",
1304                );
1305            }
1306
1307            ui.checkbox(
1308                &mut self.config.common.load_recent_state_at_launch,
1309                "Load most recent save state at launch",
1310            );
1311        });
1312        if !open {
1313            self.state.open_windows.remove(&OpenWindow::Hotkeys);
1314        }
1315    }
1316
1317    fn disable_if_waiting_for_input(&self, ui: &mut Ui) {
1318        if self.state.input_collection.is_some() {
1319            ui.disable();
1320        }
1321    }
1322
1323    fn render_input_buttons(
1324        &mut self,
1325        id: &str,
1326        mapping: InputMappingSet,
1327        buttons: &[GenericButton],
1328        ui: &mut Ui,
1329    ) {
1330        Grid::new(id).show(ui, |ui| {
1331            for button in buttons {
1332                ui.label(format!("{}:", button.label()));
1333
1334                let Some(current_value) = button.access_value(mapping, &mut self.config.input)
1335                else {
1336                    continue;
1337                };
1338
1339                let current_value_str = format_input_str(current_value.as_ref());
1340                if ui.button(current_value_str).clicked() {
1341                    self.state.input_collection = Some(InputCollectionState::new(
1342                        &self.joysticks.borrow(),
1343                        vec![*button],
1344                        mapping,
1345                        false,
1346                    ));
1347                }
1348
1349                if ui.button("Clear").clicked() {
1350                    *current_value = None;
1351                }
1352
1353                if let Some(turbo_value) =
1354                    button.access_value_turbo(mapping, &mut self.config.input)
1355                {
1356                    ui.label("Turbo");
1357
1358                    let turbo_value_str = format_input_str(turbo_value.as_ref());
1359                    if ui.button(turbo_value_str).clicked() {
1360                        self.state.input_collection = Some(InputCollectionState::new(
1361                            &self.joysticks.borrow(),
1362                            vec![*button],
1363                            mapping,
1364                            true,
1365                        ));
1366                    }
1367
1368                    if ui.button("Clear").clicked() {
1369                        *turbo_value = None;
1370                    }
1371                }
1372
1373                ui.end_row();
1374            }
1375        });
1376    }
1377
1378    fn render_mapping_set_selector(&mut self, window: OpenWindow, ui: &mut Ui) -> InputMappingSet {
1379        let field = self.state.input_mapping_sets.entry(window).or_default();
1380
1381        ui.horizontal(|ui| {
1382            for set in [InputMappingSet::One, InputMappingSet::Two] {
1383                let button = Button::new(set.label()).selected(*field == set);
1384                if ui.add(button).clicked() {
1385                    *field = set;
1386                }
1387            }
1388        });
1389
1390        *field
1391    }
1392
1393    fn render_configure_all_button(
1394        &mut self,
1395        buttons: &[GenericButton],
1396        mapping: InputMappingSet,
1397        ui: &mut Ui,
1398    ) {
1399        if ui.button("Configure all").clicked() {
1400            self.state.input_collection = Some(InputCollectionState::new(
1401                &self.joysticks.borrow(),
1402                buttons.to_vec(),
1403                mapping,
1404                false,
1405            ));
1406        }
1407    }
1408}
1409
1410fn format_input_str(value: Option<&Vec<GenericInput>>) -> String {
1411    let none = || "<None>".into();
1412
1413    let Some(value) = value else {
1414        return none();
1415    };
1416
1417    if value.is_empty() {
1418        return none();
1419    }
1420
1421    let s: Vec<_> = value.iter().map(|&input| input.to_string()).collect();
1422
1423    s.join(" + ")
1424}
1425
1426#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1427enum HotkeyCategory {
1428    General,
1429    SaveState,
1430}
1431
1432trait HotkeyExt {
1433    fn category(self) -> HotkeyCategory;
1434}
1435
1436impl HotkeyExt for Hotkey {
1437    fn category(self) -> HotkeyCategory {
1438        use Hotkey::*;
1439
1440        match self {
1441            PowerOff | Exit | ToggleFullscreen | SoftReset | HardReset | Pause | StepFrame
1442            | FastForward | Rewind | CancelMouseInput | ToggleOverclocking | OpenDebugger => {
1443                HotkeyCategory::General
1444            }
1445            SaveState | LoadState | NextSaveStateSlot | PrevSaveStateSlot | SaveStateSlot0
1446            | SaveStateSlot1 | SaveStateSlot2 | SaveStateSlot3 | SaveStateSlot4
1447            | SaveStateSlot5 | SaveStateSlot6 | SaveStateSlot7 | SaveStateSlot8
1448            | SaveStateSlot9 | LoadStateSlot0 | LoadStateSlot1 | LoadStateSlot2
1449            | LoadStateSlot3 | LoadStateSlot4 | LoadStateSlot5 | LoadStateSlot6
1450            | LoadStateSlot7 | LoadStateSlot8 | LoadStateSlot9 => HotkeyCategory::SaveState,
1451        }
1452    }
1453}
1454
1455fn hotkey_vec(category: HotkeyCategory) -> Vec<GenericButton> {
1456    Hotkey::ALL
1457        .into_iter()
1458        .filter_map(|hotkey| {
1459            (hotkey.category() == category).then_some(GenericButton::Hotkey(hotkey))
1460        })
1461        .collect()
1462}