smsgg.rsannotatedsmsgg.rssource392 lines · 14.6 KB · raw
1mod helptext;
2
3use crate::app::widgets::{
4    BiosErrorStrings, ClockModifier, OptionalPathSelector, OverclockSlider, RenderErrorEffect,
5};
6use crate::app::{App, OpenWindow, widgets};
7use egui::{Context, Window};
8use jgenesis_common::frontend::TimingMode;
9use jgenesis_native_driver::extensions::Console;
10use rfd::FileDialog;
11use smsgg_config::{GgAspectRatio, SmsAspectRatio, SmsGgRegion, SmsModel};
12use std::num::NonZeroU32;
13use std::path::PathBuf;
14use ti_sn76489::Sn76489Version;
15
16impl App {
17    pub(super) fn render_smsgg_general_settings(&mut self, ctx: &Context) {
18        const WINDOW: OpenWindow = OpenWindow::SmsGgGeneral;
19
20        let mut open = true;
21        Window::new(WINDOW.title()).open(&mut open).resizable(false).show(ctx, |ui| {
22            let rect = ui
23                .group(|ui| {
24                    ui.label("Master System timing / display mode");
25
26                    ui.horizontal(|ui| {
27                        ui.radio_value(
28                            &mut self.config.smsgg.sms_timing_mode,
29                            TimingMode::Ntsc,
30                            "NTSC (60Hz)",
31                        );
32                        ui.radio_value(
33                            &mut self.config.smsgg.sms_timing_mode,
34                            TimingMode::Pal,
35                            "PAL (50Hz)",
36                        );
37                    });
38                })
39                .response
40                .interact_rect;
41            if ui.rect_contains_pointer(rect) {
42                self.state.help_text.insert(WINDOW, helptext::TIMING_MODE);
43            }
44
45            let rect = ui
46                .group(|ui| {
47                    ui.label("Master System VDP version");
48
49                    ui.horizontal(|ui| {
50                        ui.radio_value(&mut self.config.smsgg.sms_model, SmsModel::Sms1, "SMS1")
51                            .on_hover_text(concat!(
52                                "Emulates an SMS1 quirk that is",
53                                " required for the Japanese version of Ys"
54                            ));
55
56                        ui.radio_value(&mut self.config.smsgg.sms_model, SmsModel::Sms2, "SMS2");
57                    });
58                })
59                .response
60                .interact_rect;
61            if ui.rect_contains_pointer(rect) {
62                self.state.help_text.insert(WINDOW, helptext::VDP_VERSION);
63            }
64
65            let rect = ui
66                .group(|ui| {
67                    ui.label("Hardware region");
68
69                    ui.horizontal(|ui| {
70                        ui.radio_value(&mut self.config.smsgg.forced_region, None, "Auto");
71                        ui.radio_value(
72                            &mut self.config.smsgg.forced_region,
73                            Some(SmsGgRegion::International),
74                            "International / Overseas",
75                        );
76                        ui.radio_value(
77                            &mut self.config.smsgg.forced_region,
78                            Some(SmsGgRegion::Domestic),
79                            "Domestic (Japan)",
80                        );
81                    });
82                })
83                .response
84                .interact_rect;
85            if ui.rect_contains_pointer(rect) {
86                self.state.help_text.insert(WINDOW, helptext::REGION);
87            }
88
89            ui.add_space(5.0);
90            let rect = ui
91                .checkbox(&mut self.config.smsgg.sms_boot_from_bios, "(SMS) Boot from BIOS ROM")
92                .interact_rect;
93            if ui.rect_contains_pointer(rect) {
94                self.state.help_text.insert(WINDOW, helptext::BIOS);
95            }
96
97            let rect = ui
98                .checkbox(
99                    &mut self.config.smsgg.gg_boot_from_bios,
100                    "(Game Gear) Boot from BIOS ROM",
101                )
102                .interact_rect;
103            if ui.rect_contains_pointer(rect) {
104                self.state.help_text.insert(WINDOW, helptext::BIOS);
105            }
106
107            ui.add_space(5.0);
108            let rect = ui
109                .add(OptionalPathSelector::new(
110                    "SMS BIOS Path",
111                    &mut self.config.smsgg.sms_bios_path,
112                    || pick_bios_path("sms"),
113                ))
114                .interact_rect;
115            if ui.rect_contains_pointer(rect) {
116                self.state.help_text.insert(WINDOW, helptext::BIOS);
117            }
118
119            let rect = ui
120                .add(OptionalPathSelector::new(
121                    "Game Gear BIOS Path",
122                    &mut self.config.smsgg.gg_bios_path,
123                    || pick_bios_path("gg"),
124                ))
125                .interact_rect;
126            if ui.rect_contains_pointer(rect) {
127                self.state.help_text.insert(WINDOW, helptext::BIOS);
128            }
129
130            self.render_help_text(ui, WINDOW);
131        });
132        if !open {
133            self.state.open_windows.remove(&WINDOW);
134        }
135    }
136
137    pub(super) fn render_smsgg_video_settings(&mut self, ctx: &Context) {
138        const WINDOW: OpenWindow = OpenWindow::SmsGgVideo;
139
140        let mut open = true;
141        Window::new(WINDOW.title()).open(&mut open).resizable(false).show(ctx, |ui| {
142            let rect = ui
143                .group(|ui| {
144                    ui.label("Master System / SG-1000 aspect ratio");
145
146                    ui.horizontal(|ui| {
147                        ui.radio_value(
148                            &mut self.config.smsgg.sms_aspect_ratio,
149                            SmsAspectRatio::Ntsc,
150                            "NTSC",
151                        )
152                        .on_hover_text("8:7 pixel aspect ratio");
153                        ui.radio_value(
154                            &mut self.config.smsgg.sms_aspect_ratio,
155                            SmsAspectRatio::Pal,
156                            "PAL",
157                        )
158                        .on_hover_text("11:8 pixel aspect ratio");
159                        ui.radio_value(
160                            &mut self.config.smsgg.sms_aspect_ratio,
161                            SmsAspectRatio::SquarePixels,
162                            "Square pixels",
163                        )
164                        .on_hover_text("1:1 pixel aspect ratio");
165                        ui.radio_value(
166                            &mut self.config.smsgg.sms_aspect_ratio,
167                            SmsAspectRatio::Stretched,
168                            "Stretched",
169                        )
170                        .on_hover_text("Stretch image to fill the screen");
171                    });
172                })
173                .response
174                .interact_rect;
175            if ui.rect_contains_pointer(rect) {
176                self.state.help_text.insert(WINDOW, helptext::SMS_ASPECT_RATIO);
177            }
178
179            let rect = ui
180                .group(|ui| {
181                    ui.label("Game Gear aspect ratio");
182
183                    ui.horizontal(|ui| {
184                        ui.radio_value(
185                            &mut self.config.smsgg.gg_aspect_ratio,
186                            GgAspectRatio::GgLcd,
187                            "Game Gear LCD",
188                        )
189                        .on_hover_text("6:5 pixel aspect ratio");
190                        ui.radio_value(
191                            &mut self.config.smsgg.gg_aspect_ratio,
192                            GgAspectRatio::SquarePixels,
193                            "Square pixels",
194                        )
195                        .on_hover_text("1:1 pixel aspect ratio");
196                        ui.radio_value(
197                            &mut self.config.smsgg.gg_aspect_ratio,
198                            GgAspectRatio::Stretched,
199                            "Stretched",
200                        )
201                        .on_hover_text("Stretch image to fill the screen");
202                    });
203                })
204                .response
205                .interact_rect;
206            if ui.rect_contains_pointer(rect) {
207                self.state.help_text.insert(WINDOW, helptext::GG_ASPECT_RATIO);
208            }
209
210            let rect = ui
211                .checkbox(
212                    &mut self.config.smsgg.remove_sprite_limit,
213                    "Remove sprite-per-scanline limit",
214                )
215                .interact_rect;
216            if ui.rect_contains_pointer(rect) {
217                self.state.help_text.insert(WINDOW, helptext::REMOVE_SPRITE_LIMIT);
218            }
219
220            let rect = ui
221                .checkbox(
222                    &mut self.config.smsgg.sms_crop_vertical_border,
223                    "(SMS/SG) Crop vertical border",
224                )
225                .interact_rect;
226            if ui.rect_contains_pointer(rect) {
227                self.state.help_text.insert(WINDOW, helptext::SMS_CROP_VERTICAL_BORDER);
228            }
229
230            let rect = ui
231                .checkbox(&mut self.config.smsgg.sms_crop_left_border, "(SMS/SG) Crop left border")
232                .interact_rect;
233            if ui.rect_contains_pointer(rect) {
234                self.state.help_text.insert(WINDOW, helptext::SMS_CROP_LEFT_BORDER);
235            }
236
237            let rect = ui
238                .checkbox(
239                    &mut self.config.smsgg.gg_frame_blending,
240                    "(Game Gear) Frame blending enabled",
241                )
242                .interact_rect;
243            if ui.rect_contains_pointer(rect) {
244                self.state.help_text.insert(WINDOW, helptext::GG_FRAME_BLENDING);
245            }
246
247            let rect = ui
248                .checkbox(
249                    &mut self.config.smsgg.gg_use_sms_resolution,
250                    "(Game Gear) Render in SMS resolution",
251                )
252                .interact_rect;
253            if ui.rect_contains_pointer(rect) {
254                self.state.help_text.insert(WINDOW, helptext::GG_USE_SMS_RESOLUTION);
255            }
256
257            self.render_help_text(ui, WINDOW);
258        });
259        if !open {
260            self.state.open_windows.remove(&WINDOW);
261        }
262    }
263
264    pub(super) fn render_smsgg_audio_settings(&mut self, ctx: &Context) {
265        const WINDOW: OpenWindow = OpenWindow::SmsGgAudio;
266
267        let mut open = true;
268        Window::new(WINDOW.title()).open(&mut open).resizable(false).show(ctx, |ui| {
269            let rect = ui
270                .group(|ui| {
271                    ui.label("PSG version");
272
273                    ui.horizontal(|ui| {
274                        ui.radio_value(&mut self.config.smsgg.psg_version, None, "Auto")
275                            .on_hover_text(
276                                "SMS games will use SMS2 PSG, Game Gear games will use SMS1/GG PSG",
277                            );
278                        ui.radio_value(
279                            &mut self.config.smsgg.psg_version,
280                            Some(Sn76489Version::MasterSystem2),
281                            "SMS2",
282                        )
283                        .on_hover_text("SMS2 PSG clips high volumes");
284                        ui.radio_value(
285                            &mut self.config.smsgg.psg_version,
286                            Some(Sn76489Version::Standard),
287                            "SMS1 / Game Gear",
288                        )
289                        .on_hover_text("SMS1 and Game Gear PSGs correctly play high volumes");
290                        ui.radio_value(
291                            &mut self.config.smsgg.psg_version,
292                            Some(Sn76489Version::Discrete),
293                            "SG-1000",
294                        )
295                        .on_hover_text("SG-1000 PSG has a slightly different white noise sound");
296                    });
297                })
298                .response
299                .interact_rect;
300            if ui.rect_contains_pointer(rect) {
301                self.state.help_text.insert(WINDOW, helptext::PSG_VERSION);
302            }
303
304            let rect = ui
305                .add_enabled_ui(!self.emu_runner.status().is_running_smsgg(), |ui| {
306                    ui.checkbox(
307                        &mut self.config.smsgg.fm_sound_unit_enabled,
308                        "Master System FM sound unit enabled",
309                    );
310                })
311                .response
312                .interact_rect;
313            if ui.rect_contains_pointer(rect) {
314                self.state.help_text.insert(WINDOW, helptext::SMS_FM_UNIT);
315            }
316
317            self.render_help_text(ui, WINDOW);
318        });
319        if !open {
320            self.state.open_windows.remove(&WINDOW);
321        }
322    }
323
324    pub(super) fn render_smsgg_overclock_settings(&mut self, ctx: &Context) {
325        const WINDOW: OpenWindow = OpenWindow::SmsGgOverclock;
326
327        let mut open = true;
328        Window::new(WINDOW.title()).open(&mut open).resizable(false).show(ctx, |ui| {
329            ui.add(OverclockSlider {
330                label: "Z80 clock divider",
331                current_value: &mut self.config.smsgg.z80_divider,
332                range: NonZeroU32::new(1).unwrap()..=NonZeroU32::new(15).unwrap(),
333                master_clock: smsgg_core::audio::NTSC_MCLK_FREQUENCY,
334                default_divider: smsgg_core::NATIVE_Z80_DIVIDER.into(),
335                modifier: ClockModifier::Divider,
336            });
337
338            self.state.help_text.insert(WINDOW, helptext::Z80_OVERCLOCK);
339            self.render_help_text(ui, WINDOW);
340        });
341        if !open {
342            self.state.open_windows.remove(&WINDOW);
343        }
344    }
345
346    #[must_use]
347    pub(super) fn render_sms_bios_error(
348        &mut self,
349        ctx: &Context,
350        open: &mut bool,
351    ) -> RenderErrorEffect {
352        widgets::render_bios_error(
353            ctx,
354            open,
355            BiosErrorStrings {
356                title: "Missing SMS BIOS",
357                text: "The boot from BIOS option is set but no SMS BIOS path is configured.",
358                button_label: "Configure SMS BIOS path",
359            },
360            &mut self.config.smsgg.sms_bios_path,
361            Console::MasterSystem,
362            || pick_bios_path("sms"),
363        )
364    }
365
366    #[must_use]
367    pub(super) fn render_gg_bios_error(
368        &mut self,
369        ctx: &Context,
370        open: &mut bool,
371    ) -> RenderErrorEffect {
372        widgets::render_bios_error(
373            ctx,
374            open,
375            BiosErrorStrings {
376                title: "Missing Game Gear BIOS",
377                text: "The boot from BIOS option is set but no Game Gear BIOS path is configured.",
378                button_label: "Configure Game Gear BIOS path",
379            },
380            &mut self.config.smsgg.gg_bios_path,
381            Console::GameGear,
382            || pick_bios_path("gg"),
383        )
384    }
385}
386
387fn pick_bios_path(default_extension: &str) -> Option<PathBuf> {
388    FileDialog::new()
389        .add_filter("bin", &[default_extension, "bin"])
390        .add_filter("All Files", &["*"])
391        .pick_file()
392}