Skip to main content

dear_imgui_rs/context/
core.rs

1use std::ffi::CString;
2use std::ptr;
3use std::rc::{Rc, Weak};
4
5use crate::clipboard::ClipboardContext;
6use crate::fonts::SharedFontAtlas;
7use crate::io::Io;
8use crate::sys;
9
10use super::binding::{CTX_MUTEX, clear_current_context, no_current_context, with_bound_context};
11use super::texture_registry::unregister_user_textures_for_context;
12
13/// An imgui context.
14///
15/// A context needs to be created to access most library functions. Due to current Dear ImGui
16/// design choices, at most one active Context can exist at any time. This limitation will likely
17/// be removed in a future Dear ImGui version.
18///
19/// If you need more than one context, you can use suspended contexts. As long as only one context
20/// is active at a time, it's possible to have multiple independent contexts.
21///
22/// # Examples
23///
24/// Creating a new active context:
25/// ```
26/// let ctx = dear_imgui_rs::Context::create();
27/// // ctx is dropped naturally when it goes out of scope, which deactivates and destroys the
28/// // context
29/// ```
30///
31/// Never try to create an active context when another one is active:
32///
33/// ```should_panic
34/// let ctx1 = dear_imgui_rs::Context::create();
35///
36/// let ctx2 = dear_imgui_rs::Context::create(); // PANIC
37/// ```
38#[derive(Debug)]
39pub struct Context {
40    pub(super) raw: *mut sys::ImGuiContext,
41    pub(super) alive: Rc<()>,
42    pub(in crate::context) shared_font_atlas: Option<SharedFontAtlas>,
43    pub(in crate::context) ini_filename: Option<CString>,
44    pub(in crate::context) log_filename: Option<CString>,
45    pub(in crate::context) platform_name: Option<CString>,
46    pub(in crate::context) renderer_name: Option<CString>,
47    // Boxed so the raw PlatformIO user-data pointer remains stable.
48    // Interior mutability and reentrancy guarding live inside ClipboardContext.
49    pub(in crate::context) clipboard_ctx: Box<ClipboardContext>,
50    pub(in crate::context) ui: crate::ui::Ui,
51}
52
53/// A weak token that indicates whether a `Context` is still alive.
54#[derive(Clone, Debug)]
55pub struct ContextAliveToken(Weak<()>);
56
57impl ContextAliveToken {
58    pub(in crate::context) fn new(alive: &Rc<()>) -> Self {
59        Self(Rc::downgrade(alive))
60    }
61
62    /// Returns true if the originating `Context` has not been dropped.
63    pub fn is_alive(&self) -> bool {
64        self.0.upgrade().is_some()
65    }
66}
67
68impl Context {
69    /// Tries to create a new active Dear ImGui context.
70    ///
71    /// Returns an error if another context is already active or creation fails.
72    pub fn try_create() -> crate::error::ImGuiResult<Context> {
73        Self::try_create_internal(None)
74    }
75
76    /// Tries to create a new active Dear ImGui context with a shared font atlas.
77    pub fn try_create_with_shared_font_atlas(
78        shared_font_atlas: SharedFontAtlas,
79    ) -> crate::error::ImGuiResult<Context> {
80        Self::try_create_internal(Some(shared_font_atlas))
81    }
82
83    /// Creates a new active Dear ImGui context (panics on error).
84    ///
85    /// This aligns with imgui-rs behavior. For fallible creation use `try_create()`.
86    pub fn create() -> Context {
87        Self::try_create().expect("Failed to create Dear ImGui context")
88    }
89
90    /// Creates a new active Dear ImGui context with a shared font atlas (panics on error).
91    pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Context {
92        Self::try_create_with_shared_font_atlas(shared_font_atlas)
93            .expect("Failed to create Dear ImGui context")
94    }
95
96    /// Returns the raw `ImGuiContext*` for FFI integrations.
97    pub fn as_raw(&self) -> *mut sys::ImGuiContext {
98        self.raw
99    }
100
101    /// Returns a token that can be used to check whether this context is still alive.
102    ///
103    /// Useful for extension crates that store raw pointers and need to avoid calling into FFI
104    /// after the owning `Context` has been dropped.
105    pub fn alive_token(&self) -> ContextAliveToken {
106        ContextAliveToken::new(&self.alive)
107    }
108
109    // removed legacy create_or_panic variants (use create()/try_create())
110
111    pub(super) fn io_ptr(&self, caller: &str) -> *mut sys::ImGuiIO {
112        let io = unsafe { sys::igGetIO_ContextPtr(self.raw) };
113        if io.is_null() {
114            panic!("{caller} requires a valid ImGui context");
115        }
116        io
117    }
118
119    pub(super) fn platform_io_ptr(&self, caller: &str) -> *mut sys::ImGuiPlatformIO {
120        let pio = unsafe { sys::igGetPlatformIO_ContextPtr(self.raw) };
121        if pio.is_null() {
122            panic!("{caller} requires a valid ImGui context");
123        }
124        pio
125    }
126
127    pub(super) fn assert_current_context(&self, caller: &str) {
128        assert!(
129            self.is_current_context(),
130            "{caller} requires this context to be current"
131        );
132    }
133
134    fn try_create_internal(
135        mut shared_font_atlas: Option<SharedFontAtlas>,
136    ) -> crate::error::ImGuiResult<Context> {
137        let _guard = CTX_MUTEX.lock();
138
139        if !no_current_context() {
140            return Err(crate::error::ImGuiError::ContextAlreadyActive);
141        }
142
143        let shared_font_atlas_ptr = match &mut shared_font_atlas {
144            Some(atlas) => atlas.as_ptr_mut(),
145            None => ptr::null_mut(),
146        };
147
148        // Create the actual ImGui context
149        let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
150        if raw.is_null() {
151            return Err(crate::error::ImGuiError::ContextCreation {
152                reason: "ImGui_CreateContext returned null".to_string(),
153            });
154        }
155
156        // Set it as the current context
157        unsafe {
158            sys::igSetCurrentContext(raw);
159        }
160
161        let alive = Rc::new(());
162        let ui = crate::ui::Ui::new(raw, ContextAliveToken::new(&alive));
163
164        Ok(Context {
165            raw,
166            alive,
167            shared_font_atlas,
168            ini_filename: None,
169            log_filename: None,
170            platform_name: None,
171            renderer_name: None,
172            clipboard_ctx: Box::new(ClipboardContext::dummy()),
173            ui,
174        })
175    }
176
177    /// Returns a mutable reference to this context's IO object.
178    pub fn io_mut(&mut self) -> &mut Io {
179        let _guard = CTX_MUTEX.lock();
180        unsafe {
181            let io_ptr = self.io_ptr("Context::io_mut()");
182            &mut *(io_ptr as *mut Io)
183        }
184    }
185
186    /// Get shared access to this context's IO object.
187    pub fn io(&self) -> &crate::io::Io {
188        let _guard = CTX_MUTEX.lock();
189        unsafe {
190            let io_ptr = self.io_ptr("Context::io()");
191            &*(io_ptr as *const crate::io::Io)
192        }
193    }
194
195    /// Get access to the Style structure
196    pub fn style(&self) -> &crate::style::Style {
197        let _guard = CTX_MUTEX.lock();
198        unsafe {
199            with_bound_context(self.raw, || {
200                let style_ptr = sys::igGetStyle();
201                if style_ptr.is_null() {
202                    panic!("Context::style() requires a valid ImGui context");
203                }
204                &*(style_ptr as *const crate::style::Style)
205            })
206        }
207    }
208
209    /// Get mutable access to the Style structure
210    pub fn style_mut(&mut self) -> &mut crate::style::Style {
211        let _guard = CTX_MUTEX.lock();
212        unsafe {
213            with_bound_context(self.raw, || {
214                let style_ptr = sys::igGetStyle();
215                if style_ptr.is_null() {
216                    panic!("Context::style_mut() requires a valid ImGui context");
217                }
218                &mut *(style_ptr as *mut crate::style::Style)
219            })
220        }
221    }
222
223    pub(super) fn is_current_context(&self) -> bool {
224        let ctx = unsafe { sys::igGetCurrentContext() };
225        self.raw == ctx
226    }
227}
228
229impl Drop for Context {
230    fn drop(&mut self) {
231        let _guard = CTX_MUTEX.lock();
232        unsafe {
233            if !self.raw.is_null() {
234                unregister_user_textures_for_context(self.raw);
235                if self.shared_font_atlas.is_none() {
236                    let io = sys::igGetIO_ContextPtr(self.raw);
237                    if !io.is_null() {
238                        crate::fonts::forget_font_atlas_generation((*io).Fonts);
239                    }
240                }
241                crate::platform_io::clear_typed_callbacks_for_context(self.raw);
242                with_bound_context(self.raw, || {
243                    crate::platform_io::clear_out_param_callbacks_for_current_context();
244                });
245                if sys::igGetCurrentContext() == self.raw {
246                    clear_current_context();
247                }
248                sys::igDestroyContext(self.raw);
249            }
250        }
251    }
252}