Skip to main content

cranpose_render_common/
dev_overlay.rs

1//! Renderer-drawn dev overlay (fps counter): one shared graph builder so
2//! every backend draws the identical overlay without composition impact.
3
4use cranpose_ui_graphics::{Brush, Color, CornerRadii, DrawPrimitive, Rect, Size};
5
6use crate::graph::{
7    DrawPrimitiveNode, LayerNode, PrimitiveEntry, PrimitiveNode, PrimitivePhase,
8    ProjectiveTransform, RenderGraph, RenderNode, TextPrimitiveNode,
9};
10
11/// Cache key for the last-built overlay; rebuilding only on change keeps the
12/// overlay free when the text is stable.
13#[derive(Clone, PartialEq, Eq)]
14pub struct DevOverlayKey {
15    /// The overlay text, for example the fps line.
16    pub text: String,
17    /// Bit pattern of the viewport width in logical pixels, so the key can be compared exactly.
18    pub viewport_width_bits: u32,
19    /// Bit pattern of the viewport height in logical pixels, so the key can be compared exactly.
20    pub viewport_height_bits: u32,
21}
22
23impl DevOverlayKey {
24    /// Builds the key for `text` drawn in a viewport of the given size in logical pixels.
25    pub fn new(text: &str, viewport: Size) -> Self {
26        Self {
27            text: text.to_string(),
28            viewport_width_bits: viewport.width.to_bits(),
29            viewport_height_bits: viewport.height.to_bits(),
30        }
31    }
32}
33
34/// Build the dev-overlay graph: a translucent pill with the stats text, laid
35/// out in `overlay`, in the top-right corner.
36pub fn build_dev_overlay_graph(
37    overlay: &cranpose_ui::PreparedTextLayout,
38    viewport: Size,
39    node_id: cranpose_core::NodeId,
40) -> RenderGraph {
41    let padding = 8.0;
42    let Size {
43        width: text_width,
44        height: text_height,
45    } = overlay.size();
46    let x = (viewport.width - text_width - padding * 2.0).max(padding);
47    let y = padding;
48
49    let overlay_layer = LayerNode {
50        node_id: Some(node_id),
51        local_bounds: Rect {
52            x: 0.0,
53            y: 0.0,
54            width: text_width + padding,
55            height: text_height + padding / 2.0,
56        },
57        transform_to_parent: ProjectiveTransform::translation(x, y),
58        children: vec![
59            RenderNode::Primitive(PrimitiveEntry {
60                phase: PrimitivePhase::BeforeChildren,
61                node: PrimitiveNode::Draw(Box::new(DrawPrimitiveNode {
62                    primitive: DrawPrimitive::RoundRect {
63                        rect: Rect {
64                            x: 0.0,
65                            y: 0.0,
66                            width: text_width + padding,
67                            height: text_height + padding / 2.0,
68                        },
69                        brush: Brush::Solid(Color(0.0, 0.0, 0.0, 0.7)),
70                        radii: CornerRadii::uniform(4.0),
71                        stroke: None,
72                    },
73                    clip: None,
74                })),
75            }),
76            RenderNode::Primitive(PrimitiveEntry {
77                phase: PrimitivePhase::AfterChildren,
78                node: PrimitiveNode::Text(Box::new(TextPrimitiveNode {
79                    node_id,
80                    rect: Rect {
81                        x: padding / 2.0,
82                        y: padding / 4.0,
83                        width: text_width,
84                        height: text_height,
85                    },
86                    text: std::rc::Rc::clone(&overlay.text),
87                    render_text: overlay.render_text(),
88                    layout: overlay.layout.clone(),
89                    text_style: std::sync::Arc::clone(&overlay.visual_style),
90                    font_size: overlay.visual_style.resolve_font_size(14.0),
91                    overflow: cranpose_ui::TextOverflow::Clip,
92                    clip: None,
93                    paint: Default::default(),
94                })),
95            }),
96        ],
97        ..Default::default()
98    };
99    RenderGraph::new(LayerNode {
100        local_bounds: Rect::from_size(viewport),
101        children: vec![RenderNode::Layer(Box::new(overlay_layer))],
102        ..Default::default()
103    })
104}