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}