Skip to main content

cranpose_render_pixels/
lib.rs

1#![doc = include_str!("../README.md")]
2
3mod draw;
4mod pipeline;
5pub mod scene;
6pub mod sprites;
7pub mod style;
8
9use cranpose_render_common::{
10    RenderScene, Renderer, SceneUpdates,
11    text_measure::{CachedFontTextMeasurer, SoftwareTextResources},
12};
13use cranpose_ui::LayoutTree;
14use cranpose_ui_graphics::Size;
15pub use draw::draw_scene;
16pub use scene::{HitRegion, Scene};
17
18#[derive(Debug)]
19pub enum PixelsRendererError {
20    Layout(String),
21}
22
23pub struct PixelsRenderer {
24    scene: Scene,
25    text_resources: SoftwareTextResources,
26    /// The app context this renderer gave its text measurer.
27    app_context: Option<std::rc::Weak<cranpose_ui::AppContext>>,
28}
29
30impl Default for PixelsRenderer {
31    fn default() -> Self {
32        Self::new()
33    }
34}
35
36impl PixelsRenderer {
37    pub fn new() -> Self {
38        Self {
39            scene: Scene::new(),
40            text_resources: SoftwareTextResources::default(),
41            app_context: None,
42        }
43    }
44
45    pub fn draw(&self, frame: &mut [u8], width: u32, height: u32) {
46        draw::draw_scene_with_text_resources(
47            frame,
48            width,
49            height,
50            &self.scene,
51            &self.text_resources,
52        );
53    }
54
55    /// Rasterizes logical scene coordinates at `scale` device pixels per point.
56    /// Hit regions stay in logical coordinates. Invalid scales use 1.0.
57    pub fn draw_scaled(&self, frame: &mut [u8], width: u32, height: u32, scale: f32) {
58        let scale = if scale.is_finite() && scale > 0.0 {
59            scale
60        } else {
61            1.0
62        };
63        draw::draw_scene_scaled(
64            frame,
65            width,
66            height,
67            &self.scene,
68            &self.text_resources,
69            scale,
70        );
71    }
72}
73
74impl PixelsRenderer {
75    /// The scene at `scale` device pixels per point as sprites for a GPU
76    /// compositor, into `frame`, which keeps its textures from the frames
77    /// before. Invalid scales use 1.0.
78    pub fn compose_sprites(
79        &self,
80        frame: &mut sprites::SpriteFrame,
81        width: u32,
82        height: u32,
83        scale: f32,
84    ) {
85        let scale = if scale.is_finite() && scale > 0.0 {
86            scale
87        } else {
88            1.0
89        };
90        let scene = match self.scene.graph.as_ref() {
91            Some(graph) => pipeline::build_raster_scene(graph, self.scene.diagnostics(), scale),
92            None => scene::RasterScene::new(),
93        };
94        frame.compose(
95            &scene,
96            width,
97            height,
98            self.scene.diagnostics(),
99            &self.text_resources,
100        );
101    }
102}
103
104impl Renderer for PixelsRenderer {
105    type Scene = Scene;
106    type Error = PixelsRendererError;
107
108    fn attach_app_context_services(&mut self, app_context: &cranpose_ui::AppContext) {
109        app_context.set_text_measurer(CachedFontTextMeasurer::with_text_resources(
110            self.text_resources.clone(),
111            64,
112        ));
113        self.app_context = Some(app_context.downgrade());
114    }
115
116    #[cfg(feature = "installed-fonts")]
117    fn apply_installed_fonts(&mut self, app_context: &cranpose_ui::AppContext) {
118        let Some(resources) = self.text_resources.with_installed_faces() else {
119            return;
120        };
121        self.text_resources = resources;
122        let attached = self
123            .app_context
124            .as_ref()
125            .and_then(std::rc::Weak::upgrade)
126            .is_some_and(|attached| std::ptr::eq(&*attached, app_context));
127        if attached {
128            self.attach_app_context_services(app_context);
129        }
130    }
131
132    fn scene(&self) -> &Self::Scene {
133        &self.scene
134    }
135
136    fn scene_mut(&mut self) -> &mut Self::Scene {
137        &mut self.scene
138    }
139
140    fn rebuild_scene(
141        &mut self,
142        layout_tree: &LayoutTree,
143        _viewport: Size,
144    ) -> Result<(), Self::Error> {
145        self.scene.clear();
146        pipeline::render_layout_tree(layout_tree.root(), &mut self.scene);
147        Ok(())
148    }
149
150    fn rebuild_scene_from_applier(
151        &mut self,
152        applier: &mut cranpose_core::MemoryApplier,
153        root: cranpose_core::NodeId,
154        _viewport: Size,
155    ) -> Result<(), Self::Error> {
156        self.scene.rebuild_from_applier(applier, root);
157        Ok(())
158    }
159
160    fn update_scene_from_applier(
161        &mut self,
162        applier: &mut cranpose_core::MemoryApplier,
163        root: cranpose_core::NodeId,
164        _viewport: Size,
165        updates: SceneUpdates<'_>,
166    ) -> Result<(), Self::Error> {
167        self.scene.update_from_applier(applier, root, updates, true);
168        Ok(())
169    }
170
171    fn update_visual_scene_from_applier(
172        &mut self,
173        applier: &mut cranpose_core::MemoryApplier,
174        root: cranpose_core::NodeId,
175        _viewport: Size,
176        updates: SceneUpdates<'_>,
177    ) -> Result<(), Self::Error> {
178        self.scene
179            .update_from_applier(applier, root, updates, false);
180        Ok(())
181    }
182}