simple_game_engine/
canvas.rs

1//! Provides the [`Canvas`] struct, which allows the screen to be manipulated, such as by drawing
2//! points, lines, rectangles, text, or textures to it.
3
4use std::ops::{Deref, DerefMut};
5
6#[cfg(feature = "unifont")]
7use sdl2::{pixels::Color, render::Texture};
8use sdl2::{
9    rect::Point,
10    render::{Canvas as SdlCanvas, RenderTarget, TextureCreator},
11    surface::{Surface, SurfaceContext},
12    video::{Window, WindowContext},
13};
14#[cfg(feature = "unifont")]
15use sdl2_unifont::renderer::SurfaceRenderer as TextRenderer;
16
17/// A [`Canvas`] that internally renders to a [`Surface`][sdl2::surface::Surface].
18pub type SurfaceCanvas<'a> = Canvas<Surface<'a>, SurfaceContext<'a>>;
19/// A [`Canvas`] that renders to a [`Window`][Window] on the screen.
20pub type WindowCanvas = Canvas<Window, WindowContext>;
21
22/// This struct allows you to draw to the screen in various ways. It is a wrapper around:
23/// * An [sdl2 `Canvas`][SdlCanvas], which allows you to draw points, lines, rectangles, etc, and to "blit"
24///   textures and surfaces onto the screen.
25/// * An [sdl2 `TextureCreator`][TextureCreator], which is linked to the sdl2 `Canvas`, for creating textures.
26/// * An [sdl2-unifont `SurfaceRenderer`][TextRenderer] for rendering text to a surface.
27/// This struct implements [`Deref`][std::ops::Deref] and [`DerefMut`][std::ops::DerefMut] for the sdl2 `Canvas`, so you can call any of the
28/// normal drawing routines via deref coersion.
29pub struct Canvas<T: RenderTarget, U> {
30    inner: SdlCanvas<T>,
31    texture_creator: TextureCreator<U>,
32    #[cfg(feature = "unifont")]
33    text_renderer: TextRenderer,
34    #[cfg(feature = "unifont")]
35    synced_colors: bool,
36}
37
38impl WindowCanvas {
39    /// Create a new `Canvas` from the specified sdl2 `WindowCanvas` that draws to a window on the
40    /// screen
41    pub fn new(inner: SdlCanvas<Window>) -> Self {
42        let texture_creator = inner.texture_creator();
43        #[cfg(feature = "unifont")]
44        let text_renderer = TextRenderer::new(inner.draw_color(), Color::RGBA(0, 0, 0, 0));
45        Self {
46            inner,
47            texture_creator,
48            #[cfg(feature = "unifont")]
49            text_renderer,
50            #[cfg(feature = "unifont")]
51            synced_colors: true,
52        }
53    }
54}
55
56impl<'a> SurfaceCanvas<'a> {
57    /// Create a new `Canvas` from an sdl2 `SurfaceCanvas`, that draws internally to an sdl2
58    /// `Surface`.
59    pub fn new(inner: SdlCanvas<Surface<'a>>) -> Self {
60        let texture_creator = inner.texture_creator();
61        #[cfg(feature = "unifont")]
62        let text_renderer = TextRenderer::new(inner.draw_color(), Color::RGBA(0, 0, 0, 0));
63        Self {
64            inner,
65            texture_creator,
66            #[cfg(feature = "unifont")]
67            text_renderer,
68            #[cfg(feature = "unifont")]
69            synced_colors: true,
70        }
71    }
72
73    /// Draw the specified text to a point on the screen. Returns a
74    /// [`Surface`][Surface] representing the
75    /// rendered text, or a `String` indicating an error from sdl.
76    ///
77    /// This is a `Canvas<Surface>` specific alternative to [`draw_text`][Self::draw_text], which internally creates
78    /// a texture for the rendered text.
79    #[cfg(feature = "unifont")]
80    pub fn draw_text_surface<P: Into<Point>>(
81        &mut self,
82        text: &str,
83        pos: P,
84    ) -> Result<Surface, String> {
85        let pos = pos.into();
86        let surface = self.text_renderer.draw(text)?;
87        let mut rect = surface.rect();
88        rect.set_x(pos.x());
89        rect.set_y(pos.y());
90        surface.blit(None, self.inner.surface_mut(), rect)?;
91        Ok(surface)
92    }
93}
94
95impl<T: RenderTarget, U> Canvas<T, U> {
96    /// Returns an immutable reference to the [`TextureCreator`][TextureCreator] associated with this canvas.
97    pub fn texture_creator(&self) -> &TextureCreator<U> {
98        &self.texture_creator
99    }
100
101    /// Returns a mutable reference to the [`TextureCreator`][TextureCreator] associated with this canvas.
102    pub fn texture_creator_mut(&mut self) -> &mut TextureCreator<U> {
103        &mut self.texture_creator
104    }
105
106    /// Returns an immutable reference to the [sdl2-unifont `SurfaceRenderer`][TextRenderer] for text rendering, associated with this canvas.
107    #[cfg(feature = "unifont")]
108    pub fn text_renderer(&self) -> &TextRenderer {
109        &self.text_renderer
110    }
111
112    /// Returns a mutable reference to the [sdl2-unifont `SurfaceRenderer`][TextRenderer] for text rendering, associated with this canvas.
113    #[cfg(feature = "unifont")]
114    pub fn text_renderer_mut(&mut self) -> &mut TextRenderer {
115        &mut self.text_renderer
116    }
117
118    /// Set the draw color for the standard sdl2 `canvas` drawing routines. If colors are
119    /// synchronised (I.E. `canvas.set_text_color(None)`), Also changes the default text color.
120    #[cfg(feature = "unifont")]
121    pub fn set_draw_color<C: Into<Color>>(&mut self, color: C) {
122        let color = color.into();
123        if self.synced_colors {
124            self.text_renderer.fg_color = color;
125        }
126        self.inner.set_draw_color(color)
127    }
128
129    /// If called with `Some(color)`, set the color used when rendering text. If called with `None`,
130    /// resynchronises the drawing and text colors.
131    #[cfg(feature = "unifont")]
132    pub fn set_text_color<C>(&mut self, color: C)
133    where
134        C: Into<Option<Color>>,
135    {
136        self.synced_colors = if let Some(color) = color.into() {
137            self.text_renderer.fg_color = color;
138            false
139        } else {
140            self.text_renderer.fg_color = self.inner.draw_color();
141            true
142        };
143    }
144
145    /// Draw the specified text to a point on the screen. Returns a [`Texture`] representing the
146    /// rendered text, or a `String` indicating an error from sdl.
147    #[cfg(feature = "unifont")]
148    pub fn draw_text<P: Into<Point>>(&mut self, text: &str, pos: P) -> Result<Texture, String> {
149        let pos = pos.into();
150        let surface = self.text_renderer.draw(text)?;
151        let texture = surface.as_texture(&mut self.texture_creator).unwrap();
152        let mut rect = surface.rect();
153        rect.set_x(pos.x());
154        rect.set_y(pos.y());
155        self.inner.copy(&texture, None, rect)?;
156        Ok(texture)
157    }
158
159    fn draw_circle_points(&mut self, center: Point, point: Point) -> Result<(), String> {
160        let points = &[
161            Point::new(center.x() + point.x(), center.y() + point.y()),
162            Point::new(center.x() - point.x(), center.y() + point.y()),
163            Point::new(center.x() + point.x(), center.y() - point.y()),
164            Point::new(center.x() - point.x(), center.y() - point.y()),
165            Point::new(center.x() + point.y(), center.y() + point.x()),
166            Point::new(center.x() - point.y(), center.y() + point.x()),
167            Point::new(center.x() + point.y(), center.y() - point.x()),
168            Point::new(center.x() - point.y(), center.y() - point.x()),
169        ];
170        self.inner.draw_points(points.as_ref())
171    }
172
173    fn fill_circle_lines(&mut self, center: Point, point: Point) -> Result<(), String> {
174        let points = &[
175            Point::new(center.x() + point.x(), center.y() + point.y()),
176            Point::new(center.x() - point.x(), center.y() + point.y()),
177            Point::new(center.x() + point.x(), center.y() - point.y()),
178            Point::new(center.x() - point.x(), center.y() - point.y()),
179            Point::new(center.x() + point.y(), center.y() + point.x()),
180            Point::new(center.x() - point.y(), center.y() + point.x()),
181            Point::new(center.x() + point.y(), center.y() - point.x()),
182            Point::new(center.x() - point.y(), center.y() - point.x()),
183        ];
184        self.inner.draw_lines(points.as_ref())
185    }
186
187    /// Draws a circle outline using Bresenham's algorithm, with the given center and radius.
188    pub fn draw_circle<P>(&mut self, center: P, radius: i32) -> Result<(), String>
189    where
190        P: Into<Point>,
191    {
192        let center = center.into();
193        let mut current = Point::new(0, radius);
194        let mut d = 3 - 2 * radius;
195        self.draw_circle_points(center, current)?;
196        while current.y() >= current.x() {
197            // for each pixel we will draw all eight pixels
198            current = current.offset(1, 0);
199
200            // check for decision parameter and correspondingly update d, x, y
201            if d > 0 {
202                current = current.offset(0, -1);
203                d += 4 * (current.x() - current.y()) + 10;
204            } else {
205                d += 4 * current.x() + 6;
206                self.draw_circle_points(center, current)?;
207            }
208        }
209        Ok(())
210    }
211
212    /// Draws a filled circle using Bresenham's algorithm, with the given center and radius.
213    pub fn fill_circle<P>(&mut self, center: P, radius: i32) -> Result<(), String>
214    where
215        P: Into<Point>,
216    {
217        let center = center.into();
218        let mut current = Point::new(0, radius);
219        let mut d = 3 - 2 * radius;
220        self.fill_circle_lines(center, current)?;
221        while current.y() >= current.x() {
222            // for each pixel we will draw all eight pixels
223            current = current.offset(1, 0);
224
225            // check for decision parameter and correspondingly update d, x, y
226            if d > 0 {
227                current = current.offset(0, -1);
228                d += 4 * (current.x() - current.y()) + 10;
229            } else {
230                d += 4 * current.x() + 6;
231                self.fill_circle_lines(center, current)?;
232            }
233        }
234        Ok(())
235    }
236}
237
238impl<T: RenderTarget, U> Deref for Canvas<T, U> {
239    type Target = SdlCanvas<T>;
240
241    fn deref(&self) -> &Self::Target {
242        &self.inner
243    }
244}
245
246impl<T: RenderTarget, U> DerefMut for Canvas<T, U> {
247    fn deref_mut(&mut self) -> &mut Self::Target {
248        &mut self.inner
249    }
250}