wasm_game_lib/graphics/
canvas.rs

1use super::drawable::Drawable;
2use super::color::Color;
3use super::image::Image;
4use wasm_bindgen::{JsCast, JsValue};
5
6/// A Canvas is an object on which you can draw.
7/// Only the main Canvas is displayed (returned by Window::init()).
8/// 
9/// # Example
10/// 
11/// ```rust
12/// use wasm_game_lib::graphics::window::Window;
13/// use wasm_game_lib::graphics::image::Image;
14/// use wasm_game_lib::graphics::sprite::Sprite;
15/// use wasm_game_lib::system::sleep;
16/// use std::time::Duration;
17/// 
18/// # async fn test() {
19/// // Create a sprite to demonstrate how to draw a sprite on the canvas
20/// let texture = Image::load("https://www.gravatar.com/avatar/419218774d04a581476ea1887a0921e0?s=128&d=identicon&r=PG").await.unwrap();
21/// let sprite = Sprite::<u32>::new((0,0), &texture, (150, 150));
22/// 
23/// // create the main canvas
24/// let (window, mut canvas) = Window::init(); 
25/// 
26/// loop {
27///     canvas.clear();         // clear the canvas at each iteration
28///     canvas.draw(&sprite);   // draw a sprite on the canvas
29///     // note that canvas.display() is not needed unlike a lot of graphics libraries
30///     
31///     // you may want to slow down the loop to keep your game at 60fps
32///     sleep(Duration::from_millis(16)).await; 
33/// }
34/// # }
35/// ```
36pub struct Canvas {
37    pub context: web_sys::CanvasRenderingContext2d,
38    pub(crate) element: web_sys::HtmlCanvasElement
39}
40
41impl Default for Canvas {
42    fn default() -> Self {
43        Self::new()
44    }
45}
46
47impl Canvas {
48    /// Create a canvas which will not be displayed.
49    /// To create a displayed canvas, see [Window::init()](../window/struct.Window.html#method.init).
50    /// Creating a undisplayed canvas can be useful because a canvas is drawable on another canvas.
51    pub fn new() -> Canvas {
52        let document = web_sys::window().unwrap().document().unwrap();
53        let element = document
54            .create_element("canvas")
55            .unwrap()
56            .dyn_into::<web_sys::HtmlCanvasElement>()
57            .unwrap();
58
59        let context = element
60            .get_context("2d")
61            .unwrap()
62            .unwrap()
63            .dyn_into::<web_sys::CanvasRenderingContext2d>()
64            .unwrap();
65
66        Canvas {
67            context,
68            element
69        }
70    }
71
72    /// Clear a part of the canvas.
73    pub fn clear_rect(&mut self, (x, y): (f64, f64), (w, h): (f64, f64)) {
74        self.context.clear_rect(x, y, w, h);
75    }
76
77    /// Clear all the canvas with a transparent black (white).
78    pub fn clear(&mut self) {
79        self.clear_rect(
80            (0.0, 0.0),
81            (
82                f64::from(self.element.width()),
83                f64::from(self.element.height()),
84            ),
85        )
86    }
87
88    /// Clear all the canvas with a visible black.
89    pub fn clear_with_black(&mut self) {
90        self.fill_rect(
91            (0.0, 0.0),
92            (
93                f64::from(self.element.width()),
94                f64::from(self.element.height()),
95            ),
96            Color::black()
97        );
98    }
99
100    /// Clear all the canvas with a [Color](../color/struct.Color.html).
101    pub fn clear_with_color(&mut self, color: Color) {
102        self.fill_rect(
103            (0.0, 0.0),
104            (
105                f64::from(self.element.width()),
106                f64::from(self.element.height()),
107            ),
108            color
109        );
110    }
111
112    /// Draw an object implementing the [Drawable trait](../drawable/trait.Drawable.html) on the canvas.
113    /// 
114    /// # Example
115    /// 
116    /// ```rust
117    /// # use wasm_game_lib::graphics::window::Window;
118    /// # use wasm_game_lib::graphics::image::Image;
119    /// # use wasm_game_lib::graphics::sprite::Sprite;
120    /// # use wasm_game_lib::system::sleep;
121    /// # use std::time::Duration;
122    /// # async fn test() {
123    /// // create a sprite to draw it on the canvas
124    /// let texture = Image::load("https://www.gravatar.com/avatar/419218774d04a581476ea1887a0921e0?s=128&d=identicon&r=PG").await.unwrap();
125    /// let sprite = Sprite::<u32>::new((0,0), &texture, (150, 150));
126    /// 
127    /// // create the main canvas
128    /// let (window, mut canvas) = Window::init(); 
129    /// 
130    /// // draw the sprite on the canvas
131    /// canvas.draw(&sprite);
132    /// # }
133    /// ```
134    /// 
135    /// See above for a more complete example.
136    pub fn draw(&mut self, object: &impl Drawable) {
137        object.draw_on_canvas(self);
138    }
139
140    /// Draw an image at a specific position.
141    /// This method is intended to be used inside the [Drawable trait](../drawable/trait.Drawable.html).
142    /// In the main code of your game, you should use a [Sprite](../sprite/struct.Sprite.html) and the [draw](#method.draw) method.
143    pub fn draw_image(&mut self, (x, y): (f64, f64), image: &Image) {
144        self.context
145            .draw_image_with_html_image_element(
146                image.get_html_element(),
147                x,
148                y,
149            )
150            .unwrap();
151    }
152
153    /// Draw a canvas at a specific position.
154    pub fn draw_canvas(&mut self, (x, y): (f64, f64), canvas: &Canvas) {
155        self.context
156            .draw_image_with_html_canvas_element(
157                &canvas.element,
158                x,
159                y,
160            )
161            .unwrap();
162    }
163
164    /// You can use the canvas rendering context to make advanced drawing
165    pub fn get_2d_canvas_rendering_context(&mut self) -> &mut web_sys::CanvasRenderingContext2d {
166        &mut self.context
167    }
168
169    /// You can use the html element to do advanced things
170    pub fn get_canvas_element(&self) -> &web_sys::HtmlCanvasElement {
171        &self.element
172    }
173
174    /// Fill a part of the canvas with a [Color](../color/struct.Color.html).
175    pub fn fill_rect(&mut self, (x, y): (f64, f64), (w, h): (f64, f64), color: Color) {
176        self.context.set_fill_style(&JsValue::from_str(&color.to_string()));
177        self.context.fill_rect(x, y, w, h);
178    }
179
180    /// Print text on the canvas.
181    /// The [Text](../text/struct.Text.html) struct is a better way to print text.
182    pub fn fill_text(&mut self, (x, y): (usize, usize), text: &str, max_width: Option<usize>) {
183        if let Some(max_width) = max_width {
184            self.context.fill_text_with_max_width(text, x as f64, y as f64, max_width as f64).unwrap();
185        } else {
186            self.context.fill_text(text, x as f64, y as f64).unwrap();
187        }
188    }
189    
190    /// Set the canvas width in pixels
191    pub fn set_width(&mut self, width: u32) {
192        self.element.set_width(width);
193    }
194
195    /// Set the canvas height in pixels
196    pub fn set_height(&mut self, height: u32) {
197        self.element.set_height(height);
198    }
199
200    /// Return the actual canvas width in pixels
201    pub fn get_width(&self) -> u32 {
202        self.element.width()
203    }
204
205    /// Return the actual canvas height in pixels
206    pub fn get_height(&self) -> u32 {
207        self.element.height()
208    }
209
210    /// Return the width and the height of a canvas.
211    pub fn get_size(&self) -> (u32, u32) {
212        (self.element.width(), self.element.height())
213    }
214}
215
216/// An enum representing a [lineCap mode](https://developer.mozilla.org/fr/docs/Web/API/CanvasRenderingContext2D/lineCap).
217/// You may want to use [LineStyle](struct.LineStyle.html) for a complete set of options.
218/// 
219/// # Example
220/// 
221/// ![line cap demonstration](https://media.prod.mdn.mozit.cloud/attachments/2012/07/09/236/50366ad18b04b40276d6ef95d76281b1/Canvas_linecap.png)
222pub enum LineCap {
223    /// The first line of the example above
224    Butt,
225    /// The second line of the example above
226    Round,
227    /// The third line of the example above
228    Square
229}
230
231impl ToString for LineCap {
232    fn to_string(&self) -> String {
233        match self {
234            LineCap::Butt => String::from("butt"),
235            LineCap::Round => String::from("round"),
236            LineCap::Square => String::from("square"),
237        }
238    }
239}
240
241/// An enum representing the [lineJoin mode](https://developer.mozilla.org/fr/docs/Web/API/CanvasRenderingContext2D/lineJoin).
242/// You may want to use [LineStyle](struct.LineStyle.html) for a complete set of options.
243/// 
244/// # Example
245/// 
246/// ![line join demonstration](https://media.prod.mdn.mozit.cloud/attachments/2012/07/09/237/2b7a14b3921934ae35486afd6ba6704a/Canvas_linejoin.png)
247pub enum LineJoin {
248    /// The first line of the example above
249    Round,
250    /// The second line of the example above
251    Bevel,
252    /// The third line of the example above
253    Miter,
254}
255
256impl ToString for LineJoin {
257    fn to_string(&self) -> String {
258        match self {
259            LineJoin::Miter => String::from("miter"),
260            LineJoin::Round => String::from("round"),
261            LineJoin::Bevel => String::from("bevel"),
262        }
263    }
264}
265
266/// A struct containing every line option.
267pub struct LineStyle {
268    /// The color of the line
269    pub color: Color,
270    /// The width of the line in pixels
271    pub size: f64,
272    /// The lineCap mode
273    pub cap: LineCap,
274    /// The lineJoin mode
275    pub join: LineJoin
276}
277
278impl LineStyle {
279    /// Apply these properties on a canvas
280    pub fn apply_on_canvas(&self, canvas: &mut Canvas) {
281        canvas.context.set_line_width(self.size);
282        canvas.context.set_stroke_style(&JsValue::from_str(&self.color.to_string()));
283        canvas.context.set_line_cap(&self.cap.to_string());
284        canvas.context.set_line_join(&self.join.to_string());
285    }
286}
287
288impl Default for LineStyle {
289    fn default() -> LineStyle {
290        LineStyle {
291            color: Color::black(),
292            size: 3.0,
293            cap: LineCap::Butt,
294            join: LineJoin::Miter
295        }
296    }
297}