use std::cell::RefCell;
use std::collections::HashMap;
use dinamika_cpu::{Color, Pixmap, Transform};
use crate::shape::{Shape, ShapeData};
mod measure;
mod paint;
use measure::measure;
use paint::draw;
type ShapePtr = *const RefCell<ShapeData>;
pub(super) fn clamp_axis(value: f32, min: f32, max: f32) -> f32 {
let mut v = value;
if max > 0.0 {
v = v.min(max);
}
if min > 0.0 {
v = v.max(min);
}
v
}
pub fn render_scene(width: u32, height: u32, background: Color, shapes: &[Shape]) -> Pixmap {
let mut pixmap = Pixmap::new(width.max(1), height.max(1)).expect("non-zero pixmap size");
pixmap.fill(background);
let canvas_w = width as f32;
let canvas_h = height as f32;
let mut cache: HashMap<ShapePtr, (f32, f32)> = HashMap::new();
for shape in shapes {
let (nat_w, nat_h) = measure(shape, &mut cache);
let (x, y, w, h) = {
let d = shape.inner.borrow();
let w = match d.width_percent {
Some(p) => clamp_axis(p * canvas_w, d.min_width.get(), d.max_width.get()),
None => nat_w,
};
let h = match d.height_percent {
Some(p) => clamp_axis(p * canvas_h, d.min_height.get(), d.max_height.get()),
None => nat_h,
};
(d.x.get(), d.y.get(), w, h)
};
draw(&mut pixmap, shape, x, y, w, h, Transform::identity(), &mut cache);
}
pixmap
}
#[cfg(test)]
mod tests {
use super::*;
use crate::shape::{Align, Direction, Justify, Length};
fn alpha_at(pm: &Pixmap, x: u32, y: u32) -> u8 {
pm.pixel(x, y).unwrap().alpha()
}
#[test]
fn fills_rect_pixels() {
let s = Shape::rect()
.at(10.0, 10.0)
.size(40.0, 40.0)
.background(Color::from_rgba8(255, 0, 0, 255));
let pm = render_scene(80, 80, Color::TRANSPARENT, std::slice::from_ref(&s));
assert_eq!(alpha_at(&pm, 30, 30), 255); assert_eq!(alpha_at(&pm, 70, 70), 0); }
#[test]
fn centers_child_in_container() {
let child = Shape::rect().size(20.0, 20.0).background(Color::from_rgba8(0, 0, 255, 255));
let container = Shape::rect()
.size(100.0, 100.0)
.background(Color::TRANSPARENT)
.justify(Justify::Center)
.align(Align::Center)
.child(child);
let pm = render_scene(100, 100, Color::TRANSPARENT, std::slice::from_ref(&container));
assert_eq!(alpha_at(&pm, 50, 50), 255);
assert_eq!(alpha_at(&pm, 10, 10), 0);
assert_eq!(alpha_at(&pm, 90, 90), 0);
}
#[test]
fn root_percent_size_fills_canvas_and_centers_child() {
let child = Shape::rect().size(20.0, 20.0).background(Color::from_rgba8(0, 0, 255, 255));
let root = Shape::layout()
.width(Length::percent(100.0))
.height(Length::percent(100.0))
.justify(Justify::Center)
.align(Align::Center)
.child(child);
let pm = render_scene(100, 100, Color::TRANSPARENT, std::slice::from_ref(&root));
assert_eq!(alpha_at(&pm, 50, 50), 255);
assert_eq!(alpha_at(&pm, 10, 10), 0); assert_eq!(alpha_at(&pm, 90, 90), 0);
}
#[test]
fn scale_enlarges_shape_around_center() {
let s = Shape::rect()
.at(30.0, 30.0)
.size(20.0, 20.0)
.scale(2.0)
.background(Color::from_rgba8(0, 255, 0, 255));
let pm = render_scene(80, 80, Color::TRANSPARENT, std::slice::from_ref(&s));
assert_eq!(alpha_at(&pm, 40, 40), 255); assert_eq!(alpha_at(&pm, 25, 40), 255);
assert_eq!(alpha_at(&pm, 10, 40), 0); }
#[test]
fn percent_fills_parent_content_area() {
let child = Shape::rect()
.width(Length::percent(100.0))
.height(Length::percent(100.0))
.background(Color::from_rgba8(0, 200, 0, 255));
let container = Shape::rect()
.size(100.0, 100.0)
.background(Color::TRANSPARENT)
.child(child);
let pm = render_scene(100, 100, Color::TRANSPARENT, std::slice::from_ref(&container));
assert_eq!(alpha_at(&pm, 5, 5), 255);
assert_eq!(alpha_at(&pm, 95, 95), 255);
}
#[test]
fn max_clamps_stretched_cross_size() {
let child = Shape::rect()
.height(Length::percent(100.0))
.max_width(40.0)
.background(Color::from_rgba8(0, 0, 200, 255));
let container = Shape::rect()
.size(100.0, 100.0)
.background(Color::TRANSPARENT)
.direction(Direction::Column)
.align(Align::Stretch)
.child(child);
let pm = render_scene(100, 100, Color::TRANSPARENT, std::slice::from_ref(&container));
assert_eq!(alpha_at(&pm, 20, 50), 255); assert_eq!(alpha_at(&pm, 60, 50), 0); assert_eq!(alpha_at(&pm, 20, 95), 255); }
#[test]
fn group_opacity_applies_to_subtree() {
let child = Shape::rect().size(20.0, 20.0).background(Color::from_rgba8(255, 0, 0, 255));
let parent = Shape::rect()
.at(0.0, 0.0)
.size(20.0, 20.0)
.background(Color::TRANSPARENT)
.opacity(0.5)
.child(child);
let pm = render_scene(20, 20, Color::TRANSPARENT, std::slice::from_ref(&parent));
let a = alpha_at(&pm, 10, 10);
assert!((a as i32 - 128).abs() <= 4, "alpha={a}");
}
#[test]
fn overlapping_children_do_not_double_show_through() {
let red = Color::from_rgba8(255, 0, 0, 255);
let parent = Shape::rect()
.at(0.0, 0.0)
.background(Color::TRANSPARENT)
.opacity(0.5)
.direction(Direction::Row)
.gap(-10.0)
.child(Shape::rect().size(20.0, 20.0).background(red))
.child(Shape::rect().size(20.0, 20.0).background(red));
let pm = render_scene(40, 20, Color::TRANSPARENT, std::slice::from_ref(&parent));
let solo = alpha_at(&pm, 5, 10); let overlap = alpha_at(&pm, 15, 10); assert!((solo as i32 - 128).abs() <= 4, "solo={solo}");
assert!((overlap as i32 - 128).abs() <= 4, "overlap={overlap}");
}
}