Skip to main content

alpha2_engine/layout/
draw.rs

1//! A prepared layout drawn into the render graph: one composite of its
2//! paint list over transparency, as a text generator's picture is.
3
4use super::paint::{PaintItem, Placement};
5use crate::TextTexture;
6use alpha2_render::{
7    BlendMode, GraphError, GraphLayer, LayerTransform, RenderError, RenderGraph, RenderNodeId,
8    RenderSize,
9};
10use std::sync::Arc;
11
12/// A layout resolved and drawn at one render size: its paint list, and the
13/// store key of each text node's texture (`None` for empty text).
14///
15/// A host's [`crate::TextTextures`] prepares it, and lends it to the graph
16/// through [`crate::FrameProvider::layout`].
17#[derive(Debug)]
18pub struct PreparedLayout {
19    pub(crate) items: Vec<PaintItem>,
20    pub(crate) texts: Vec<Option<u64>>,
21}
22
23/// A prepared layout with the textures its text nodes draw.
24#[derive(Clone, Debug)]
25pub(crate) struct LaidOut<'t> {
26    pub(crate) layout: Arc<PreparedLayout>,
27    pub(crate) texts: Vec<Option<TextTexture<'t>>>,
28}
29
30impl<'t> LaidOut<'t> {
31    /// `layout` with its textures from `frames`, or `None` when one is
32    /// missing.
33    pub(crate) fn attach(
34        layout: Arc<PreparedLayout>,
35        frames: &'t dyn crate::FrameProvider,
36    ) -> Option<Self> {
37        let texts = layout
38            .texts
39            .iter()
40            .map(|key| {
41                key.map(|key| frames.text(key))
42                    .map_or(Some(None), |text| text.map(Some))
43            })
44            .collect::<Option<Vec<_>>>()?;
45        Some(Self { layout, texts })
46    }
47
48    /// The layout's picture: its paint list composited over transparency.
49    pub(crate) fn add_to(&self, graph: &mut RenderGraph<'t>) -> Result<RenderNodeId, RenderError> {
50        let layers = self.layers(graph, &self.layout.items)?;
51        graph
52            .add_composite(layers)
53            .map_err(GraphError::into_render_error)
54    }
55
56    fn layers(
57        &self,
58        graph: &mut RenderGraph<'t>,
59        items: &[PaintItem],
60    ) -> Result<Vec<GraphLayer>, RenderError> {
61        let size = graph.size();
62        let canvas = [size.width, size.height];
63        let mut layers = Vec::with_capacity(items.len());
64        for item in items {
65            let (source, place, extent) = match item {
66                PaintItem::Vector { scene, place } => {
67                    (graph.add_vector(Arc::clone(scene)), *place, canvas)
68                }
69                PaintItem::Text { index, place } => {
70                    let texture = self
71                        .texts
72                        .get(*index)
73                        .copied()
74                        .flatten()
75                        .ok_or(RenderError::MissingText)?;
76                    let source = match texture.cache_key {
77                        Some(key) => graph.add_source_with_key(texture.texture, key),
78                        None => graph.add_source(texture.texture),
79                    };
80                    (source, *place, [texture.width, texture.height])
81                }
82                PaintItem::Group { items, clip, place } => {
83                    let inner = self.layers(graph, items)?;
84                    let mut node = graph
85                        .add_composite(inner)
86                        .map_err(GraphError::into_render_error)?;
87                    if let Some(clip) = clip {
88                        let coverage = graph.add_vector(Arc::clone(clip));
89                        node = crate::frame::coverage_mask(
90                            graph,
91                            node,
92                            &[(coverage, false, alpha2_model::MaskMode::Add)],
93                        )?;
94                    }
95                    (node, *place, canvas)
96                }
97            };
98            layers.push(GraphLayer {
99                source,
100                visual: layer_transform(place, extent, size),
101                blend_mode: BlendMode::Normal,
102            });
103        }
104        Ok(layers)
105    }
106}
107
108/// `place` as the compositor's layer transform, for a source `extent`
109/// pixels in size drawn 1:1 on a picture of `size`.
110#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
111fn layer_transform(place: Placement, extent: [u32; 2], size: RenderSize) -> LayerTransform {
112    let output = [f64::from(size.width), f64::from(size.height)];
113    let extent = extent.map(f64::from);
114    LayerTransform {
115        position: [
116            (place.position[0] / output[0]) as f32,
117            (place.position[1] / output[1]) as f32,
118        ],
119        anchor: [
120            (place.anchor[0] / extent[0]) as f32,
121            (place.anchor[1] / extent[1]) as f32,
122        ],
123        scale: [
124            (place.scale[0] * extent[0] / output[0]) as f32,
125            (place.scale[1] * extent[1] / output[1]) as f32,
126        ],
127        rotation_degrees: place.rotation_degrees as f32,
128        opacity: place.opacity,
129    }
130}