cranpose-render-wgpu 0.0.58

WGPU renderer backend for Cranpose
Documentation
#[cfg(test)]
pub(crate) use cranpose_render_common::graph::quad_bounds;
#[cfg(test)]
pub(crate) use cranpose_render_common::layer_transform::{
    apply_layer_affine_to_rect, apply_layer_to_quad,
};
#[cfg(test)]
pub(crate) use cranpose_render_common::layer_transform::{
    apply_layer_to_rect, layer_uniform_scale,
};
pub(crate) use cranpose_render_common::style_shared::{
    apply_layer_to_brush, apply_layer_to_color, scale_corner_radii,
};
#[cfg(test)]
pub(crate) use cranpose_render_common::style_shared::{
    compose_color_filters, primitives_for_placement, DrawPlacement,
};
#[cfg(test)]
use cranpose_ui::DrawCommand;
#[cfg(test)]
use cranpose_ui_graphics::{
    BlendMode, CornerRadii, DrawPrimitive, GraphicsLayer, Rect, RoundedCornerShape, Size,
};

#[cfg(test)]
use crate::scene::CompositorScene;

#[cfg(test)]
#[allow(clippy::too_many_arguments)]
pub(crate) fn apply_draw_commands(
    commands: &[DrawCommand],
    placement: DrawPlacement,
    rect: Rect,
    size: Size,
    layer: &GraphicsLayer,
    clip: Option<Rect>,
    scene: &mut CompositorScene,
) {
    fn emit_primitive(
        primitive: DrawPrimitive,
        layer_bounds: Rect,
        layer: &GraphicsLayer,
        clip: Option<Rect>,
        scene: &mut CompositorScene,
        blend_mode: Option<BlendMode>,
    ) {
        match primitive {
            DrawPrimitive::Content => {}
            DrawPrimitive::Blend {
                primitive,
                blend_mode: nested,
            } => emit_primitive(
                *primitive,
                layer_bounds,
                layer,
                clip,
                scene,
                blend_mode.or(Some(nested)),
            ),
            DrawPrimitive::Rect {
                rect: local_rect,
                brush,
            } => {
                let draw_rect = local_rect.translate(layer_bounds.x, layer_bounds.y);
                let local_rect = apply_layer_affine_to_rect(draw_rect, layer_bounds, layer);
                let quad = apply_layer_to_quad(draw_rect, layer_bounds, layer);
                let transformed = quad_bounds(quad);
                let brush = apply_layer_to_brush(brush, layer);
                scene.push_shape_with_geometry(
                    transformed,
                    local_rect,
                    quad,
                    brush,
                    None,
                    clip,
                    blend_mode.unwrap_or(BlendMode::SrcOver),
                );
            }
            DrawPrimitive::RoundRect {
                rect: local_rect,
                brush,
                radii,
            } => {
                let draw_rect = local_rect.translate(layer_bounds.x, layer_bounds.y);
                let local_rect = apply_layer_affine_to_rect(draw_rect, layer_bounds, layer);
                let quad = apply_layer_to_quad(draw_rect, layer_bounds, layer);
                let transformed = quad_bounds(quad);
                let scaled_radii = scale_corner_radii(radii, layer_uniform_scale(layer));
                let shape = RoundedCornerShape::with_radii(scaled_radii);
                let brush = apply_layer_to_brush(brush, layer);
                scene.push_shape_with_geometry(
                    transformed,
                    local_rect,
                    quad,
                    brush,
                    Some(shape),
                    clip,
                    blend_mode.unwrap_or(BlendMode::SrcOver),
                );
            }
            DrawPrimitive::Image {
                rect: local_rect,
                image,
                alpha,
                color_filter,
                src_rect,
            } => {
                let draw_rect = local_rect.translate(layer_bounds.x, layer_bounds.y);
                let local_rect = apply_layer_affine_to_rect(draw_rect, layer_bounds, layer);
                let quad = apply_layer_to_quad(draw_rect, layer_bounds, layer);
                let transformed = quad_bounds(quad);
                let combined_alpha = (alpha * layer.alpha).clamp(0.0, 1.0);
                let combined_filter = compose_color_filters(color_filter, layer.color_filter);
                scene.push_image_with_geometry(
                    transformed,
                    local_rect,
                    quad,
                    image,
                    combined_alpha,
                    combined_filter,
                    clip,
                    src_rect,
                    blend_mode.unwrap_or(BlendMode::SrcOver),
                    false,
                );
            }
            DrawPrimitive::Shadow(shadow_prim) => {
                super::push_shadow_primitive(shadow_prim, layer_bounds, layer, clip, scene);
            }
        }
    }

    for command in commands {
        let primitives = primitives_for_placement(command, placement, size);
        for primitive in primitives {
            emit_primitive(primitive, rect, layer, clip, scene, None);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use cranpose_ui::Brush;
    use cranpose_ui_graphics::{Color, TransformOrigin};
    use std::rc::Rc;

    #[test]
    fn apply_layer_to_rect_rotates_around_center() {
        let rect = Rect {
            x: 0.0,
            y: 0.0,
            width: 100.0,
            height: 40.0,
        };
        let layer = GraphicsLayer {
            rotation_z: 90.0,
            ..Default::default()
        };

        let transformed = apply_layer_to_rect(rect, rect, &layer);
        assert!((transformed.width - 40.0).abs() < 0.01);
        assert!((transformed.height - 100.0).abs() < 0.01);
    }

    #[test]
    fn apply_layer_to_rect_honors_transform_origin() {
        let rect = Rect {
            x: 0.0,
            y: 0.0,
            width: 100.0,
            height: 40.0,
        };
        let layer = GraphicsLayer {
            rotation_z: 90.0,
            transform_origin: TransformOrigin::new(0.0, 0.0),
            ..Default::default()
        };

        let transformed = apply_layer_to_rect(rect, rect, &layer);
        assert!((transformed.x + 40.0).abs() < 0.01);
        assert!((transformed.y - 0.0).abs() < 0.01);
    }

    #[test]
    fn apply_layer_to_rect_camera_distance_changes_projection() {
        let rect = Rect {
            x: 0.0,
            y: 0.0,
            width: 100.0,
            height: 60.0,
        };
        let near_camera = GraphicsLayer {
            rotation_y: 25.0,
            camera_distance: 8.0,
            ..Default::default()
        };
        let far_camera = GraphicsLayer {
            rotation_y: 25.0,
            camera_distance: 24.0,
            ..Default::default()
        };

        let near = apply_layer_to_rect(rect, rect, &near_camera);
        let far = apply_layer_to_rect(rect, rect, &far_camera);
        let delta = (near.x - far.x).abs()
            + (near.y - far.y).abs()
            + (near.width - far.width).abs()
            + (near.height - far.height).abs();
        assert!(delta > 0.05);
    }

    #[test]
    fn apply_draw_commands_scales_round_rect_radii_with_uniform_axis_scale() {
        let command = DrawCommand::Behind(Rc::new(|_size| {
            vec![DrawPrimitive::RoundRect {
                rect: Rect {
                    x: 0.0,
                    y: 0.0,
                    width: 80.0,
                    height: 40.0,
                },
                brush: Brush::solid(Color::BLACK),
                radii: CornerRadii::uniform(10.0),
            }]
        }));

        let layer = GraphicsLayer {
            scale: 1.0,
            scale_x: 2.0,
            scale_y: 0.5,
            ..Default::default()
        };
        let mut scene = CompositorScene::new();
        let bounds = Rect {
            x: 0.0,
            y: 0.0,
            width: 80.0,
            height: 40.0,
        };
        apply_draw_commands(
            &[command],
            DrawPlacement::Behind,
            bounds,
            Size {
                width: 80.0,
                height: 40.0,
            },
            &layer,
            None,
            &mut scene,
        );

        let shape = scene.shapes[0].shape.expect("rounded shape");
        let radii = shape.radii();
        assert!((radii.top_left - 5.0).abs() < 1e-6);
        assert!((radii.top_right - 5.0).abs() < 1e-6);
        assert!((radii.bottom_right - 5.0).abs() < 1e-6);
        assert!((radii.bottom_left - 5.0).abs() < 1e-6);
    }
}