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}