use std::sync::{Mutex, OnceLock};
use fenestra_core::{Element, Fonts, FrameState, Theme, build_frame};
use image::RgbaImage;
use crate::{Headless, ShellError};
static SHARED: OnceLock<Mutex<Headless>> = OnceLock::new();
static FONTS: OnceLock<Mutex<Fonts>> = OnceLock::new();
pub fn with_fonts<R>(f: impl FnOnce(&mut Fonts) -> R) -> R {
let fonts = FONTS.get_or_init(|| Mutex::new(Fonts::embedded()));
let mut guard = fonts
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
f(&mut guard)
}
pub fn with_headless<R>(f: impl FnOnce(&mut Headless) -> R) -> Result<R, ShellError> {
if SHARED.get().is_none() {
let headless = Headless::new()?;
let _ = SHARED.set(Mutex::new(headless));
}
let mutex = SHARED.get().ok_or(ShellError::NoDevice)?;
let mut guard = mutex
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
Ok(f(&mut guard))
}
pub fn render_element<Msg>(el: Element<Msg>, theme: &Theme, size: (u32, u32)) -> RgbaImage {
let mut state = FrameState::new();
state.reduced_motion = true;
render_element_with_state(el, theme, size, &mut state)
}
pub fn render_element_with<Msg>(
el: Element<Msg>,
theme: &Theme,
size: (u32, u32),
fonts: &mut Fonts,
) -> RgbaImage {
let size =
with_headless(|h| h.clamp_size(size.0, size.1)).expect("headless renderer unavailable");
let mut state = FrameState::new();
state.reduced_motion = true;
#[expect(clippy::cast_precision_loss, reason = "window sizes fit in f32")]
let frame = build_frame(
&el,
theme,
fonts,
&mut state,
(size.0 as f32, size.1 as f32),
1.0,
);
let scene = frame.paint(fonts, &mut state);
with_headless(|headless| headless.render(&scene, size.0, size.1, theme.bg))
.expect("headless renderer unavailable")
.expect("headless render failed")
}
pub fn render_element_with_state<Msg>(
el: Element<Msg>,
theme: &Theme,
size: (u32, u32),
state: &mut FrameState,
) -> RgbaImage {
let size =
with_headless(|h| h.clamp_size(size.0, size.1)).expect("headless renderer unavailable");
let scene = with_fonts(|fonts| {
#[expect(clippy::cast_precision_loss, reason = "window sizes fit in f32")]
let frame = build_frame(
&el,
theme,
fonts,
state,
(size.0 as f32, size.1 as f32),
1.0,
);
frame.paint(fonts, state)
});
with_headless(|headless| headless.render(&scene, size.0, size.1, theme.bg))
.expect("headless renderer unavailable")
.expect("headless render failed")
}