fenestra_shell/
element_render.rs1use std::sync::{Mutex, OnceLock};
4
5use fenestra_core::{Element, Fonts, FrameState, Theme, build_frame};
6use image::RgbaImage;
7
8use crate::{Headless, ShellError};
9
10static SHARED: OnceLock<Mutex<Headless>> = OnceLock::new();
11static FONTS: OnceLock<Mutex<Fonts>> = OnceLock::new();
12
13pub fn with_fonts<R>(f: impl FnOnce(&mut Fonts) -> R) -> R {
16 let fonts = FONTS.get_or_init(|| Mutex::new(Fonts::embedded()));
17 let mut guard = fonts
18 .lock()
19 .unwrap_or_else(std::sync::PoisonError::into_inner);
20 f(&mut guard)
21}
22
23pub fn with_headless<R>(f: impl FnOnce(&mut Headless) -> R) -> Result<R, ShellError> {
26 if SHARED.get().is_none() {
29 let headless = Headless::new()?;
30 let _ = SHARED.set(Mutex::new(headless));
31 }
32 let mutex = SHARED.get().ok_or(ShellError::NoDevice)?;
33 let mut guard = mutex
34 .lock()
35 .unwrap_or_else(std::sync::PoisonError::into_inner);
36 Ok(f(&mut guard))
37}
38
39pub fn render_element<Msg>(el: Element<Msg>, theme: &Theme, size: (u32, u32)) -> RgbaImage {
50 let mut state = FrameState::new();
51 state.reduced_motion = true;
52 render_element_with_state(el, theme, size, &mut state)
53}
54
55pub fn render_element_with_state<Msg>(
62 el: Element<Msg>,
63 theme: &Theme,
64 size: (u32, u32),
65 state: &mut FrameState,
66) -> RgbaImage {
67 let size =
69 with_headless(|h| h.clamp_size(size.0, size.1)).expect("headless renderer unavailable");
70 let scene = with_fonts(|fonts| {
71 #[expect(clippy::cast_precision_loss, reason = "window sizes fit in f32")]
72 let frame = build_frame(
73 &el,
74 theme,
75 fonts,
76 state,
77 (size.0 as f32, size.1 as f32),
78 1.0,
79 );
80 frame.paint(fonts, state)
81 });
82 with_headless(|headless| headless.render(&scene, size.0, size.1, theme.bg))
83 .expect("headless renderer unavailable")
84 .expect("headless render failed")
85}