1 extern crate sdl2;
2 
3 use sdl2::event::Event;
4 use sdl2::image::{LoadTexture, InitFlag};
5 use sdl2::keyboard::Keycode;
6 use sdl2::pixels::Color;
7 use sdl2::render::{TextureCreator, Texture};
8 use sdl2::ttf::{Font, Sdl2TtfContext};
9 
10 use std::env;
11 use std::borrow::Borrow;
12 use std::collections::HashMap;
13 use std::hash::Hash;
14 use std::rc::Rc;
15 
main() -> Result<(), String>16 fn main() -> Result<(), String> {
17     let args: Vec<_> = env::args().collect();
18 
19     if args.len() < 3 {
20         println!("Usage: cargo run /path/to/image.(png|jpg) /path/to/font.(ttf|ttc|fon)")
21     } else {
22         let image_path = &args[1];
23         let font_path = &args[2];
24 
25         let sdl_context = sdl2::init()?;
26         let video_subsystem = sdl_context.video()?;
27         let font_context = sdl2::ttf::init().map_err(|e| e.to_string())?;
28         let _image_context = sdl2::image::init(InitFlag::PNG | InitFlag::JPG)?;
29         let window = video_subsystem
30             .window("rust-sdl2 resource-manager demo", 800, 600)
31             .position_centered()
32             .build()
33             .map_err(|e| e.to_string())?;
34         let mut canvas = window.into_canvas().software().build().map_err(|e| e.to_string())?;
35         let texture_creator = canvas.texture_creator();
36         let mut texture_manager = TextureManager::new(&texture_creator);
37         let mut font_manager = FontManager::new(&font_context);
38         let details = FontDetails {
39             path: font_path.clone(),
40             size: 32,
41         };
42 
43         'mainloop: loop {
44             for event in sdl_context.event_pump()?.poll_iter() {
45                 match event {
46                     Event::KeyDown { keycode: Some(Keycode::Escape), .. } |
47                     Event::Quit { .. } => break 'mainloop,
48                     _ => {}
49                 }
50             }
51             // will load the image texture + font only once
52             let texture = texture_manager.load(image_path)?;
53             let font = font_manager.load(&details)?;
54 
55             // not recommended to create a texture from the font each iteration
56             // but it is the simplest thing to do for this example
57             let surface = font.render("Hello Rust!")
58                 .blended(Color::RGBA(255, 0, 0, 255)).map_err(|e| e.to_string())?;
59             let font_texture = texture_creator
60                 .create_texture_from_surface(&surface).map_err(|e| e.to_string())?;
61 
62             //draw all
63             canvas.clear();
64             canvas.copy(&texture, None, None)?;
65             canvas.copy(&font_texture, None, None)?;
66             canvas.present();
67         }
68     }
69 
70     Ok(())
71 }
72 
73 type TextureManager<'l, T> = ResourceManager<'l, String, Texture<'l>, TextureCreator<T>>;
74 type FontManager<'l> = ResourceManager<'l, FontDetails, Font<'l, 'static>, Sdl2TtfContext>;
75 
76 // Generic struct to cache any resource loaded by a ResourceLoader
77 pub struct ResourceManager<'l, K, R, L>
78     where K: Hash + Eq,
79           L: 'l + ResourceLoader<'l, R>
80 {
81     loader: &'l L,
82     cache: HashMap<K, Rc<R>>,
83 }
84 
85 impl<'l, K, R, L> ResourceManager<'l, K, R, L>
86     where K: Hash + Eq,
87           L: ResourceLoader<'l, R>
88 {
new(loader: &'l L) -> Self89     pub fn new(loader: &'l L) -> Self {
90         ResourceManager {
91             cache: HashMap::new(),
92             loader: loader,
93         }
94     }
95 
96     // Generics magic to allow a HashMap to use String as a key
97     // while allowing it to use &str for gets
load<D>(&mut self, details: &D) -> Result<Rc<R>, String> where L: ResourceLoader<'l, R, Args = D>, D: Eq + Hash + ?Sized, K: Borrow<D> + for<'a> From<&'a D>98     pub fn load<D>(&mut self, details: &D) -> Result<Rc<R>, String>
99         where L: ResourceLoader<'l, R, Args = D>,
100               D: Eq + Hash + ?Sized,
101               K: Borrow<D> + for<'a> From<&'a D>
102     {
103         self.cache
104             .get(details)
105             .cloned()
106             .map_or_else(|| {
107                              let resource = Rc::new(self.loader.load(details)?);
108                              self.cache.insert(details.into(), resource.clone());
109                              Ok(resource)
110                          },
111                          Ok)
112     }
113 }
114 
115 // TextureCreator knows how to load Textures
116 impl<'l, T> ResourceLoader<'l, Texture<'l>> for TextureCreator<T> {
117     type Args = str;
load(&'l self, path: &str) -> Result<Texture, String>118     fn load(&'l self, path: &str) -> Result<Texture, String> {
119         println!("LOADED A TEXTURE");
120         self.load_texture(path)
121     }
122 }
123 
124 // Font Context knows how to load Fonts
125 impl<'l> ResourceLoader<'l, Font<'l, 'static>> for Sdl2TtfContext {
126     type Args = FontDetails;
load(&'l self, details: &FontDetails) -> Result<Font<'l, 'static>, String>127     fn load(&'l self, details: &FontDetails) -> Result<Font<'l, 'static>, String> {
128         println!("LOADED A FONT");
129         self.load_font(&details.path, details.size)
130     }
131 }
132 
133 // Generic trait to Load any Resource Kind
134 pub trait ResourceLoader<'l, R> {
135     type Args: ?Sized;
load(&'l self, data: &Self::Args) -> Result<R, String>136     fn load(&'l self, data: &Self::Args) -> Result<R, String>;
137 }
138 
139 // Information needed to load a Font
140 #[derive(PartialEq, Eq, Hash)]
141 pub struct FontDetails {
142     pub path: String,
143     pub size: u16,
144 }
145 
146 impl<'a> From<&'a FontDetails> for FontDetails {
from(details: &'a FontDetails) -> FontDetails147     fn from(details: &'a FontDetails) -> FontDetails {
148         FontDetails {
149             path: details.path.clone(),
150             size: details.size,
151         }
152     }
153 }
154