iced_tiny_skia/window/
compositor.rs

1use crate::core::{Color, Rectangle, Size};
2use crate::graphics::compositor::{self, Information};
3use crate::graphics::damage;
4use crate::graphics::error::{self, Error};
5use crate::graphics::{self, Shell, Viewport};
6use crate::{Layer, Renderer, Settings};
7
8use std::collections::VecDeque;
9use std::num::NonZeroU32;
10
11pub struct Compositor {
12    context: softbuffer::Context<Box<dyn compositor::Display>>,
13    settings: Settings,
14}
15
16pub struct Surface {
17    window: softbuffer::Surface<
18        Box<dyn compositor::Display>,
19        Box<dyn compositor::Window>,
20    >,
21    clip_mask: tiny_skia::Mask,
22    layer_stack: VecDeque<Vec<Layer>>,
23    background_color: Color,
24    max_age: u8,
25}
26
27impl crate::graphics::Compositor for Compositor {
28    type Renderer = Renderer;
29    type Surface = Surface;
30
31    async fn with_backend(
32        settings: graphics::Settings,
33        display: impl compositor::Display,
34        _compatible_window: impl compositor::Window,
35        _shell: Shell,
36        backend: Option<&str>,
37    ) -> Result<Self, Error> {
38        match backend {
39            None | Some("tiny-skia") | Some("tiny_skia") => {
40                Ok(new(settings.into(), display))
41            }
42            Some(backend) => Err(Error::GraphicsAdapterNotFound {
43                backend: "tiny-skia",
44                reason: error::Reason::DidNotMatch {
45                    preferred_backend: backend.to_owned(),
46                },
47            }),
48        }
49    }
50
51    fn create_renderer(&self) -> Self::Renderer {
52        Renderer::new(
53            self.settings.default_font,
54            self.settings.default_text_size,
55        )
56    }
57
58    fn create_surface<W: compositor::Window + Clone>(
59        &mut self,
60        window: W,
61        width: u32,
62        height: u32,
63    ) -> Self::Surface {
64        let window = softbuffer::Surface::new(
65            &self.context,
66            Box::new(window.clone()) as _,
67        )
68        .expect("Create softbuffer surface for window");
69
70        let mut surface = Surface {
71            window,
72            clip_mask: tiny_skia::Mask::new(1, 1).expect("Create clip mask"),
73            layer_stack: VecDeque::new(),
74            background_color: Color::BLACK,
75            max_age: 0,
76        };
77
78        if width > 0 && height > 0 {
79            self.configure_surface(&mut surface, width, height);
80        }
81
82        surface
83    }
84
85    fn configure_surface(
86        &mut self,
87        surface: &mut Self::Surface,
88        width: u32,
89        height: u32,
90    ) {
91        surface
92            .window
93            .resize(
94                NonZeroU32::new(width).expect("Non-zero width"),
95                NonZeroU32::new(height).expect("Non-zero height"),
96            )
97            .expect("Resize surface");
98
99        surface.clip_mask =
100            tiny_skia::Mask::new(width, height).expect("Create clip mask");
101        surface.layer_stack.clear();
102    }
103
104    fn information(&self) -> Information {
105        Information {
106            adapter: String::from("CPU"),
107            backend: String::from("tiny-skia"),
108        }
109    }
110
111    fn present(
112        &mut self,
113        renderer: &mut Self::Renderer,
114        surface: &mut Self::Surface,
115        viewport: &Viewport,
116        background_color: Color,
117        on_pre_present: impl FnOnce(),
118    ) -> Result<(), compositor::SurfaceError> {
119        present(
120            renderer,
121            surface,
122            viewport,
123            background_color,
124            on_pre_present,
125        )
126    }
127
128    fn screenshot(
129        &mut self,
130        renderer: &mut Self::Renderer,
131        viewport: &Viewport,
132        background_color: Color,
133    ) -> Vec<u8> {
134        screenshot(renderer, viewport, background_color)
135    }
136}
137
138pub fn new(
139    settings: Settings,
140    display: impl compositor::Display,
141) -> Compositor {
142    #[allow(unsafe_code)]
143    let context = softbuffer::Context::new(Box::new(display) as _)
144        .expect("Create softbuffer context");
145
146    Compositor { context, settings }
147}
148
149pub fn present(
150    renderer: &mut Renderer,
151    surface: &mut Surface,
152    viewport: &Viewport,
153    background_color: Color,
154    on_pre_present: impl FnOnce(),
155) -> Result<(), compositor::SurfaceError> {
156    let physical_size = viewport.physical_size();
157
158    let mut buffer = surface
159        .window
160        .buffer_mut()
161        .map_err(|_| compositor::SurfaceError::Lost)?;
162
163    let last_layers = {
164        let age = buffer.age();
165
166        surface.max_age = surface.max_age.max(age);
167        surface.layer_stack.truncate(surface.max_age as usize);
168
169        if age > 0 {
170            surface.layer_stack.get(age as usize - 1)
171        } else {
172            None
173        }
174    };
175
176    let damage = last_layers
177        .and_then(|last_layers| {
178            (surface.background_color == background_color).then(|| {
179                damage::diff(
180                    last_layers,
181                    renderer.layers(),
182                    |layer| vec![layer.bounds],
183                    Layer::damage,
184                )
185            })
186        })
187        .unwrap_or_else(|| vec![Rectangle::with_size(viewport.logical_size())]);
188
189    if damage.is_empty() {
190        if let Some(last_layers) = last_layers {
191            surface.layer_stack.push_front(last_layers.clone());
192        }
193    } else {
194        surface.layer_stack.push_front(renderer.layers().to_vec());
195        surface.background_color = background_color;
196
197        let damage = damage::group(
198            damage,
199            Rectangle::with_size(viewport.logical_size()),
200        );
201
202        let mut pixels = tiny_skia::PixmapMut::from_bytes(
203            bytemuck::cast_slice_mut(&mut buffer),
204            physical_size.width,
205            physical_size.height,
206        )
207        .expect("Create pixel map");
208
209        renderer.draw(
210            &mut pixels,
211            &mut surface.clip_mask,
212            viewport,
213            &damage,
214            background_color,
215        );
216    }
217
218    on_pre_present();
219    buffer.present().map_err(|_| compositor::SurfaceError::Lost)
220}
221
222pub fn screenshot(
223    renderer: &mut Renderer,
224    viewport: &Viewport,
225    background_color: Color,
226) -> Vec<u8> {
227    let size = viewport.physical_size();
228
229    let mut offscreen_buffer: Vec<u32> =
230        vec![0; size.width as usize * size.height as usize];
231
232    let mut clip_mask = tiny_skia::Mask::new(size.width, size.height)
233        .expect("Create clip mask");
234
235    renderer.draw(
236        &mut tiny_skia::PixmapMut::from_bytes(
237            bytemuck::cast_slice_mut(&mut offscreen_buffer),
238            size.width,
239            size.height,
240        )
241        .expect("Create offscreen pixel map"),
242        &mut clip_mask,
243        viewport,
244        &[Rectangle::with_size(Size::new(
245            size.width as f32,
246            size.height as f32,
247        ))],
248        background_color,
249    );
250
251    offscreen_buffer.iter().fold(
252        Vec::with_capacity(offscreen_buffer.len() * 4),
253        |mut acc, pixel| {
254            const A_MASK: u32 = 0xFF_00_00_00;
255            const R_MASK: u32 = 0x00_FF_00_00;
256            const G_MASK: u32 = 0x00_00_FF_00;
257            const B_MASK: u32 = 0x00_00_00_FF;
258
259            let a = ((A_MASK & pixel) >> 24) as u8;
260            let r = ((R_MASK & pixel) >> 16) as u8;
261            let g = ((G_MASK & pixel) >> 8) as u8;
262            let b = (B_MASK & pixel) as u8;
263
264            acc.extend([r, g, b, a]);
265            acc
266        },
267    )
268}