cranpose-render-common 0.1.164

Common rendering contracts for Cranpose
Documentation
use cranpose_ui_graphics::{Brush, Color, DrawPrimitive};

use super::*;

fn test_layer(local_bounds: Rect, children: Vec<RenderNode>) -> LayerNode {
    LayerNode {
        local_bounds,
        children,
        ..Default::default()
    }
}

#[test]
fn projective_transform_translation_maps_points() {
    let transform = ProjectiveTransform::translation(7.0, -3.5);
    let mapped = transform.map_point(Point { x: 2.0, y: 4.0 });
    assert!((mapped.x - 9.0).abs() < 1e-6);
    assert!((mapped.y - 0.5).abs() < 1e-6);
}

#[test]
fn projective_transform_then_composes_in_parent_order() {
    let child = ProjectiveTransform::translation(4.0, 2.0);
    let parent = ProjectiveTransform::translation(10.0, -1.0);
    let composed = child.then(parent);
    let mapped = composed.map_point(Point { x: 1.0, y: 1.0 });
    assert!((mapped.x - 15.0).abs() < 1e-6);
    assert!((mapped.y - 2.0).abs() < 1e-6);
}

#[test]
fn homography_maps_rect_corners_to_target_quad() {
    let rect = Rect {
        x: 0.0,
        y: 0.0,
        width: 20.0,
        height: 10.0,
    };
    let quad = [[5.0, 7.0], [25.0, 6.0], [7.0, 20.0], [28.0, 21.0]];
    let transform = ProjectiveTransform::from_rect_to_quad(rect, quad);
    let mapped = transform.map_rect(rect);
    for (expected, actual) in quad.into_iter().zip(mapped) {
        assert!((expected[0] - actual[0]).abs() < 1e-4);
        assert!((expected[1] - actual[1]).abs() < 1e-4);
    }
}

#[test]
fn axis_aligned_rect_to_quad_keeps_exact_affine_matrix() {
    let rect = Rect {
        x: 2.0,
        y: 3.0,
        width: 20.0,
        height: 10.0,
    };
    let quad = [[12.0, 9.0], [32.0, 9.0], [12.0, 19.0], [32.0, 19.0]];
    let transform = ProjectiveTransform::from_rect_to_quad(rect, quad);

    assert_eq!(
        transform.matrix(),
        [[1.0, 0.0, 10.0], [0.0, 1.0, 6.0], [0.0, 0.0, 1.0]]
    );
}

#[test]
fn axis_aligned_rect_to_quad_keeps_exact_axis_aligned_scale() {
    let rect = Rect {
        x: 4.0,
        y: 6.0,
        width: 10.0,
        height: 8.0,
    };
    let quad = [[20.0, 18.0], [50.0, 18.0], [20.0, 42.0], [50.0, 42.0]];
    let transform = ProjectiveTransform::from_rect_to_quad(rect, quad);

    assert_eq!(
        transform.matrix(),
        [[3.0, 0.0, 8.0], [0.0, 3.0, 0.0], [0.0, 0.0, 1.0]]
    );
}

#[test]
fn retained_visual_observation_nodes_collect_layers_and_command_owners() {
    let bounds = Rect {
        x: 0.0,
        y: 0.0,
        width: 20.0,
        height: 20.0,
    };
    let command = |node_id| DrawCommandId {
        node_id,
        command_index: 0,
        placement: DrawPlacement::Behind,
    };
    let mut child = test_layer(
        bounds,
        vec![RenderNode::DrawRun(DrawRunNode::for_command(
            PrimitivePhase::BeforeChildren,
            Some(command(17)),
            Vec::new(),
        ))],
    );
    child.node_id = Some(13);
    let mut root = test_layer(
        bounds,
        vec![
            RenderNode::DrawRun(DrawRunNode::for_command(
                PrimitivePhase::BeforeChildren,
                Some(command(9)),
                Vec::new(),
            )),
            RenderNode::DrawRun(DrawRunNode::new(PrimitivePhase::BeforeChildren, Vec::new())),
            RenderNode::Layer(Box::new(child)),
        ],
    );

    root.node_id = Some(5);

    let mut graph = RenderGraph::new(root);
    let mut nodes = HashSet::from_iter([999]);
    graph.collect_retained_visual_observation_nodes(&mut nodes);
    assert_eq!(nodes, HashSet::from_iter([5, 9, 13, 17]));
    let capacity = nodes.capacity();
    graph.root.children.clear();
    graph.root.node_id = Some(23);
    graph.collect_retained_visual_observation_nodes(&mut nodes);
    assert_eq!(nodes, HashSet::from_iter([23]));
    assert_eq!(nodes.capacity(), capacity);
}

#[test]
fn render_graph_new_recomputes_manual_layer_hashes() {
    let primitive = PrimitiveEntry {
        phase: PrimitivePhase::BeforeChildren,
        node: PrimitiveNode::Draw(DrawPrimitiveNode {
            primitive: DrawPrimitive::Rect {
                rect: Rect {
                    x: 1.0,
                    y: 2.0,
                    width: 8.0,
                    height: 6.0,
                },
                brush: Brush::solid(Color::WHITE),
                stroke: None,
            },
            clip: None,
        }),
    };
    let mut root = test_layer(
        Rect {
            x: 0.0,
            y: 0.0,
            width: 20.0,
            height: 20.0,
        },
        vec![RenderNode::Primitive(primitive)],
    );
    root.graphics_layer.render_effect = Some(RenderEffect::blur(3.0));
    let mut expected = root.clone();
    expected.recompute_raster_cache_hashes();

    let graph = RenderGraph::new(root);
    assert_eq!(
        graph.root.target_content_hash(),
        expected.target_content_hash()
    );
    assert_eq!(graph.root.effect_hash(), expected.effect_hash());
}

#[test]
fn motion_source_content_hash_ignores_translated_content_offset() {
    let primitive = PrimitiveEntry {
        phase: PrimitivePhase::BeforeChildren,
        node: PrimitiveNode::Draw(DrawPrimitiveNode {
            primitive: DrawPrimitive::Rect {
                rect: Rect {
                    x: 1.0,
                    y: 2.0,
                    width: 8.0,
                    height: 6.0,
                },
                brush: Brush::solid(Color::WHITE),
                stroke: None,
            },
            clip: None,
        }),
    };
    let mut base = test_layer(
        Rect {
            x: 0.0,
            y: 0.0,
            width: 20.0,
            height: 20.0,
        },
        vec![RenderNode::Primitive(primitive)],
    );
    base.translated_content_context = true;
    base.translated_content_offset = Point::new(0.0, -24.0);
    base.recompute_raster_cache_hashes();

    let mut moved = base.clone();
    moved.translated_content_offset = Point::new(0.0, -72.0);
    moved.recompute_raster_cache_hashes();

    assert_ne!(base.target_content_hash(), moved.target_content_hash());
    assert_eq!(
        base.motion_source_content_hash(),
        moved.motion_source_content_hash()
    );
}