dear_imgui_rs/context/frame.rs
1use crate::sys;
2
3use super::Context;
4use super::binding::CTX_MUTEX;
5
6/// Runtime state for a Dear ImGui frame owned by an external engine schedule.
7#[derive(Copy, Clone, Debug, Eq, PartialEq)]
8pub enum FrameLifecycleState {
9 /// No Dear ImGui frame is currently open for this context.
10 Idle,
11 /// A frame was opened and can accept UI commands.
12 InFrame,
13 /// The last opened frame has been rendered and draw data is available until the next frame.
14 Rendered,
15}
16
17/// Options used by [`Context::prepare_frame`].
18#[derive(Copy, Clone, Debug)]
19pub struct FramePrepareOptions {
20 /// Main display size in pixels.
21 pub display_size: [f32; 2],
22 /// Time elapsed since the previous frame, in seconds.
23 pub delta_time: f32,
24 /// Optional framebuffer scale for HiDPI render targets.
25 pub framebuffer_scale: Option<[f32; 2]>,
26 /// Backend capability flags to OR into the context before opening the frame.
27 pub backend_flags: crate::BackendFlags,
28}
29
30impl FramePrepareOptions {
31 /// Create frame preparation options with display size and delta time.
32 pub fn new(display_size: [f32; 2], delta_time: f32) -> Self {
33 Self {
34 display_size,
35 delta_time,
36 framebuffer_scale: None,
37 backend_flags: crate::BackendFlags::empty(),
38 }
39 }
40
41 /// Set the framebuffer scale used by the frame.
42 #[must_use]
43 pub fn framebuffer_scale(mut self, scale: [f32; 2]) -> Self {
44 self.framebuffer_scale = Some(scale);
45 self
46 }
47
48 /// OR backend capability flags into the context before opening the frame.
49 #[must_use]
50 pub fn backend_flags(mut self, flags: crate::BackendFlags) -> Self {
51 self.backend_flags |= flags;
52 self
53 }
54
55 /// Convenience for modern renderers that support ImGui 1.92 texture requests.
56 #[must_use]
57 pub fn renderer_has_textures(self) -> Self {
58 self.backend_flags(crate::BackendFlags::RENDERER_HAS_TEXTURES)
59 }
60}
61
62/// A frame opened by [`Context::begin_frame`].
63///
64/// This token is intended for engine integrations that need to make the Dear ImGui frame boundary
65/// explicit: one system opens the frame, several user systems draw through [`Self::ui`], and one
66/// system consumes the token to render or snapshot the frame. The existing [`Context::frame`] and
67/// [`Context::render`] calls remain available for traditional immediate-mode loops.
68pub struct FrameToken<'ctx> {
69 ctx: &'ctx mut Context,
70}
71
72/// Result returned by [`Context::frame_with_result`].
73pub struct FrameResult<'ctx, T> {
74 /// Value returned by the UI-building closure.
75 pub value: T,
76 /// Draw data produced by rendering the frame after the closure returned.
77 pub draw_data: &'ctx mut crate::render::DrawData,
78}
79
80impl Context {
81 /// Prepare IO values commonly needed before starting a frame.
82 ///
83 /// This does not call `NewFrame()`. Engine backends can call it from their input/window update
84 /// stage and then open the actual Dear ImGui frame later in the schedule with
85 /// [`Context::begin_frame`].
86 pub fn prepare_frame(&mut self, options: FramePrepareOptions) {
87 let io = self.io_mut();
88 io.set_display_size(options.display_size);
89 io.set_delta_time(options.delta_time);
90 if let Some(scale) = options.framebuffer_scale {
91 io.set_display_framebuffer_scale(scale);
92 }
93 if !options.backend_flags.is_empty() {
94 io.set_backend_flags(io.backend_flags() | options.backend_flags);
95 }
96 }
97
98 /// Return the current frame lifecycle state for this context.
99 pub fn frame_lifecycle_state(&self) -> FrameLifecycleState {
100 let _guard = CTX_MUTEX.lock();
101 self.assert_current_context("Context::frame_lifecycle_state()");
102 self.frame_lifecycle_state_unlocked()
103 }
104
105 /// Begin a Dear ImGui frame and return an explicit frame token.
106 ///
107 /// Engine integrations should prefer this when the frame is owned by a schedule rather than a
108 /// single function. Draw UI through [`FrameToken::ui`] and then consume the token with
109 /// [`FrameToken::render`] or [`FrameToken::render_snapshot`].
110 pub fn begin_frame(&mut self) -> FrameToken<'_> {
111 let _ = self.frame();
112 FrameToken { ctx: self }
113 }
114
115 /// Creates a new frame and returns a Ui object for building the interface.
116 ///
117 /// Note: you must update `io.DisplaySize` (and usually `io.DeltaTime`) before calling this,
118 /// unless you are using a platform backend that does it for you (e.g. `dear-imgui-winit`).
119 pub fn frame(&mut self) -> &mut crate::ui::Ui {
120 let _guard = CTX_MUTEX.lock();
121 self.assert_current_context("Context::frame()");
122 self.assert_can_begin_frame_unlocked("Context::frame()");
123
124 unsafe {
125 // Dear ImGui initializes DisplaySize to (-1, -1). Calling NewFrame() without a
126 // platform backend (or without setting DisplaySize manually) will trip an internal
127 // assertion and abort the process. Fail fast with a Rust panic to make the setup
128 // requirement obvious.
129 let io = sys::igGetIO_Nil();
130 if !io.is_null() && ((*io).DisplaySize.x < 0.0 || (*io).DisplaySize.y < 0.0) {
131 panic!(
132 "Context::frame() called with invalid io.DisplaySize ({}, {}). \
133Set io.DisplaySize (and typically io.DeltaTime) before starting a frame. \
134If you are using a windowing/event-loop library, prefer a platform backend such as \
135dear-imgui-winit::WinitPlatform::prepare_frame().",
136 (*io).DisplaySize.x,
137 (*io).DisplaySize.y
138 );
139 }
140 sys::igNewFrame();
141 }
142 &mut self.ui
143 }
144
145 /// Create a new frame with a callback
146 pub fn frame_with<F, R>(&mut self, f: F) -> R
147 where
148 F: FnOnce(&crate::ui::Ui) -> R,
149 {
150 let ui = self.frame();
151 f(ui)
152 }
153
154 /// Begin a frame, run a UI-building closure, render the frame, and return both values.
155 ///
156 /// This is a convenience for callers that want the old callback style but also want the draw
157 /// data produced by closing the frame. Use [`Context::begin_frame`] when the UI is built across
158 /// several engine systems.
159 pub fn frame_with_result<F, R>(&mut self, f: F) -> FrameResult<'_, R>
160 where
161 F: FnOnce(&crate::ui::Ui) -> R,
162 {
163 let frame = self.begin_frame();
164 let value = f(frame.ui());
165 let draw_data = frame.render();
166 FrameResult { value, draw_data }
167 }
168
169 /// Renders the frame and returns a mutable reference to the resulting draw data
170 ///
171 /// This finalizes the Dear ImGui frame and prepares all draw data for rendering.
172 /// The returned draw data contains all the information needed to render the frame.
173 ///
174 /// Renderer backends receive mutable draw data because ImGui 1.92 texture requests require
175 /// backends to write `TexID`/`Status` feedback into `ImTextureData`.
176 pub fn render(&mut self) -> &mut crate::render::DrawData {
177 let _guard = CTX_MUTEX.lock();
178 self.assert_current_context("Context::render()");
179 self.assert_can_render_unlocked("Context::render()");
180
181 unsafe {
182 sys::igRender();
183 let dd = sys::igGetDrawData();
184 if dd.is_null() {
185 panic!("Context::render() returned null draw data");
186 }
187 &mut *(dd as *mut crate::render::DrawData)
188 }
189 }
190
191 /// Render the current frame and build a thread-safe snapshot for all platform viewports.
192 ///
193 /// With the `multi-viewport` feature enabled, Dear ImGui stores draw data on each viewport.
194 /// `Context::render()` and `igGetDrawData()` only expose the main viewport draw data, so
195 /// engine backends that render secondary OS windows should use this method after opening a
196 /// frame. The returned snapshot still keeps [`FrameSnapshot::draw`] as the main viewport for
197 /// compatibility and adds per-viewport draw data in
198 /// [`FrameSnapshot::viewports`](crate::render::snapshot::FrameSnapshot::viewports).
199 #[cfg(feature = "multi-viewport")]
200 pub fn render_platform_viewport_snapshot(
201 &mut self,
202 options: crate::render::snapshot::SnapshotOptions,
203 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
204 {
205 let _ = self.render();
206 self.platform_viewport_snapshot(options)
207 }
208
209 /// Build a thread-safe snapshot from the current platform viewport draw data.
210 ///
211 /// This does not call `Render()`. Call it only after a frame has already been rendered and
212 /// before the next frame starts. Engine integrations can use this when they need to run
213 /// platform-window maintenance after `Render()` but before cloning draw data for another
214 /// render schedule.
215 #[cfg(feature = "multi-viewport")]
216 pub fn platform_viewport_snapshot(
217 &mut self,
218 options: crate::render::snapshot::SnapshotOptions,
219 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
220 {
221 let _guard = CTX_MUTEX.lock();
222 self.assert_current_context("Context::platform_viewport_snapshot()");
223 if self.frame_lifecycle_state_unlocked() != FrameLifecycleState::Rendered {
224 panic!(
225 "Context::platform_viewport_snapshot() called before rendering the current frame"
226 );
227 }
228 crate::render::snapshot::FrameSnapshot::from_platform_io(self.platform_io(), options)
229 }
230
231 /// Gets the draw data for the current frame
232 ///
233 /// This returns the draw data without calling render. Only valid after
234 /// `render()` has been called and before the next `new_frame()`.
235 pub fn draw_data(&self) -> Option<&crate::render::DrawData> {
236 let _guard = CTX_MUTEX.lock();
237 self.assert_current_context("Context::draw_data()");
238
239 unsafe {
240 let draw_data = sys::igGetDrawData();
241 if draw_data.is_null() {
242 None
243 } else {
244 let data = &*(draw_data as *const crate::render::DrawData);
245 if data.valid() { Some(data) } else { None }
246 }
247 }
248 }
249
250 /// Gets mutable draw data for the current frame.
251 ///
252 /// This returns the draw data without calling render. Only valid after `render()` has been
253 /// called and before the next `new_frame()`. Use this when a renderer needs to process texture
254 /// updates after draw data has already been produced.
255 pub fn draw_data_mut(&mut self) -> Option<&mut crate::render::DrawData> {
256 let _guard = CTX_MUTEX.lock();
257 self.assert_current_context("Context::draw_data_mut()");
258
259 unsafe {
260 let draw_data = sys::igGetDrawData();
261 if draw_data.is_null() {
262 None
263 } else {
264 let data = &mut *(draw_data as *mut crate::render::DrawData);
265 if data.valid() { Some(data) } else { None }
266 }
267 }
268 }
269
270 fn frame_lifecycle_state_unlocked(&self) -> FrameLifecycleState {
271 unsafe {
272 let raw = &*self.raw;
273 if raw.WithinFrameScope {
274 FrameLifecycleState::InFrame
275 } else if raw.FrameCountRendered == raw.FrameCount && raw.FrameCount > 0 {
276 FrameLifecycleState::Rendered
277 } else {
278 FrameLifecycleState::Idle
279 }
280 }
281 }
282
283 fn assert_can_begin_frame_unlocked(&self, caller: &str) {
284 if self.frame_lifecycle_state_unlocked() == FrameLifecycleState::InFrame {
285 panic!("{caller} called while another Dear ImGui frame is already open");
286 }
287 }
288
289 fn assert_can_render_unlocked(&self, caller: &str) {
290 if self.frame_lifecycle_state_unlocked() != FrameLifecycleState::InFrame {
291 panic!("{caller} called without an open Dear ImGui frame");
292 }
293 }
294}
295
296impl<'ctx> FrameToken<'ctx> {
297 /// Borrow the UI for this frame.
298 ///
299 /// This can be called repeatedly by an engine-owned frame runner to let multiple systems draw
300 /// into the same frame, as long as those systems are scheduled sequentially.
301 pub fn ui(&self) -> &crate::ui::Ui {
302 &self.ctx.ui
303 }
304
305 /// Return the lifecycle state while this token owns the open frame.
306 pub fn lifecycle_state(&self) -> FrameLifecycleState {
307 self.ctx.frame_lifecycle_state()
308 }
309
310 /// Render this frame and return the resulting draw data.
311 pub fn render(self) -> &'ctx mut crate::render::DrawData {
312 self.ctx.render()
313 }
314
315 /// Render this frame and build a thread-safe snapshot from the resulting draw data.
316 ///
317 /// This is the preferred handoff shape for render-world integrations such as Bevy, where raw
318 /// ImGui pointers must not cross the engine extraction boundary.
319 pub fn render_snapshot(
320 self,
321 options: crate::render::snapshot::SnapshotOptions,
322 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
323 {
324 let draw_data = self.ctx.render();
325 crate::render::snapshot::FrameSnapshot::from_draw_data(draw_data, options)
326 }
327
328 /// Render this frame and build a thread-safe snapshot for all platform viewports.
329 #[cfg(feature = "multi-viewport")]
330 pub fn render_platform_viewport_snapshot(
331 self,
332 options: crate::render::snapshot::SnapshotOptions,
333 ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
334 {
335 self.ctx.render_platform_viewport_snapshot(options)
336 }
337}