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 with_headless(|headless| {
84 headless.render_plan(&frame, fonts, &mut state, size.0, size.1, theme.bg)
85 })
86 .expect("headless renderer unavailable")
87 .expect("headless render failed")
88}
89
90pub fn render_element_with_state<Msg>(
97 el: Element<Msg>,
98 theme: &Theme,
99 size: (u32, u32),
100 state: &mut FrameState,
101) -> RgbaImage {
102 let size =
104 with_headless(|h| h.clamp_size(size.0, size.1)).expect("headless renderer unavailable");
105 with_fonts(|fonts| {
108 #[expect(clippy::cast_precision_loss, reason = "window sizes fit in f32")]
109 let frame = build_frame(
110 &el,
111 theme,
112 fonts,
113 state,
114 (size.0 as f32, size.1 as f32),
115 1.0,
116 );
117 with_headless(|headless| {
118 headless.render_plan(&frame, fonts, state, size.0, size.1, theme.bg)
119 })
120 .expect("headless renderer unavailable")
121 })
122 .expect("headless render failed")
123}