1use std::{fmt::Display, marker::PhantomData};
2
3use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
4
5use crate::prelude::*;
6
7#[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
43pub trait Render {
46 fn render(&self, loc: Vec2, buffer: &mut Buffer) -> Vec2;
48
49 fn size(&self) -> Vec2 {
51 let mut buf = Buffer::new((u16::MAX, u16::MAX));
52 render!(buf, vec2(0, 0) => [ self ]);
53 buf.shrink();
54 buf.size()
55 }
56
57 fn render_clipped(&self, loc: Vec2, clip_size: Vec2, buffer: &mut Buffer) -> Vec2 {
59 let mut buff = Buffer::new((100, 100));
60 render!(buff, vec2(0, 0) => [ self ]);
61 buff.shrink();
62
63 buff.render_clipped(loc, clip_size, buffer)
64 }
65}
66
67impl Render for char {
69 fn render(&self, mut loc: Vec2, buffer: &mut Buffer) -> Vec2 {
70 buffer.set(loc, *self);
71 loc.x += self.width().unwrap_or(1).saturating_sub(1) as u16;
72 loc
73 }
74
75 fn size(&self) -> Vec2 {
76 vec2(self.width().unwrap_or(1) as u16, 1)
77 }
78
79 fn render_clipped(&self, loc: Vec2, clip_size: Vec2, buffer: &mut Buffer) -> Vec2 {
80 let char_width = self.width().unwrap_or(1) as u16;
81
82 if clip_size.x >= char_width && clip_size.y >= 1 {
84 buffer.set(loc, *self);
85 vec2(loc.x + char_width, loc.y)
86 } else {
87 loc
88 }
89 }
90}
91
92impl Render for &str {
93 fn render(&self, loc: Vec2, buffer: &mut Buffer) -> Vec2 {
94 render!(buffer, loc => [ StyledContent::new(ContentStyle::default(), self) ])
95 }
96
97 fn size(&self) -> Vec2 {
98 StyledContent::new(ContentStyle::default(), self).size()
99 }
100
101 fn render_clipped(&self, loc: Vec2, clip_size: Vec2, buffer: &mut Buffer) -> Vec2 {
102 StyledContent::new(ContentStyle::default(), self).render_clipped(loc, clip_size, buffer)
103 }
104}
105
106impl<R: Render + 'static> From<R> for Box<dyn Render> {
107 fn from(value: R) -> Self {
108 Box::new(value)
109 }
110}
111
112impl<R: Into<Box<dyn Render>> + Clone> Render for Vec<R> {
113 fn render(&self, mut loc: Vec2, buffer: &mut Buffer) -> Vec2 {
114 let items: Vec<Box<dyn Render>> = self.iter().map(|x| x.clone().into()).collect();
115 for item in items {
116 loc = render!(buffer, loc => [ item ]);
117 }
118 loc
119 }
120
121 fn render_clipped(&self, mut loc: Vec2, clip_size: Vec2, buffer: &mut Buffer) -> Vec2 {
122 let start_loc = loc;
123 let items: Vec<Box<dyn Render>> = self.iter().map(|x| x.clone().into()).collect();
124
125 for item in items {
126 let used_x = loc.x.saturating_sub(start_loc.x);
128 let used_y = loc.y.saturating_sub(start_loc.y);
129
130 if used_y >= clip_size.y {
131 break;
132 }
133
134 let remaining_clip = vec2(
135 clip_size.x.saturating_sub(used_x),
136 clip_size.y.saturating_sub(used_y),
137 );
138
139 if remaining_clip.x == 0 || remaining_clip.y == 0 {
140 break;
141 }
142
143 loc = item.render_clipped(loc, remaining_clip, buffer);
144 }
145 loc
146 }
147}
148
149pub struct CharString<D: Display, F: Into<StyledContent<D>> + Clone> {
152 pub text: F,
153 marker: PhantomData<D>,
154}
155
156impl<D: Display, F: Into<StyledContent<D>> + Clone> CharString<D, F> {
157 pub fn new(text: F) -> Self {
158 Self {
159 text,
160 marker: PhantomData {},
161 }
162 }
163}
164
165impl<D: Display, F: Into<StyledContent<D>> + Clone> Render for CharString<D, F> {
166 fn render(&self, loc: Vec2, buffer: &mut Buffer) -> Vec2 {
167 render!(buffer, loc => [ Cell::styled(self.text.clone().into()) ])
168 }
169
170 fn render_clipped(&self, loc: Vec2, clip_size: Vec2, buffer: &mut Buffer) -> Vec2 {
171 let cell = Cell::styled(self.text.clone().into());
172 let cell_width = cell.width();
173
174 if clip_size.x >= cell_width && clip_size.y >= 1 {
176 buffer.set(loc, cell);
177 vec2(loc.x + cell_width, loc.y)
178 } else {
179 loc
180 }
181 }
182}
183
184impl Render for String {
185 fn render(&self, loc: Vec2, buffer: &mut Buffer) -> Vec2 {
186 render!(buffer, loc => [ self.as_str() ])
187 }
188
189 fn render_clipped(&self, loc: Vec2, clip_size: Vec2, buffer: &mut Buffer) -> Vec2 {
190 self.as_str().render_clipped(loc, clip_size, buffer)
191 }
192}
193
194impl<D: Display> Render for StyledContent<D> {
195 fn render(&self, mut loc: Vec2, buffer: &mut Buffer) -> Vec2 {
196 let base_x = loc.x;
197 for line in format!("{}", self.content()).split('\n') {
198 loc.x = base_x;
199 for chr in line.chars().collect::<Vec<char>>() {
200 buffer.set(loc, StyledContent::new(*self.style(), chr));
201 loc.x += chr.width().unwrap_or(1) as u16;
202 }
203 loc.y += 1;
204 }
205 loc.y -= 1;
206 loc
207 }
208
209 fn size(&self) -> Vec2 {
210 let mut width = 0;
211 let mut height = 0;
212 for line in format!("{}", self.content()).split('\n') {
213 width = line.chars().count().max(width);
214 height += line.width() as u16;
215 }
216 vec2(width as u16, height)
217 }
218
219 fn render_clipped(&self, mut loc: Vec2, clip_size: Vec2, buffer: &mut Buffer) -> Vec2 {
220 let base_x = loc.x;
221 let start_y = loc.y;
222 let mut lines_rendered = 0;
223
224 for line in format!("{}", self.content()).split('\n') {
225 if lines_rendered >= clip_size.y {
226 break;
227 }
228
229 loc.x = base_x;
230 let mut chars_rendered = 0;
231
232 for chr in line.chars().collect::<Vec<char>>() {
233 let chr_width = chr.width().unwrap_or(1) as u16;
234
235 if chars_rendered + chr_width > clip_size.x {
236 break;
237 }
238
239 buffer.set(loc, StyledContent::new(*self.style(), chr));
240 loc.x += chr_width;
241 chars_rendered += chr_width;
242 }
243
244 loc.y += 1;
245 lines_rendered += 1;
246 }
247
248 vec2(
249 base_x + lines_rendered.min(clip_size.x),
250 start_y + lines_rendered.min(clip_size.y),
251 )
252 }
253}