macroquad_canvas/
lib.rs

1#![forbid(unsafe_code)]
2#![warn(
3    clippy::cargo_common_metadata,
4    clippy::cloned_instead_of_copied,
5    clippy::equatable_if_let,
6    clippy::if_then_some_else_none,
7    clippy::lossy_float_literal,
8    clippy::map_unwrap_or,
9    missing_docs,
10    clippy::doc_link_with_quotes,
11    clippy::doc_markdown,
12    clippy::missing_errors_doc,
13    clippy::missing_panics_doc
14)]
15#![doc = include_str!("../README.md")]
16
17//---------------------------------------------------------------------
18
19use macroquad::prelude::*;
20use std::ops::{Deref, DerefMut};
21
22//---------------------------------------------------------------------
23
24/// Fixed size 2D canvas
25///
26/// # Description
27///
28/// `Canvas2D` is basicaly a wrapper around `Camera2D` with convinience
29/// methods to make life easier.
30///
31/// # Note
32///
33/// `Deref` and `DerefMut` traits are implemented; this means you can access camera's fields and
34/// methods directly from canvas like so:
35///
36/// ```rust,no_run
37/// # use macroquad_canvas::Canvas2D;
38/// let canvas = Canvas2D::new(800_f32, 600_f32);
39///
40/// // These lines do the same thing
41/// println!("{}", canvas.zoom);
42/// println!("{}", canvas.camera.zoom);
43/// ```
44///
45/// # Implementation Detail
46///
47/// There's a [bug](https://github.com/not-fl3/macroquad/issues/171#issuecomment-880601087) that
48/// mirrors render target on the Y axis, as a workaround, the render target gets flipped vertically.
49pub struct Canvas2D {
50    /// The wrapped `Camera2D` necessary for all the calculations
51    pub camera: Camera2D,
52}
53
54impl Deref for Canvas2D {
55    type Target = Camera2D;
56
57    fn deref(&self) -> &Self::Target {
58        &self.camera
59    }
60}
61
62impl DerefMut for Canvas2D {
63    fn deref_mut(&mut self) -> &mut Self::Target {
64        &mut self.camera
65    }
66}
67
68impl Canvas2D {
69    /// Creates a new canvas.
70    ///
71    /// # Why does it take floats instead of integers?
72    ///
73    /// The reason it takes floats and not integers is because
74    /// Macroquad uses floats in (almost) all of its functions.
75    pub fn new(width: f32, height: f32) -> Self {
76        let mut camera = Camera2D::from_display_rect(Rect::new(0.0, 0.0, width, height));
77        camera.render_target = Some(render_target(width as u32, height as u32));
78        // Flip vertically
79        camera.zoom.y = -camera.zoom.y;
80
81        Self { camera }
82    }
83
84    /// Draws canvas to the screen.
85    #[inline]
86    pub fn draw(&self) {
87        self.draw_ex(screen_width(), screen_height());
88    }
89
90    /// Draws canvas with target width/height.
91    pub fn draw_ex(&self, target_width: f32, target_height: f32) {
92        let (left_padding, top_padding, dimensions) =
93            self.get_size_and_padding(target_width, target_height);
94
95        draw_texture_ex(
96            self.get_texture(),
97            left_padding,
98            top_padding,
99            WHITE,
100            DrawTextureParams {
101                dest_size: Some(dimensions),
102                ..Default::default()
103            },
104        );
105    }
106
107    /// Returns canvas width.
108    #[inline]
109    pub fn width(&self) -> f32 {
110        self.get_texture().width()
111    }
112
113    /// Returns canvas height.
114    #[inline]
115    pub fn height(&self) -> f32 {
116        self.get_texture().height()
117    }
118
119    /// Returns mouse position on the canvas
120    #[inline]
121    pub fn mouse_position(&self) -> (f32, f32) {
122        self.mouse_position_ex(screen_width(), screen_height())
123    }
124
125    /// Returns mouse position with target width/height.
126    pub fn mouse_position_ex(&self, target_width: f32, target_height: f32) -> (f32, f32) {
127        // Mouse position on screen
128        let (mouse_x, mouse_y) = mouse_position();
129
130        let scale = self.get_min_scale_factor(target_width, target_height);
131
132        // Mouse position on canvas
133        let virtual_mouse_x = (mouse_x - (target_width - (self.width() * scale)) * 0.5) / scale;
134        let virtual_mouse_y = (mouse_y - (target_height - (self.height() * scale)) * 0.5) / scale;
135
136        (
137            virtual_mouse_x.clamp(0.0, self.width()).floor(),
138            virtual_mouse_y.clamp(0.0, self.height()).floor(),
139        )
140    }
141
142    /// Returns a reference to the canvas texture.
143    #[inline]
144    #[allow(clippy::missing_panics_doc)]
145    pub fn get_texture(&self) -> &Texture2D {
146        &self.render_target.as_ref().unwrap().texture
147    }
148
149    /// Returns a mutable reference to the canvas texture.
150    #[inline]
151    #[allow(clippy::missing_panics_doc)]
152    pub fn get_texture_mut(&mut self) -> &mut Texture2D {
153        &mut self.render_target.as_mut().unwrap().texture
154    }
155
156    /// Calculate size of the canvas so it can fit inside of the target.
157    pub fn get_size(&self, target_width: f32, target_height: f32) -> Vec2 {
158        // Get the min scale factor
159        let min_scale_factor: f32 = self.get_min_scale_factor(target_width, target_height);
160
161        // Calculate windows new size
162        let new_width: f32 = self.width() * min_scale_factor;
163        let new_height: f32 = self.height() * min_scale_factor;
164
165        Vec2::new(new_width, new_height)
166    }
167
168    /// Returns padding of the canvas.
169    ///
170    /// # Note
171    ///
172    /// Internally it uses [`Canvas2D::get_size_and_padding`] and simply drops the size,
173    /// so if you also need size, consider just using [`Canvas2D::get_size_and_padding`]
174    pub fn get_padding(&self, target_width: f32, target_height: f32) -> (f32, f32) {
175        let (left_padding, top_padding, _) = self.get_size_and_padding(target_width, target_height);
176
177        (left_padding, top_padding)
178    }
179
180    /// Returns size and padding of the canvas. Used in [`Canvas2D::draw_ex`] for fitting the canvas
181    /// on the screen and for centering.
182    pub fn get_size_and_padding(&self, target_width: f32, target_height: f32) -> (f32, f32, Vec2) {
183        let new_size: Vec2 = self.get_size(target_width, target_height);
184
185        // Calculate padding
186        let left_padding: f32 = (target_width - new_size.x) / 2.0;
187        let top_padding: f32 = (target_height - new_size.y) / 2.0;
188
189        (left_padding, top_padding, new_size)
190    }
191
192    /// Returns scale factors.
193    pub fn get_scale_factor(&self, target_width: f32, target_height: f32) -> (f32, f32) {
194        (target_width / self.width(), target_height / self.height())
195    }
196
197    /// Returns scale factors' minimum value.
198    pub fn get_min_scale_factor(&self, target_width: f32, target_height: f32) -> f32 {
199        let (scale_width, scale_height) = self.get_scale_factor(target_width, target_height);
200
201        f32::min(scale_width, scale_height)
202    }
203}