dotzuki_renderer/layout_engine/elements/
cursor.rs1use dotzuki_engine::render::painter::Painter;
15use dotzuki_engine::render::TilePos;
16
17use crate::layout_engine::elements::text::parse_color;
18use crate::layout_engine::types::{CursorParams, DataContext, LayoutElement, Theme};
19
20pub fn render_cursor(
22 element: &LayoutElement,
23 params: &CursorParams,
24 ctx: &DataContext,
25 theme: &Theme,
26 painter: &mut dyn Painter,
27) {
28 let base_tx = element.rect.tx.resolve(ctx);
29 let base_ty = element.rect.ty.resolve(ctx);
30 let col = params.col.resolve(ctx);
31 let row = params.row.resolve(ctx);
32
33 let tx = base_tx + col * params.col_step;
34 let ty = base_ty + row * params.row_step;
35
36 let color = match params.color.as_deref() {
38 Some(c) => parse_color(c),
39 None => theme.cursor_ink(),
40 };
41
42 if theme.proportional(painter.supports_proportional()) {
45 let mut buf = [0u8; 4];
46 painter.draw_text_px(tx * 8, ty * 8, params.glyph_char().encode_utf8(&mut buf), color);
47 } else {
48 painter.draw_glyph(TilePos::new(tx, ty), params.glyph_char(), color);
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::layout_engine::types::{Coord, ElementParams, ElementRect, Visibility};
56 use dotzuki_engine::render::{Rgba, TileRect};
57 use std::cell::RefCell;
58
59 #[derive(Default)]
60 struct Rec {
61 glyphs: RefCell<Vec<(u32, u32, char)>>,
62 }
63 impl Painter for Rec {
64 fn clear(&mut self, _c: Rgba) {}
65 fn draw_text_box(&mut self, _r: TileRect, _c: Rgba) {}
66 fn draw_text(&mut self, _p: TilePos, _t: &str, _c: Rgba) {}
67 fn draw_glyph(&mut self, p: TilePos, g: char, _c: Rgba) {
68 self.glyphs.borrow_mut().push((p.tx, p.ty, g));
69 }
70 fn draw_pixel_rect(&mut self, _x: u32, _y: u32, _w: u32, _h: u32, _c: Rgba) {}
71 fn draw_gb_tile(&mut self, _p: TilePos, _t: u8, _f: &str, _c: Rgba) {}
72 }
73
74 fn elem(tx: u32, ty: u32, params: CursorParams) -> LayoutElement {
75 LayoutElement {
76 id: String::new(),
77 element_type: "cursor".into(),
78 rect: ElementRect {
79 tx: Coord::Literal(tx),
80 ty: Coord::Literal(ty),
81 tw: Some(1),
82 th: Some(1),
83 },
84 visible: Visibility::Static(true),
85 z_index: 0,
86 params: ElementParams::Cursor(params),
87 }
88 }
89
90 fn cparams(col: Coord, row: Coord, col_step: u32, row_step: u32) -> CursorParams {
91 CursorParams { glyph: None, color: None, col, row, col_step, row_step }
92 }
93
94 #[test]
95 fn grid_position_computed_from_col_row() {
96 let mut ctx = DataContext::new();
98 ctx.set("c", 1i64);
99 ctx.set("r", 1i64);
100 let e = elem(1, 12, cparams(Coord::Template("{c}".into()), Coord::Template("{r}".into()), 9, 2));
101 let ElementParams::Cursor(ref p) = e.params else { unreachable!() };
102 let mut painter = Rec::default();
103 render_cursor(&e, p, &ctx, &Theme::default(), &mut painter);
104 assert_eq!(painter.glyphs.borrow()[0], (10, 14, '\u{25B6}'));
105 }
106
107 #[test]
108 fn defaults_to_base_and_triangle_glyph() {
109 let e = elem(3, 5, cparams(Coord::Literal(0), Coord::Literal(0), 0, 0));
110 let ElementParams::Cursor(ref p) = e.params else { unreachable!() };
111 let mut painter = Rec::default();
112 render_cursor(&e, p, &DataContext::new(), &Theme::default(), &mut painter);
113 assert_eq!(painter.glyphs.borrow()[0], (3, 5, '\u{25B6}'));
114 }
115}