Skip to main content

dear_imgui_rs/context/
core.rs

1use std::ffi::CString;
2use std::ptr;
3use std::rc::Rc;
4
5use crate::clipboard::ClipboardContext;
6use crate::fonts::SharedFontAtlas;
7use crate::io::Io;
8use crate::sys;
9
10use super::attachment::{
11    AttachmentRegistry, ContextAttachment, ContextAttachmentError, ContextAttachmentHandle,
12    ContextAttachmentLease, ContextAttachmentPhase, ContextAttachmentRole,
13    ContextPlatformAttachmentRelease, ContextPlatformAttachmentReleaseError, run_post_destroy,
14    run_pre_destroy_phase,
15};
16use super::binding::{
17    CTX_MUTEX, ContextAliveToken, ContextBinding, ContextId, ContextState, RawBoundContextGuard,
18    no_current_context, set_current_context, with_bound_context,
19};
20use super::snapshot_hub::SnapshotHub;
21use super::texture_registry::{ManagedTextureRegistry, SharedTextureRegistry};
22
23/// An imgui context.
24///
25/// A context needs to be created to access most library functions. Due to current Dear ImGui
26/// design choices, at most one active Context can exist at any time. This limitation will likely
27/// be removed in a future Dear ImGui version.
28///
29/// If you need more than one context, you can use suspended contexts. As long as only one context
30/// is active at a time, it's possible to have multiple independent contexts.
31///
32/// # Examples
33///
34/// Creating a new active context:
35/// ```
36/// let ctx = dear_imgui_rs::Context::create();
37/// // ctx is dropped naturally when it goes out of scope, which deactivates and destroys the
38/// // context
39/// ```
40///
41/// Never try to create an active context when another one is active:
42///
43/// ```should_panic
44/// let ctx1 = dear_imgui_rs::Context::create();
45///
46/// let ctx2 = dear_imgui_rs::Context::create(); // PANIC
47/// ```
48#[doc(
49    alias = "CreateContext",
50    alias = "DestroyContext",
51    alias = "GetCurrentContext",
52    alias = "SetCurrentContext"
53)]
54#[derive(Debug)]
55pub struct Context {
56    pub(super) raw: *mut sys::ImGuiContext,
57    pub(super) state: Rc<ContextState>,
58    pub(super) attachments: AttachmentRegistry,
59    pub(super) snapshot_hub: SnapshotHub,
60    pub(crate) texture_registry: SharedTextureRegistry,
61    pub(in crate::context) shared_font_atlas: Option<SharedFontAtlas>,
62    pub(in crate::context) ini_filename: Option<CString>,
63    pub(in crate::context) log_filename: Option<CString>,
64    pub(in crate::context) platform_name: Option<CString>,
65    pub(in crate::context) renderer_name: Option<CString>,
66    // Boxed so the raw PlatformIO user-data pointer remains stable.
67    // Interior mutability and reentrancy guarding live inside ClipboardContext.
68    pub(in crate::context) clipboard_ctx: Box<ClipboardContext>,
69    pub(in crate::context) ui: crate::ui::Ui,
70}
71
72impl Context {
73    /// Tries to create a new active Dear ImGui context.
74    ///
75    /// Returns an error if another context is already active or creation fails.
76    pub fn try_create() -> crate::error::ImGuiResult<Context> {
77        Self::try_create_internal(None)
78    }
79
80    /// Tries to create a new active Dear ImGui context with a shared font atlas.
81    ///
82    /// Multiple contexts may share the atlas while using legacy renderer-managed texture handling.
83    /// Once a managed renderer claims the atlas, registering another context returns
84    /// [`ImGuiError::SharedFontAtlasManaged`](crate::ImGuiError::SharedFontAtlasManaged).
85    /// If its prior managed Context was dropped without a committed renderer reset, this returns
86    /// [`ImGuiError::SharedFontAtlasRendererReleasePending`](crate::ImGuiError::SharedFontAtlasRendererReleasePending).
87    pub fn try_create_with_shared_font_atlas(
88        shared_font_atlas: SharedFontAtlas,
89    ) -> crate::error::ImGuiResult<Context> {
90        Self::try_create_internal(Some(shared_font_atlas))
91    }
92
93    /// Creates a new active Dear ImGui context (panics on error).
94    ///
95    /// This aligns with imgui-rs behavior. For fallible creation use `try_create()`.
96    pub fn create() -> Context {
97        Self::try_create().expect("Failed to create Dear ImGui context")
98    }
99
100    /// Creates a new active Dear ImGui context with a shared font atlas (panics on error).
101    ///
102    /// This panics if a managed renderer has already claimed the atlas. Use
103    /// [`Context::try_create_with_shared_font_atlas`] to handle ownership and pending-release
104    /// errors.
105    pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Context {
106        Self::try_create_with_shared_font_atlas(shared_font_atlas)
107            .expect("Failed to create Dear ImGui context")
108    }
109
110    /// Returns the raw `ImGuiContext*` for FFI integrations.
111    pub fn as_raw(&self) -> *mut sys::ImGuiContext {
112        self.raw
113    }
114
115    /// Returns the process-unique identity of this Context.
116    pub fn id(&self) -> ContextId {
117        self.state.id()
118    }
119
120    /// Returns a persistent capability for calling against this Context while it is alive.
121    pub fn binding(&self) -> ContextBinding {
122        ContextBinding::new(&self.state)
123    }
124
125    /// Returns a token that can be used to check whether this context is still alive.
126    ///
127    /// Useful for extension crates that store raw pointers and need to avoid calling into FFI
128    /// after the owning `Context` has been dropped.
129    pub fn alive_token(&self) -> ContextAliveToken {
130        ContextAliveToken::from_binding(self.binding())
131    }
132
133    /// Registers a typed lifecycle attachment owned by this Context.
134    ///
135    /// The marker type identifies the attachment independently of its erased implementation.
136    /// Platform and renderer roles are exclusive, and a renderer requires an active platform.
137    pub fn register_attachment<Marker: 'static>(
138        &mut self,
139        role: ContextAttachmentRole,
140        attachment: Rc<dyn ContextAttachment>,
141    ) -> Result<ContextAttachmentLease, ContextAttachmentError> {
142        self.attachments
143            .register::<Marker>(self.state.lifecycle(), role, attachment)
144    }
145
146    /// Validate a typed lifecycle attachment registration without mutating the registry.
147    ///
148    /// Backends use this to preflight a multi-Context transaction before registering any
149    /// attachment. A successful result remains valid until the attachment registry or Context
150    /// lifecycle changes.
151    pub fn preflight_attachment_registration<Marker: 'static>(
152        &self,
153        role: ContextAttachmentRole,
154    ) -> Result<(), ContextAttachmentError> {
155        self.attachments
156            .preflight_register::<Marker>(self.state.lifecycle(), role)
157    }
158
159    /// Prepares an explicit release of this Context's exact platform attachment generation.
160    ///
161    /// Preparation fails while a renderer attachment is active. No frame, callback, or native
162    /// state is changed on failure. The returned permit exclusively borrows this Context; perform
163    /// any fallible platform cleanup through [`ContextPlatformAttachmentRelease::context_mut`]
164    /// and call [`ContextPlatformAttachmentRelease::commit`] only after native cleanup succeeds.
165    pub fn prepare_platform_attachment_release(
166        &mut self,
167        handle: &ContextAttachmentHandle,
168    ) -> Result<ContextPlatformAttachmentRelease<'_>, ContextPlatformAttachmentReleaseError> {
169        let control = self.attachments.prepare_platform_release(handle)?;
170        Ok(ContextPlatformAttachmentRelease::new(self, control))
171    }
172
173    // removed legacy create_or_panic variants (use create()/try_create())
174
175    pub(super) fn io_ptr(&self, caller: &str) -> *mut sys::ImGuiIO {
176        let io = unsafe { sys::igGetIO_ContextPtr(self.raw) };
177        if io.is_null() {
178            panic!("{caller} requires a valid ImGui context");
179        }
180        io
181    }
182
183    pub(super) fn platform_io_ptr(&self, caller: &str) -> *mut sys::ImGuiPlatformIO {
184        let pio = unsafe { sys::igGetPlatformIO_ContextPtr(self.raw) };
185        if pio.is_null() {
186            panic!("{caller} requires a valid ImGui context");
187        }
188        pio
189    }
190
191    pub(super) fn assert_current_context(&self, caller: &str) {
192        assert!(
193            self.is_current_context(),
194            "{caller} requires this context to be current"
195        );
196    }
197
198    fn try_create_internal(
199        shared_font_atlas: Option<SharedFontAtlas>,
200    ) -> crate::error::ImGuiResult<Context> {
201        let _guard = CTX_MUTEX.lock();
202
203        if !no_current_context() {
204            return Err(crate::error::ImGuiError::ContextAlreadyActive);
205        }
206
207        let shared_font_atlas_ptr = match &shared_font_atlas {
208            Some(atlas) => atlas.as_ptr(),
209            None => ptr::null_mut(),
210        };
211        crate::fonts::validate_font_atlas_context_registration(shared_font_atlas_ptr)?;
212
213        let id =
214            ContextId::allocate().ok_or_else(|| crate::error::ImGuiError::ContextCreation {
215                reason: "process Context identity space is exhausted".to_string(),
216            })?;
217
218        // Create the actual ImGui context
219        let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
220        if raw.is_null() {
221            return Err(crate::error::ImGuiError::ContextCreation {
222                reason: "ImGui_CreateContext returned null".to_string(),
223            });
224        }
225
226        // Set it as the current context
227        set_current_context(raw);
228
229        unsafe {
230            let io = sys::igGetIO_ContextPtr(raw);
231            assert!(
232                !io.is_null(),
233                "new ImGui context returned a null IO pointer"
234            );
235            crate::fonts::register_font_atlas_context((*io).Fonts, raw);
236        }
237
238        let state = ContextState::new(id, raw);
239        let texture_registry = ManagedTextureRegistry::new(id);
240        let ui = crate::ui::Ui::new(raw, ContextBinding::new(&state), texture_registry.clone());
241
242        Ok(Context {
243            raw,
244            state,
245            attachments: AttachmentRegistry::default(),
246            snapshot_hub: SnapshotHub::new(id),
247            texture_registry,
248            shared_font_atlas,
249            ini_filename: None,
250            log_filename: None,
251            platform_name: None,
252            renderer_name: None,
253            clipboard_ctx: Box::new(ClipboardContext::dummy()),
254            ui,
255        })
256    }
257
258    /// Returns a mutable reference to this context's IO object.
259    pub fn io_mut(&mut self) -> &mut Io {
260        let _guard = CTX_MUTEX.lock();
261        unsafe {
262            let io_ptr = self.io_ptr("Context::io_mut()");
263            &mut *(io_ptr as *mut Io)
264        }
265    }
266
267    /// Get shared access to this context's IO object.
268    pub fn io(&self) -> &crate::io::Io {
269        let _guard = CTX_MUTEX.lock();
270        unsafe {
271            let io_ptr = self.io_ptr("Context::io()");
272            &*(io_ptr as *const crate::io::Io)
273        }
274    }
275
276    /// Get access to the Style structure
277    pub fn style(&self) -> &crate::style::Style {
278        let _guard = CTX_MUTEX.lock();
279        unsafe {
280            with_bound_context(self.raw, || {
281                let style_ptr = sys::igGetStyle();
282                if style_ptr.is_null() {
283                    panic!("Context::style() requires a valid ImGui context");
284                }
285                &*(style_ptr as *const crate::style::Style)
286            })
287        }
288    }
289
290    /// Get mutable access to the Style structure
291    pub fn style_mut(&mut self) -> &mut crate::style::Style {
292        let _guard = CTX_MUTEX.lock();
293        unsafe {
294            with_bound_context(self.raw, || {
295                let style_ptr = sys::igGetStyle();
296                if style_ptr.is_null() {
297                    panic!("Context::style_mut() requires a valid ImGui context");
298                }
299                &mut *(style_ptr as *mut crate::style::Style)
300            })
301        }
302    }
303
304    pub(super) fn is_current_context(&self) -> bool {
305        let ctx = unsafe { sys::igGetCurrentContext() };
306        self.raw == ctx
307    }
308}
309
310impl Drop for Context {
311    fn drop(&mut self) {
312        let _lock = CTX_MUTEX.lock();
313        if self.raw.is_null() {
314            self.state.mark_native_destroyed();
315            return;
316        }
317
318        self.state.begin_drop();
319        let attachment_controls = self.attachments.begin_teardown();
320        let context_id = self.state.id();
321        let raw = self.raw;
322        let _bound = RawBoundContextGuard::bind(raw);
323
324        // End the native frame while backend callbacks and attachment state are still live.
325        // EndFrame may update viewport bookkeeping, so quiescing backends first would make an
326        // otherwise recoverable dropped FrameToken depend on torn-down callback state.
327        self.end_frame_for_teardown_unlocked();
328
329        for phase in [
330            ContextAttachmentPhase::Quiesce,
331            ContextAttachmentPhase::RendererResources,
332            ContextAttachmentPhase::PlatformWindows,
333        ] {
334            if !run_pre_destroy_phase(&attachment_controls, self, phase) {
335                // Continuing would destroy resources whose preceding teardown phase failed. We
336                // cannot unwind safely either: native Context fields still borrow Rust storage.
337                std::process::abort();
338            }
339            if phase == ContextAttachmentPhase::RendererResources {
340                // Renderer attachments must still see their active consumer and any detached
341                // epoch while proving that a texture reset is safe. After that phase no native
342                // renderer resource may observe a completion, so close the hub before platform
343                // windows and native Context state begin disappearing.
344                self.snapshot_hub.close();
345            }
346        }
347
348        unsafe {
349            let io = sys::igGetIO_ContextPtr(raw);
350            let font_atlas = if io.is_null() {
351                std::ptr::null_mut()
352            } else {
353                (*io).Fonts
354            };
355            let owned_font_atlas = if self.shared_font_atlas.is_none() {
356                font_atlas
357            } else {
358                std::ptr::null_mut()
359            };
360            self.texture_registry.borrow_mut().prepare_teardown();
361            with_bound_context(raw, || {
362                crate::platform_io::clear_aggregate_callbacks_for_current_context();
363            });
364            #[cfg(feature = "stack-layout")]
365            sys::ImGuiStack_DestroyContextState(raw);
366            crate::fonts::unregister_font_atlas_context(font_atlas, raw, context_id);
367            if let Some(shared_font_atlas) = &self.shared_font_atlas {
368                with_bound_context(raw, || {
369                    shared_font_atlas.unregister_from_current_context();
370                });
371            }
372            sys::igDestroyContext(raw);
373            self.texture_registry
374                .borrow_mut()
375                .release_after_native_destroy();
376            // Native context destruction may invoke typed destroy callbacks, so their registry
377            // entries must outlive `igDestroyContext` itself.
378            crate::platform_io::clear_typed_callbacks_for_context(raw);
379            crate::fonts::forget_font_atlas_generation(owned_font_atlas);
380        }
381
382        self.raw = ptr::null_mut();
383        self.state.mark_native_destroyed();
384        if !run_post_destroy(attachment_controls, context_id) {
385            std::process::abort();
386        }
387    }
388}