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