ferravex_render/
canvas.rs1use crate::prelude::*;
3
4#[derive(Debug, Clone, Copy)]
7pub struct Canvas2D {
8 pub size: Vec2,
9 pub screen: Vec2,
10 pub scale: f32,
11 pub offset: Vec2,
12}
13
14impl Canvas2D {
15 pub fn fit(size: Vec2, screen: Vec2) -> Self {
16 assert!(
17 size.is_finite()
18 && screen.is_finite()
19 && size.min_element() > 0.0
20 && screen.min_element() > 0.0,
21 "canvas and screen dimensions must be finite and positive"
22 );
23 let scale = (screen / size).min_element();
24 Self {
25 size,
26 screen,
27 scale,
28 offset: (screen - size * scale) * 0.5,
29 }
30 }
31
32 pub fn project(self, point: Vec2) -> Vec2 {
33 self.offset + point * self.scale
34 }
35 pub fn unproject(self, point: Vec2) -> Vec2 {
36 (point - self.offset) / self.scale
37 }
38
39 pub fn pointer(self, point: Vec2) -> Option<Vec2> {
41 let p = self.unproject(point);
42 (p.is_finite() && p.x >= 0.0 && p.y >= 0.0 && p.x <= self.size.x && p.y <= self.size.y)
43 .then_some(p)
44 }
45
46 pub fn camera(self) -> Camera2D {
47 Camera2D {
48 target: self.size * 0.5,
49 zoom: Vec2::splat(2.0 * self.scale) / self.screen,
51 ..Default::default()
52 }
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 #[test]
60 fn input_tracks_letterboxed_drawing_after_resize() {
61 for screen in [
62 vec2(1280.0, 800.0),
63 vec2(390.0, 844.0),
64 vec2(2560.0, 1440.0),
65 ] {
66 let canvas = Canvas2D::fit(vec2(1280.0, 800.0), screen);
67 for point in [Vec2::ZERO, vec2(640.0, 400.0), vec2(1280.0, 800.0)] {
68 assert!(canvas.unproject(canvas.project(point)).distance(point) < 0.001);
69 }
70 assert!(canvas.project(canvas.size * 0.5).distance(screen * 0.5) < 0.001);
71 assert!(canvas.pointer(vec2(-1.0, -1.0)).is_none());
72 assert_eq!(canvas.pointer(screen * 0.5), Some(canvas.size * 0.5));
73 assert!(canvas.camera().zoom.y > 0.0);
74 }
75 }
76}