macroquad_canvas_2d/
lib.rs

1/*
2  Copyright 2024 Nicolas Cesar Sabbatini Vrech
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17use macroquad::prelude::*;
18
19pub struct Canvas2D {
20    camera: Camera2D,
21    width: f32,
22    height: f32,
23}
24
25impl Canvas2D {
26    /// Create a new canvas with the given width and height.
27    #[must_use]
28    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
29    pub fn new(width: f32, height: f32) -> Self {
30        let mut camera = Camera2D::from_display_rect(Rect::new(0.0, 0.0, width, height));
31        camera.render_target = Some(render_target(width as u32, height as u32));
32        // Temp fix or maybe I am doing something wrong
33        // https://github.com/not-fl3/macroquad/issues/171#issuecomment-880601087
34        camera.zoom.y = -camera.zoom.y;
35        Canvas2D {
36            camera,
37            width,
38            height,
39        }
40    }
41
42    /// Get width.
43    #[must_use]
44    pub fn width(&self) -> f32 {
45        self.width
46    }
47
48    /// Get height.
49    #[must_use]
50    pub fn height(&self) -> f32 {
51        self.height
52    }
53
54    /// Get width and height.
55    #[must_use]
56    pub fn width_height(&self) -> (f32, f32) {
57        (self.width, self.height)
58    }
59
60    /// Get a reference of the canvas texture.
61    #[must_use]
62    #[allow(clippy::missing_panics_doc)]
63    pub fn get_texture(&self) -> &Texture2D {
64        &self.camera.render_target.as_ref().unwrap().texture
65    }
66
67    /// Get a mutable reference of the canvas texture.
68    #[must_use]
69    #[allow(clippy::missing_panics_doc)]
70    pub fn get_texture_mut(&mut self) -> &mut Texture2D {
71        &mut self.camera.render_target.as_mut().unwrap().texture
72    }
73
74    /// Set the canvas as te default camera to draw
75    /// if you want to draw to the screen you should call.
76    /// `macroquad::camera::set_default_camera()`
77    pub fn set_camera(&self) {
78        set_camera(&self.camera);
79    }
80
81    /// Calculate size and padding of the canvas so it can fit inside
82    /// of the target and its position is in the center.
83    #[must_use]
84    pub fn calculate_size_and_padding(
85        &self,
86        target_width: f32,
87        target_height: f32,
88    ) -> (f32, f32, Vec2) {
89        let new_size: Vec2 = self.calculate_size(target_width, target_height);
90
91        // Calculate padding
92        let left_padding: f32 = (target_width - new_size.x) / 2.0;
93        let top_padding: f32 = (target_height - new_size.y) / 2.0;
94
95        (left_padding, top_padding, new_size)
96    }
97
98    /// Calculate size of the canvas so it can fit inside of the target
99    /// respecting the aspect ratio.
100    #[must_use]
101    pub fn calculate_size(&self, target_width: f32, target_height: f32) -> Vec2 {
102        let min_scale_factor: f32 = self.calculate_min_scale_factor(target_width, target_height);
103
104        // Calculate windows new size
105        let new_width: f32 = self.width * min_scale_factor;
106        let new_height: f32 = self.height * min_scale_factor;
107
108        Vec2::new(new_width, new_height)
109    }
110
111    /// Calculate the minimum scale factor so the canvas can fit inside of the target
112    /// respecting the aspect ratio of the canvas.
113    #[must_use]
114    pub fn calculate_min_scale_factor(&self, target_width: f32, target_height: f32) -> f32 {
115        let (scale_factor_w, scale_factor_h) =
116            self.calculate_scale_factor(target_width, target_height);
117        f32::min(scale_factor_w, scale_factor_h)
118    }
119
120    /// Calculate scale factor so the canvas can fit inside of the target.
121    #[must_use]
122    pub fn calculate_scale_factor(&self, target_width: f32, target_height: f32) -> (f32, f32) {
123        (target_width / self.width, target_height / self.height)
124    }
125
126    /// Convert from the parent coordinates to canvas coordinates.
127    ///
128    /// Warning it can return negative numbers or values grater than the canvas
129    /// when the mouse is outside of the canvas.
130    #[must_use]
131    pub fn parent_coordinates_to_canvas_coordinates(
132        &self,
133        parent_width: f32,
134        parent_height: f32,
135        screen_x: f32,
136        screen_y: f32,
137        offset_x: f32,
138        offset_y: f32,
139    ) -> (f32, f32) {
140        let scale_factor = self.calculate_min_scale_factor(parent_width, parent_height);
141
142        let camera_offset = self.camera.offset / self.camera.zoom;
143
144        let x = (screen_x - offset_x) / scale_factor - camera_offset.x;
145        let y = (screen_y - offset_y) / scale_factor - camera_offset.y;
146        (x, y)
147    }
148
149    /// Convert from the canvas coordinates to parent coordinates.
150    ///
151    /// Warning do to float division it can be a small margin of error.
152    #[must_use]
153    pub fn canvas_coordinates_to_parent_coordinates(
154        &self,
155        parent_width: f32,
156        parent_height: f32,
157        canvas_x: f32,
158        canvas_y: f32,
159        offset_x: f32,
160        offset_y: f32,
161    ) -> (f32, f32) {
162        let scale_factor = self.calculate_min_scale_factor(parent_width, parent_height);
163        let camera_offset = self.camera.offset / self.camera.zoom;
164
165        let x = ((canvas_x + camera_offset.x) * scale_factor) + offset_x;
166        let y = ((canvas_y + camera_offset.y) * scale_factor) + offset_y;
167        (x, y)
168    }
169
170    /// A wrapper around the `parent_to_canvas` for better ergonomic.
171    /// Convert from the screen coordinates to canvas coordinates.
172    ///
173    /// Warning it can return negative numbers or values grater than the canvas
174    /// when the mouse is outside of the canvas.
175    #[must_use]
176    pub fn screen_coordinates_to_canvas_coordinates(
177        &self,
178        screen_x: f32,
179        screen_y: f32,
180        offset_x: f32,
181        offset_y: f32,
182    ) -> (f32, f32) {
183        self.parent_coordinates_to_canvas_coordinates(
184            screen_width(),
185            screen_height(),
186            screen_x,
187            screen_y,
188            offset_x,
189            offset_y,
190        )
191    }
192
193    /// A wrapper around the `canvas_to_parent` for better ergonomic.
194    /// Convert from the canvas coordinates to screen coordinates.
195    ///
196    /// Warning do to float division it can be a small margin of error.
197    #[must_use]
198    pub fn canvas_coordinates_to_screen_coordinates(
199        &self,
200        canvas_x: f32,
201        canvas_y: f32,
202        offset_x: f32,
203        offset_y: f32,
204    ) -> (f32, f32) {
205        self.canvas_coordinates_to_parent_coordinates(
206            screen_width(),
207            screen_height(),
208            canvas_x,
209            canvas_y,
210            offset_x,
211            offset_y,
212        )
213    }
214
215    /// Get the mouse position in canvas coordinates.
216    ///
217    /// Warning it can return negative numbers or values grater than the canvas
218    #[must_use]
219    pub fn screen_mouse_position_to_canvas(&self, offset_x: f32, offset_y: f32) -> (f32, f32) {
220        let (x, y) = mouse_position();
221        self.screen_coordinates_to_canvas_coordinates(x, y, offset_x, offset_y)
222    }
223
224    /// Draws the canvas to the middle of the screen, keeping the aspect ratio.
225    /// It calls `set_default_camera` before drawing.
226    pub fn draw_to_screen(&self) {
227        set_default_camera();
228        // Get canvas dimensions and padding
229        let (left_padding, top_padding, dimensions) =
230            self.calculate_size_and_padding(screen_width(), screen_height());
231
232        // Draw canvas on screen
233        draw_texture_ex(
234            self.get_texture(),
235            left_padding,
236            top_padding,
237            WHITE,
238            DrawTextureParams {
239                dest_size: Some(dimensions),
240                ..Default::default()
241            },
242        );
243    }
244
245    /// Zoom in/out the camera.
246    pub fn zoom(&mut self, zoom: f32) {
247        self.camera.zoom += zoom;
248    }
249
250    /// Set the camara Zoom.
251    pub fn set_zoom(&mut self, zoom: f32) {
252        self.camera.zoom = vec2(zoom, zoom);
253    }
254
255    /// Rotate the camera by a factor.
256    /// The angle is in degrees.
257    pub fn rotate(&mut self, angle: f32) {
258        self.camera.rotation += angle;
259    }
260
261    /// Set the camara rotation.
262    /// The angle is in degrees.
263    pub fn set_rotation(&mut self, angle: f32) {
264        self.camera.rotation = angle;
265    }
266
267    /// Move the camera by a amount in pixels.
268    pub fn move_camera_by(&mut self, x_amount: f32, y_amount: f32) {
269        let offset = vec2(x_amount * self.camera.zoom.x, y_amount * self.camera.zoom.y);
270        self.camera.offset += offset;
271    }
272
273    /// Move the camera to the given position.
274    pub fn move_camera_to(&mut self, x_position: f32, y_position: f32) {
275        let offset = vec2(
276            x_position * self.camera.zoom.x,
277            y_position * self.camera.zoom.y,
278        );
279        self.camera.offset = offset;
280    }
281}