dear_imgui_rs/
context.rs

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