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;
11
12use cranpose_render_common::{
13 RenderScene, Renderer, SceneUpdates,
14 text_measure::{CachedFontTextMeasurer, SoftwareTextResources},
15};
16use cranpose_ui::LayoutTree;
17use cranpose_ui_graphics::Size;
18pub use draw::draw_scene;
19pub use scene::{HitRegion, Scene};
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub struct PixelRect {
25 pub x: u32,
27 pub y: u32,
29 pub width: u32,
31 pub height: u32,
33}
34
35fn pixel_rect(bounds: draw::ClipBounds) -> PixelRect {
36 PixelRect {
37 x: bounds.min_x.max(0) as u32,
38 y: bounds.min_y.max(0) as u32,
39 width: (bounds.max_x - bounds.min_x).max(0) as u32,
40 height: (bounds.max_y - bounds.min_y).max(0) as u32,
41 }
42}
43
44#[derive(Default)]
48pub struct Picture {
49 raster: Option<scene::RasterScene>,
50 spare: scene::RasterScene,
52 width: u32,
53 height: u32,
54 scale: f32,
55 changed: Vec<draw::ClipBounds>,
56}
57
58fn valid_scale(scale: f32) -> f32 {
59 if scale.is_finite() && scale > 0.0 {
60 scale
61 } else {
62 1.0
63 }
64}
65
66pub 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 {
84 Self::with_fonts(&[])
85 }
86
87 pub fn with_fonts(fonts: &[&'static [u8]]) -> Self {
91 Self {
92 scene: Scene::new(),
93 text_resources: SoftwareTextResources::from_fonts(fonts),
94 app_context: None,
95 }
96 }
97
98 pub fn draw(&self, frame: &mut [u8], width: u32, height: u32) {
101 draw::draw_scene_with_text_resources(
102 frame,
103 width,
104 height,
105 &self.scene,
106 &self.text_resources,
107 );
108 }
109
110 pub fn draw_scaled(&self, frame: &mut [u8], width: u32, height: u32, scale: f32) {
113 draw::draw_scene_scaled(
114 frame,
115 width,
116 height,
117 &self.scene,
118 &self.text_resources,
119 valid_scale(scale),
120 );
121 }
122
123 fn fill_raster_scene(&self, raster: &mut scene::RasterScene, scale: f32) {
125 match self.scene.graph.as_ref() {
126 Some(graph) => {
127 pipeline::fill_raster_scene(raster, graph, self.scene.diagnostics(), scale);
128 }
129 None => raster.clear(),
130 }
131 }
132
133 pub fn update_picture(
138 &self,
139 picture: &mut Picture,
140 width: u32,
141 height: u32,
142 scale: f32,
143 changed: &mut Vec<PixelRect>,
144 ) {
145 let scale = valid_scale(scale);
146 let mut raster = std::mem::take(&mut picture.spare);
147 self.fill_raster_scene(&mut raster, scale);
148 changed.clear();
149 match picture.raster.as_ref() {
150 Some(old)
151 if (picture.width, picture.height, picture.scale) == (width, height, scale) =>
152 {
153 let window = draw::ClipBounds {
154 min_x: 0,
155 min_y: 0,
156 max_x: i32::try_from(width).unwrap_or(i32::MAX),
157 max_y: i32::try_from(height).unwrap_or(i32::MAX),
158 };
159 damage::changed_areas(old, &raster, window, &mut picture.changed);
160 changed.extend(picture.changed.iter().copied().map(pixel_rect));
161 }
162 _ if width > 0 && height > 0 => changed.push(PixelRect {
163 x: 0,
164 y: 0,
165 width,
166 height,
167 }),
168 _ => {}
169 }
170 if let Some(old) = picture.raster.replace(raster) {
171 picture.spare = old;
172 }
173 picture.width = width;
174 picture.height = height;
175 picture.scale = scale;
176 }
177
178 pub fn draw_picture_area(&self, picture: &Picture, area: PixelRect, pixels: &mut [u8]) {
182 let Some(raster) = picture.raster.as_ref() else {
183 return;
184 };
185 let len = area.width as usize * area.height as usize * 4;
186 let Some(pixels) = pixels.get_mut(..len) else {
187 return;
188 };
189 let mut target = draw::Target {
190 pixels,
191 left: i32::try_from(area.x).unwrap_or(i32::MAX),
192 top: i32::try_from(area.y).unwrap_or(i32::MAX),
193 width: area.width,
194 height: area.height,
195 };
196 draw::draw_raster_window(
197 &mut target,
198 raster,
199 self.scene.diagnostics(),
200 &self.text_resources,
201 );
202 }
203}
204
205impl PixelsRenderer {
206 pub fn compose_sprites(
210 &self,
211 frame: &mut sprites::SpriteFrame,
212 width: u32,
213 height: u32,
214 scale: f32,
215 ) {
216 let mut scene = std::mem::take(&mut frame.scene);
217 self.fill_raster_scene(&mut scene, valid_scale(scale));
218 frame.compose(
219 &scene,
220 width,
221 height,
222 self.scene.diagnostics(),
223 &self.text_resources,
224 );
225 frame.scene = scene;
226 }
227}
228
229impl Renderer for PixelsRenderer {
230 type Scene = Scene;
231 type Error = std::convert::Infallible;
232
233 fn attach_app_context_services(&mut self, app_context: &cranpose_ui::AppContext) {
234 app_context.set_text_measurer(CachedFontTextMeasurer::with_text_resources(
235 self.text_resources.clone(),
236 64,
237 ));
238 self.app_context = Some(app_context.downgrade());
239 }
240
241 #[cfg(feature = "installed-fonts")]
242 fn apply_installed_fonts(&mut self, app_context: &cranpose_ui::AppContext) {
243 let Some(resources) = self.text_resources.with_installed_faces() else {
244 return;
245 };
246 self.text_resources = resources;
247 let attached = self
248 .app_context
249 .as_ref()
250 .and_then(std::rc::Weak::upgrade)
251 .is_some_and(|attached| std::ptr::eq(&*attached, app_context));
252 if attached {
253 self.attach_app_context_services(app_context);
254 }
255 }
256
257 fn scene(&self) -> &Self::Scene {
258 &self.scene
259 }
260
261 fn scene_mut(&mut self) -> &mut Self::Scene {
262 &mut self.scene
263 }
264
265 fn rebuild_scene(
266 &mut self,
267 layout_tree: &LayoutTree,
268 _viewport: Size,
269 ) -> Result<(), Self::Error> {
270 self.scene.clear();
271 pipeline::render_layout_tree(layout_tree.root(), &mut self.scene);
272 Ok(())
273 }
274
275 fn rebuild_scene_from_applier(
276 &mut self,
277 applier: &mut cranpose_core::MemoryApplier,
278 root: cranpose_core::NodeId,
279 _viewport: Size,
280 ) -> Result<(), Self::Error> {
281 self.scene.rebuild_from_applier(applier, root);
282 Ok(())
283 }
284
285 fn update_scene_from_applier(
286 &mut self,
287 applier: &mut cranpose_core::MemoryApplier,
288 root: cranpose_core::NodeId,
289 _viewport: Size,
290 updates: SceneUpdates<'_>,
291 ) -> Result<(), Self::Error> {
292 self.scene.update_from_applier(applier, root, updates, true);
293 Ok(())
294 }
295
296 fn update_visual_scene_from_applier(
297 &mut self,
298 applier: &mut cranpose_core::MemoryApplier,
299 root: cranpose_core::NodeId,
300 _viewport: Size,
301 updates: SceneUpdates<'_>,
302 ) -> Result<(), Self::Error> {
303 self.scene
304 .update_from_applier(applier, root, updates, false);
305 Ok(())
306 }
307}