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(
248            raw,
249            ContextBinding::new(&state),
250            texture_registry.clone(),
251            state.ime.clone(),
252        );
253
254        Ok(Context {
255            raw,
256            state,
257            _thread_lease: thread_lease,
258            attachments: AttachmentRegistry::default(),
259            snapshot_hub: SnapshotHub::new(id),
260            texture_registry,
261            shared_font_atlas,
262            ini_filename: None,
263            log_filename: None,
264            platform_name: None,
265            renderer_name: None,
266            clipboard_ctx: Box::new(ClipboardContext::dummy()),
267            ui,
268        })
269    }
270
271    /// Returns a mutable reference to this context's IO object.
272    pub fn io_mut(&mut self) -> &mut Io {
273        let _guard = CTX_MUTEX.lock();
274        unsafe {
275            let io_ptr = self.io_ptr("Context::io_mut()");
276            &mut *(io_ptr as *mut Io)
277        }
278    }
279
280    /// Get shared access to this context's IO object.
281    pub fn io(&self) -> &crate::io::Io {
282        let _guard = CTX_MUTEX.lock();
283        unsafe {
284            let io_ptr = self.io_ptr("Context::io()");
285            &*(io_ptr as *const crate::io::Io)
286        }
287    }
288
289    /// Get access to the Style structure
290    pub fn style(&self) -> &crate::style::Style {
291        let _guard = CTX_MUTEX.lock();
292        unsafe {
293            with_bound_context(self.raw, || {
294                let style_ptr = sys::igGetStyle();
295                if style_ptr.is_null() {
296                    panic!("Context::style() requires a valid ImGui context");
297                }
298                &*(style_ptr as *const crate::style::Style)
299            })
300        }
301    }
302
303    /// Get mutable access to the Style structure
304    pub fn style_mut(&mut self) -> &mut crate::style::Style {
305        let _guard = CTX_MUTEX.lock();
306        unsafe {
307            with_bound_context(self.raw, || {
308                let style_ptr = sys::igGetStyle();
309                if style_ptr.is_null() {
310                    panic!("Context::style_mut() requires a valid ImGui context");
311                }
312                &mut *(style_ptr as *mut crate::style::Style)
313            })
314        }
315    }
316
317    pub(super) fn is_current_context(&self) -> bool {
318        let ctx = unsafe { sys::igGetCurrentContext() };
319        self.raw == ctx
320    }
321}
322
323impl Drop for Context {
324    fn drop(&mut self) {
325        let _lock = CTX_MUTEX.lock();
326        if self.raw.is_null() {
327            self.state.mark_native_destroyed();
328            return;
329        }
330
331        self.state.begin_drop();
332        let attachment_controls = self.attachments.begin_teardown();
333        let context_id = self.state.id();
334        let raw = self.raw;
335        let _bound = RawBoundContextGuard::bind(raw);
336
337        // End the native frame while backend callbacks and attachment state are still live.
338        // EndFrame may update viewport bookkeeping, so quiescing backends first would make an
339        // otherwise recoverable dropped FrameToken depend on torn-down callback state.
340        self.end_frame_for_teardown_unlocked();
341
342        for phase in [
343            ContextAttachmentPhase::Quiesce,
344            ContextAttachmentPhase::RendererResources,
345            ContextAttachmentPhase::PlatformWindows,
346        ] {
347            if !run_pre_destroy_phase(&attachment_controls, self, phase) {
348                // Continuing would destroy resources whose preceding teardown phase failed. We
349                // cannot unwind safely either: native Context fields still borrow Rust storage.
350                std::process::abort();
351            }
352            if phase == ContextAttachmentPhase::RendererResources {
353                // Renderer attachments must still see their active consumer and any detached
354                // epoch while proving that a texture reset is safe. After that phase no native
355                // renderer resource may observe a completion, so close the hub before platform
356                // windows and native Context state begin disappearing.
357                self.snapshot_hub.close();
358            }
359        }
360
361        unsafe {
362            let io = sys::igGetIO_ContextPtr(raw);
363            let font_atlas = if io.is_null() {
364                std::ptr::null_mut()
365            } else {
366                (*io).Fonts
367            };
368            let owned_font_atlas = if self.shared_font_atlas.is_none() {
369                font_atlas
370            } else {
371                std::ptr::null_mut()
372            };
373            self.texture_registry.borrow_mut().prepare_teardown();
374            with_bound_context(raw, || {
375                crate::platform_io::clear_aggregate_callbacks_for_current_context();
376            });
377            #[cfg(feature = "stack-layout")]
378            sys::ImGuiStack_DestroyContextState(raw);
379            crate::fonts::unregister_font_atlas_context(font_atlas, raw, context_id);
380            if let Some(shared_font_atlas) = &self.shared_font_atlas {
381                with_bound_context(raw, || {
382                    shared_font_atlas.unregister_from_current_context();
383                });
384            }
385            sys::igDestroyContext(raw);
386            self.texture_registry
387                .borrow_mut()
388                .release_after_native_destroy();
389            // Native context destruction may invoke typed destroy callbacks, so their registry
390            // entries must outlive `igDestroyContext` itself.
391            crate::platform_io::clear_typed_callbacks_for_context(raw);
392            crate::fonts::forget_font_atlas_generation(owned_font_atlas);
393        }
394
395        self.raw = ptr::null_mut();
396        self.state.mark_native_destroyed();
397        if !run_post_destroy(attachment_controls, context_id) {
398            std::process::abort();
399        }
400    }
401}