gb.rsannotatedgb.rssource366 lines · 13.5 KB · raw
1mod helptext;
2
3use crate::app::widgets::{BiosErrorStrings, OptionalPathSelector, RenderErrorEffect};
4use crate::app::{App, OpenWindow, widgets};
5use egui::{Context, Slider, Ui, Window};
6use gb_config::{GbAspectRatio, GbAudioResampler, GbPalette, GbcColorCorrection};
7use jgenesis_native_config::gb::GameBoyAppConfig;
8use jgenesis_native_driver::extensions::Console;
9use rfd::FileDialog;
10use std::path::PathBuf;
11
12impl App {
13    pub(super) fn render_gb_general_settings(&mut self, ctx: &Context) {
14        const WINDOW: OpenWindow = OpenWindow::GameBoyGeneral;
15
16        let mut open = true;
17        Window::new(WINDOW.title()).default_width(400.0).open(&mut open).resizable(false).show(
18            ctx,
19            |ui| {
20                let rect = ui
21                    .checkbox(
22                        &mut self.config.game_boy.pretend_to_be_gba,
23                        "Pretend to be a Game Boy Advance",
24                    )
25                    .interact_rect;
26                if ui.rect_contains_pointer(rect) {
27                    self.state.help_text.insert(WINDOW, helptext::PRETEND_GBA_MODE);
28                }
29
30                ui.add_space(5.0);
31
32                let rect = ui
33                    .checkbox(
34                        &mut self.config.game_boy.force_dmg_mode,
35                        "Force Game Boy mode in software with Game Boy Color support",
36                    )
37                    .interact_rect;
38                if ui.rect_contains_pointer(rect) {
39                    self.state.help_text.insert(WINDOW, helptext::FORCE_DMG_MODE);
40                }
41
42                let rect = ui
43                    .checkbox(
44                        &mut self.config.game_boy.force_cgb_mode,
45                        "Force Game Boy Color mode in Game Boy-only software (requires GBC boot ROM)",
46                    )
47                    .interact_rect;
48                if ui.rect_contains_pointer(rect) {
49                    self.state.help_text.insert(WINDOW, helptext::FORCE_CGB_MODE);
50                }
51
52                ui.add_space(5.0);
53
54                let rect = ui
55                    .checkbox(
56                        &mut self.config.game_boy.dmg_boot_rom,
57                        "Boot from boot ROM in Game Boy mode",
58                    )
59                    .interact_rect;
60                if ui.rect_contains_pointer(rect) {
61                    self.state.help_text.insert(WINDOW, helptext::BOOT_ROM);
62                }
63
64                // CGB boot ROM is required for CGB's DMG compatibility mode
65                self.config.game_boy.cgb_boot_rom |= self.config.game_boy.force_cgb_mode;
66
67                ui.add_enabled_ui(!self.config.game_boy.force_cgb_mode, |ui| {
68                    let rect = ui
69                        .checkbox(
70                            &mut self.config.game_boy.cgb_boot_rom,
71                            "Boot from boot ROM in Game Boy Color mode",
72                        )
73                        .interact_rect;
74                    if ui.rect_contains_pointer(rect) {
75                        self.state.help_text.insert(WINDOW, helptext::BOOT_ROM);
76                    }
77                });
78
79                ui.add_space(5.0);
80
81                let rect = ui
82                    .add(OptionalPathSelector::new(
83                        "Game Boy boot ROM",
84                        &mut self.config.game_boy.dmg_boot_rom_path,
85                        || pick_boot_rom_path("gb"),
86                    ))
87                    .interact_rect;
88                if ui.rect_contains_pointer(rect) {
89                    self.state.help_text.insert(WINDOW, helptext::BOOT_ROM);
90                }
91
92                let rect = ui
93                    .add(OptionalPathSelector::new(
94                        "Game Boy Color boot ROM",
95                        &mut self.config.game_boy.cgb_boot_rom_path,
96                        || pick_boot_rom_path("gbc"),
97                    ))
98                    .interact_rect;
99                if ui.rect_contains_pointer(rect) {
100                    self.state.help_text.insert(WINDOW, helptext::BOOT_ROM);
101                }
102
103                self.render_help_text(ui, WINDOW);
104            },
105        );
106        if !open {
107            self.state.open_windows.remove(&WINDOW);
108        }
109    }
110
111    pub(super) fn render_gb_video_settings(&mut self, ctx: &Context) {
112        const WINDOW: OpenWindow = OpenWindow::GameBoyVideo;
113
114        let mut open = true;
115        Window::new(WINDOW.title()).open(&mut open).resizable(false).show(ctx, |ui| {
116            let rect = ui
117                .group(|ui| {
118                    ui.label("Aspect ratio");
119
120                    ui.horizontal(|ui| {
121                        ui.radio_value(
122                            &mut self.config.game_boy.aspect_ratio,
123                            GbAspectRatio::SquarePixels,
124                            "Square pixels",
125                        );
126                        ui.radio_value(
127                            &mut self.config.game_boy.aspect_ratio,
128                            GbAspectRatio::Stretched,
129                            "Stretched",
130                        );
131                    });
132                })
133                .response
134                .interact_rect;
135            if ui.rect_contains_pointer(rect) {
136                self.state.help_text.insert(WINDOW, helptext::ASPECT_RATIO);
137            }
138
139            ui.add_space(5.0);
140
141            let rect = ui
142                .checkbox(&mut self.config.game_boy.frame_blending, "Frame blending enabled")
143                .interact_rect;
144            if ui.rect_contains_pointer(rect) {
145                self.state.help_text.insert(WINDOW, helptext::FRAME_BLENDING);
146            }
147
148            ui.add_space(5.0);
149
150            let rect = ui
151                .group(|ui| {
152                    ui.label("GB color palette");
153
154                    ui.horizontal(|ui| {
155                        ui.radio_value(
156                            &mut self.config.game_boy.gb_palette,
157                            GbPalette::BlackAndWhite,
158                            "Black and white",
159                        );
160                        ui.radio_value(
161                            &mut self.config.game_boy.gb_palette,
162                            GbPalette::GreenTint,
163                            "Green tint",
164                        );
165                        ui.radio_value(
166                            &mut self.config.game_boy.gb_palette,
167                            GbPalette::LimeGreen,
168                            "Lime green",
169                        );
170                        ui.radio_value(
171                            &mut self.config.game_boy.gb_palette,
172                            GbPalette::Custom,
173                            "Custom",
174                        );
175                    });
176
177                    ui.add_enabled_ui(self.config.game_boy.gb_palette == GbPalette::Custom, |ui| {
178                        ui.label("Custom palette:");
179                        render_custom_palette_widget(
180                            &mut self.config.game_boy.gb_custom_palette,
181                            ui,
182                        );
183                    });
184                })
185                .response
186                .interact_rect;
187            if ui.rect_contains_pointer(rect) {
188                self.state.help_text.insert(WINDOW, helptext::GB_COLOR_PALETTE);
189            }
190
191            let rect = ui
192                .group(|ui| {
193                    ui.label("GBC color correction");
194
195                    ui.horizontal(|ui| {
196                        ui.radio_value(
197                            &mut self.config.game_boy.gbc_color_correction,
198                            GbcColorCorrection::None,
199                            "None",
200                        );
201                        ui.radio_value(
202                            &mut self.config.game_boy.gbc_color_correction,
203                            GbcColorCorrection::GbcLcd,
204                            "Game Boy Color LCD",
205                        );
206                        ui.radio_value(
207                            &mut self.config.game_boy.gbc_color_correction,
208                            GbcColorCorrection::GbaLcd,
209                            "Game Boy Advance LCD",
210                        );
211                    });
212
213                    ui.add_enabled_ui(
214                        self.config.game_boy.gbc_color_correction != GbcColorCorrection::None,
215                        |ui| {
216                            ui.horizontal(|ui| {
217                                ui.label("Console screen gamma");
218
219                                let (gamma, default) =
220                                    match self.config.game_boy.gbc_color_correction {
221                                        GbcColorCorrection::GbaLcd => (
222                                            &mut self.config.game_boy.gba_correction_gamma,
223                                            GameBoyAppConfig::default().gba_correction_gamma,
224                                        ),
225                                        _ => (
226                                            &mut self.config.game_boy.gbc_correction_gamma,
227                                            GameBoyAppConfig::default().gbc_correction_gamma,
228                                        ),
229                                    };
230
231                                ui.add(
232                                    Slider::new(gamma, 0.1..=5.0)
233                                        .custom_formatter(|value, _| format!("{value:.1}")),
234                                );
235
236                                if ui.button("Default").clicked() {
237                                    *gamma = default;
238                                }
239                            });
240                        },
241                    );
242                })
243                .response
244                .interact_rect;
245            if ui.rect_contains_pointer(rect) {
246                self.state.help_text.insert(WINDOW, helptext::GBC_COLOR_CORRECTION);
247            }
248
249            self.render_help_text(ui, WINDOW);
250        });
251        if !open {
252            self.state.open_windows.remove(&WINDOW);
253        }
254    }
255
256    pub(super) fn render_gb_audio_settings(&mut self, ctx: &Context) {
257        const WINDOW: OpenWindow = OpenWindow::GameBoyAudio;
258        let mut open = true;
259        Window::new(WINDOW.title()).open(&mut open).show(ctx, |ui| {
260            let rect = ui
261                .group(|ui| {
262                    ui.label("Audio resampling algorithm");
263
264                    ui.radio_value(
265                        &mut self.config.game_boy.audio_resampler,
266                        GbAudioResampler::LowPassNearestNeighbor,
267                        "Low-pass filter + nearest neighbor (Faster)",
268                    );
269                    ui.radio_value(
270                        &mut self.config.game_boy.audio_resampler,
271                        GbAudioResampler::WindowedSinc,
272                        "Windowed sinc interpolation (Higher quality)",
273                    );
274                })
275                .response
276                .interact_rect;
277            if ui.rect_contains_pointer(rect) {
278                self.state.help_text.insert(WINDOW, helptext::AUDIO_RESAMPLING);
279            }
280
281            ui.add_space(5.0);
282            let rect = ui
283                .checkbox(
284                    &mut self.config.game_boy.audio_60hz_hack,
285                    "Enable audio sync timing hack",
286                )
287                .interact_rect;
288            if ui.rect_contains_pointer(rect) {
289                self.state.help_text.insert(WINDOW, helptext::AUDIO_TIMING_HACK);
290            }
291
292            self.render_help_text(ui, WINDOW);
293        });
294        if !open {
295            self.state.open_windows.remove(&WINDOW);
296        }
297    }
298
299    #[must_use]
300    pub(super) fn render_dmg_boot_rom_error(
301        &mut self,
302        ctx: &Context,
303        open: &mut bool,
304    ) -> RenderErrorEffect {
305        widgets::render_bios_error(
306            ctx,
307            open,
308            BiosErrorStrings {
309                title: "Missing DMG Boot ROM",
310                text: "The boot from boot ROM option is set but no DMG boot ROM is configured.",
311                button_label: "Configure DMG boot ROM",
312            },
313            &mut self.config.game_boy.dmg_boot_rom_path,
314            Console::GameBoy,
315            || pick_boot_rom_path("gb"),
316        )
317    }
318
319    #[must_use]
320    pub(super) fn render_cgb_boot_rom_error(
321        &mut self,
322        ctx: &Context,
323        open: &mut bool,
324    ) -> RenderErrorEffect {
325        widgets::render_bios_error(
326            ctx,
327            open,
328            BiosErrorStrings {
329                title: "Missing CGB Boot ROM",
330                text: "The boot from boot ROM option is set but no CGB boot ROM is configured.",
331                button_label: "Configure CGB boot ROM",
332            },
333            &mut self.config.game_boy.cgb_boot_rom_path,
334            Console::GameBoyColor,
335            || pick_boot_rom_path("gbc"),
336        )
337    }
338}
339
340fn render_custom_palette_widget(custom_palette: &mut [(u8, u8, u8); 4], ui: &mut Ui) {
341    ui.horizontal(|ui| {
342        ui.add_space(30.0);
343        ui.label("Light");
344
345        for (r, g, b) in custom_palette.iter_mut() {
346            let mut rgb = [*r, *g, *b];
347            ui.color_edit_button_srgb(&mut rgb);
348            [*r, *g, *b] = rgb;
349        }
350
351        ui.label("Dark");
352    });
353
354    ui.add_space(5.0);
355
356    if ui.button("Default custom palette").clicked() {
357        *custom_palette = jgenesis_native_config::gb::default_gb_custom_palette();
358    }
359}
360
361fn pick_boot_rom_path(default_extension: &str) -> Option<PathBuf> {
362    FileDialog::new()
363        .add_filter("Boot ROM", &[default_extension, "bin"])
364        .add_filter("All Files", &["*"])
365        .pick_file()
366}