1#![deny(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4mod damage;
5mod draw;
6mod growth_trim;
7mod pipeline;
8mod pixel;
9mod rewrite;
10pub mod scene;
12mod scene_item;
13pub mod sprites;
14mod style;
15mod surface;
16#[cfg(test)]
17#[path = "tests/text_draws.rs"]
18mod text_draws;
19
20pub use cranpose_render_common::rgb565::Rgb565Order;
21use cranpose_render_common::{
22 RenderScene, Renderer, SceneUpdates,
23 rgb565::{Rgb565Be, Rgb565Le},
24 text_fonts::TextFonts,
25};
26use cranpose_ui::LayoutTree;
27use cranpose_ui_graphics::{MemoryHeadroom, Size};
28pub use draw::draw_scene;
29use pixel::Pixel;
30pub use scene::{HitRegion, Scene};
31
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
35pub struct PixelRect {
36 pub x: u32,
38 pub y: u32,
40 pub width: u32,
42 pub height: u32,
44}
45
46fn pixel_rect(bounds: draw::ClipBounds) -> PixelRect {
47 PixelRect {
48 x: bounds.min_x.max(0) as u32,
49 y: bounds.min_y.max(0) as u32,
50 width: (bounds.max_x - bounds.min_x).max(0) as u32,
51 height: (bounds.max_y - bounds.min_y).max(0) as u32,
52 }
53}
54
55#[derive(Default)]
59pub struct Picture {
60 raster: Option<scene::RasterScene>,
61 rewrite: Option<Box<rewrite::Rewrite>>,
63 width: u32,
64 height: u32,
65 scale: f32,
66 changed: Vec<draw::ClipBounds>,
67 reach: Vec<Option<draw::ClipBounds>>,
70}
71
72fn valid_scale(scale: f32) -> f32 {
73 if scale.is_finite() && scale > 0.0 {
74 scale
75 } else {
76 1.0
77 }
78}
79
80pub struct PixelsRenderer {
82 scene: Scene,
83 text: draw::TextRaster,
84 app_context: Option<std::rc::Weak<cranpose_ui::AppContext>>,
86}
87
88impl Default for PixelsRenderer {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94impl PixelsRenderer {
95 pub fn new() -> Self {
98 Self::with_fonts(&[])
99 }
100
101 pub fn with_fonts(fonts: &[&'static [u8]]) -> Self {
105 Self {
106 scene: Scene::new(),
107 text: draw::TextRaster::new(TextFonts::from_fonts_or_default(fonts)),
108 app_context: None,
109 }
110 }
111
112 pub fn draw(&self, frame: &mut [u8], width: u32, height: u32) {
115 draw::draw_scene_scaled(frame, width, height, &self.scene, &self.text, 1.0);
116 }
117
118 pub fn draw_scaled(&self, frame: &mut [u8], width: u32, height: u32, scale: f32) {
121 draw::draw_scene_scaled(
122 frame,
123 width,
124 height,
125 &self.scene,
126 &self.text,
127 valid_scale(scale),
128 );
129 }
130
131 fn fill_raster_scene(
134 &self,
135 raster: &mut scene::RasterScene,
136 scale: f32,
137 previous: Option<&scene::RasterScene>,
138 ) {
139 match self.scene.graph.as_ref() {
140 Some(graph) => pipeline::fill_raster_scene(
141 raster,
142 graph,
143 self.scene.diagnostics(),
144 scale,
145 previous,
146 &self.text,
147 ),
148 None => raster.clear(),
149 }
150 }
151
152 pub fn update_picture(
157 &self,
158 picture: &mut Picture,
159 width: u32,
160 height: u32,
161 scale: f32,
162 changed: &mut Vec<PixelRect>,
163 ) {
164 let scale = valid_scale(scale);
165 if let Some(reach) = self.update_picture_in_place(picture, width, height, scale) {
166 changed.clear();
167 changed.extend(picture.changed.iter().copied().map(pixel_rect));
168 match reach {
169 pipeline::UpdatedReach::Held => {}
170 pipeline::UpdatedReach::Lost => picture.measure_reach(),
171 }
172 if cranpose_ui::current_memory_headroom() == MemoryHeadroom::Tight {
176 if let Some(rewrite) = picture.rewrite.as_mut() {
177 rewrite.release_spill();
178 }
179 growth_trim::trim(picture);
180 }
181 return;
182 }
183 let updated = (!picture.changed.is_empty()).then(|| std::mem::take(&mut picture.changed));
186 let mut raster = picture
187 .rewrite
188 .as_mut()
189 .map(|rewrite| rewrite.take_storage())
190 .unwrap_or_default();
191 self.fill_raster_scene(&mut raster, scale, picture.raster.as_ref());
192 changed.clear();
193 match picture.raster.as_ref() {
194 Some(old)
195 if (picture.width, picture.height, picture.scale) == (width, height, scale) =>
196 {
197 damage::changed_areas(
198 old,
199 &picture.reach,
200 &raster,
201 picture.frame(),
202 &mut picture.changed,
203 );
204 if let Some(mut updated) = updated {
205 picture.changed.append(&mut updated);
206 damage::merge_areas(&mut picture.changed);
207 }
208 changed.extend(picture.changed.iter().copied().map(pixel_rect));
209 }
210 _ if width > 0 && height > 0 => changed.push(PixelRect {
211 x: 0,
212 y: 0,
213 width,
214 height,
215 }),
216 _ => {}
217 }
218 picture.raster = Some(raster);
219 picture.width = width;
220 picture.height = height;
221 picture.scale = scale;
222 picture.measure_reach();
223 if cranpose_ui::current_memory_headroom() == MemoryHeadroom::Tight {
224 growth_trim::trim(picture);
225 }
226 }
227
228 fn update_picture_in_place(
235 &self,
236 picture: &mut Picture,
237 width: u32,
238 height: u32,
239 scale: f32,
240 ) -> Option<pipeline::UpdatedReach> {
241 picture.changed.clear();
242 let (Some(raster), Some(graph)) = (picture.raster.as_mut(), self.scene.graph.as_ref())
243 else {
244 return None;
245 };
246 if (picture.width, picture.height, picture.scale) != (width, height, scale) {
247 return None;
248 }
249 let window = draw::ClipBounds {
250 min_x: 0,
251 min_y: 0,
252 max_x: i32::try_from(width).unwrap_or(i32::MAX),
253 max_y: i32::try_from(height).unwrap_or(i32::MAX),
254 };
255 pipeline::InPlaceUpdate {
256 scene: raster,
257 reach: &mut picture.reach,
258 rewrite: &mut picture.rewrite,
259 }
260 .run(
261 graph,
262 self.scene.diagnostics(),
263 scale,
264 &self.text,
265 window,
266 &mut picture.changed,
267 )
268 }
269
270 pub fn prepare_picture_area(&self, picture: &Picture, area: PixelRect) {
275 let Some(raster) = picture.raster.as_ref() else {
276 return;
277 };
278 let window = draw::ClipBounds {
279 min_x: i32::try_from(area.x).unwrap_or(i32::MAX),
280 min_y: i32::try_from(area.y).unwrap_or(i32::MAX),
281 max_x: i32::try_from(area.x.saturating_add(area.width)).unwrap_or(i32::MAX),
282 max_y: i32::try_from(area.y.saturating_add(area.height)).unwrap_or(i32::MAX),
283 };
284 draw::prepare_raster_window(raster, window, &self.text);
285 }
286
287 pub fn draw_picture_area(&self, picture: &Picture, area: PixelRect, pixels: &mut [u8]) {
291 self.draw_picture_area_as(picture, area, pixels.as_chunks_mut::<4>().0);
292 }
293
294 pub fn draw_picture_area_rgb565(
301 &self,
302 picture: &Picture,
303 area: PixelRect,
304 pixels: &mut [u16],
305 order: Rgb565Order,
306 ) {
307 match order {
308 Rgb565Order::LittleEndian => self.draw_picture_area_as::<Rgb565Le>(
309 picture,
310 area,
311 bytemuck::cast_slice_mut(pixels),
312 ),
313 Rgb565Order::BigEndian => self.draw_picture_area_as::<Rgb565Be>(
314 picture,
315 area,
316 bytemuck::cast_slice_mut(pixels),
317 ),
318 }
319 }
320
321 #[inline(never)]
324 fn draw_picture_area_as<P: Pixel>(&self, picture: &Picture, area: PixelRect, pixels: &mut [P]) {
325 let Some(raster) = picture.raster.as_ref() else {
326 return;
327 };
328 let len = area.width as usize * area.height as usize;
329 let Some(pixels) = pixels.get_mut(..len) else {
330 return;
331 };
332 let mut target = draw::Target {
333 pixels,
334 left: i32::try_from(area.x).unwrap_or(i32::MAX),
335 top: i32::try_from(area.y).unwrap_or(i32::MAX),
336 width: area.width,
337 height: area.height,
338 };
339 draw::draw_raster_window(
340 &mut target,
341 raster,
342 &picture.reach,
343 self.scene.diagnostics(),
344 &self.text,
345 );
346 }
347
348 pub fn compose_sprites(
352 &self,
353 frame: &mut sprites::SpriteFrame,
354 width: u32,
355 height: u32,
356 scale: f32,
357 ) {
358 let mut scene = std::mem::take(&mut frame.scene);
359 self.fill_raster_scene(&mut scene, valid_scale(scale), None);
360 frame.compose(&scene, width, height, self.scene.diagnostics(), &self.text);
361 frame.scene = scene;
362 }
363}
364
365impl Picture {
366 fn frame(&self) -> draw::ClipBounds {
367 draw::ClipBounds {
368 min_x: 0,
369 min_y: 0,
370 max_x: i32::try_from(self.width).unwrap_or(i32::MAX),
371 max_y: i32::try_from(self.height).unwrap_or(i32::MAX),
372 }
373 }
374
375 fn measure_reach(&mut self) {
376 let frame = self.frame();
377 match self.raster.as_ref() {
378 Some(raster) => raster.reach_into(frame, &mut self.reach),
379 None => self.reach.clear(),
380 }
381 }
382}
383
384impl Renderer for PixelsRenderer {
385 type Scene = Scene;
386 type Error = std::convert::Infallible;
387
388 fn attach_app_context_services(&mut self, app_context: &cranpose_ui::AppContext) {
389 app_context.set_text_fonts(self.text.fonts.resolver());
390 self.app_context = Some(app_context.downgrade());
391 }
392
393 #[cfg(feature = "installed-fonts")]
394 fn apply_installed_fonts(&mut self, app_context: &cranpose_ui::AppContext) {
395 let Some(fonts) = self.text.fonts.with_installed_faces() else {
396 return;
397 };
398 self.text = draw::TextRaster::new(fonts);
399 let attached = self
400 .app_context
401 .as_ref()
402 .and_then(std::rc::Weak::upgrade)
403 .is_some_and(|attached| std::ptr::eq(&*attached, app_context));
404 if attached {
405 self.attach_app_context_services(app_context);
406 }
407 }
408
409 fn scene(&self) -> &Self::Scene {
410 &self.scene
411 }
412
413 fn scene_mut(&mut self) -> &mut Self::Scene {
414 &mut self.scene
415 }
416
417 fn rebuild_scene(
418 &mut self,
419 layout_tree: &LayoutTree,
420 _viewport: Size,
421 ) -> Result<(), Self::Error> {
422 self.scene.clear();
423 pipeline::render_layout_tree(layout_tree.root(), &mut self.scene);
424 Ok(())
425 }
426
427 fn rebuild_scene_from_applier(
428 &mut self,
429 applier: &mut cranpose_core::MemoryApplier,
430 root: cranpose_core::NodeId,
431 _viewport: Size,
432 ) -> Result<(), Self::Error> {
433 self.scene.rebuild_from_applier(applier, root);
434 Ok(())
435 }
436
437 fn update_scene_from_applier(
438 &mut self,
439 applier: &mut cranpose_core::MemoryApplier,
440 root: cranpose_core::NodeId,
441 _viewport: Size,
442 updates: SceneUpdates<'_>,
443 ) -> Result<(), Self::Error> {
444 self.scene.update_from_applier(applier, root, updates);
445 Ok(())
446 }
447}