use crate::layout_engine::types::{
DataContext, Direction, DividerParams, LayoutElement, RenderContext, RenderError,
};
use dotzuki_engine::render::painter::Painter;
use dotzuki_engine::render::Rgba;
use dotzuki_engine::render::TilePos;
pub fn render_divider(
element: &LayoutElement,
params: &DividerParams,
ctx: &DataContext,
_render_ctx: &RenderContext,
painter: &mut dyn Painter,
) -> Result<(), RenderError> {
let rect = &element.rect;
let tiles = ¶ms.tiles;
if tiles.is_empty() {
return Ok(());
}
let main_axis_len = match params.orientation {
Direction::Horizontal => rect.tw.unwrap_or(1),
Direction::Vertical => rect.th.unwrap_or(1),
};
let repeat = params.repeat;
let last_tile = *tiles.last().unwrap();
for i in 0..main_axis_len {
let tile_id = if i < tiles.len() as u32 {
tiles[i as usize]
} else if repeat > 0 && i < tiles.len() as u32 + repeat {
last_tile
} else {
break;
};
let base_tx = rect.tx.resolve(ctx);
let base_ty = rect.ty.resolve(ctx);
let (tx, ty) = match params.orientation {
Direction::Horizontal => (base_tx + i, base_ty),
Direction::Vertical => (base_tx, base_ty + i),
};
painter.draw_gb_tile(
TilePos::new(tx, ty),
tile_id as u8,
"\u{25A0}",
Rgba::INK_BLACK,
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout_engine::types::{Coord, ElementParams, ElementRect};
use dotzuki_engine::render::Rgba as EngineRgba;
#[derive(Debug, Default)]
struct RecordingPainter {
tile_calls: Vec<(u32, u32, u8)>,
pixel_rects: Vec<(u32, u32, u32, u32, EngineRgba)>,
}
impl RecordingPainter {
fn new() -> Self {
Self::default()
}
}
impl Painter for RecordingPainter {
fn clear(&mut self, _color: EngineRgba) {}
fn draw_text_box(
&mut self,
_rect: dotzuki_engine::render::TileRect,
_color: EngineRgba,
) {
}
fn draw_text(
&mut self,
_pos: TilePos,
_text: &str,
_color: EngineRgba,
) {
}
fn draw_glyph(&mut self, _pos: TilePos, _glyph: char, _color: EngineRgba) {}
fn draw_pixel_rect(
&mut self,
px: u32,
py: u32,
pw: u32,
ph: u32,
color: EngineRgba,
) {
self.pixel_rects.push((px, py, pw, ph, color));
}
fn draw_gb_tile(
&mut self,
pos: TilePos,
tile_id: u8,
_fallback: &str,
_color: EngineRgba,
) {
self.tile_calls.push((pos.tx, pos.ty, tile_id));
}
}
fn make_element(rect: ElementRect, params: DividerParams) -> LayoutElement {
LayoutElement {
id: String::new(),
element_type: "divider".to_string(),
rect,
visible: crate::layout_engine::types::Visibility::Static(true),
z_index: 0,
params: ElementParams::Divider(params),
}
}
#[test]
fn horizontal_places_tiles_left_to_right() {
let rect = ElementRect {
tx: Coord::Literal(2),
ty: Coord::Literal(3),
tw: Some(5),
th: None,
};
let params = DividerParams {
tiles: vec![10, 20, 30, 40, 50],
repeat: 0,
orientation: Direction::Horizontal,
};
let elem = make_element(rect, params);
let ctx = DataContext::new();
let rc = RenderContext {
screen: "test",
theme: &Default::default(),
fonts: &std::collections::HashMap::new(),
tilesets: &std::collections::HashMap::new(),
images: crate::layout_engine::types::empty_image_registry(),
};
let mut painter = RecordingPainter::new();
render_divider(&elem, if let ElementParams::Divider(ref p) = elem.params { p } else { unreachable!() }, &ctx, &rc, &mut painter).unwrap();
assert_eq!(painter.tile_calls.len(), 5);
assert_eq!(painter.tile_calls[0], (2, 3, 10));
assert_eq!(painter.tile_calls[1], (3, 3, 20));
assert_eq!(painter.tile_calls[2], (4, 3, 30));
assert_eq!(painter.tile_calls[3], (5, 3, 40));
assert_eq!(painter.tile_calls[4], (6, 3, 50));
}
#[test]
fn horizontal_default_tw_is_1() {
let rect = ElementRect {
tx: Coord::Literal(0),
ty: Coord::Literal(0),
tw: None,
th: None,
};
let params = DividerParams {
tiles: vec![99],
repeat: 0,
orientation: Direction::Horizontal,
};
let elem = make_element(rect, params);
let ctx = DataContext::new();
let rc = RenderContext {
screen: "test",
theme: &Default::default(),
fonts: &std::collections::HashMap::new(),
tilesets: &std::collections::HashMap::new(),
images: crate::layout_engine::types::empty_image_registry(),
};
let mut painter = RecordingPainter::new();
render_divider(&elem, if let ElementParams::Divider(ref p) = elem.params { p } else { unreachable!() }, &ctx, &rc, &mut painter).unwrap();
assert_eq!(painter.tile_calls.len(), 1);
assert_eq!(painter.tile_calls[0], (0, 0, 99));
}
#[test]
fn vertical_places_tiles_top_to_bottom() {
let rect = ElementRect {
tx: Coord::Literal(5),
ty: Coord::Literal(1),
tw: None,
th: Some(4),
};
let params = DividerParams {
tiles: vec![1, 2, 3],
repeat: 1,
orientation: Direction::Vertical,
};
let elem = make_element(rect, params);
let ctx = DataContext::new();
let rc = RenderContext {
screen: "test",
theme: &Default::default(),
fonts: &std::collections::HashMap::new(),
tilesets: &std::collections::HashMap::new(),
images: crate::layout_engine::types::empty_image_registry(),
};
let mut painter = RecordingPainter::new();
render_divider(&elem, if let ElementParams::Divider(ref p) = elem.params { p } else { unreachable!() }, &ctx, &rc, &mut painter).unwrap();
assert_eq!(painter.tile_calls.len(), 4);
assert_eq!(painter.tile_calls[0], (5, 1, 1));
assert_eq!(painter.tile_calls[1], (5, 2, 2));
assert_eq!(painter.tile_calls[2], (5, 3, 3));
assert_eq!(painter.tile_calls[3], (5, 4, 3));
}
#[test]
fn vertical_default_th_is_1() {
let rect = ElementRect {
tx: Coord::Literal(1),
ty: Coord::Literal(2),
tw: None,
th: None,
};
let params = DividerParams {
tiles: vec![42],
repeat: 0,
orientation: Direction::Vertical,
};
let elem = make_element(rect, params);
let ctx = DataContext::new();
let rc = RenderContext {
screen: "test",
theme: &Default::default(),
fonts: &std::collections::HashMap::new(),
tilesets: &std::collections::HashMap::new(),
images: crate::layout_engine::types::empty_image_registry(),
};
let mut painter = RecordingPainter::new();
render_divider(&elem, if let ElementParams::Divider(ref p) = elem.params { p } else { unreachable!() }, &ctx, &rc, &mut painter).unwrap();
assert_eq!(painter.tile_calls.len(), 1);
assert_eq!(painter.tile_calls[0], (1, 2, 42));
}
#[test]
fn repeat_fills_remaining_rect() {
let rect = ElementRect {
tx: Coord::Literal(0),
ty: Coord::Literal(0),
tw: Some(10),
th: None,
};
let params = DividerParams {
tiles: vec![1, 2],
repeat: 8,
orientation: Direction::Horizontal,
};
let elem = make_element(rect, params);
let ctx = DataContext::new();
let rc = RenderContext {
screen: "test",
theme: &Default::default(),
fonts: &std::collections::HashMap::new(),
tilesets: &std::collections::HashMap::new(),
images: crate::layout_engine::types::empty_image_registry(),
};
let mut painter = RecordingPainter::new();
render_divider(&elem, if let ElementParams::Divider(ref p) = elem.params { p } else { unreachable!() }, &ctx, &rc, &mut painter).unwrap();
assert_eq!(painter.tile_calls.len(), 10);
assert_eq!(painter.tile_calls[0], (0, 0, 1));
assert_eq!(painter.tile_calls[1], (1, 0, 2));
for i in 2..10 {
assert_eq!(painter.tile_calls[i], (i as u32, 0, 2));
}
}
#[test]
fn repeat_zero_stops_after_explicit_tiles() {
let rect = ElementRect {
tx: Coord::Literal(0),
ty: Coord::Literal(0),
tw: Some(10),
th: None,
};
let params = DividerParams {
tiles: vec![1, 2],
repeat: 0,
orientation: Direction::Horizontal,
};
let elem = make_element(rect, params);
let ctx = DataContext::new();
let rc = RenderContext {
screen: "test",
theme: &Default::default(),
fonts: &std::collections::HashMap::new(),
tilesets: &std::collections::HashMap::new(),
images: crate::layout_engine::types::empty_image_registry(),
};
let mut painter = RecordingPainter::new();
render_divider(&elem, if let ElementParams::Divider(ref p) = elem.params { p } else { unreachable!() }, &ctx, &rc, &mut painter).unwrap();
assert_eq!(painter.tile_calls.len(), 2);
}
#[test]
fn empty_tiles_is_noop() {
let rect = ElementRect {
tx: Coord::Literal(0),
ty: Coord::Literal(0),
tw: Some(5),
th: None,
};
let params = DividerParams {
tiles: vec![],
repeat: 0,
orientation: Direction::Horizontal,
};
let elem = make_element(rect, params);
let ctx = DataContext::new();
let rc = RenderContext {
screen: "test",
theme: &Default::default(),
fonts: &std::collections::HashMap::new(),
tilesets: &std::collections::HashMap::new(),
images: crate::layout_engine::types::empty_image_registry(),
};
let mut painter = RecordingPainter::new();
render_divider(&elem, if let ElementParams::Divider(ref p) = elem.params { p } else { unreachable!() }, &ctx, &rc, &mut painter).unwrap();
assert_eq!(painter.tile_calls.len(), 0);
}
#[test]
fn default_orientation_is_horizontal() {
let params = DividerParams {
tiles: vec![1],
repeat: 0,
orientation: Direction::Horizontal,
};
let json = r#"{"tiles":[1],"repeat":0}"#;
let deserialized: DividerParams = serde_json::from_str(json).unwrap();
assert_eq!(deserialized.tiles, params.tiles);
}
}