1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! Drawing methods.
//!
//! Provides a [Draw] trait as well standard draw methods.
//!
//! Provided [`PixState`] methods:
//!
//! - [`PixState::clear`]: Clear the render target to the current background [Color].
//! - [`PixState::save_canvas`]: Save the current render target out to a [png] file.
//!
//! # Example
//!
//! ```
//! # use pix_engine::prelude::*;
//! # struct App;
//! # impl PixEngine for App {
//! fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
//!     s.background(Color::ALICE_BLUE);
//!     s.clear();
//!     let rect = rect![0, 0, 100, 100];
//!     s.fill(Color::RED);
//!     s.stroke(Color::BLACK);
//!     s.rect(rect)?;
//!     Ok(())
//! }
//! # }
//! ```

use anyhow::Context;

use crate::{prelude::*, renderer::Rendering};
use log::info;
use std::{fs::File, io::BufWriter, path::Path};

/// Trait for objects that can be drawn to the screen.
pub trait Draw {
    /// Draw object to the current [`PixState`] canvas.
    ///
    /// # Errors
    ///
    /// If the renderer fails to draw to the current render target, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     let rect = rect![0, 0, 100, 100];
    ///     // The following two lines are equivalent.
    ///     s.rect(rect)?;
    ///     rect.draw(s)?;
    ///     Ok(())
    /// }
    /// # }
    /// ```
    fn draw(&self, s: &mut PixState) -> PixResult<()>;
}

impl PixState {
    /// Clears the render target to the current background [Color] set by [`PixState::background`].
    ///
    /// # Errors
    ///
    /// If the current render target is closed or dropped, then an error is returned.
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
    ///     s.background(Color::CADET_BLUE);
    ///     s.clear();
    ///     Ok(())
    /// }
    /// # }
    /// ```
    #[inline]
    pub fn clear(&mut self) -> PixResult<()> {
        self.renderer.set_draw_color(self.settings.background)?;
        self.renderer.clear()
    }

    /// Save a portion `src` of the currently rendered target to a [png] file. Passing `None` for
    /// `src` saves the entire target.
    ///
    /// # Errors
    ///
    /// Returns an error for any of the following:
    ///     - The current render target is closed or dropped.
    ///     - The renderer fails to read pixels from the current window target.
    ///     - An [`io::Error`] occurs attempting to create the [png] file.
    ///     - A [`png::EncodingError`] occurs attempting to write image bytes.
    ///
    /// [`io::Error`]: std::io::Error
    ///
    /// # Example
    ///
    /// ```
    /// # use pix_engine::prelude::*;
    /// # struct App;
    /// # impl PixEngine for App {
    /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
    /// fn on_key_pressed(&mut self, s: &mut PixState, event: KeyEvent) -> PixResult<bool> {
    ///     if let Key::S = event.key {
    ///         s.save_canvas(None, "test_image.png")?;
    ///     }
    ///     Ok(false)
    /// }
    /// # }
    /// ```
    pub fn save_canvas<P, R>(&mut self, src: R, path: P) -> PixResult<()>
    where
        P: AsRef<Path>,
        R: Into<Option<Rect<i32>>>,
    {
        info!("Saving canvas to {}", path.as_ref().display());
        if let Some(src) = src.into() {
            // Copy current texture target to a texture
            let bytes = self.renderer.to_bytes()?;
            let render_texture = self.create_texture(self.width()?, self.height()?, None)?;
            self.update_texture(render_texture, None, bytes, self.width()? as usize * 4)?;
            // Render the `src` rect from texture onto another texture, and save it
            let src_texture = self.create_texture(src.width() as u32, src.height() as u32, None)?;
            self.set_texture_target(src_texture)?;
            self.texture(render_texture, src, None)?;
            self.save_canvas(None, path)?;
            self.clear_texture_target();
            self.delete_texture(render_texture)?;
            self.delete_texture(src_texture)?;
            Ok(())
        } else {
            let path = path.as_ref();
            let png_file = BufWriter::new(File::create(path)?);
            let mut png = png::Encoder::new(png_file, self.width()?, self.height()?);
            png.set_color(PixelFormat::Rgba.into());
            png.set_depth(png::BitDepth::Eight);
            let mut writer = png
                .write_header()
                .with_context(|| format!("failed to write png header: {path:?}"))?;

            writer
                .write_image_data(&self.renderer.to_bytes()?)
                .with_context(|| format!("failed to write png data: {path:?}"))
        }
    }
}