pronto_graphics/window.rs
1use std::{collections::VecDeque, process::exit};
2
3use sfml::{
4 graphics::{
5 CircleShape, Font as SfmlFont, PrimitiveType, RectangleShape,
6 RenderTarget, RenderWindow, Shape, Text, Texture as SfmlTexture,
7 Transformable, Vertex, VertexArray,
8 },
9 system::Clock,
10 window::{mouse::Button, Event, Key, Style, VideoMode},
11};
12
13use crate::{
14 color::Color,
15 font::{
16 default_font, font_store, font_store_add, init_default_font,
17 init_font_store, Font,
18 },
19 input::InputState,
20 render_parameters::RenderParameterState,
21 shape::{RenderTask, ShapeStore, Shapes},
22 texture::{init_texture_store, texture_store, texture_store_add, Texture},
23};
24
25/// The core type of the Pronto Graphics library.
26/// All drawing and keyboard/mouse interaction happens through an instance of [`Window`].
27/// It has to be updated every frame with [`Window::update`] for drawings to be rendered and the keyboard/mouse state to be updated.
28///
29/// # Examples
30/// ```
31/// let mut pg = Window::new(800, 600, "Window Title"); // Create window
32/// loop {
33/// pg.circle((200,200), 50); // Draw to it
34/// pg.update(); // Update for drawing to appear
35/// }
36/// ```
37pub struct Window<'a> {
38 window: RenderWindow,
39 input_state: InputState,
40 render_queue: VecDeque<RenderTask>,
41 background_color: Color,
42 font: Option<Font>,
43 render_parameter_state: RenderParameterState,
44 shape_store: ShapeStore<'a>,
45 deltatime_clock: Clock,
46 deltatime: f32,
47 runtime_clock: Clock,
48 runtime: f32,
49}
50
51impl Window<'_> {
52 fn new_from_renderwindow(window: RenderWindow) -> Self {
53 init_texture_store();
54 init_default_font();
55 init_font_store();
56
57 let mut circle_shape = CircleShape::new(0., 32);
58 circle_shape.set_outline_thickness(1.);
59 let mut rectangle_shape = RectangleShape::new();
60 rectangle_shape.set_outline_thickness(1.);
61
62 let text = default_font()
63 .and_then(|default_font| Some(Text::new("", &default_font, 16)));
64
65 Self {
66 window,
67 input_state: InputState::new(),
68 render_queue: VecDeque::new(),
69 background_color: Color::LIGHT_GRAY,
70 font: None,
71 render_parameter_state: Default::default(),
72 shape_store: ShapeStore {
73 circle: circle_shape,
74 rectangle: rectangle_shape,
75 texture: RectangleShape::new(),
76 text: text,
77 },
78 runtime_clock: Clock::start(),
79 deltatime_clock: Clock::start(),
80 deltatime: 1. / 60., // So that we don't get problems in the first frame
81 runtime: 0.,
82 }
83 }
84
85 /// Create a new window of size (`width`, `height`) and with title `name`.
86 /// Can be directly drawn to with functions like [`Window::circle`]
87 /// and has to be updated with [`Window::update`].
88 pub fn new(width: u32, height: u32, name: &str) -> Self {
89 let mut window = RenderWindow::new(
90 (width, height),
91 name,
92 Style::TITLEBAR | Style::CLOSE,
93 &Default::default(),
94 );
95 window.set_vertical_sync_enabled(true);
96 window.set_key_repeat_enabled(false);
97
98 Self::new_from_renderwindow(window)
99 }
100
101 /// Create a new fullscreen window.
102 /// Can be directly drawn to with functions like [`Window::circle`]
103 /// and has to be updated with [`Window::update`].
104 pub fn new_fullscreen() -> Self {
105 let mut window = RenderWindow::new(
106 VideoMode::desktop_mode(),
107 "",
108 Style::FULLSCREEN,
109 &Default::default(),
110 );
111 window.set_vertical_sync_enabled(true);
112 window.set_key_repeat_enabled(false);
113
114 Self::new_from_renderwindow(window)
115 }
116
117 /// Has to be called every frame for drawings to appear on the screen and keyboard/mouse to be updated.
118 /// Note that this function will block for vertical sync.
119 pub fn update(&mut self) {
120 self.update_events();
121 self.update_draw();
122
123 self.deltatime = self.deltatime_clock.restart().as_seconds();
124 self.runtime = self.runtime_clock.elapsed_time().as_seconds();
125
126 self.render_parameter_state = Default::default();
127 }
128
129 fn update_events(&mut self) {
130 self.input_state.clear();
131 while let Some(event) = self.window.poll_event() {
132 self.input_state.handle_event(event);
133 match event {
134 Event::Closed
135 | Event::KeyPressed {
136 code: Key::ESCAPE, ..
137 } => exit(0),
138 _ => {}
139 }
140 }
141 }
142
143 fn update_draw(&mut self) {
144 self.window.clear(self.background_color.into());
145 for task in &self.render_queue {
146 let RenderTask {
147 pos,
148 shape,
149 render_parameter_state: color_state,
150 } = task;
151
152 match shape {
153 Shapes::Circle { radius } => {
154 let s = &mut self.shape_store.circle;
155 s.set_radius(*radius);
156 s.set_origin((s.radius(), s.radius()));
157 s.set_position(*pos);
158 s.set_fill_color(color_state.fill_color.into());
159 s.set_outline_color(color_state.outline_color.into());
160 self.window.draw(s);
161 }
162 Shapes::Rectangle { width, height } => {
163 let s = &mut self.shape_store.rectangle;
164 s.set_size((*width, *height));
165 // s.set_origin((*width / 2., *height / 2.));
166 s.set_position(*pos);
167 s.set_fill_color(color_state.fill_color.into());
168 s.set_outline_color(color_state.outline_color.into());
169 self.window.draw(s);
170 }
171 Shapes::Lines { coords } => {
172 let mut va =
173 VertexArray::new(PrimitiveType::LINES, coords.len());
174 for (i, v) in coords.iter().enumerate() {
175 va[i] = Vertex::with_pos_color(
176 (*v).into(),
177 color_state.line_color.into(),
178 );
179 }
180
181 self.window.draw(&va);
182 }
183 Shapes::Texture {
184 texture,
185 width,
186 height,
187 } => {
188 if let Some(tex) = &texture_store(*texture) {
189 let s = &mut self.shape_store.texture;
190 s.set_texture(tex, false);
191 s.set_size((*width, *height));
192 // s.set_origin((*width / 2., *height / 2.));
193 s.set_position(*pos);
194 self.window.draw(s);
195 }
196 }
197 Shapes::Text { string, font } => {
198 if let Some(t) = &mut self.shape_store.text {
199 let sfml_font = font
200 .and_then(|font| font_store(font)) // Get custom font from font store
201 .or(default_font()); // Or use the default font
202 if let Some(sfml_font) = sfml_font {
203 // If some kind of font was found, set it
204 t.set_font(sfml_font)
205 }
206 t.set_character_size(color_state.font_size);
207 t.set_string(string);
208 t.set_fill_color(color_state.font_color.into());
209 t.set_position(*pos);
210 self.window.draw(t);
211 }
212 }
213 }
214 }
215 self.render_queue.clear();
216 self.window.display();
217 }
218
219 /// Set the background color of the window.
220 /// The background color does _not_ reset at the beginning of a new frame.
221 /// The initial value for the background color is [`Color::LIGHT_GRAY`].
222 pub fn background_color<C: Into<Color>>(&mut self, color: C) {
223 self.background_color = color.into();
224 }
225
226 /// Set the fill color for drawing shapes like [`Window::circle`].
227 /// The fill color is reset at the beginning of a new frame to a default value of [`Color::WHITE`].
228 pub fn fill_color<C: Into<Color>>(&mut self, color: C) {
229 self.render_parameter_state.fill_color = color.into();
230 }
231
232 /// Set the outline color for drawing shapes like [`Window::circle`].
233 /// The outline color is reset at the beginning of a new frame to a default value of [`Color::TRANSPARENT`].
234 pub fn outline_color<C: Into<Color>>(&mut self, color: C) {
235 self.render_parameter_state.outline_color = color.into();
236 }
237
238 /// Set the line color for drawing lines with [`Window::line`].
239 /// The line color is reset at the beginning of a new frame to a default value of [`Color::BLACK`].
240 pub fn line_color<C: Into<Color>>(&mut self, color: C) {
241 self.render_parameter_state.line_color = color.into();
242 }
243
244 /// Set the line color for drawing text with [`Window::text`].
245 /// The font color is reset at the beginning of a new frame to a default value of [`Color::BLACK`].
246 pub fn font_color<C: Into<Color>>(&mut self, color: C) {
247 self.render_parameter_state.font_color = color.into();
248 }
249
250 /// Set the font size for drawing text with [`Window::text`].
251 /// The font size is reset at the beginning of a new frame to a default value of `16`.
252 pub fn font_size(&mut self, size: u32) {
253 self.render_parameter_state.font_size = size;
254 }
255
256 /// Set the font for drawing text with [`Window::text`].
257 /// The font does _not_ reset at the beginning of a new frame.
258 /// Fonts can be loaded with [`Window::load_font`].
259 /// Initially or if a value of `None` is passed to this function,
260 /// a default font built into the library is used (Processing Sans Pro).
261 ///
262 /// # Examples
263 /// ```
264 /// let mut pg = Window::new_fullscreen();
265 /// let my_font = pg.load_font("MyFont.ttf").unwrap();
266 /// pg.font(Some(my_font));
267 /// loop {
268 /// pg.text((20, 20), "This text is drawn in MyFont.");
269 ///
270 /// pg.update();
271 /// }
272 /// ```
273 pub fn font(&mut self, font: Option<Font>) {
274 self.font = font
275 }
276
277 /// Draw a circle at position `pos` with radius `radius`.
278 /// The origin of the circle is at it's center.
279 pub fn circle(&mut self, pos: (f32, f32), radius: f32) {
280 self.render_queue.push_back(RenderTask {
281 pos,
282 shape: Shapes::Circle { radius },
283 render_parameter_state: self.render_parameter_state,
284 })
285 }
286
287 /// Draw a rectangle at position `pos` with width and height of `(width, height)`.
288 /// The origin of the rectangle is at it's top left.
289 pub fn rectangle(&mut self, pos: (f32, f32), width: f32, height: f32) {
290 self.render_queue.push_back(RenderTask {
291 pos,
292 shape: Shapes::Rectangle { width, height },
293 render_parameter_state: self.render_parameter_state,
294 })
295 }
296
297 /// Draw a square at position `pos` with a width and height of `size`.
298 /// The origin of the square is at it's top left.
299 pub fn square(&mut self, pos: (f32, f32), size: f32) {
300 self.render_queue.push_back(RenderTask {
301 pos,
302 shape: Shapes::Rectangle {
303 width: size,
304 height: size,
305 },
306 render_parameter_state: self.render_parameter_state,
307 })
308 }
309
310 /// Draw a texture `texture` at position `pos` with width and height of `(width, height)`.
311 /// The origin of the texture is at it's top left.
312 /// Textures can be loaded with [`Window::load_texture`].
313 /// # Examples
314 /// ```
315 /// let mut pg = Window::new_fullscreen();
316 /// let my_texture = pg.load_texture("my_texture.png").unwrap();
317 /// loop {
318 /// pg.texture((100., 250.), my_texture, 100., 150.);
319 ///
320 /// pg.update();
321 /// }
322 /// ```
323 pub fn texture(
324 &mut self,
325 pos: (f32, f32),
326 texture: Texture,
327 width: f32,
328 height: f32,
329 ) {
330 self.render_queue.push_back(RenderTask {
331 pos,
332 shape: Shapes::Texture {
333 texture,
334 width,
335 height,
336 },
337 render_parameter_state: self.render_parameter_state,
338 })
339 }
340
341 /// Draw a texture `texture` at position `pos` with width of `width`,
342 /// and height according to the aspect ratio of the texture.
343 /// The origin of the texture is at it's top left.
344 /// Textures can be loaded with [`Window::load_texture`].
345 /// # Examples
346 /// ```
347 /// let mut pg = Window::new_fullscreen();
348 /// let my_texture = pg.load_texture("my_texture.png").unwrap();
349 /// loop {
350 /// pg.texture_((100., 250.), my_texture, 100.);
351 ///
352 /// pg.update();
353 /// }
354 /// ```
355 pub fn texture_(&mut self, pos: (f32, f32), texture: Texture, width: f32) {
356 self.render_queue.push_back(RenderTask {
357 pos,
358 shape: Shapes::Texture {
359 texture,
360 width,
361 height: width / texture.aspect(),
362 },
363 render_parameter_state: self.render_parameter_state,
364 })
365 }
366
367 /// Draw text `string` at position `pos`.
368 /// The default font size is 16 and can be changed with [`Window::font_size`].
369 /// The default font color is [`Color::BLACK`] and can be changed with [`Window::font_color`].
370 /// Uses the default font built into the library (Processing Sans Pro)
371 /// or the font set with [`Window::font`].
372 /// # Examples
373 /// ```
374 /// let mut pg = Window::new(720, 480, "Window Title");
375 /// loop {
376 /// pg.fill_color(Color::BLACK);
377 /// pg.text((10., 10.), "Hello World!");
378 /// pg.update();
379 /// }
380 /// ```
381 pub fn text(&mut self, pos: (f32, f32), string: &str) {
382 self.render_queue.push_back(RenderTask {
383 pos,
384 shape: Shapes::Text {
385 string: string.to_string(),
386 font: self.font,
387 },
388 render_parameter_state: self.render_parameter_state,
389 })
390 }
391
392 /// Draw a line from position `from` to position `to`.
393 /// The line's color is set with [`Window::line_color`].
394 pub fn line(&mut self, from: (f32, f32), to: (f32, f32)) {
395 match self.render_queue.back_mut() {
396 Some(RenderTask {
397 shape: Shapes::Lines { coords },
398 render_parameter_state: color_state,
399 ..
400 }) if color_state.line_color
401 == self.render_parameter_state.line_color =>
402 {
403 coords.push(from);
404 coords.push(to);
405 }
406 _ => {
407 self.render_queue.push_back(RenderTask {
408 pos: (0., 0.),
409 shape: Shapes::Lines {
410 coords: vec![from, to],
411 },
412 render_parameter_state: self.render_parameter_state,
413 });
414 }
415 }
416 }
417
418 /// Whether the keyboard key `key` is currently held pressed.
419 /// # Examples
420 /// ```
421 /// if pg.key_pressed(Key::SPACE) {
422 /// /*...*/
423 /// }
424 /// ```
425 pub fn key_pressed(&self, key: Key) -> bool {
426 self.input_state.key_pressed(key)
427 }
428
429 /// Whether the keyboard key `key` has just been pressed in this frame.
430 /// # Examples
431 /// ```
432 /// if pg.key_just_pressed(Key::SPACE) {
433 /// /*...*/
434 /// }
435 /// ```
436 pub fn key_just_pressed(&self, key: Key) -> bool {
437 self.input_state.key_just_pressed(key)
438 }
439
440 /// Whether the mouse button `button` is currently held pressed.
441 /// # Examples
442 /// ```
443 /// if pg.mouse_pressed(Button::LEFT) {
444 /// /*...*/
445 /// }
446 /// ```
447 pub fn mouse_pressed(&self, button: Button) -> bool {
448 self.input_state.mouse_pressed(button)
449 }
450
451 /// Whether the mouse button `button` has just been pressed in this frame.
452 /// # Examples
453 /// ```
454 /// if pg.mouse_just_pressed(Button::LEFT) {
455 /// /*...*/
456 /// }
457 /// ```
458 pub fn mouse_just_pressed(&self, button: Button) -> bool {
459 self.input_state.mouse_just_pressed(button)
460 }
461
462 /// The current mouse position inside the window.
463 pub fn mouse_position(&self) -> (f32, f32) {
464 self.input_state.mouse_position()
465 }
466
467 /// The current cumulative scroll wheel state of the mouse.
468 pub fn mouse_wheel(&self) -> f32 {
469 self.input_state.mouse_wheel()
470 }
471
472 /// How much the scroll wheel has been scrolled in this frame.
473 pub fn mouse_wheel_delta(&self) -> f32 {
474 self.input_state.mouse_wheel_delta()
475 }
476
477 /// The width of the window, or the width of the screen in fullscreen mode.
478 pub fn width(&self) -> f32 {
479 self.window.size().x as f32
480 }
481
482 /// The height of the window, or the height of the screen in fullscreen mode.
483 pub fn height(&self) -> f32 {
484 self.window.size().y as f32
485 }
486
487 /// The time since the window has been created in seconds.
488 pub fn time(&self) -> f32 {
489 self.runtime
490 }
491
492 /// How much time has passed since the last frame.
493 pub fn deltatime(&self) -> f32 {
494 self.deltatime
495 }
496
497 /// Load a texture from path `path`.
498 /// A return value of `None` means that the texture could not be loaded.
499 /// On success, returns a [`Texture`] object that can be passed to the [`Window::texture`] function to draw the texture to the screen.
500 ///
501 /// # Examples
502 /// ```
503 /// let mut pg = Window::new_fullscreen();
504 /// let my_texture = pg.load_texture("my_texture.png").unwrap();
505 /// loop {
506 /// pg.texture_((100., 250.), my_texture, 100.);
507 ///
508 /// pg.update();
509 /// }
510 /// ```
511 pub fn load_texture(&mut self, path: &str) -> Option<Texture> {
512 texture_store_add(SfmlTexture::from_file(path)?)
513 }
514
515 /// Load a font from path `path`.
516 /// A return value of `None` means that the font could not be loaded.
517 /// On success, returns a [`Font`] object that can be passed to the [`Window::font`] function
518 /// to set the font to be used for drawing text with [`Window::text`].
519 ///
520 /// # Examples
521 /// ```
522 /// let mut pg = Window::new_fullscreen();
523 /// let my_font = pg.load_font("MyFont.ttf").unwrap();
524 /// pg.font(Some(my_font));
525 /// loop {
526 /// pg.text((20, 20), "This text is drawn in MyFont.");
527 ///
528 /// pg.update();
529 /// }
530 /// ```
531 pub fn load_font(&mut self, path: &str) -> Option<Font> {
532 font_store_add(SfmlFont::from_file(path)?)
533 }
534}