zng_ext_image/
render.rs

1use std::{any::Any, sync::Arc};
2
3use zng_app::{
4    update::{EventUpdate, UPDATES},
5    widget::{
6        WIDGET,
7        node::{BoxedUiNode, UiNode, UiNodeOp, match_node},
8        property,
9    },
10    window::{WINDOW, WindowId},
11};
12use zng_layout::unit::Factor;
13use zng_state_map::{StateId, static_id};
14use zng_var::{ArcVar, IntoVar, Var, WeakVar, types::WeakArcVar, var};
15use zng_view_api::{image::ImageMaskMode, window::RenderMode};
16
17use crate::{IMAGES, IMAGES_SV, ImageManager, ImageRenderArgs, ImageSource, ImageVar, ImagesService, Img};
18
19impl ImagesService {
20    fn render<N, R>(&mut self, mask: Option<ImageMaskMode>, render: N) -> ImageVar
21    where
22        N: FnOnce() -> R + Send + Sync + 'static,
23        R: ImageRenderWindowRoot,
24    {
25        let result = var(Img::new_none(None));
26        let windows = self.render.windows();
27        self.render_img(
28            mask,
29            move || {
30                let r = render();
31                windows.enable_frame_capture_in_window_context(None);
32                Box::new(r)
33            },
34            &result,
35        );
36        result.read_only()
37    }
38
39    fn render_node<U, N>(&mut self, render_mode: RenderMode, scale_factor: Factor, mask: Option<ImageMaskMode>, render: N) -> ImageVar
40    where
41        U: UiNode,
42        N: FnOnce() -> U + Send + Sync + 'static,
43    {
44        let scale_factor = scale_factor.into();
45        let result = var(Img::new_none(None));
46        let windows = self.render.windows();
47        self.render_img(
48            mask,
49            move || {
50                let node = render();
51                let r = windows.new_window_root(node.boxed(), render_mode, scale_factor);
52                windows.enable_frame_capture_in_window_context(mask);
53                r
54            },
55            &result,
56        );
57        result.read_only()
58    }
59
60    pub(super) fn render_img<N>(&mut self, mask: Option<ImageMaskMode>, render: N, result: &ArcVar<Img>)
61    where
62        N: FnOnce() -> Box<dyn ImageRenderWindowRoot> + Send + Sync + 'static,
63    {
64        self.render.requests.push(RenderRequest {
65            render: Box::new(render),
66            image: result.downgrade(),
67            mask,
68        });
69        UPDATES.update(None);
70    }
71}
72
73impl ImageSource {
74    /// New image from a function that generates a headless window.
75    ///
76    /// The function is called every time the image source is resolved and it is not found in the cache.
77    ///
78    /// # Examples
79    ///
80    /// ```
81    /// # use zng_ext_image::*;
82    /// # use zng_color::colors;
83    /// # use std::any::Any;
84    /// # struct WindowRoot;
85    /// # impl ImageRenderWindowRoot for WindowRoot { fn into_any(self: Box<Self>) -> Box<dyn Any> { self } }
86    /// # macro_rules! Window { ($($property:ident = $value:expr;)+) => { {$(let _ = $value;)+ WindowRoot } } }
87    /// # macro_rules! Text { ($($tt:tt)*) => { () } }
88    /// # fn main() { }
89    /// # fn demo() {
90    /// # let _ = ImageSource::render(
91    ///     |args| Window! {
92    ///         size = (500, 400);
93    ///         parent = args.parent;
94    ///         background_color = colors::GREEN;
95    ///         child = Text!("Rendered!");
96    ///     }
97    /// )
98    /// # ; }
99    /// ```
100    ///
101    pub fn render<F, R>(new_img: F) -> Self
102    where
103        F: Fn(&ImageRenderArgs) -> R + Send + Sync + 'static,
104        R: ImageRenderWindowRoot,
105    {
106        let window = IMAGES_SV.read().render.windows();
107        Self::Render(
108            Arc::new(Box::new(move |args| {
109                if let Some(parent) = args.parent {
110                    window.set_parent_in_window_context(parent);
111                }
112                let r = new_img(args);
113                window.enable_frame_capture_in_window_context(None);
114                Box::new(r)
115            })),
116            None,
117        )
118    }
119
120    /// New image from a function that generates a new [`UiNode`].
121    ///
122    /// The function is called every time the image source is resolved and it is not found in the cache.
123    ///
124    /// Note that the generated [`UiNode`] is not a child of the widget that renders the image, it is the root widget of a headless
125    /// surface, not a part of the context where it is rendered. See [`IMAGES.render`] for more information.
126    ///
127    /// # Examples
128    ///
129    /// ```
130    /// # use zng_ext_image::*;
131    /// # use zng_view_api::window::RenderMode;
132    /// # use std::any::Any;
133    /// # struct WindowRoot;
134    /// # impl ImageRenderWindowRoot for WindowRoot { fn into_any(self: Box<Self>) -> Box<dyn Any> { self } }
135    /// # macro_rules! Container { ($($tt:tt)*) => { zng_app::widget::node::FillUiNode } }
136    /// # fn main() { }
137    /// # fn demo() {
138    /// # let _ =
139    /// ImageSource::render_node(
140    ///     RenderMode::Software,
141    ///     |_args| Container! {
142    ///         size = (500, 400);
143    ///         background_color = colors::GREEN;
144    ///         child = Text!("Rendered!");
145    ///     }
146    /// )
147    /// # ; }
148    /// ```
149    ///
150    /// [`IMAGES.render`]: crate::IMAGES::render
151    /// [`UiNode`]: zng_app::widget::node::UiNode
152    pub fn render_node<U, N>(render_mode: RenderMode, render: N) -> Self
153    where
154        U: UiNode,
155        N: Fn(&ImageRenderArgs) -> U + Send + Sync + 'static,
156    {
157        let window = IMAGES_SV.read().render.windows();
158        Self::Render(
159            Arc::new(Box::new(move |args| {
160                if let Some(parent) = args.parent {
161                    window.set_parent_in_window_context(parent);
162                }
163                let node = render(args);
164                window.enable_frame_capture_in_window_context(None);
165                window.new_window_root(node.boxed(), render_mode, None)
166            })),
167            None,
168        )
169    }
170}
171
172impl IMAGES {
173    /// Render the *window* generated by `render` to an image.
174    ///
175    /// The *window* is created as a headless surface and rendered to the returned image. You can use the
176    /// [`IMAGE_RENDER.retain`] var create an image that updates with new frames, or by default only render once.
177    ///
178    /// The closure runs in the [`WINDOW`] context of the headless window.
179    ///
180    /// [`IMAGE_RENDER.retain`]: IMAGE_RENDER::retain
181    /// [`WINDOW`]: zng_app::window::WINDOW
182    pub fn render<N, R>(&self, mask: Option<ImageMaskMode>, render: N) -> ImageVar
183    where
184        N: FnOnce() -> R + Send + Sync + 'static,
185        R: ImageRenderWindowRoot,
186    {
187        IMAGES_SV.write().render(mask, render)
188    }
189
190    /// Render an [`UiNode`] to an image.
191    ///
192    /// This method is a shortcut to [`render`] a node without needing to declare the headless window, note that
193    /// a headless window is still used, the node does not have the same context as the calling widget.
194    ///
195    /// [`render`]: Self::render
196    /// [`UiNode`]: zng_app::widget::node::UiNode
197    pub fn render_node<U, N>(
198        &self,
199        render_mode: RenderMode,
200        scale_factor: impl Into<Factor>,
201        mask: Option<ImageMaskMode>,
202        render: N,
203    ) -> ImageVar
204    where
205        U: UiNode,
206        N: FnOnce() -> U + Send + Sync + 'static,
207    {
208        IMAGES_SV.write().render_node(render_mode, scale_factor.into(), mask, render)
209    }
210}
211
212/// Images render window hook.
213#[expect(non_camel_case_types)]
214pub struct IMAGES_WINDOW;
215impl IMAGES_WINDOW {
216    /// Sets the windows service used to manage the headless windows used to render images.
217    ///
218    /// This must be called by the windows implementation only.
219    pub fn hook_render_windows_service(&self, service: Box<dyn ImageRenderWindowsService>) {
220        let mut img = IMAGES_SV.write();
221        assert!(img.render.windows.is_none());
222        img.render.windows = Some(service);
223    }
224}
225
226impl ImageManager {
227    /// AppExtension::update
228    pub(super) fn update_render(&mut self) {
229        let mut images = IMAGES_SV.write();
230
231        if !images.render.active.is_empty() {
232            let windows = images.render.windows();
233
234            images.render.active.retain(|r| {
235                let mut retain = false;
236                if let Some(img) = r.image.upgrade() {
237                    retain = img.with(Img::is_loading) || r.retain.get();
238                }
239
240                if !retain {
241                    windows.close_window(r.window_id);
242                }
243
244                retain
245            });
246        }
247
248        if !images.render.requests.is_empty() {
249            let windows = images.render.windows();
250
251            for req in images.render.requests.drain(..) {
252                if let Some(img) = req.image.upgrade() {
253                    let windows_in = windows.clone_boxed();
254                    windows.open_headless_window(Box::new(move || {
255                        let ctx = ImageRenderCtx::new();
256                        let retain = ctx.retain.clone();
257                        WINDOW.set_state(*IMAGE_RENDER_ID, ctx);
258
259                        let w = (req.render)();
260
261                        windows_in.enable_frame_capture_in_window_context(req.mask);
262
263                        let a = ActiveRenderer {
264                            window_id: WINDOW.id(),
265                            image: img.downgrade(),
266                            retain,
267                        };
268                        IMAGES_SV.write().render.active.push(a);
269
270                        w
271                    }));
272                }
273            }
274        }
275    }
276
277    /// AppExtension::event_preview
278    pub(super) fn event_preview_render(&mut self, update: &EventUpdate) {
279        let imgs = IMAGES_SV.read();
280        if !imgs.render.active.is_empty() {
281            if let Some((id, img)) = imgs.render.windows().on_frame_image_ready(update) {
282                if let Some(a) = imgs.render.active.iter().find(|a| a.window_id == id) {
283                    if let Some(img_var) = a.image.upgrade() {
284                        img_var.set(img.clone());
285                    }
286                }
287            }
288        }
289    }
290}
291
292/// Fields for [`Images`] related to the render operation.
293#[derive(Default)]
294pub(super) struct ImagesRender {
295    windows: Option<Box<dyn ImageRenderWindowsService>>,
296    requests: Vec<RenderRequest>,
297    active: Vec<ActiveRenderer>,
298}
299impl ImagesRender {
300    fn windows(&self) -> Box<dyn ImageRenderWindowsService> {
301        self.windows.as_ref().expect("render windows service not inited").clone_boxed()
302    }
303}
304
305struct ActiveRenderer {
306    window_id: WindowId,
307    image: WeakArcVar<Img>,
308    retain: ArcVar<bool>,
309}
310
311struct RenderRequest {
312    render: Box<dyn FnOnce() -> Box<dyn ImageRenderWindowRoot> + Send + Sync>,
313    image: WeakArcVar<Img>,
314    mask: Option<ImageMaskMode>,
315}
316
317#[derive(Clone)]
318struct ImageRenderCtx {
319    retain: ArcVar<bool>,
320}
321impl ImageRenderCtx {
322    fn new() -> Self {
323        Self { retain: var(false) }
324    }
325}
326
327static_id! {
328    static ref IMAGE_RENDER_ID: StateId<ImageRenderCtx>;
329}
330
331/// Controls properties of the render window used by [`IMAGES.render`].
332///
333/// [`IMAGES.render`]: IMAGES::render
334#[expect(non_camel_case_types)]
335pub struct IMAGE_RENDER;
336impl IMAGE_RENDER {
337    /// If the current context is an [`IMAGES.render`] closure, window or widget.
338    ///
339    /// [`IMAGES.render`]: IMAGES::render
340    pub fn is_in_render(&self) -> bool {
341        WINDOW.contains_state(*IMAGE_RENDER_ID)
342    }
343
344    /// If the render task is kept alive after a frame is produced, this is `false` by default
345    /// meaning the image only renders once, if set to `true` the image will automatically update
346    /// when the render widget requests a new frame.
347    pub fn retain(&self) -> ArcVar<bool> {
348        WINDOW.req_state(*IMAGE_RENDER_ID).retain
349    }
350}
351
352/// If the render task is kept alive after a frame is produced, this is `false` by default
353/// meaning the image only renders once, if set to `true` the image will automatically update
354/// when the render widget requests a new frame.
355///
356/// This property sets and binds `retain` to [`IMAGE_RENDER.retain`].
357///
358/// [`IMAGE_RENDER.retain`]: IMAGE_RENDER::retain
359#[property(CONTEXT, default(false))]
360pub fn render_retain(child: impl UiNode, retain: impl IntoVar<bool>) -> impl UiNode {
361    let retain = retain.into_var();
362    match_node(child, move |_, op| {
363        if let UiNodeOp::Init = op {
364            if IMAGE_RENDER.is_in_render() {
365                let actual_retain = IMAGE_RENDER.retain();
366                actual_retain.set_from(&retain);
367                let handle = actual_retain.bind(&retain);
368                WIDGET.push_var_handle(handle);
369            } else {
370                tracing::error!("can only set `render_retain` in render widgets")
371            }
372        }
373    })
374}
375
376/// Reference to a windows manager service that [`IMAGES`] can use to render images.
377///
378/// This service must be implemented by the window implementer, the `WINDOWS` service implements it.
379pub trait ImageRenderWindowsService: Send + Sync + 'static {
380    /// Clone the service reference.
381    fn clone_boxed(&self) -> Box<dyn ImageRenderWindowsService>;
382
383    /// Create a window root that presents the node.
384    fn new_window_root(&self, node: BoxedUiNode, render_mode: RenderMode, scale_factor: Option<Factor>) -> Box<dyn ImageRenderWindowRoot>;
385
386    /// Set parent window for the headless render window.
387    fn set_parent_in_window_context(&self, parent_id: WindowId);
388
389    /// Enable frame capture for the window.
390    ///
391    /// If `mask` is set captures only the given channel, if not set will capture the full BGRA image.
392    ///
393    /// Called inside the [`WINDOW`] context for the new window.
394    ///
395    /// [`WINDOW`]: zng_app::window::WINDOW
396    fn enable_frame_capture_in_window_context(&self, mask: Option<ImageMaskMode>);
397
398    /// Open the window.
399    ///
400    /// The `new_window_root` is called inside the [`WINDOW`] context for the new window.
401    ///
402    /// [`WINDOW`]: zng_app::window::WINDOW
403    fn open_headless_window(&self, new_window_root: Box<dyn FnOnce() -> Box<dyn ImageRenderWindowRoot> + Send>);
404
405    /// Returns the rendered frame image if it is ready for reading.
406    fn on_frame_image_ready(&self, update: &EventUpdate) -> Option<(WindowId, Img)>;
407
408    /// Close the window, does nothing if the window is not found.
409    fn close_window(&self, window_id: WindowId);
410}
411
412/// Implemented for the root window type.
413///
414/// This is implemented for the `WindowRoot` type.
415pub trait ImageRenderWindowRoot: Send + Any + 'static {
416    /// Cast to `Box<dyn Any>`.
417    fn into_any(self: Box<Self>) -> Box<dyn Any>;
418}