1#![deny(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4mod damage;
5mod draw;
6mod pipeline;
7pub mod scene;
9pub mod sprites;
10mod style;
11mod surface;
12
13use cranpose_render_common::{
14 RenderScene, Renderer, SceneUpdates,
15 text_measure::{CachedFontTextMeasurer, SoftwareTextResources},
16};
17use cranpose_ui::LayoutTree;
18use cranpose_ui_graphics::Size;
19pub use draw::draw_scene;
20pub use scene::{HitRegion, Scene};
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct PixelRect {
26 pub x: u32,
28 pub y: u32,
30 pub width: u32,
32 pub height: u32,
34}
35
36fn pixel_rect(bounds: draw::ClipBounds) -> PixelRect {
37 PixelRect {
38 x: bounds.min_x.max(0) as u32,
39 y: bounds.min_y.max(0) as u32,
40 width: (bounds.max_x - bounds.min_x).max(0) as u32,
41 height: (bounds.max_y - bounds.min_y).max(0) as u32,
42 }
43}
44
45#[derive(Default)]
49pub struct Picture {
50 raster: Option<scene::RasterScene>,
51 spare: scene::RasterScene,
53 width: u32,
54 height: u32,
55 scale: f32,
56 changed: Vec<draw::ClipBounds>,
57 repushed: Vec<(usize, usize)>,
59}
60
61fn valid_scale(scale: f32) -> f32 {
62 if scale.is_finite() && scale > 0.0 {
63 scale
64 } else {
65 1.0
66 }
67}
68
69pub struct PixelsRenderer {
71 scene: Scene,
72 text: draw::TextRaster,
73 app_context: Option<std::rc::Weak<cranpose_ui::AppContext>>,
75}
76
77impl Default for PixelsRenderer {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl PixelsRenderer {
84 pub fn new() -> Self {
87 Self::with_fonts(&[])
88 }
89
90 pub fn with_fonts(fonts: &[&'static [u8]]) -> Self {
94 Self {
95 scene: Scene::new(),
96 text: draw::TextRaster::new(SoftwareTextResources::from_fonts(fonts)),
97 app_context: None,
98 }
99 }
100
101 pub fn draw(&self, frame: &mut [u8], width: u32, height: u32) {
104 draw::draw_scene_scaled(frame, width, height, &self.scene, &self.text, 1.0);
105 }
106
107 pub fn draw_scaled(&self, frame: &mut [u8], width: u32, height: u32, scale: f32) {
110 draw::draw_scene_scaled(
111 frame,
112 width,
113 height,
114 &self.scene,
115 &self.text,
116 valid_scale(scale),
117 );
118 }
119
120 fn fill_raster_scene(
123 &self,
124 raster: &mut scene::RasterScene,
125 scale: f32,
126 previous: Option<&scene::RasterScene>,
127 ) {
128 match self.scene.graph.as_ref() {
129 Some(graph) => pipeline::fill_raster_scene(
130 raster,
131 graph,
132 self.scene.diagnostics(),
133 scale,
134 previous,
135 &self.text,
136 ),
137 None => raster.clear(),
138 }
139 }
140
141 pub fn update_picture(
146 &self,
147 picture: &mut Picture,
148 width: u32,
149 height: u32,
150 scale: f32,
151 changed: &mut Vec<PixelRect>,
152 ) {
153 let scale = valid_scale(scale);
154 if self.update_picture_in_place(picture, width, height, scale) {
155 changed.clear();
156 changed.extend(picture.changed.iter().copied().map(pixel_rect));
157 return;
158 }
159 let mut raster = std::mem::take(&mut picture.spare);
160 self.fill_raster_scene(&mut raster, scale, picture.raster.as_ref());
161 changed.clear();
162 match picture.raster.as_ref() {
163 Some(old)
164 if (picture.width, picture.height, picture.scale) == (width, height, scale) =>
165 {
166 let window = draw::ClipBounds {
167 min_x: 0,
168 min_y: 0,
169 max_x: i32::try_from(width).unwrap_or(i32::MAX),
170 max_y: i32::try_from(height).unwrap_or(i32::MAX),
171 };
172 damage::changed_areas(old, &raster, window, &mut picture.changed);
173 changed.extend(picture.changed.iter().copied().map(pixel_rect));
174 }
175 _ if width > 0 && height > 0 => changed.push(PixelRect {
176 x: 0,
177 y: 0,
178 width,
179 height,
180 }),
181 _ => {}
182 }
183 if let Some(old) = picture.raster.replace(raster) {
184 picture.spare = old;
185 }
186 picture.width = width;
187 picture.height = height;
188 picture.scale = scale;
189 }
190
191 fn update_picture_in_place(
195 &self,
196 picture: &mut Picture,
197 width: u32,
198 height: u32,
199 scale: f32,
200 ) -> bool {
201 let (Some(raster), Some(graph)) = (picture.raster.as_mut(), self.scene.graph.as_ref())
202 else {
203 return false;
204 };
205 if (picture.width, picture.height, picture.scale) != (width, height, scale) {
206 return false;
207 }
208 let window = draw::ClipBounds {
209 min_x: 0,
210 min_y: 0,
211 max_x: i32::try_from(width).unwrap_or(i32::MAX),
212 max_y: i32::try_from(height).unwrap_or(i32::MAX),
213 };
214 pipeline::InPlaceUpdate {
215 scene: raster,
216 scratch: &mut picture.spare,
217 pushed: &mut picture.repushed,
218 }
219 .run(
220 graph,
221 self.scene.diagnostics(),
222 scale,
223 &self.text,
224 window,
225 &mut picture.changed,
226 )
227 }
228
229 pub fn draw_picture_area(&self, picture: &Picture, area: PixelRect, pixels: &mut [u8]) {
233 let Some(raster) = picture.raster.as_ref() else {
234 return;
235 };
236 let len = area.width as usize * area.height as usize * 4;
237 let Some(pixels) = pixels.get_mut(..len) else {
238 return;
239 };
240 let mut target = draw::Target {
241 pixels,
242 left: i32::try_from(area.x).unwrap_or(i32::MAX),
243 top: i32::try_from(area.y).unwrap_or(i32::MAX),
244 width: area.width,
245 height: area.height,
246 };
247 draw::draw_raster_window(&mut target, raster, self.scene.diagnostics(), &self.text);
248 }
249}
250
251impl PixelsRenderer {
252 pub fn compose_sprites(
256 &self,
257 frame: &mut sprites::SpriteFrame,
258 width: u32,
259 height: u32,
260 scale: f32,
261 ) {
262 let mut scene = std::mem::take(&mut frame.scene);
263 self.fill_raster_scene(&mut scene, valid_scale(scale), None);
264 frame.compose(&scene, width, height, self.scene.diagnostics(), &self.text);
265 frame.scene = scene;
266 }
267}
268
269impl Renderer for PixelsRenderer {
270 type Scene = Scene;
271 type Error = std::convert::Infallible;
272
273 fn attach_app_context_services(&mut self, app_context: &cranpose_ui::AppContext) {
274 app_context.set_text_measurer(CachedFontTextMeasurer::with_text_resources(
275 self.text.resources.clone(),
276 64,
277 ));
278 self.app_context = Some(app_context.downgrade());
279 }
280
281 #[cfg(feature = "installed-fonts")]
282 fn apply_installed_fonts(&mut self, app_context: &cranpose_ui::AppContext) {
283 let Some(resources) = self.text.resources.with_installed_faces() else {
284 return;
285 };
286 self.text = draw::TextRaster::new(resources);
287 let attached = self
288 .app_context
289 .as_ref()
290 .and_then(std::rc::Weak::upgrade)
291 .is_some_and(|attached| std::ptr::eq(&*attached, app_context));
292 if attached {
293 self.attach_app_context_services(app_context);
294 }
295 }
296
297 fn scene(&self) -> &Self::Scene {
298 &self.scene
299 }
300
301 fn scene_mut(&mut self) -> &mut Self::Scene {
302 &mut self.scene
303 }
304
305 fn rebuild_scene(
306 &mut self,
307 layout_tree: &LayoutTree,
308 _viewport: Size,
309 ) -> Result<(), Self::Error> {
310 self.scene.clear();
311 pipeline::render_layout_tree(layout_tree.root(), &mut self.scene);
312 Ok(())
313 }
314
315 fn rebuild_scene_from_applier(
316 &mut self,
317 applier: &mut cranpose_core::MemoryApplier,
318 root: cranpose_core::NodeId,
319 _viewport: Size,
320 ) -> Result<(), Self::Error> {
321 self.scene.rebuild_from_applier(applier, root);
322 Ok(())
323 }
324
325 fn update_scene_from_applier(
326 &mut self,
327 applier: &mut cranpose_core::MemoryApplier,
328 root: cranpose_core::NodeId,
329 _viewport: Size,
330 updates: SceneUpdates<'_>,
331 ) -> Result<(), Self::Error> {
332 self.scene.update_from_applier(applier, root, updates, true);
333 Ok(())
334 }
335
336 fn update_visual_scene_from_applier(
337 &mut self,
338 applier: &mut cranpose_core::MemoryApplier,
339 root: cranpose_core::NodeId,
340 _viewport: Size,
341 updates: SceneUpdates<'_>,
342 ) -> Result<(), Self::Error> {
343 self.scene
344 .update_from_applier(applier, root, updates, false);
345 Ok(())
346 }
347}