extern crate ferretro_components; extern crate sdl2; use std::ffi::CStr; use std::path::PathBuf; use std::pin::Pin; use sdl2::render::{WindowCanvas, BlendMode}; use sdl2::image::ImageRWops; use sdl2::surface::Surface; use sdl2::pixels::{Color, PixelFormatEnum}; use sdl2::rect::Rect; use rand::{SeedableRng, Rng}; use rand_xoshiro::Xoshiro128Plus; use structopt::StructOpt; use ferretro_components::base::ControlFlow; use ferretro_components::prelude::*; use ferretro_components::provided::{ sdl2::{Sdl2SurfaceComponent, SimpleSdl2AudioComponent}, stdlib::{SleepFramerateLimitComponent, PathBufComponent}, }; type Result = std::result::Result>; #[derive(StructOpt)] struct Opt { /// Core module to use. #[structopt(short, long, parse(from_os_str))] core: PathBuf, /// ROM to load using the core. #[structopt(short, long, parse(from_os_str))] rom: PathBuf, /// System directory, often containing BIOS files #[structopt(short, long, parse(from_os_str))] system: Option, } struct Zretro { emu: Pin>, canvas: WindowCanvas, font: Surface<'static>, rng: Xoshiro128Plus, ui_bg: Option>, snowflakes: Vec, sdl2surf_comp_id: RetroComponentId, } const FONT_PNG: &'static [u8] = include_bytes!("lifont.png"); impl Zretro { fn new(opt: Opt) -> Result { let mut emu = RetroComponentBase::new(&opt.core); let geometry = emu.libretro_core().get_system_av_info().geometry; let sys_info = emu.libretro_core().get_system_info(); let title = format!( "zretro - {}", unsafe { CStr::from_ptr(sys_info.library_name) }.to_string_lossy() ); let mut sdl_context = sdl2::init()?; let window = sdl_context .video()? .window(title.as_str(), geometry.base_width, geometry.base_height) .borderless() .build()?; let canvas = window.into_canvas().build()?; let sdl2surf_comp = Sdl2SurfaceComponent::new(emu.libretro_core())?; let sdl2surf_comp_id = emu.register_component(sdl2surf_comp)?; let sdl2_audio = SimpleSdl2AudioComponent::new(&mut sdl_context, emu.libretro_core()); emu.register_component(sdl2_audio)?; let sleep_fps = SleepFramerateLimitComponent::new(emu.libretro_core()); emu.register_component(sleep_fps)?; emu.register_component(PathBufComponent { sys_path: opt.system.clone(), libretro_path: Some(opt.core.to_path_buf()), core_assets_path: None, save_path: Some(std::env::temp_dir()), })?; emu.init().unwrap(); emu.load_game(&opt.rom).unwrap(); let rwops = sdl2::rwops::RWops::from_bytes(FONT_PNG)?; let font_shortlived = rwops.load_png()?; // there's a nonsemantic inheritance of lifetime from ImageRWops::load_png. TODO: pr let font = unsafe { let ptr = font_shortlived.raw(); std::mem::forget(font_shortlived); Surface::from_ll(ptr) }; let mut rng = rand_xoshiro::Xoshiro128Plus::from_entropy(); let mut ui_bg = Surface::new(geometry.base_width, geometry.base_height, PixelFormatEnum::ABGR8888)?; ui_bg.fill_rect(None, Color::RGBA(0, 0, 128, 128))?; let snowflakes = (0..100).into_iter() .map(|_| Rect::new( rng.gen_range(0..ui_bg.width() as i32), rng.gen_range(0..ui_bg.height() as i32), 2, 2)) .collect(); Ok(Zretro { emu, canvas, font, rng, ui_bg: Some(ui_bg), snowflakes, sdl2surf_comp_id, }) } fn update_snow(&mut self) -> Result<()> { let mut ui_bg = self.ui_bg.take().ok_or("no ui_bg?")?; ui_bg.fill_rect(None, Color::RGBA(0, 0, 128, 128))?; let w = ui_bg.width() as i32; let h = ui_bg.height() as i32; for flake in &mut self.snowflakes { let x = flake.x() + self.rng.gen_range(-1..=1); let y = flake.y() + 1; flake.set_x(x % w); flake.set_y(y % h); } let mut ui_canvas = ui_bg.into_canvas()?; ui_canvas.set_draw_color(Color::RGBA(255, 255, 255, 160)); ui_canvas.draw_rects(&self.snowflakes[..])?; self.ui_bg = Some(ui_canvas.into_surface()); Ok(()) } fn run(&mut self) -> Result<()> { let tc = self.canvas.texture_creator(); let font_tx = tc.create_texture_from_surface(&self.font)?; let mut font_rect = self.font.rect(); font_rect.set_height(64); while let ControlFlow::Continue = self.emu.run() { self.update_snow()?; let ref_mut = self.emu.component_mut::(self.sdl2surf_comp_id)?; let surface = ref_mut.surface(); let emu_tx = tc.create_texture_from_surface(surface)?; let ui_bg_surf_ref = self.ui_bg.as_ref().ok_or("no bg?")?; let mut ui_tx = tc.create_texture_from_surface(ui_bg_surf_ref)?; ui_tx.set_blend_mode(BlendMode::Blend); self.canvas.clear(); self.canvas.copy(&emu_tx, None, None)?; self.canvas.copy(&ui_tx, None, None)?; self.canvas.copy(&font_tx, font_rect, font_rect)?; self.canvas.present(); } Ok(()) } } fn main() -> Result<()> { let opt: Opt = Opt::from_args(); let mut frontend = Zretro::new(opt)?; frontend.run()?; Ok(()) }