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<Msg>(
63 el: Element<Msg>,
64 theme: &Theme,
65 size: (u32, u32),
66 fonts: &mut Fonts,
67) -> RgbaImage {
68 let size =
69 with_headless(|h| h.clamp_size(size.0, size.1)).expect("headless renderer unavailable");
70 let mut state = FrameState::new();
71 state.reduced_motion = true;
72 #[expect(clippy::cast_precision_loss, reason = "window sizes fit in f32")]
73 let frame = build_frame(
74 &el,
75 theme,
76 fonts,
77 &mut state,
78 (size.0 as f32, size.1 as f32),
79 1.0,
80 );
81 let scene = frame.paint(fonts, &mut state);
82 with_headless(|headless| headless.render(&scene, size.0, size.1, theme.bg))
83 .expect("headless renderer unavailable")
84 .expect("headless render failed")
85}
86
87pub fn render_element_with_state<Msg>(
94 el: Element<Msg>,
95 theme: &Theme,
96 size: (u32, u32),
97 state: &mut FrameState,
98) -> RgbaImage {
99 let size =
101 with_headless(|h| h.clamp_size(size.0, size.1)).expect("headless renderer unavailable");
102 let scene = with_fonts(|fonts| {
103 #[expect(clippy::cast_precision_loss, reason = "window sizes fit in f32")]
104 let frame = build_frame(
105 &el,
106 theme,
107 fonts,
108 state,
109 (size.0 as f32, size.1 as f32),
110 1.0,
111 );
112 frame.paint(fonts, state)
113 });
114 with_headless(|headless| headless.render(&scene, size.0, size.1, theme.bg))
115 .expect("headless renderer unavailable")
116 .expect("headless render failed")
117}