1use std::ops::{ControlFlow, Deref};
2
3use anathema_geometry::{LocalPos, Pos, Region, Size};
4use anathema_store::indexmap::IndexMap;
5use anathema_store::slab::SlabIndex;
6use anathema_value_resolver::{AttributeStorage, Attributes};
7use unicode_segmentation::{Graphemes, UnicodeSegmentation};
8use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
9
10use crate::layout::Display;
11use crate::layout::display::DISPLAY;
12use crate::nodes::element::Element;
13use crate::tree::{FilterOutput, WidgetPositionFilter};
14use crate::widget::Style;
15use crate::{PaintChildren, WidgetContainer, WidgetKind};
16
17pub type GlyphMap = IndexMap<GlyphIndex, String>;
18
19pub struct Glyphs<'a> {
20 inner: Graphemes<'a>,
21}
22
23impl<'a> Glyphs<'a> {
24 pub fn new(src: &'a str) -> Self {
25 let inner = src.graphemes(true);
26 Self { inner }
27 }
28
29 pub fn next(&mut self, map: &mut GlyphMap) -> Option<Glyph> {
30 let g = self.inner.next()?;
31 let mut chars = g.chars();
32 let c = chars.next()?;
33
34 match chars.next() {
35 None => Glyph::Single(c, c.width().unwrap_or(0) as u8),
36 Some(_) => {
37 let width = g.width();
38 let glyph = map.insert(g.into());
39 Glyph::Cluster(glyph, width as u8)
40 }
41 }
42 .into()
43 }
44}
45
46#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
47pub enum Glyph {
48 Single(char, u8),
50 Cluster(GlyphIndex, u8),
52}
53
54impl Glyph {
55 pub fn space() -> Self {
56 Self::Single(' ', 1)
57 }
58
59 pub fn is_newline(&self) -> bool {
60 matches!(self, Self::Single('\n', _))
61 }
62
63 pub fn width(&self) -> usize {
64 match self {
65 Glyph::Single(_, width) | Glyph::Cluster(_, width) => *width as usize,
66 }
67 }
68
69 pub const fn from_char(c: char, width: u8) -> Self {
70 Self::Single(c, width)
71 }
72}
73
74pub trait WidgetRenderer {
75 fn draw_glyph(&mut self, glyph: Glyph, local_pos: Pos);
76
77 fn draw(&mut self) {
78 todo!(
79 "this function is only here to remind us that we should have a raw draw function for Kitty image protocol and such"
80 );
81 }
82
83 fn set_attributes(&mut self, attribs: &Attributes<'_>, local_pos: Pos);
84
85 fn set_style(&mut self, style: Style, local_pos: Pos);
86
87 fn size(&self) -> Size;
88}
89
90#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)]
91pub struct GlyphIndex(u32);
92
93impl SlabIndex for GlyphIndex {
94 const MAX: usize = u32::MAX as usize;
95
96 fn as_usize(&self) -> usize {
97 self.0 as usize
98 }
99
100 fn from_usize(index: usize) -> Self
101 where
102 Self: Sized,
103 {
104 Self(index as u32)
105 }
106}
107
108#[derive(Debug, Copy, Clone)]
109pub struct PaintFilter(WidgetPositionFilter);
110
111impl PaintFilter {
112 pub fn fixed() -> Self {
113 Self(WidgetPositionFilter::Fixed)
114 }
115
116 pub fn floating() -> Self {
117 Self(WidgetPositionFilter::Floating)
118 }
119}
120
121impl<'bp> crate::widget::Filter<'bp> for PaintFilter {
122 type Output = Element<'bp>;
123
124 fn filter<'a>(
125 &mut self,
126 widget: &'a mut WidgetContainer<'bp>,
127 attribute_storage: &AttributeStorage<'_>,
128 ) -> FilterOutput<&'a mut Self::Output, Self> {
129 match &mut widget.kind {
130 WidgetKind::Element(element) => {
131 let attributes = attribute_storage.get(element.id());
132 match attributes.get_as::<Display>(DISPLAY).unwrap_or_default() {
133 Display::Show => match self.0 {
134 WidgetPositionFilter::Floating => match element.is_floating() {
135 true => FilterOutput::Include(element, PaintFilter::fixed()),
136 false => FilterOutput::Continue,
137 },
138 WidgetPositionFilter::Fixed => match element.is_floating() {
139 false => FilterOutput::Include(element, *self),
140 true => FilterOutput::Exclude,
141 },
142 WidgetPositionFilter::All => FilterOutput::Include(element, *self),
143 WidgetPositionFilter::None => FilterOutput::Exclude,
144 },
145 Display::Hide | Display::Exclude => FilterOutput::Exclude,
146 }
147 }
148 _ => FilterOutput::Continue,
149 }
150 }
151}
152
153pub fn paint<'bp>(
154 surface: &mut impl WidgetRenderer,
155 glyph_index: &mut GlyphMap,
156 mut widgets: PaintChildren<'_, 'bp>,
157 attribute_storage: &AttributeStorage<'bp>,
158) {
159 _ = widgets.each(|widget, children| {
160 let ctx = PaintCtx::new(surface, None, glyph_index);
161 widget.paint(children, ctx, attribute_storage);
162 ControlFlow::Continue(())
163 });
164}
165
166#[derive(Debug, Copy, Clone)]
167pub struct Unsized;
168
169pub struct SizePos {
170 pub local_size: Size,
171 pub global_pos: Pos,
172}
173
174impl SizePos {
175 pub fn new(local_size: Size, global_pos: Pos) -> Self {
176 Self { local_size, global_pos }
177 }
178}
179
180pub struct PaintCtx<'surface, Size> {
188 surface: &'surface mut dyn WidgetRenderer,
189 pub clip: Option<Region>,
190 pub(crate) state: Size,
191 glyph_map: &'surface mut GlyphMap,
192}
193
194impl<'surface> Deref for PaintCtx<'surface, SizePos> {
195 type Target = SizePos;
196
197 fn deref(&self) -> &Self::Target {
198 &self.state
199 }
200}
201
202impl<'surface> PaintCtx<'surface, Unsized> {
203 pub fn new(
204 surface: &'surface mut dyn WidgetRenderer,
205 clip: Option<Region>,
206 glyph_map: &'surface mut GlyphMap,
207 ) -> Self {
208 Self {
209 surface,
210 clip,
211 state: Unsized,
212 glyph_map,
213 }
214 }
215
216 pub fn into_sized(self, size: Size, global_pos: Pos) -> PaintCtx<'surface, SizePos> {
218 PaintCtx {
219 surface: self.surface,
220 glyph_map: self.glyph_map,
221 clip: self.clip,
222 state: SizePos::new(size, global_pos),
223 }
224 }
225}
226
227impl<'screen> PaintCtx<'screen, SizePos> {
228 pub fn to_unsized(&mut self) -> PaintCtx<'_, Unsized> {
229 PaintCtx::new(self.surface, self.clip, self.glyph_map)
230 }
231
232 pub fn update(&mut self, new_size: Size, new_pos: Pos) {
233 self.state.local_size = new_size;
234 self.state.global_pos = new_pos;
235 }
236
237 pub fn set_clip_region(&mut self, region: Region) {
239 let current = self.clip.get_or_insert(region);
240 *current = current.intersect_with(®ion);
241 }
242
243 pub fn create_region(&self) -> Region {
244 let mut region = Region::new(
245 self.global_pos,
246 Pos::new(
247 self.global_pos.x + self.local_size.width as i32,
248 self.global_pos.y + self.local_size.height as i32,
249 ),
250 );
251
252 if let Some(existing) = self.clip {
253 region.constrain(&existing);
254 }
255
256 region
257 }
258
259 fn clip(&self, local_pos: LocalPos, clip: &Region) -> bool {
260 let pos = self.global_pos + local_pos;
261 clip.contains(pos)
262 }
263
264 fn pos_inside_local_region(&self, pos: LocalPos, width: u16) -> bool {
265 pos.x + width <= self.local_size.width && pos.y < self.local_size.height
266 }
267
268 pub fn translate_to_global(&self, local: LocalPos) -> Option<Pos> {
271 let screen_x = local.x as i32 + self.global_pos.x;
272 let screen_y = local.y as i32 + self.global_pos.y;
273
274 let (width, height) = self.surface.size().into();
275 if screen_x < 0 || screen_y < 0 || screen_x >= width || screen_y >= height {
276 return None;
277 }
278
279 Some(Pos {
280 x: screen_x,
281 y: screen_y,
282 })
283 }
284
285 fn newline(&mut self, pos: LocalPos) -> Option<LocalPos> {
286 let y = pos.y + 1; if y >= self.local_size.height { None } else { Some(LocalPos { x: 0, y }) }
288 }
289
290 pub fn to_glyphs<'a>(&mut self, s: &'a str) -> Glyphs<'a> {
291 Glyphs::new(s)
292 }
293
294 pub fn place_glyphs(&mut self, mut glyphs: Glyphs<'_>, mut pos: LocalPos) -> Option<LocalPos> {
295 while let Some(glyph) = glyphs.next(self.glyph_map) {
296 pos = self.place_glyph(glyph, pos)?;
297 }
298 Some(pos)
299 }
300
301 pub fn set_style(&mut self, style: Style, pos: LocalPos) {
302 if let Some(clip) = self.clip.as_ref() {
304 if !self.clip(pos, clip) {
305 return;
306 }
307 }
308
309 let screen_pos = match self.translate_to_global(pos) {
310 Some(pos) => pos,
311 None => return,
312 };
313
314 self.surface.set_style(style, screen_pos);
315 }
316
317 pub fn set_attributes(&mut self, attrs: &Attributes<'_>, pos: LocalPos) {
318 if let Some(clip) = self.clip.as_ref() {
320 if !self.clip(pos, clip) {
321 return;
322 }
323 }
324
325 let screen_pos = match self.translate_to_global(pos) {
326 Some(pos) => pos,
327 None => return,
328 };
329
330 self.surface.set_attributes(attrs, screen_pos);
331 }
332
333 pub fn place_glyph(&mut self, glyph: Glyph, input_pos: LocalPos) -> Option<LocalPos> {
340 let width = glyph.width() as u16;
341 let next = LocalPos {
342 x: input_pos.x + width,
343 y: input_pos.y,
344 };
345
346 if let Some(clip) = self.clip.as_ref() {
348 if !self.clip(input_pos, clip) {
349 return Some(next);
350 }
351 }
352
353 if glyph.is_newline() {
355 return self.newline(input_pos);
356 }
357
358 if !self.pos_inside_local_region(input_pos, width) {
360 return None;
361 }
362
363 let screen_pos = match self.translate_to_global(input_pos) {
365 Some(pos) => pos,
366 None => return Some(next),
367 };
368
369 self.surface.draw_glyph(glyph, screen_pos);
371
372 if input_pos.x >= self.local_size.width {
374 self.newline(input_pos)
375 } else {
376 Some(LocalPos {
377 x: input_pos.x + width,
378 y: input_pos.y,
379 })
380 }
381 }
382}