dear_imgui/
context.rs

1use parking_lot::ReentrantMutex;
2use std::cell::UnsafeCell;
3use std::ffi::CString;
4use std::ops::Drop;
5use std::path::PathBuf;
6use std::ptr;
7
8use crate::clipboard::{ClipboardBackend, ClipboardContext};
9use crate::fonts::{Font, FontAtlas, SharedFontAtlas};
10use crate::io::Io;
11
12use crate::sys;
13
14/// An imgui context.
15///
16/// A context needs to be created to access most library functions. Due to current Dear ImGui
17/// design choices, at most one active Context can exist at any time. This limitation will likely
18/// be removed in a future Dear ImGui version.
19///
20/// If you need more than one context, you can use suspended contexts. As long as only one context
21/// is active at a time, it's possible to have multiple independent contexts.
22///
23/// # Examples
24///
25/// Creating a new active context:
26/// ```
27/// let ctx = dear_imgui::Context::create();
28/// // ctx is dropped naturally when it goes out of scope, which deactivates and destroys the
29/// // context
30/// ```
31///
32/// Never try to create an active context when another one is active:
33///
34/// ```should_panic
35/// let ctx1 = dear_imgui::Context::create();
36///
37/// let ctx2 = dear_imgui::Context::create(); // PANIC
38/// ```
39#[derive(Debug)]
40pub struct Context {
41    raw: *mut sys::ImGuiContext,
42    shared_font_atlas: Option<SharedFontAtlas>,
43    ini_filename: Option<CString>,
44    log_filename: Option<CString>,
45    platform_name: Option<CString>,
46    renderer_name: Option<CString>,
47    // We need to box this because we hand imgui a pointer to it,
48    // and we don't want to deal with finding `clipboard_ctx`.
49    // We also put it in an UnsafeCell since we're going to give
50    // imgui a mutable pointer to it.
51    clipboard_ctx: Box<UnsafeCell<ClipboardContext>>,
52    ui: crate::ui::Ui,
53}
54
55// This mutex needs to be used to guard all public functions that can affect the underlying
56// Dear ImGui active context
57static CTX_MUTEX: ReentrantMutex<()> = parking_lot::const_reentrant_mutex(());
58
59fn clear_current_context() {
60    unsafe {
61        sys::igSetCurrentContext(ptr::null_mut());
62    }
63}
64
65fn no_current_context() -> bool {
66    let ctx = unsafe { sys::igGetCurrentContext() };
67    ctx.is_null()
68}
69
70impl Context {
71    /// Tries to create a new active Dear ImGui context.
72    ///
73    /// Returns an error if another context is already active or creation fails.
74    pub fn try_create() -> crate::error::ImGuiResult<Context> {
75        Self::try_create_internal(None)
76    }
77
78    /// Tries to create a new active Dear ImGui context with a shared font atlas.
79    pub fn try_create_with_shared_font_atlas(
80        shared_font_atlas: SharedFontAtlas,
81    ) -> crate::error::ImGuiResult<Context> {
82        Self::try_create_internal(Some(shared_font_atlas))
83    }
84
85    /// Creates a new active Dear ImGui context (panics on error).
86    ///
87    /// This aligns with imgui-rs behavior. For fallible creation use `try_create()`.
88    pub fn create() -> Context {
89        Self::try_create().expect("Failed to create Dear ImGui context")
90    }
91
92    /// Creates a new active Dear ImGui context with a shared font atlas (panics on error).
93    pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Context {
94        Self::try_create_with_shared_font_atlas(shared_font_atlas)
95            .expect("Failed to create Dear ImGui context")
96    }
97
98    // removed legacy create_or_panic variants (use create()/try_create())
99
100    fn try_create_internal(
101        mut shared_font_atlas: Option<SharedFontAtlas>,
102    ) -> crate::error::ImGuiResult<Context> {
103        let _guard = CTX_MUTEX.lock();
104
105        if !no_current_context() {
106            return Err(crate::error::ImGuiError::ContextAlreadyActive);
107        }
108
109        let shared_font_atlas_ptr = match &mut shared_font_atlas {
110            Some(atlas) => atlas.as_ptr_mut(),
111            None => ptr::null_mut(),
112        };
113
114        // Create the actual ImGui context
115        let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
116        if raw.is_null() {
117            return Err(crate::error::ImGuiError::ContextCreation {
118                reason: "ImGui_CreateContext returned null".to_string(),
119            });
120        }
121
122        // Set it as the current context
123        unsafe {
124            sys::igSetCurrentContext(raw);
125        }
126
127        Ok(Context {
128            raw,
129            shared_font_atlas,
130            ini_filename: None,
131            log_filename: None,
132            platform_name: None,
133            renderer_name: None,
134            clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
135            ui: crate::ui::Ui::new(),
136        })
137    }
138
139    /// Returns a mutable reference to the active context's IO object
140    pub fn io_mut(&mut self) -> &mut Io {
141        let _guard = CTX_MUTEX.lock();
142        unsafe {
143            // Bindings provide igGetIO_Nil; use it to access current IO
144            let io_ptr = sys::igGetIO_Nil();
145            &mut *(io_ptr as *mut Io)
146        }
147    }
148
149    /// Get access to the IO structure
150    pub fn io(&self) -> &crate::io::Io {
151        let _guard = CTX_MUTEX.lock();
152        unsafe {
153            // Bindings provide igGetIO_Nil; use it to access current IO
154            let io_ptr = sys::igGetIO_Nil();
155            &*(io_ptr as *const crate::io::Io)
156        }
157    }
158
159    /// Get access to the Style structure
160    pub fn style(&self) -> &crate::style::Style {
161        let _guard = CTX_MUTEX.lock();
162        unsafe {
163            let style_ptr = sys::igGetStyle();
164            &*(style_ptr as *const crate::style::Style)
165        }
166    }
167
168    /// Get mutable access to the Style structure
169    pub fn style_mut(&mut self) -> &mut crate::style::Style {
170        let _guard = CTX_MUTEX.lock();
171        unsafe {
172            let style_ptr = sys::igGetStyle();
173            &mut *(style_ptr as *mut crate::style::Style)
174        }
175    }
176
177    /// Creates a new frame and returns a Ui object for building the interface
178    pub fn frame(&mut self) -> &mut crate::ui::Ui {
179        let _guard = CTX_MUTEX.lock();
180
181        unsafe {
182            sys::igNewFrame();
183        }
184        &mut self.ui
185    }
186
187    /// Create a new frame with a callback
188    pub fn frame_with<F, R>(&mut self, f: F) -> R
189    where
190        F: FnOnce(&crate::ui::Ui) -> R,
191    {
192        let ui = self.frame();
193        f(ui)
194    }
195
196    /// Renders the frame and returns a reference to the resulting draw data
197    ///
198    /// This finalizes the Dear ImGui frame and prepares all draw data for rendering.
199    /// The returned draw data contains all the information needed to render the frame.
200    pub fn render(&mut self) -> &crate::render::DrawData {
201        let _guard = CTX_MUTEX.lock();
202        unsafe {
203            sys::igRender();
204            &*(sys::igGetDrawData() as *const crate::render::DrawData)
205        }
206    }
207
208    /// Gets the draw data for the current frame
209    ///
210    /// This returns the draw data without calling render. Only valid after
211    /// `render()` has been called and before the next `new_frame()`.
212    pub fn draw_data(&self) -> Option<&crate::render::DrawData> {
213        let _guard = CTX_MUTEX.lock();
214        unsafe {
215            let draw_data = sys::igGetDrawData();
216            if draw_data.is_null() {
217                None
218            } else {
219                let data = &*(draw_data as *const crate::render::DrawData);
220                if data.valid() { Some(data) } else { None }
221            }
222        }
223    }
224
225    /// Sets the INI filename for settings persistence
226    ///
227    /// # Errors
228    ///
229    /// Returns an error if the filename contains null bytes
230    pub fn set_ini_filename<P: Into<PathBuf>>(
231        &mut self,
232        filename: Option<P>,
233    ) -> crate::error::ImGuiResult<()> {
234        use crate::error::SafeStringConversion;
235        let _guard = CTX_MUTEX.lock();
236
237        self.ini_filename = match filename {
238            Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
239            None => None,
240        };
241
242        unsafe {
243            let io = sys::igGetIO_Nil();
244            let ptr = self
245                .ini_filename
246                .as_ref()
247                .map(|s| s.as_ptr())
248                .unwrap_or(ptr::null());
249            (*io).IniFilename = ptr;
250        }
251        Ok(())
252    }
253
254    // removed legacy set_ini_filename_or_panic (use set_ini_filename())
255
256    /// Sets the log filename
257    ///
258    /// # Errors
259    ///
260    /// Returns an error if the filename contains null bytes
261    pub fn set_log_filename<P: Into<PathBuf>>(
262        &mut self,
263        filename: Option<P>,
264    ) -> crate::error::ImGuiResult<()> {
265        use crate::error::SafeStringConversion;
266        let _guard = CTX_MUTEX.lock();
267
268        self.log_filename = match filename {
269            Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
270            None => None,
271        };
272
273        unsafe {
274            let io = sys::igGetIO_Nil();
275            let ptr = self
276                .log_filename
277                .as_ref()
278                .map(|s| s.as_ptr())
279                .unwrap_or(ptr::null());
280            (*io).LogFilename = ptr;
281        }
282        Ok(())
283    }
284
285    // removed legacy set_log_filename_or_panic (use set_log_filename())
286
287    /// Sets the platform name
288    ///
289    /// # Errors
290    ///
291    /// Returns an error if the name contains null bytes
292    pub fn set_platform_name<S: Into<String>>(
293        &mut self,
294        name: Option<S>,
295    ) -> crate::error::ImGuiResult<()> {
296        use crate::error::SafeStringConversion;
297        let _guard = CTX_MUTEX.lock();
298
299        self.platform_name = match name {
300            Some(n) => Some(n.into().to_cstring_safe()?),
301            None => None,
302        };
303
304        unsafe {
305            let io = sys::igGetIO_Nil();
306            let ptr = self
307                .platform_name
308                .as_ref()
309                .map(|s| s.as_ptr())
310                .unwrap_or(ptr::null());
311            (*io).BackendPlatformName = ptr;
312        }
313        Ok(())
314    }
315
316    // removed legacy set_platform_name_or_panic (use set_platform_name())
317
318    /// Sets the renderer name
319    ///
320    /// # Errors
321    ///
322    /// Returns an error if the name contains null bytes
323    pub fn set_renderer_name<S: Into<String>>(
324        &mut self,
325        name: Option<S>,
326    ) -> crate::error::ImGuiResult<()> {
327        use crate::error::SafeStringConversion;
328        let _guard = CTX_MUTEX.lock();
329
330        self.renderer_name = match name {
331            Some(n) => Some(n.into().to_cstring_safe()?),
332            None => None,
333        };
334
335        unsafe {
336            let io = sys::igGetIO_Nil();
337            let ptr = self
338                .renderer_name
339                .as_ref()
340                .map(|s| s.as_ptr())
341                .unwrap_or(ptr::null());
342            (*io).BackendRendererName = ptr;
343        }
344        Ok(())
345    }
346
347    // removed legacy set_renderer_name_or_panic (use set_renderer_name())
348
349    /// Get mutable access to the platform IO
350    #[cfg(feature = "multi-viewport")]
351    pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
352        let _guard = CTX_MUTEX.lock();
353        unsafe {
354            let pio = sys::igGetPlatformIO_Nil();
355            crate::platform_io::PlatformIo::from_raw_mut(pio)
356        }
357    }
358
359    /// Enable multi-viewport support flags
360    #[cfg(feature = "multi-viewport")]
361    pub fn enable_multi_viewport(&mut self) {
362        // Enable viewport flags
363        crate::viewport_backend::utils::enable_viewport_flags(self.io_mut());
364    }
365
366    /// Update platform windows
367    ///
368    /// This function should be called every frame when multi-viewport is enabled.
369    /// It updates all platform windows and handles viewport management.
370    #[cfg(feature = "multi-viewport")]
371    pub fn update_platform_windows(&mut self) {
372        let _guard = CTX_MUTEX.lock();
373        unsafe {
374            // Ensure main viewport is properly set up before updating platform windows
375            let main_viewport = sys::igGetMainViewport();
376            if !main_viewport.is_null() && (*main_viewport).PlatformHandle.is_null() {
377                eprintln!("update_platform_windows: main viewport not set up, setting it up now");
378                // The main viewport needs to be set up - this should be done by the backend
379                // For now, we'll just log this and continue
380            }
381
382            sys::igUpdatePlatformWindows();
383        }
384    }
385
386    /// Render platform windows with default implementation
387    ///
388    /// This function renders all platform windows using the default implementation.
389    /// It calls the platform and renderer backends to render each viewport.
390    #[cfg(feature = "multi-viewport")]
391    pub fn render_platform_windows_default(&mut self) {
392        let _guard = CTX_MUTEX.lock();
393        unsafe {
394            sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
395        }
396    }
397
398    /// Destroy all platform windows
399    ///
400    /// This function should be called during shutdown to properly clean up
401    /// all platform windows and their associated resources.
402    #[cfg(feature = "multi-viewport")]
403    pub fn destroy_platform_windows(&mut self) {
404        let _guard = CTX_MUTEX.lock();
405        unsafe {
406            sys::igDestroyPlatformWindows();
407        }
408    }
409
410    /// Suspends this context so another context can be the active context
411    pub fn suspend(self) -> SuspendedContext {
412        let _guard = CTX_MUTEX.lock();
413        assert!(
414            self.is_current_context(),
415            "context to be suspended is not the active context"
416        );
417        clear_current_context();
418        SuspendedContext(self)
419    }
420
421    fn is_current_context(&self) -> bool {
422        let ctx = unsafe { sys::igGetCurrentContext() };
423        self.raw == ctx
424    }
425
426    /// Push a font onto the font stack
427    pub fn push_font(&mut self, font: &Font) {
428        let _guard = CTX_MUTEX.lock();
429        unsafe {
430            sys::igPushFont(font.raw(), 0.0);
431        }
432    }
433
434    /// Pop a font from the font stack
435    ///
436    /// This restores the previous font. Must be paired with a call to `push_font()`.
437    #[doc(alias = "PopFont")]
438    pub fn pop_font(&mut self) {
439        let _guard = CTX_MUTEX.lock();
440        unsafe {
441            sys::igPopFont();
442        }
443    }
444
445    /// Get the current font
446    #[doc(alias = "GetFont")]
447    pub fn current_font(&self) -> &Font {
448        let _guard = CTX_MUTEX.lock();
449        unsafe { Font::from_raw(sys::igGetFont() as *const _) }
450    }
451
452    /// Get the current font size
453    #[doc(alias = "GetFontSize")]
454    pub fn current_font_size(&self) -> f32 {
455        let _guard = CTX_MUTEX.lock();
456        unsafe { sys::igGetFontSize() }
457    }
458
459    /// Get the font atlas from the IO structure
460    pub fn font_atlas(&self) -> FontAtlas {
461        let _guard = CTX_MUTEX.lock();
462        unsafe {
463            let io = sys::igGetIO_Nil();
464            let atlas_ptr = (*io).Fonts;
465            FontAtlas::from_raw(atlas_ptr)
466        }
467    }
468
469    /// Get a mutable reference to the font atlas from the IO structure
470    pub fn font_atlas_mut(&mut self) -> FontAtlas {
471        let _guard = CTX_MUTEX.lock();
472
473        // For WASM, return a null atlas to avoid pointer issues
474        #[cfg(target_arch = "wasm32")]
475        {
476            return unsafe { FontAtlas::from_raw(ptr::null_mut()) };
477        }
478
479        #[cfg(not(target_arch = "wasm32"))]
480        unsafe {
481            let io = sys::igGetIO_Nil();
482            let atlas_ptr = (*io).Fonts;
483            FontAtlas::from_raw(atlas_ptr)
484        }
485    }
486
487    /// Returns the font atlas (alias for font_atlas_mut)
488    ///
489    /// This provides compatibility with imgui-rs naming convention
490    pub fn fonts(&mut self) -> FontAtlas {
491        self.font_atlas_mut()
492    }
493
494    /// Attempts to clone the interior shared font atlas **if it exists**.
495    pub fn clone_shared_font_atlas(&mut self) -> Option<SharedFontAtlas> {
496        self.shared_font_atlas.clone()
497    }
498
499    /// Loads settings from a string slice containing settings in .Ini file format
500    #[doc(alias = "LoadIniSettingsFromMemory")]
501    pub fn load_ini_settings(&mut self, data: &str) {
502        let _guard = CTX_MUTEX.lock();
503        unsafe {
504            sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len());
505        }
506    }
507
508    /// Saves settings to a mutable string buffer in .Ini file format
509    #[doc(alias = "SaveIniSettingsToMemory")]
510    pub fn save_ini_settings(&mut self, buf: &mut String) {
511        let _guard = CTX_MUTEX.lock();
512        unsafe {
513            let data_ptr = sys::igSaveIniSettingsToMemory(ptr::null_mut());
514            if !data_ptr.is_null() {
515                let data = std::ffi::CStr::from_ptr(data_ptr);
516                buf.push_str(&data.to_string_lossy());
517            }
518        }
519    }
520
521    /// Sets the clipboard backend used for clipboard operations
522    pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
523        let clipboard_ctx: Box<UnsafeCell<_>> =
524            Box::new(UnsafeCell::new(ClipboardContext::new(backend)));
525
526        // Set the clipboard callbacks in the ImGui PlatformIO
527        unsafe {
528            let platform_io = sys::igGetPlatformIO_Nil();
529            (*platform_io).Platform_SetClipboardTextFn = Some(crate::clipboard::set_clipboard_text);
530            (*platform_io).Platform_GetClipboardTextFn = Some(crate::clipboard::get_clipboard_text);
531            (*platform_io).Platform_ClipboardUserData = clipboard_ctx.get() as *mut _;
532        }
533
534        self.clipboard_ctx = clipboard_ctx;
535    }
536}
537
538impl Drop for Context {
539    fn drop(&mut self) {
540        let _guard = CTX_MUTEX.lock();
541        unsafe {
542            if !self.raw.is_null() {
543                if sys::igGetCurrentContext() == self.raw {
544                    clear_current_context();
545                }
546                sys::igDestroyContext(self.raw);
547            }
548        }
549    }
550}
551
552/// A suspended Dear ImGui context
553///
554/// A suspended context retains its state, but is not usable without activating it first.
555#[derive(Debug)]
556pub struct SuspendedContext(Context);
557
558impl SuspendedContext {
559    /// Tries to create a new suspended Dear ImGui context
560    pub fn try_create() -> crate::error::ImGuiResult<Self> {
561        Self::try_create_internal(None)
562    }
563
564    /// Tries to create a new suspended Dear ImGui context with a shared font atlas
565    pub fn try_create_with_shared_font_atlas(
566        shared_font_atlas: SharedFontAtlas,
567    ) -> crate::error::ImGuiResult<Self> {
568        Self::try_create_internal(Some(shared_font_atlas))
569    }
570
571    /// Creates a new suspended Dear ImGui context (panics on error)
572    pub fn create() -> Self {
573        Self::try_create().expect("Failed to create Dear ImGui context")
574    }
575
576    /// Creates a new suspended Dear ImGui context with a shared font atlas (panics on error)
577    pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
578        Self::try_create_with_shared_font_atlas(shared_font_atlas)
579            .expect("Failed to create Dear ImGui context")
580    }
581
582    // removed legacy create_or_panic variants (use create()/try_create())
583
584    fn try_create_internal(
585        mut shared_font_atlas: Option<SharedFontAtlas>,
586    ) -> crate::error::ImGuiResult<Self> {
587        let _guard = CTX_MUTEX.lock();
588
589        let shared_font_atlas_ptr = match &mut shared_font_atlas {
590            Some(atlas) => atlas.as_ptr_mut(),
591            None => ptr::null_mut(),
592        };
593
594        let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
595        if raw.is_null() {
596            return Err(crate::error::ImGuiError::ContextCreation {
597                reason: "ImGui_CreateContext returned null".to_string(),
598            });
599        }
600
601        let ctx = Context {
602            raw,
603            shared_font_atlas,
604            ini_filename: None,
605            log_filename: None,
606            platform_name: None,
607            renderer_name: None,
608            clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
609            ui: crate::ui::Ui::new(),
610        };
611
612        // If the context was activated during creation, deactivate it
613        if ctx.is_current_context() {
614            clear_current_context();
615        }
616
617        Ok(SuspendedContext(ctx))
618    }
619
620    /// Attempts to activate this suspended context
621    ///
622    /// If there is no active context, this suspended context is activated and `Ok` is returned.
623    /// If there is already an active context, nothing happens and `Err` is returned.
624    pub fn activate(self) -> Result<Context, SuspendedContext> {
625        let _guard = CTX_MUTEX.lock();
626        if no_current_context() {
627            unsafe {
628                sys::igSetCurrentContext(self.0.raw);
629            }
630            Ok(self.0)
631        } else {
632            Err(self)
633        }
634    }
635}
636
637// Dear ImGui is not thread-safe. The Context must not be sent or shared across
638// threads. If you need multi-threaded rendering, capture render data via
639// OwnedDrawData and move that to another thread for rendering.