extern crate sdl2; use sdl2::event::Event; use sdl2::image::{LoadTexture, InitFlag}; use sdl2::keyboard::Keycode; use sdl2::pixels::Color; use sdl2::render::{TextureCreator, Texture}; use sdl2::ttf::{Font, Sdl2TtfContext}; use std::env; use std::borrow::Borrow; use std::collections::HashMap; use std::hash::Hash; use std::rc::Rc; fn main() -> Result<(), String> { let args: Vec<_> = env::args().collect(); if args.len() < 3 { println!("Usage: cargo run /path/to/image.(png|jpg) /path/to/font.(ttf|ttc|fon)") } else { let image_path = &args[1]; let font_path = &args[2]; let sdl_context = sdl2::init()?; let video_subsystem = sdl_context.video()?; let font_context = sdl2::ttf::init().map_err(|e| e.to_string())?; let _image_context = sdl2::image::init(InitFlag::PNG | InitFlag::JPG)?; let window = video_subsystem .window("rust-sdl2 resource-manager demo", 800, 600) .position_centered() .build() .map_err(|e| e.to_string())?; let mut canvas = window.into_canvas().software().build().map_err(|e| e.to_string())?; let texture_creator = canvas.texture_creator(); let mut texture_manager = TextureManager::new(&texture_creator); let mut font_manager = FontManager::new(&font_context); let details = FontDetails { path: font_path.clone(), size: 32, }; 'mainloop: loop { for event in sdl_context.event_pump()?.poll_iter() { match event { Event::KeyDown { keycode: Some(Keycode::Escape), .. } | Event::Quit { .. } => break 'mainloop, _ => {} } } // will load the image texture + font only once let texture = texture_manager.load(image_path)?; let font = font_manager.load(&details)?; // not recommended to create a texture from the font each iteration // but it is the simplest thing to do for this example let surface = font.render("Hello Rust!") .blended(Color::RGBA(255, 0, 0, 255)).map_err(|e| e.to_string())?; let font_texture = texture_creator .create_texture_from_surface(&surface).map_err(|e| e.to_string())?; //draw all canvas.clear(); canvas.copy(&texture, None, None)?; canvas.copy(&font_texture, None, None)?; canvas.present(); } } Ok(()) } type TextureManager<'l, T> = ResourceManager<'l, String, Texture<'l>, TextureCreator>; type FontManager<'l> = ResourceManager<'l, FontDetails, Font<'l, 'static>, Sdl2TtfContext>; // Generic struct to cache any resource loaded by a ResourceLoader pub struct ResourceManager<'l, K, R, L> where K: Hash + Eq, L: 'l + ResourceLoader<'l, R> { loader: &'l L, cache: HashMap>, } impl<'l, K, R, L> ResourceManager<'l, K, R, L> where K: Hash + Eq, L: ResourceLoader<'l, R> { pub fn new(loader: &'l L) -> Self { ResourceManager { cache: HashMap::new(), loader: loader, } } // Generics magic to allow a HashMap to use String as a key // while allowing it to use &str for gets pub fn load(&mut self, details: &D) -> Result, String> where L: ResourceLoader<'l, R, Args = D>, D: Eq + Hash + ?Sized, K: Borrow + for<'a> From<&'a D> { self.cache .get(details) .cloned() .map_or_else(|| { let resource = Rc::new(self.loader.load(details)?); self.cache.insert(details.into(), resource.clone()); Ok(resource) }, Ok) } } // TextureCreator knows how to load Textures impl<'l, T> ResourceLoader<'l, Texture<'l>> for TextureCreator { type Args = str; fn load(&'l self, path: &str) -> Result { println!("LOADED A TEXTURE"); self.load_texture(path) } } // Font Context knows how to load Fonts impl<'l> ResourceLoader<'l, Font<'l, 'static>> for Sdl2TtfContext { type Args = FontDetails; fn load(&'l self, details: &FontDetails) -> Result, String> { println!("LOADED A FONT"); self.load_font(&details.path, details.size) } } // Generic trait to Load any Resource Kind pub trait ResourceLoader<'l, R> { type Args: ?Sized; fn load(&'l self, data: &Self::Args) -> Result; } // Information needed to load a Font #[derive(PartialEq, Eq, Hash)] pub struct FontDetails { pub path: String, pub size: u16, } impl<'a> From<&'a FontDetails> for FontDetails { fn from(details: &'a FontDetails) -> FontDetails { FontDetails { path: details.path.clone(), size: details.size, } } }