1use crate::app::widgets::{BiosErrorStrings, OptionalPathSelector, RenderErrorEffect}; 2use crate::app::{App, OpenWindow, widgets}; 3use egui::{Context, Slider, Window}; 4use gba_config::{GbaAspectRatio, GbaAudioInterpolation, GbaColorCorrection, GbaSaveMemory}; 5use jgenesis_native_config::gba::GameBoyAdvanceAppConfig; 6use jgenesis_native_driver::extensions::Console; 7use rfd::FileDialog; 8use std::path::PathBuf; 9 10mod helptext; 11 12impl App { 13 pub(super) fn render_gba_general_settings(&mut self, ctx: &Context) { 14 const WINDOW: OpenWindow = OpenWindow::GbaGeneral; 15 16 let mut open = true; 17 18 Window::new(WINDOW.title()).open(&mut open).resizable(false).show(ctx, |ui| { 19 let rect = ui 20 .add(OptionalPathSelector::new( 21 "BIOS path", 22 &mut self.config.game_boy_advance.bios_path, 23 pick_bios_path, 24 )) 25 .interact_rect; 26 if ui.rect_contains_pointer(rect) { 27 self.state.help_text.insert(WINDOW, helptext::BIOS_PATH); 28 } 29 30 ui.add_space(5.0); 31 32 let rect = ui 33 .checkbox( 34 &mut self.config.game_boy_advance.skip_bios_animation, 35 "Skip BIOS intro animation", 36 ) 37 .interact_rect; 38 if ui.rect_contains_pointer(rect) { 39 self.state.help_text.insert(WINDOW, helptext::SKIP_BIOS_ANIMATION); 40 } 41 42 ui.add_space(5.0); 43 44 let rect = ui 45 .group(|ui| { 46 ui.label("Save memory type"); 47 48 for (value, label) in [ 49 (None, "Auto-detect"), 50 (Some(GbaSaveMemory::Sram), "SRAM"), 51 (Some(GbaSaveMemory::EepromUnknownSize), "EEPROM - Auto-detect size"), 52 (Some(GbaSaveMemory::Eeprom512), "EEPROM - 512 bytes"), 53 (Some(GbaSaveMemory::Eeprom8K), "EEPROM - 8 KB"), 54 (Some(GbaSaveMemory::FlashRom64K), "Flash ROM - 64 KB"), 55 (Some(GbaSaveMemory::FlashRom128K), "Flash ROM - 128 KB"), 56 (Some(GbaSaveMemory::None), "No save memory"), 57 ] { 58 ui.radio_value( 59 &mut self.config.game_boy_advance.forced_save_memory_type, 60 value, 61 label, 62 ); 63 } 64 }) 65 .response 66 .interact_rect; 67 if ui.rect_contains_pointer(rect) { 68 self.state.help_text.insert(WINDOW, helptext::SAVE_MEMORY_TYPE); 69 } 70 71 self.render_help_text(ui, WINDOW); 72 }); 73 74 if !open { 75 self.state.open_windows.remove(&WINDOW); 76 } 77 } 78 79 pub(super) fn render_gba_video_settings(&mut self, ctx: &Context) { 80 const WINDOW: OpenWindow = OpenWindow::GbaVideo; 81 82 let mut open = true; 83 84 Window::new(WINDOW.title()).open(&mut open).resizable(false).show(ctx, |ui| { 85 let rect = ui 86 .group(|ui| { 87 ui.label("Aspect ratio"); 88 89 ui.horizontal(|ui| { 90 for (value, label) in [ 91 (GbaAspectRatio::SquarePixels, "Square pixels"), 92 (GbaAspectRatio::Stretched, "Stretched"), 93 ] { 94 ui.radio_value( 95 &mut self.config.game_boy_advance.aspect_ratio, 96 value, 97 label, 98 ); 99 } 100 }); 101 }) 102 .response 103 .interact_rect; 104 if ui.rect_contains_pointer(rect) { 105 self.state.help_text.insert(WINDOW, helptext::ASPECT_RATIO); 106 } 107 108 let rect = ui 109 .group(|ui| { 110 ui.label("Color correction"); 111 112 ui.horizontal(|ui| { 113 for (value, label) in [ 114 (GbaColorCorrection::None, "None"), 115 (GbaColorCorrection::GbaLcd, "Game Boy Advance LCD"), 116 ] { 117 ui.radio_value( 118 &mut self.config.game_boy_advance.color_correction, 119 value, 120 label, 121 ); 122 } 123 }); 124 125 ui.add_enabled_ui( 126 self.config.game_boy_advance.color_correction == GbaColorCorrection::GbaLcd, 127 |ui| { 128 ui.horizontal(|ui| { 129 ui.label("Console screen gamma"); 130 131 ui.add( 132 Slider::new( 133 &mut self.config.game_boy_advance.color_correction_gamma, 134 0.1..=5.0, 135 ) 136 .custom_formatter(|value, _| format!("{value:.1}")), 137 ); 138 139 if ui.button("Default").clicked() { 140 self.config.game_boy_advance.color_correction_gamma = 141 GameBoyAdvanceAppConfig::default().color_correction_gamma; 142 } 143 }); 144 }, 145 ); 146 }) 147 .response 148 .interact_rect; 149 if ui.rect_contains_pointer(rect) { 150 self.state.help_text.insert(WINDOW, helptext::COLOR_CORRECTION); 151 } 152 153 ui.add_space(5.0); 154 155 let rect = ui 156 .checkbox( 157 &mut self.config.game_boy_advance.frame_blending, 158 "Frame blending enabled", 159 ) 160 .interact_rect; 161 if ui.rect_contains_pointer(rect) { 162 self.state.help_text.insert(WINDOW, helptext::FRAME_BLENDING); 163 } 164 165 self.render_help_text(ui, WINDOW); 166 }); 167 168 if !open { 169 self.state.open_windows.remove(&WINDOW); 170 } 171 } 172 173 pub(super) fn render_gba_audio_settings(&mut self, ctx: &Context) { 174 const WINDOW: OpenWindow = OpenWindow::GbaAudio; 175 176 let mut open = true; 177 Window::new(WINDOW.title()).open(&mut open).resizable(false).show(ctx, |ui| { 178 ui.group(|ui| { 179 let rect = ui 180 .scope(|ui| { 181 ui.label("Audio interpolation"); 182 183 for (value, label) in [ 184 (GbaAudioInterpolation::NearestNeighbor, "Nearest neighbor (Native)"), 185 (GbaAudioInterpolation::CubicHermite, "Cubic Hermite"), 186 (GbaAudioInterpolation::WindowedSinc, "Windowed sinc"), 187 ] { 188 ui.radio_value( 189 &mut self.config.game_boy_advance.audio_interpolation, 190 value, 191 label, 192 ); 193 } 194 }) 195 .response 196 .interact_rect; 197 if ui.rect_contains_pointer(rect) { 198 self.state.help_text.insert(WINDOW, helptext::AUDIO_INTERPOLATION); 199 } 200 201 let rect = ui 202 .add_enabled_ui( 203 self.config.game_boy_advance.audio_interpolation 204 != GbaAudioInterpolation::NearestNeighbor, 205 |ui| { 206 ui.checkbox( 207 &mut self.config.game_boy_advance.psg_low_pass, 208 "Apply low-pass filter to PSG", 209 ); 210 }, 211 ) 212 .response 213 .interact_rect; 214 if ui.rect_contains_pointer(rect) { 215 self.state.help_text.insert(WINDOW, helptext::PSG_LPF); 216 } 217 }); 218 219 let rect = ui 220 .group(|ui| { 221 ui.label("Enabled audio channels"); 222 223 ui.checkbox( 224 &mut self.config.game_boy_advance.pulse_1_enabled, 225 "Channel 1 (Pulse with sweep)", 226 ); 227 ui.checkbox( 228 &mut self.config.game_boy_advance.pulse_2_enabled, 229 "Channel 2 (Pulse)", 230 ); 231 ui.checkbox( 232 &mut self.config.game_boy_advance.wavetable_enabled, 233 "Channel 3 (Wavetable)", 234 ); 235 ui.checkbox( 236 &mut self.config.game_boy_advance.noise_enabled, 237 "Channel 4 (Noise)", 238 ); 239 ui.checkbox( 240 &mut self.config.game_boy_advance.pcm_a_enabled, 241 "Channel A (Direct Sound)", 242 ); 243 ui.checkbox( 244 &mut self.config.game_boy_advance.pcm_b_enabled, 245 "Channel B (Direct Sound)", 246 ); 247 }) 248 .response 249 .interact_rect; 250 if ui.rect_contains_pointer(rect) { 251 self.state.help_text.insert(WINDOW, helptext::AUDIO_CHANNELS_ENABLED); 252 } 253 254 self.render_help_text(ui, WINDOW); 255 }); 256 257 if !open { 258 self.state.open_windows.remove(&WINDOW); 259 } 260 } 261 262 pub(super) fn render_gba_bios_error( 263 &mut self, 264 ctx: &Context, 265 open: &mut bool, 266 ) -> RenderErrorEffect { 267 widgets::render_bios_error( 268 ctx, 269 open, 270 BiosErrorStrings { 271 title: "Missing GBA BIOS", 272 text: "No GBA BIOS path is configured. A BIOS ROM is required for GBA emulation.", 273 button_label: "Configure GBA BIOS path", 274 }, 275 &mut self.config.game_boy_advance.bios_path, 276 Console::GameBoyAdvance, 277 pick_bios_path, 278 ) 279 } 280} 281 282fn pick_bios_path() -> Option<PathBuf> { 283 FileDialog::new().add_filter("bin", &["bin"]).add_filter("All Files", &["*"]).pick_file() 284}