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