Skip to main content

dear_imgui_rs/context/
frame.rs

1use crate::sys;
2
3use super::Context;
4use super::binding::{CTX_MUTEX, with_bound_context};
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.
68#[must_use = "dropping FrameToken ends the frame without rendering; call render() or render_snapshot() to produce draw data"]
69pub struct FrameToken<'ctx> {
70    ctx: &'ctx mut Context,
71    closed: bool,
72}
73
74/// Result returned by [`Context::frame_with_result`].
75pub struct FrameResult<'ctx, T> {
76    /// Value returned by the UI-building closure.
77    pub value: T,
78    /// Draw data produced by rendering the frame after the closure returned.
79    pub draw_data: &'ctx mut crate::render::DrawData,
80}
81
82impl Context {
83    /// Prepare IO values commonly needed before starting a frame.
84    ///
85    /// This does not call `NewFrame()`. Engine backends can call it from their input/window update
86    /// stage and then open the actual Dear ImGui frame later in the schedule with
87    /// [`Context::begin_frame`].
88    pub fn prepare_frame(&mut self, options: FramePrepareOptions) {
89        let io = self.io_mut();
90        io.set_display_size(options.display_size);
91        io.set_delta_time(options.delta_time);
92        if let Some(scale) = options.framebuffer_scale {
93            io.set_display_framebuffer_scale(scale);
94        }
95        if !options.backend_flags.is_empty() {
96            io.set_backend_flags(io.backend_flags() | options.backend_flags);
97        }
98    }
99
100    /// Return the current frame lifecycle state for this context.
101    pub fn frame_lifecycle_state(&self) -> FrameLifecycleState {
102        let _guard = CTX_MUTEX.lock();
103        self.assert_current_context("Context::frame_lifecycle_state()");
104        self.frame_lifecycle_state_unlocked()
105    }
106
107    /// Begin a Dear ImGui frame and return an explicit frame token.
108    ///
109    /// Engine integrations should prefer this when the frame is owned by a schedule rather than a
110    /// single function. Draw UI through [`FrameToken::ui`] and then consume the token with
111    /// [`FrameToken::render`] or [`FrameToken::render_snapshot`].
112    pub fn begin_frame(&mut self) -> FrameToken<'_> {
113        let _ = self.frame();
114        FrameToken {
115            ctx: self,
116            closed: false,
117        }
118    }
119
120    /// Creates a new frame and returns a Ui object for building the interface.
121    ///
122    /// Note: you must update `io.DisplaySize` (and usually `io.DeltaTime`) before calling this,
123    /// unless you are using a platform backend that does it for you (e.g. `dear-imgui-winit`).
124    pub fn frame(&mut self) -> &mut crate::ui::Ui {
125        let _guard = CTX_MUTEX.lock();
126        self.assert_current_context("Context::frame()");
127        self.assert_can_begin_frame_unlocked("Context::frame()");
128
129        unsafe {
130            // Dear ImGui initializes DisplaySize to (-1, -1). Calling NewFrame() without a
131            // platform backend (or without setting DisplaySize manually) will trip an internal
132            // assertion and abort the process. Fail fast with a Rust panic to make the setup
133            // requirement obvious.
134            let io = sys::igGetIO_Nil();
135            if !io.is_null() && ((*io).DisplaySize.x < 0.0 || (*io).DisplaySize.y < 0.0) {
136                panic!(
137                    "Context::frame() called with invalid io.DisplaySize ({}, {}). \
138Set io.DisplaySize (and typically io.DeltaTime) before starting a frame. \
139If you are using a windowing/event-loop library, prefer a platform backend such as \
140dear-imgui-winit::WinitPlatform::prepare_frame().",
141                    (*io).DisplaySize.x,
142                    (*io).DisplaySize.y
143                );
144            }
145            sys::igNewFrame();
146        }
147        &mut self.ui
148    }
149
150    /// Create a new frame with a callback
151    pub fn frame_with<F, R>(&mut self, f: F) -> R
152    where
153        F: FnOnce(&crate::ui::Ui) -> R,
154    {
155        let ui = self.frame();
156        f(ui)
157    }
158
159    /// Begin a frame, run a UI-building closure, render the frame, and return both values.
160    ///
161    /// This is a convenience for callers that want the old callback style but also want the draw
162    /// data produced by closing the frame. Use [`Context::begin_frame`] when the UI is built across
163    /// several engine systems.
164    pub fn frame_with_result<F, R>(&mut self, f: F) -> FrameResult<'_, R>
165    where
166        F: FnOnce(&crate::ui::Ui) -> R,
167    {
168        let frame = self.begin_frame();
169        let value = f(frame.ui());
170        let draw_data = frame.render();
171        FrameResult { value, draw_data }
172    }
173
174    /// Renders the frame and returns a mutable reference to the resulting draw data
175    ///
176    /// This finalizes the Dear ImGui frame and prepares all draw data for rendering.
177    /// The returned draw data contains all the information needed to render the frame.
178    ///
179    /// Renderer backends receive mutable draw data because ImGui 1.92 texture requests require
180    /// backends to write `TexID`/`Status` feedback into `ImTextureData`.
181    pub fn render(&mut self) -> &mut crate::render::DrawData {
182        let _guard = CTX_MUTEX.lock();
183        self.assert_current_context("Context::render()");
184        self.assert_can_render_unlocked("Context::render()");
185
186        unsafe {
187            sys::igRender();
188            let dd = sys::igGetDrawData();
189            if dd.is_null() {
190                panic!("Context::render() returned null draw data");
191            }
192            &mut *(dd as *mut crate::render::DrawData)
193        }
194    }
195
196    /// Render the current frame and build a thread-safe snapshot for all platform viewports.
197    ///
198    /// With the `multi-viewport` feature enabled, Dear ImGui stores draw data on each viewport.
199    /// `Context::render()` and `igGetDrawData()` only expose the main viewport draw data, so
200    /// engine backends that render secondary OS windows should use this method after opening a
201    /// frame. The returned snapshot still keeps [`FrameSnapshot::draw`] as the main viewport for
202    /// compatibility and adds per-viewport draw data in
203    /// [`FrameSnapshot::viewports`](crate::render::snapshot::FrameSnapshot::viewports).
204    #[cfg(feature = "multi-viewport")]
205    pub fn render_platform_viewport_snapshot(
206        &mut self,
207        options: crate::render::snapshot::SnapshotOptions,
208    ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
209    {
210        let _ = self.render();
211        self.platform_viewport_snapshot(options)
212    }
213
214    /// Build a thread-safe snapshot from the current platform viewport draw data.
215    ///
216    /// This does not call `Render()`. Call it only after a frame has already been rendered and
217    /// before the next frame starts. Engine integrations can use this when they need to run
218    /// platform-window maintenance after `Render()` but before cloning draw data for another
219    /// render schedule.
220    #[cfg(feature = "multi-viewport")]
221    pub fn platform_viewport_snapshot(
222        &mut self,
223        options: crate::render::snapshot::SnapshotOptions,
224    ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
225    {
226        let _guard = CTX_MUTEX.lock();
227        self.assert_current_context("Context::platform_viewport_snapshot()");
228        if self.frame_lifecycle_state_unlocked() != FrameLifecycleState::Rendered {
229            panic!(
230                "Context::platform_viewport_snapshot() called before rendering the current frame"
231            );
232        }
233        crate::render::snapshot::FrameSnapshot::from_platform_io(self.platform_io(), options)
234    }
235
236    /// Gets the draw data for the current frame
237    ///
238    /// This returns the draw data without calling render. Only valid after
239    /// `render()` has been called and before the next `new_frame()`.
240    pub fn draw_data(&self) -> Option<&crate::render::DrawData> {
241        let _guard = CTX_MUTEX.lock();
242        self.assert_current_context("Context::draw_data()");
243
244        unsafe {
245            let draw_data = sys::igGetDrawData();
246            if draw_data.is_null() {
247                None
248            } else {
249                let data = &*(draw_data as *const crate::render::DrawData);
250                if data.valid() { Some(data) } else { None }
251            }
252        }
253    }
254
255    /// Gets mutable draw data for the current frame.
256    ///
257    /// This returns the draw data without calling render. Only valid after `render()` has been
258    /// called and before the next `new_frame()`. Use this when a renderer needs to process texture
259    /// updates after draw data has already been produced.
260    pub fn draw_data_mut(&mut self) -> Option<&mut crate::render::DrawData> {
261        let _guard = CTX_MUTEX.lock();
262        self.assert_current_context("Context::draw_data_mut()");
263
264        unsafe {
265            let draw_data = sys::igGetDrawData();
266            if draw_data.is_null() {
267                None
268            } else {
269                let data = &mut *(draw_data as *mut crate::render::DrawData);
270                if data.valid() { Some(data) } else { None }
271            }
272        }
273    }
274
275    fn frame_lifecycle_state_unlocked(&self) -> FrameLifecycleState {
276        unsafe {
277            let raw = &*self.raw;
278            if raw.WithinFrameScope {
279                FrameLifecycleState::InFrame
280            } else if raw.FrameCountRendered == raw.FrameCount && raw.FrameCount > 0 {
281                FrameLifecycleState::Rendered
282            } else {
283                FrameLifecycleState::Idle
284            }
285        }
286    }
287
288    fn assert_can_begin_frame_unlocked(&self, caller: &str) {
289        if self.frame_lifecycle_state_unlocked() == FrameLifecycleState::InFrame {
290            panic!("{caller} called while another Dear ImGui frame is already open");
291        }
292    }
293
294    fn assert_can_render_unlocked(&self, caller: &str) {
295        if self.frame_lifecycle_state_unlocked() != FrameLifecycleState::InFrame {
296            panic!("{caller} called without an open Dear ImGui frame");
297        }
298    }
299}
300
301impl<'ctx> FrameToken<'ctx> {
302    /// Borrow the UI for this frame.
303    ///
304    /// This can be called repeatedly by an engine-owned frame runner to let multiple systems draw
305    /// into the same frame, as long as those systems are scheduled sequentially.
306    pub fn ui(&self) -> &crate::ui::Ui {
307        &self.ctx.ui
308    }
309
310    /// Return the lifecycle state while this token owns the open frame.
311    pub fn lifecycle_state(&self) -> FrameLifecycleState {
312        self.ctx.frame_lifecycle_state()
313    }
314
315    /// Render this frame and return the resulting draw data.
316    pub fn render(mut self) -> &'ctx mut crate::render::DrawData {
317        let ctx = self.ctx as *mut Context;
318        let draw_data = unsafe { (&mut *ctx).render() };
319        self.closed = true;
320        std::mem::forget(self);
321        draw_data
322    }
323
324    /// Render this frame and build a thread-safe snapshot from the resulting draw data.
325    ///
326    /// This is the preferred handoff shape for render-world integrations such as Bevy, where raw
327    /// ImGui pointers must not cross the engine extraction boundary.
328    pub fn render_snapshot(
329        mut self,
330        options: crate::render::snapshot::SnapshotOptions,
331    ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
332    {
333        let ctx = self.ctx as *mut Context;
334        let draw_data = unsafe { (&mut *ctx).render() };
335        self.closed = true;
336        std::mem::forget(self);
337        crate::render::snapshot::FrameSnapshot::from_draw_data(draw_data, options)
338    }
339
340    /// Render this frame and build a thread-safe snapshot for all platform viewports.
341    #[cfg(feature = "multi-viewport")]
342    pub fn render_platform_viewport_snapshot(
343        mut self,
344        options: crate::render::snapshot::SnapshotOptions,
345    ) -> Result<crate::render::snapshot::FrameSnapshot, crate::render::snapshot::SnapshotError>
346    {
347        let ctx = self.ctx as *mut Context;
348        let snapshot = unsafe { (&mut *ctx).render_platform_viewport_snapshot(options) };
349        self.closed = true;
350        std::mem::forget(self);
351        snapshot
352    }
353}
354
355impl Drop for FrameToken<'_> {
356    fn drop(&mut self) {
357        if self.closed {
358            return;
359        }
360
361        let _guard = CTX_MUTEX.lock();
362        if self.ctx.frame_lifecycle_state_unlocked() == FrameLifecycleState::InFrame {
363            unsafe { with_bound_context(self.ctx.raw, || sys::igEndFrame()) };
364        }
365        self.closed = true;
366    }
367}