use std::{any::Any, sync::Arc};
use zng_app::{
update::{EventUpdate, UPDATES},
widget::{
node::{match_node, BoxedUiNode, UiNode, UiNodeOp},
property, WIDGET,
},
window::{WindowId, WINDOW},
};
use zng_layout::unit::Factor;
use zng_state_map::{static_id, StateId};
use zng_var::{types::WeakArcVar, var, ArcVar, IntoVar, Var, WeakVar};
use zng_view_api::{image::ImageMaskMode, window::RenderMode};
use crate::{ImageManager, ImageRenderArgs, ImageSource, ImageVar, ImagesService, Img, IMAGES, IMAGES_SV};
impl ImagesService {
fn render<N, R>(&mut self, mask: Option<ImageMaskMode>, render: N) -> ImageVar
where
N: FnOnce() -> R + Send + Sync + 'static,
R: ImageRenderWindowRoot,
{
let result = var(Img::new_none(None));
let windows = self.render.windows();
self.render_img(
mask,
move || {
let r = render();
windows.enable_frame_capture_in_window_context(None);
Box::new(r)
},
&result,
);
result.read_only()
}
fn render_node<U, N>(&mut self, render_mode: RenderMode, scale_factor: Factor, mask: Option<ImageMaskMode>, render: N) -> ImageVar
where
U: UiNode,
N: FnOnce() -> U + Send + Sync + 'static,
{
let scale_factor = scale_factor.into();
let result = var(Img::new_none(None));
let windows = self.render.windows();
self.render_img(
mask,
move || {
let node = render();
let r = windows.new_window_root(node.boxed(), render_mode, scale_factor);
windows.enable_frame_capture_in_window_context(mask);
r
},
&result,
);
result.read_only()
}
pub(super) fn render_img<N>(&mut self, mask: Option<ImageMaskMode>, render: N, result: &ArcVar<Img>)
where
N: FnOnce() -> Box<dyn ImageRenderWindowRoot> + Send + Sync + 'static,
{
self.render.requests.push(RenderRequest {
render: Box::new(render),
image: result.downgrade(),
mask,
});
UPDATES.update(None);
}
}
impl ImageSource {
pub fn render<F, R>(new_img: F) -> Self
where
F: Fn(&ImageRenderArgs) -> R + Send + Sync + 'static,
R: ImageRenderWindowRoot,
{
let window = IMAGES_SV.read().render.windows();
Self::Render(
Arc::new(Box::new(move |args| {
if let Some(parent) = args.parent {
window.set_parent_in_window_context(parent);
}
let r = new_img(args);
window.enable_frame_capture_in_window_context(None);
Box::new(r)
})),
None,
)
}
pub fn render_node<U, N>(render_mode: RenderMode, render: N) -> Self
where
U: UiNode,
N: Fn(&ImageRenderArgs) -> U + Send + Sync + 'static,
{
let window = IMAGES_SV.read().render.windows();
Self::Render(
Arc::new(Box::new(move |args| {
if let Some(parent) = args.parent {
window.set_parent_in_window_context(parent);
}
let node = render(args);
window.enable_frame_capture_in_window_context(None);
window.new_window_root(node.boxed(), render_mode, None)
})),
None,
)
}
}
impl IMAGES {
pub fn render<N, R>(&self, mask: Option<ImageMaskMode>, render: N) -> ImageVar
where
N: FnOnce() -> R + Send + Sync + 'static,
R: ImageRenderWindowRoot,
{
IMAGES_SV.write().render(mask, render)
}
pub fn render_node<U, N>(
&self,
render_mode: RenderMode,
scale_factor: impl Into<Factor>,
mask: Option<ImageMaskMode>,
render: N,
) -> ImageVar
where
U: UiNode,
N: FnOnce() -> U + Send + Sync + 'static,
{
IMAGES_SV.write().render_node(render_mode, scale_factor.into(), mask, render)
}
}
#[expect(non_camel_case_types)]
pub struct IMAGES_WINDOW;
impl IMAGES_WINDOW {
pub fn hook_render_windows_service(&self, service: Box<dyn ImageRenderWindowsService>) {
let mut img = IMAGES_SV.write();
assert!(img.render.windows.is_none());
img.render.windows = Some(service);
}
}
impl ImageManager {
pub(super) fn update_render(&mut self) {
let mut images = IMAGES_SV.write();
if !images.render.active.is_empty() {
let windows = images.render.windows();
images.render.active.retain(|r| {
let mut retain = false;
if let Some(img) = r.image.upgrade() {
retain = img.with(Img::is_loading) || r.retain.get();
}
if !retain {
windows.close_window(r.window_id);
}
retain
});
}
if !images.render.requests.is_empty() {
let windows = images.render.windows();
for req in images.render.requests.drain(..) {
if let Some(img) = req.image.upgrade() {
let windows_in = windows.clone_boxed();
windows.open_headless_window(Box::new(move || {
let ctx = ImageRenderCtx::new();
let retain = ctx.retain.clone();
WINDOW.set_state(*IMAGE_RENDER_ID, ctx);
let w = (req.render)();
windows_in.enable_frame_capture_in_window_context(req.mask);
let a = ActiveRenderer {
window_id: WINDOW.id(),
image: img.downgrade(),
retain,
};
IMAGES_SV.write().render.active.push(a);
w
}));
}
}
}
}
pub(super) fn event_preview_render(&mut self, update: &EventUpdate) {
let imgs = IMAGES_SV.read();
if !imgs.render.active.is_empty() {
if let Some((id, img)) = imgs.render.windows().on_frame_image_ready(update) {
if let Some(a) = imgs.render.active.iter().find(|a| a.window_id == id) {
if let Some(img_var) = a.image.upgrade() {
img_var.set(img.clone());
}
}
}
}
}
}
#[derive(Default)]
pub(super) struct ImagesRender {
windows: Option<Box<dyn ImageRenderWindowsService>>,
requests: Vec<RenderRequest>,
active: Vec<ActiveRenderer>,
}
impl ImagesRender {
fn windows(&self) -> Box<dyn ImageRenderWindowsService> {
self.windows.as_ref().expect("render windows service not inited").clone_boxed()
}
}
struct ActiveRenderer {
window_id: WindowId,
image: WeakArcVar<Img>,
retain: ArcVar<bool>,
}
struct RenderRequest {
render: Box<dyn FnOnce() -> Box<dyn ImageRenderWindowRoot> + Send + Sync>,
image: WeakArcVar<Img>,
mask: Option<ImageMaskMode>,
}
#[derive(Clone)]
struct ImageRenderCtx {
retain: ArcVar<bool>,
}
impl ImageRenderCtx {
fn new() -> Self {
Self { retain: var(false) }
}
}
static_id! {
static ref IMAGE_RENDER_ID: StateId<ImageRenderCtx>;
}
#[expect(non_camel_case_types)]
pub struct IMAGE_RENDER;
impl IMAGE_RENDER {
pub fn is_in_render(&self) -> bool {
WINDOW.contains_state(*IMAGE_RENDER_ID)
}
pub fn retain(&self) -> ArcVar<bool> {
WINDOW.req_state(*IMAGE_RENDER_ID).retain
}
}
#[property(CONTEXT, default(false))]
pub fn render_retain(child: impl UiNode, retain: impl IntoVar<bool>) -> impl UiNode {
let retain = retain.into_var();
match_node(child, move |_, op| {
if let UiNodeOp::Init = op {
if IMAGE_RENDER.is_in_render() {
let actual_retain = IMAGE_RENDER.retain();
actual_retain.set_from(&retain);
let handle = actual_retain.bind(&retain);
WIDGET.push_var_handle(handle);
} else {
tracing::error!("can only set `render_retain` in render widgets")
}
}
})
}
pub trait ImageRenderWindowsService: Send + Sync + 'static {
fn clone_boxed(&self) -> Box<dyn ImageRenderWindowsService>;
fn new_window_root(&self, node: BoxedUiNode, render_mode: RenderMode, scale_factor: Option<Factor>) -> Box<dyn ImageRenderWindowRoot>;
fn set_parent_in_window_context(&self, parent_id: WindowId);
fn enable_frame_capture_in_window_context(&self, mask: Option<ImageMaskMode>);
fn open_headless_window(&self, new_window_root: Box<dyn FnOnce() -> Box<dyn ImageRenderWindowRoot> + Send>);
fn on_frame_image_ready(&self, update: &EventUpdate) -> Option<(WindowId, Img)>;
fn close_window(&self, window_id: WindowId);
}
pub trait ImageRenderWindowRoot: Send + Any + 'static {
fn into_any(self: Box<Self>) -> Box<dyn Any>;
}