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 in
35/// the top-right corner, sized with fixed monospace-ish metrics so the
36/// overlay never depends on renderer text measurement.
37pub fn build_dev_overlay_graph(
38    text: &str,
39    viewport: Size,
40    node_id: cranpose_core::NodeId,
41) -> RenderGraph {
42    let padding = 8.0;
43    let font_size = 14.0;
44    let char_width = 7.0;
45    let text_width = text.len() as f32 * char_width;
46    let text_height = font_size * 1.4;
47    let x = (viewport.width - text_width - padding * 2.0).max(padding);
48    let y = padding;
49
50    let overlay_layer = LayerNode {
51        node_id: Some(node_id),
52        local_bounds: Rect {
53            x: 0.0,
54            y: 0.0,
55            width: text_width + padding,
56            height: text_height + padding / 2.0,
57        },
58        transform_to_parent: ProjectiveTransform::translation(x, y),
59        children: vec![
60            RenderNode::Primitive(PrimitiveEntry {
61                phase: PrimitivePhase::BeforeChildren,
62                node: PrimitiveNode::Draw(Box::new(DrawPrimitiveNode {
63                    primitive: DrawPrimitive::RoundRect {
64                        rect: Rect {
65                            x: 0.0,
66                            y: 0.0,
67                            width: text_width + padding,
68                            height: text_height + padding / 2.0,
69                        },
70                        brush: Brush::Solid(Color(0.0, 0.0, 0.0, 0.7)),
71                        radii: CornerRadii::uniform(4.0),
72                        stroke: None,
73                    },
74                    clip: None,
75                })),
76            }),
77            RenderNode::Primitive(PrimitiveEntry {
78                phase: PrimitivePhase::AfterChildren,
79                node: PrimitiveNode::Text(Box::new(TextPrimitiveNode {
80                    node_id,
81                    rect: Rect {
82                        x: padding / 2.0,
83                        y: padding / 4.0,
84                        width: text_width,
85                        height: text_height,
86                    },
87                    text: cranpose_ui::text::shared_plain_annotated_string(text),
88                    render_text: cranpose_ui::text::shared_plain_render_string(text),
89                    text_style: Default::default(),
90                    font_size,
91                    layout_options: cranpose_ui::TextLayoutOptions::default(),
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}