tuit/
draw.rs

1//! # The draw module is responsible for the traits that let you draw to the screen
2//!
3//! Tuit itself does not handle I/O by itself. This means that Tuit users are left responsible
4//! for rendering the terminal as well as handling input and output.
5//!
6//! ## Example
7//! ```compile_fail
8//! use tuit::errors::Error;
9//! use tuit::prelude::*;
10//!
11//! #[derive(GpuMagic)]
12//! struct MyGPU {}
13//!
14//! impl Renderer for MyGPU {
15//!     fn render(&mut self, terminal: impl TerminalConst) -> tuit::Result<()> {
16//!         for character in terminal.cells() {
17//!             self.gpu_magic(character)
18//!         }
19//!
20//!         Ok(())
21//!     }
22//! }
23//! ```
24
25#[cfg(feature = "ansi_renderer")]
26use core::fmt::{Formatter, Write};
27#[cfg(feature = "ansi_renderer")]
28use anyhow::anyhow;
29#[cfg(feature = "ansi_renderer")]
30use crate::terminal::Cell;
31use crate::terminal::TerminalConst;
32
33/// This trait is written by the implementor and is responsible for rendering the terminal's data
34/// to the screen.
35///
36/// It only has one method, which is [`Renderer::render`].
37///
38/// The method receives a reference to a type that implements the [`TerminalConst`] trait, and uses the data within to render the terminal.
39///
40/// ```feature,stdout_render
41/// use tuit::draw::Target;
42/// use tuit::terminal::ConstantSize;
43///
44/// let my_terminal = ConstantSize::<20, 20>::new();
45/// let mut stdout = std::io::stdout();
46///
47/// stdout.render(&my_terminal).expect("Failed to draw to stdout");
48/// ```
49///
50pub trait Renderer {
51    /// This method gets called when the implementor calls [`TerminalConst::display`]
52    ///
53    /// ## Dummy render implementation:
54    ///
55    /// ```
56    /// use tuit::prelude::*;
57    /// use tuit::terminal::Cell;
58    /// struct MyGPU;
59    ///
60    /// impl MyGPU {
61    ///     pub fn gpu_magic(&mut self, character: &Cell) {
62    ///         // Huzzah!
63    ///     }
64    /// }
65    ///
66    /// impl Renderer for MyGPU {
67    ///     fn render(&mut self, terminal: impl TerminalConst) -> tuit::Result<()> {
68    ///         let cells = terminal.cells();
69    ///
70    ///         // Do some magic to render characters!
71    ///         for cell in cells {
72    ///             self.gpu_magic(cell)
73    ///         }
74    ///
75    ///         return Ok(());
76    ///     }
77    /// }
78    /// ```
79    ///
80    /// # Errors
81    ///
82    /// When you implement [`Renderer`], you can return an [`Err`] that will help you better cope
83    /// with render failures and may help with debugging.
84    fn render(&mut self, terminal: impl TerminalConst) -> crate::Result<()>;
85}
86
87/// Doesn't really do anything when [`Renderer::render`] is called. I mean... what would you
88/// expect a struct called [`DummyTarget`] to accomplish? World peace?
89pub struct DummyTarget;
90
91impl Renderer for DummyTarget {
92    fn render(&mut self, _terminal: impl TerminalConst) -> crate::Result<()> {
93        Ok(())
94    }
95}
96
97#[cfg(feature = "ansi_renderer")]
98/// A [`Renderer`] that takes in a writer and outputs ANSI escape codes to it to use for formatting.
99pub struct AnsiRenderer<T>(pub T);
100
101#[cfg(feature = "ansi_renderer")]
102impl<T: Write> Renderer for AnsiRenderer<T> {
103    fn render(&mut self, terminal: impl TerminalConst) -> crate::Result<()> {
104        let terminal_width = terminal.width();
105
106        let characters = terminal.cells();
107
108        for (idx, character_cell) in characters.enumerate() {
109            if idx % terminal_width == 0 {
110                writeln!(self.0).map_err(|e| anyhow!(e))?;
111            }
112
113            let mut character_cell = *character_cell;
114
115            // Protect against alignment issues that can arise from characters
116            // like `\0` or `\t` by replacing them with a space.
117            //
118            // FIXME: Wide characters not handled.
119            if character_cell.character.is_whitespace() || character_cell.character.is_control() {
120                character_cell.character = ' ';
121            }
122
123            write!(self.0, "{character_cell}").map_err(|e| anyhow!(e))?;
124        }
125
126        Ok(())
127    }
128}
129
130#[cfg(feature = "ansi_renderer")]
131impl core::fmt::Display for Cell {
132    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
133        use owo_colors::OwoColorize;
134
135        let owo_style: owo_colors::Style = self.style.into();
136
137        write!(f, "{}", self.character.style(owo_style))
138    }
139}