Skip to main content

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}