ascii_forge/renderer/
render.rs

1use std::{fmt::Display, marker::PhantomData};
2
3use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
4
5use crate::prelude::*;
6
7/// A macro to simplify rendering lots of items at once.
8/// The Buffer can be anything that implements AsMut<Buffer>
9/// This render will return the location of which the last element finished rendering.
10/**
11`Example`
12```rust
13# use ascii_forge::prelude::*;
14# fn main() -> std::io::Result<()> {
15// Create a buffer
16let mut buffer = Buffer::new((32, 32));
17
18// Render This works! and Another Element! To the window's buffer
19render!(
20    buffer,
21        (16, 16) => [ "This works!" ],
22        (0, 0) => [ "Another Element!" ]
23);
24
25# Ok(())
26# }
27```
28*/
29#[macro_export]
30macro_rules! render {
31    ($buffer:expr, $( $loc:expr => [$($render:expr),* $(,)?]),* $(,)?  ) => {{
32        #[allow(unused_mut)]
33        let mut loc;
34        $(
35            loc = Vec2::from($loc);
36            $(loc = $render.render(loc, $buffer.as_mut()));*;
37            let _ = loc;
38        )*
39        loc
40    }};
41}
42
43/// The main system that will render an element at a location to the buffer.
44/// Render's return type is the location the render ended at.
45pub trait Render {
46    fn render(&self, loc: Vec2, buffer: &mut Buffer) -> Vec2;
47    fn size(&self) -> Vec2 {
48        let mut buf = Buffer::new((u16::MAX, u16::MAX));
49        render!(buf, vec2(0, 0) => [ self ]);
50        buf.shrink();
51        buf.size()
52    }
53}
54
55/* --------------- Implementations --------------- */
56impl Render for char {
57    fn render(&self, mut loc: Vec2, buffer: &mut Buffer) -> Vec2 {
58        buffer.set(loc, *self);
59        loc.x += self.width().unwrap_or(1).saturating_sub(1) as u16;
60        loc
61    }
62    fn size(&self) -> Vec2 {
63        vec2(self.width().unwrap_or(1) as u16, 1)
64    }
65}
66
67impl Render for &str {
68    fn render(&self, loc: Vec2, buffer: &mut Buffer) -> Vec2 {
69        render!(buffer, loc => [ StyledContent::new(ContentStyle::default(), self) ])
70    }
71    fn size(&self) -> Vec2 {
72        StyledContent::new(ContentStyle::default(), self).size()
73    }
74}
75
76impl<R: Render + 'static> From<R> for Box<dyn Render> {
77    fn from(value: R) -> Self {
78        Box::new(value)
79    }
80}
81
82impl<R: Into<Box<dyn Render>> + Clone> Render for Vec<R> {
83    fn render(&self, mut loc: Vec2, buffer: &mut Buffer) -> Vec2 {
84        let items: Vec<Box<dyn Render>> = self.iter().map(|x| x.clone().into()).collect();
85        for item in items {
86            loc = render!(buffer, loc => [ item ]);
87        }
88        loc
89    }
90}
91
92/// A Render type that doesn't get split. It purely renders the one item to the screen.
93/// Useful for multi-character emojis.
94pub struct CharString<D: Display, F: Into<StyledContent<D>> + Clone> {
95    pub text: F,
96    marker: PhantomData<D>,
97}
98
99impl<D: Display, F: Into<StyledContent<D>> + Clone> CharString<D, F> {
100    pub fn new(text: F) -> Self {
101        Self {
102            text,
103            marker: PhantomData {},
104        }
105    }
106}
107
108impl<D: Display, F: Into<StyledContent<D>> + Clone> Render for CharString<D, F> {
109    fn render(&self, loc: Vec2, buffer: &mut Buffer) -> Vec2 {
110        render!(buffer, loc => [ Cell::styled(self.text.clone().into()) ])
111    }
112}
113
114impl Render for String {
115    fn render(&self, loc: Vec2, buffer: &mut Buffer) -> Vec2 {
116        render!(buffer, loc => [ self.as_str() ])
117    }
118}
119
120impl<D: Display> Render for StyledContent<D> {
121    fn render(&self, mut loc: Vec2, buffer: &mut Buffer) -> Vec2 {
122        let base_x = loc.x;
123        for line in format!("{}", self.content()).split('\n') {
124            loc.x = base_x;
125            for chr in line.chars().collect::<Vec<char>>() {
126                buffer.set(loc, StyledContent::new(*self.style(), chr));
127                loc.x += chr.width().unwrap_or(1) as u16;
128            }
129            loc.y += 1;
130        }
131        loc.y -= 1;
132        loc
133    }
134    fn size(&self) -> Vec2 {
135        let mut width = 0;
136        let mut height = 0;
137        for line in format!("{}", self.content()).split('\n') {
138            width = line.chars().count().max(width);
139            height += line.width() as u16;
140        }
141        vec2(width as u16, height)
142    }
143}