1#![doc = include_str!("../README.md")]
2
3mod damage;
4mod draw;
5mod pipeline;
6pub mod scene;
7pub mod sprites;
8pub mod style;
9
10use cranpose_render_common::{
11 RenderScene, Renderer, SceneUpdates,
12 text_measure::{CachedFontTextMeasurer, SoftwareTextResources},
13};
14use cranpose_ui::LayoutTree;
15use cranpose_ui_graphics::Size;
16pub use draw::draw_scene;
17pub use scene::{HitRegion, Scene};
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct PixelRect {
23 pub x: u32,
25 pub y: u32,
27 pub width: u32,
29 pub height: u32,
31}
32
33fn pixel_rect(bounds: draw::ClipBounds) -> PixelRect {
34 PixelRect {
35 x: bounds.min_x.max(0) as u32,
36 y: bounds.min_y.max(0) as u32,
37 width: (bounds.max_x - bounds.min_x).max(0) as u32,
38 height: (bounds.max_y - bounds.min_y).max(0) as u32,
39 }
40}
41
42#[derive(Default)]
46pub struct Picture {
47 raster: Option<scene::RasterScene>,
48 width: u32,
49 height: u32,
50 scale: f32,
51 changed: Vec<draw::ClipBounds>,
52}
53
54fn valid_scale(scale: f32) -> f32 {
55 if scale.is_finite() && scale > 0.0 {
56 scale
57 } else {
58 1.0
59 }
60}
61
62#[derive(Debug)]
63pub enum PixelsRendererError {
64 Layout(String),
65}
66
67pub struct PixelsRenderer {
68 scene: Scene,
69 text_resources: SoftwareTextResources,
70 app_context: Option<std::rc::Weak<cranpose_ui::AppContext>>,
72}
73
74impl Default for PixelsRenderer {
75 fn default() -> Self {
76 Self::new()
77 }
78}
79
80impl PixelsRenderer {
81 pub fn new() -> Self {
82 Self::with_fonts(&[])
83 }
84
85 pub fn with_fonts(fonts: &[&'static [u8]]) -> Self {
89 Self {
90 scene: Scene::new(),
91 text_resources: SoftwareTextResources::from_fonts(fonts),
92 app_context: None,
93 }
94 }
95
96 pub fn draw(&self, frame: &mut [u8], width: u32, height: u32) {
97 draw::draw_scene_with_text_resources(
98 frame,
99 width,
100 height,
101 &self.scene,
102 &self.text_resources,
103 );
104 }
105
106 pub fn draw_scaled(&self, frame: &mut [u8], width: u32, height: u32, scale: f32) {
109 draw::draw_scene_scaled(
110 frame,
111 width,
112 height,
113 &self.scene,
114 &self.text_resources,
115 valid_scale(scale),
116 );
117 }
118
119 fn raster_scene(&self, scale: f32) -> scene::RasterScene {
120 match self.scene.graph.as_ref() {
121 Some(graph) => pipeline::build_raster_scene(graph, self.scene.diagnostics(), scale),
122 None => scene::RasterScene::new(),
123 }
124 }
125
126 pub fn update_picture(
131 &self,
132 picture: &mut Picture,
133 width: u32,
134 height: u32,
135 scale: f32,
136 changed: &mut Vec<PixelRect>,
137 ) {
138 let scale = valid_scale(scale);
139 let raster = self.raster_scene(scale);
140 changed.clear();
141 match picture.raster.as_ref() {
142 Some(old)
143 if (picture.width, picture.height, picture.scale) == (width, height, scale) =>
144 {
145 let window = draw::ClipBounds {
146 min_x: 0,
147 min_y: 0,
148 max_x: i32::try_from(width).unwrap_or(i32::MAX),
149 max_y: i32::try_from(height).unwrap_or(i32::MAX),
150 };
151 damage::changed_areas(old, &raster, window, &mut picture.changed);
152 changed.extend(picture.changed.iter().copied().map(pixel_rect));
153 }
154 _ if width > 0 && height > 0 => changed.push(PixelRect {
155 x: 0,
156 y: 0,
157 width,
158 height,
159 }),
160 _ => {}
161 }
162 picture.raster = Some(raster);
163 picture.width = width;
164 picture.height = height;
165 picture.scale = scale;
166 }
167
168 pub fn draw_picture_area(&self, picture: &Picture, area: PixelRect, pixels: &mut [u8]) {
172 let Some(raster) = picture.raster.as_ref() else {
173 return;
174 };
175 let len = area.width as usize * area.height as usize * 4;
176 let Some(pixels) = pixels.get_mut(..len) else {
177 return;
178 };
179 let mut target = draw::Target {
180 pixels,
181 left: i32::try_from(area.x).unwrap_or(i32::MAX),
182 top: i32::try_from(area.y).unwrap_or(i32::MAX),
183 width: area.width,
184 height: area.height,
185 };
186 draw::draw_raster_window(
187 &mut target,
188 raster,
189 self.scene.diagnostics(),
190 &self.text_resources,
191 );
192 }
193}
194
195impl PixelsRenderer {
196 pub fn compose_sprites(
200 &self,
201 frame: &mut sprites::SpriteFrame,
202 width: u32,
203 height: u32,
204 scale: f32,
205 ) {
206 let scene = self.raster_scene(valid_scale(scale));
207 frame.compose(
208 &scene,
209 width,
210 height,
211 self.scene.diagnostics(),
212 &self.text_resources,
213 );
214 }
215}
216
217impl Renderer for PixelsRenderer {
218 type Scene = Scene;
219 type Error = PixelsRendererError;
220
221 fn attach_app_context_services(&mut self, app_context: &cranpose_ui::AppContext) {
222 app_context.set_text_measurer(CachedFontTextMeasurer::with_text_resources(
223 self.text_resources.clone(),
224 64,
225 ));
226 self.app_context = Some(app_context.downgrade());
227 }
228
229 #[cfg(feature = "installed-fonts")]
230 fn apply_installed_fonts(&mut self, app_context: &cranpose_ui::AppContext) {
231 let Some(resources) = self.text_resources.with_installed_faces() else {
232 return;
233 };
234 self.text_resources = resources;
235 let attached = self
236 .app_context
237 .as_ref()
238 .and_then(std::rc::Weak::upgrade)
239 .is_some_and(|attached| std::ptr::eq(&*attached, app_context));
240 if attached {
241 self.attach_app_context_services(app_context);
242 }
243 }
244
245 fn scene(&self) -> &Self::Scene {
246 &self.scene
247 }
248
249 fn scene_mut(&mut self) -> &mut Self::Scene {
250 &mut self.scene
251 }
252
253 fn rebuild_scene(
254 &mut self,
255 layout_tree: &LayoutTree,
256 _viewport: Size,
257 ) -> Result<(), Self::Error> {
258 self.scene.clear();
259 pipeline::render_layout_tree(layout_tree.root(), &mut self.scene);
260 Ok(())
261 }
262
263 fn rebuild_scene_from_applier(
264 &mut self,
265 applier: &mut cranpose_core::MemoryApplier,
266 root: cranpose_core::NodeId,
267 _viewport: Size,
268 ) -> Result<(), Self::Error> {
269 self.scene.rebuild_from_applier(applier, root);
270 Ok(())
271 }
272
273 fn update_scene_from_applier(
274 &mut self,
275 applier: &mut cranpose_core::MemoryApplier,
276 root: cranpose_core::NodeId,
277 _viewport: Size,
278 updates: SceneUpdates<'_>,
279 ) -> Result<(), Self::Error> {
280 self.scene.update_from_applier(applier, root, updates, true);
281 Ok(())
282 }
283
284 fn update_visual_scene_from_applier(
285 &mut self,
286 applier: &mut cranpose_core::MemoryApplier,
287 root: cranpose_core::NodeId,
288 _viewport: Size,
289 updates: SceneUpdates<'_>,
290 ) -> Result<(), Self::Error> {
291 self.scene
292 .update_from_applier(applier, root, updates, false);
293 Ok(())
294 }
295}