Skip to main content

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            if io_ptr.is_null() {
153                panic!("Context::io_mut() requires an active ImGui context");
154            }
155            &mut *(io_ptr as *mut Io)
156        }
157    }
158
159    /// Get access to the IO structure
160    pub fn io(&self) -> &crate::io::Io {
161        let _guard = CTX_MUTEX.lock();
162        unsafe {
163            // Bindings provide igGetIO_Nil; use it to access current IO
164            let io_ptr = sys::igGetIO_Nil();
165            if io_ptr.is_null() {
166                panic!("Context::io() requires an active ImGui context");
167            }
168            &*(io_ptr as *const crate::io::Io)
169        }
170    }
171
172    /// Get access to the Style structure
173    pub fn style(&self) -> &crate::style::Style {
174        let _guard = CTX_MUTEX.lock();
175        unsafe {
176            let style_ptr = sys::igGetStyle();
177            if style_ptr.is_null() {
178                panic!("Context::style() requires an active ImGui context");
179            }
180            &*(style_ptr as *const crate::style::Style)
181        }
182    }
183
184    /// Get mutable access to the Style structure
185    pub fn style_mut(&mut self) -> &mut crate::style::Style {
186        let _guard = CTX_MUTEX.lock();
187        unsafe {
188            let style_ptr = sys::igGetStyle();
189            if style_ptr.is_null() {
190                panic!("Context::style_mut() requires an active ImGui context");
191            }
192            &mut *(style_ptr as *mut crate::style::Style)
193        }
194    }
195
196    /// Creates a new frame and returns a Ui object for building the interface
197    pub fn frame(&mut self) -> &mut crate::ui::Ui {
198        let _guard = CTX_MUTEX.lock();
199
200        unsafe {
201            sys::igNewFrame();
202        }
203        &mut self.ui
204    }
205
206    /// Create a new frame with a callback
207    pub fn frame_with<F, R>(&mut self, f: F) -> R
208    where
209        F: FnOnce(&crate::ui::Ui) -> R,
210    {
211        let ui = self.frame();
212        f(ui)
213    }
214
215    /// Renders the frame and returns a reference to the resulting draw data
216    ///
217    /// This finalizes the Dear ImGui frame and prepares all draw data for rendering.
218    /// The returned draw data contains all the information needed to render the frame.
219    pub fn render(&mut self) -> &crate::render::DrawData {
220        let _guard = CTX_MUTEX.lock();
221        unsafe {
222            sys::igRender();
223            let dd = sys::igGetDrawData();
224            if dd.is_null() {
225                panic!("Context::render() returned null draw data");
226            }
227            &*(dd as *const crate::render::DrawData)
228        }
229    }
230
231    /// Gets the draw data for the current frame
232    ///
233    /// This returns the draw data without calling render. Only valid after
234    /// `render()` has been called and before the next `new_frame()`.
235    pub fn draw_data(&self) -> Option<&crate::render::DrawData> {
236        let _guard = CTX_MUTEX.lock();
237        unsafe {
238            let draw_data = sys::igGetDrawData();
239            if draw_data.is_null() {
240                None
241            } else {
242                let data = &*(draw_data as *const crate::render::DrawData);
243                if data.valid() { Some(data) } else { None }
244            }
245        }
246    }
247
248    /// Register a user-created texture in ImGui's global texture list (ImGui 1.92+).
249    ///
250    /// Dear ImGui builds `DrawData::textures()` from its internal `PlatformIO.Textures[]` list.
251    /// If you create a `TextureData` yourself (e.g. `OwnedTextureData::new()`), you must register
252    /// it for renderer backends (with `BackendFlags::RENDERER_HAS_TEXTURES`) to receive
253    /// Create/Update/Destroy requests automatically.
254    ///
255    /// Note: `RegisterUserTexture()` is currently an experimental ImGui API.
256    ///
257    /// # Safety & Lifetime
258    /// The underlying `ImTextureData` must remain alive and registered until you call
259    /// `unregister_user_texture()`. Unregister before dropping the texture to avoid leaving a
260    /// dangling pointer inside ImGui.
261    pub fn register_user_texture(&mut self, texture: &mut crate::texture::TextureData) {
262        let _guard = CTX_MUTEX.lock();
263        assert!(
264            self.is_current_context(),
265            "Context::register_user_texture() requires the context to be current"
266        );
267        unsafe {
268            sys::igRegisterUserTexture(texture.as_raw_mut());
269        }
270    }
271
272    /// Register a user-created texture and return an RAII token which unregisters on drop.
273    ///
274    /// This is a convenience wrapper around `register_user_texture()`.
275    ///
276    /// # Safety & Drop Ordering
277    /// The returned token must be dropped before the underlying `ImGuiContext` is destroyed.
278    /// If you store it in a struct alongside `Context`, ensure the token is dropped first.
279    pub fn register_user_texture_token(
280        &mut self,
281        texture: &mut crate::texture::TextureData,
282    ) -> RegisteredUserTexture {
283        self.register_user_texture(texture);
284        RegisteredUserTexture {
285            ctx: self.raw,
286            tex: texture.as_raw_mut(),
287        }
288    }
289
290    /// Unregister a user texture previously registered with `register_user_texture()`.
291    ///
292    /// This removes the `ImTextureData*` from ImGui's internal texture list.
293    pub fn unregister_user_texture(&mut self, texture: &mut crate::texture::TextureData) {
294        let _guard = CTX_MUTEX.lock();
295        assert!(
296            self.is_current_context(),
297            "Context::unregister_user_texture() requires the context to be current"
298        );
299        unsafe {
300            sys::igUnregisterUserTexture(texture.as_raw_mut());
301        }
302    }
303
304    /// Sets the INI filename for settings persistence
305    ///
306    /// # Errors
307    ///
308    /// Returns an error if the filename contains null bytes
309    pub fn set_ini_filename<P: Into<PathBuf>>(
310        &mut self,
311        filename: Option<P>,
312    ) -> crate::error::ImGuiResult<()> {
313        use crate::error::SafeStringConversion;
314        let _guard = CTX_MUTEX.lock();
315
316        self.ini_filename = match filename {
317            Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
318            None => None,
319        };
320
321        unsafe {
322            let io = sys::igGetIO_Nil();
323            let ptr = self
324                .ini_filename
325                .as_ref()
326                .map(|s| s.as_ptr())
327                .unwrap_or(ptr::null());
328            (*io).IniFilename = ptr;
329        }
330        Ok(())
331    }
332
333    // removed legacy set_ini_filename_or_panic (use set_ini_filename())
334
335    /// Sets the log filename
336    ///
337    /// # Errors
338    ///
339    /// Returns an error if the filename contains null bytes
340    pub fn set_log_filename<P: Into<PathBuf>>(
341        &mut self,
342        filename: Option<P>,
343    ) -> crate::error::ImGuiResult<()> {
344        use crate::error::SafeStringConversion;
345        let _guard = CTX_MUTEX.lock();
346
347        self.log_filename = match filename {
348            Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
349            None => None,
350        };
351
352        unsafe {
353            let io = sys::igGetIO_Nil();
354            let ptr = self
355                .log_filename
356                .as_ref()
357                .map(|s| s.as_ptr())
358                .unwrap_or(ptr::null());
359            (*io).LogFilename = ptr;
360        }
361        Ok(())
362    }
363
364    // removed legacy set_log_filename_or_panic (use set_log_filename())
365
366    /// Sets the platform name
367    ///
368    /// # Errors
369    ///
370    /// Returns an error if the name contains null bytes
371    pub fn set_platform_name<S: Into<String>>(
372        &mut self,
373        name: Option<S>,
374    ) -> crate::error::ImGuiResult<()> {
375        use crate::error::SafeStringConversion;
376        let _guard = CTX_MUTEX.lock();
377
378        self.platform_name = match name {
379            Some(n) => Some(n.into().to_cstring_safe()?),
380            None => None,
381        };
382
383        unsafe {
384            let io = sys::igGetIO_Nil();
385            let ptr = self
386                .platform_name
387                .as_ref()
388                .map(|s| s.as_ptr())
389                .unwrap_or(ptr::null());
390            (*io).BackendPlatformName = ptr;
391        }
392        Ok(())
393    }
394
395    // removed legacy set_platform_name_or_panic (use set_platform_name())
396
397    /// Sets the renderer name
398    ///
399    /// # Errors
400    ///
401    /// Returns an error if the name contains null bytes
402    pub fn set_renderer_name<S: Into<String>>(
403        &mut self,
404        name: Option<S>,
405    ) -> crate::error::ImGuiResult<()> {
406        use crate::error::SafeStringConversion;
407        let _guard = CTX_MUTEX.lock();
408
409        self.renderer_name = match name {
410            Some(n) => Some(n.into().to_cstring_safe()?),
411            None => None,
412        };
413
414        unsafe {
415            let io = sys::igGetIO_Nil();
416            if io.is_null() {
417                panic!("igGetIO_Nil() returned null");
418            }
419            let ptr = self
420                .renderer_name
421                .as_ref()
422                .map(|s| s.as_ptr())
423                .unwrap_or(ptr::null());
424            (*io).BackendRendererName = ptr;
425        }
426        Ok(())
427    }
428
429    // removed legacy set_renderer_name_or_panic (use set_renderer_name())
430
431    /// Get mutable access to the platform IO.
432    ///
433    /// Note: `ImGuiPlatformIO` exists even when multi-viewport is disabled. We expose it
434    /// unconditionally so callers can use ImGui 1.92+ texture management via `PlatformIO.Textures[]`.
435    pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
436        let _guard = CTX_MUTEX.lock();
437        unsafe {
438            let pio = sys::igGetPlatformIO_Nil();
439            if pio.is_null() {
440                panic!("igGetPlatformIO_Nil() returned null");
441            }
442            crate::platform_io::PlatformIo::from_raw_mut(pio)
443        }
444    }
445
446    /// Returns a reference to the main Dear ImGui viewport.
447    ///
448    /// The returned reference is owned by the currently active ImGui context and
449    /// must not be used after the context is destroyed.
450    #[doc(alias = "GetMainViewport")]
451    pub fn main_viewport(&mut self) -> &crate::platform_io::Viewport {
452        let _guard = CTX_MUTEX.lock();
453        unsafe {
454            let ptr = sys::igGetMainViewport();
455            if ptr.is_null() {
456                panic!("Context::main_viewport() requires an active ImGui context");
457            }
458            crate::platform_io::Viewport::from_raw(ptr as *const sys::ImGuiViewport)
459        }
460    }
461
462    /// Enable multi-viewport support flags
463    #[cfg(feature = "multi-viewport")]
464    pub fn enable_multi_viewport(&mut self) {
465        // Enable viewport flags
466        crate::viewport_backend::utils::enable_viewport_flags(self.io_mut());
467    }
468
469    /// Update platform windows
470    ///
471    /// This function should be called every frame when multi-viewport is enabled.
472    /// It updates all platform windows and handles viewport management.
473    #[cfg(feature = "multi-viewport")]
474    pub fn update_platform_windows(&mut self) {
475        let _guard = CTX_MUTEX.lock();
476        unsafe {
477            // Ensure main viewport is properly set up before updating platform windows
478            let main_viewport = sys::igGetMainViewport();
479            if !main_viewport.is_null() && (*main_viewport).PlatformHandle.is_null() {
480                eprintln!("update_platform_windows: main viewport not set up, setting it up now");
481                // The main viewport needs to be set up - this should be done by the backend
482                // For now, we'll just log this and continue
483            }
484
485            sys::igUpdatePlatformWindows();
486        }
487    }
488
489    /// Render platform windows with default implementation
490    ///
491    /// This function renders all platform windows using the default implementation.
492    /// It calls the platform and renderer backends to render each viewport.
493    #[cfg(feature = "multi-viewport")]
494    pub fn render_platform_windows_default(&mut self) {
495        let _guard = CTX_MUTEX.lock();
496        unsafe {
497            sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
498        }
499    }
500
501    /// Destroy all platform windows
502    ///
503    /// This function should be called during shutdown to properly clean up
504    /// all platform windows and their associated resources.
505    #[cfg(feature = "multi-viewport")]
506    pub fn destroy_platform_windows(&mut self) {
507        let _guard = CTX_MUTEX.lock();
508        unsafe {
509            sys::igDestroyPlatformWindows();
510        }
511    }
512
513    /// Suspends this context so another context can be the active context
514    pub fn suspend(self) -> SuspendedContext {
515        let _guard = CTX_MUTEX.lock();
516        assert!(
517            self.is_current_context(),
518            "context to be suspended is not the active context"
519        );
520        clear_current_context();
521        SuspendedContext(self)
522    }
523
524    fn is_current_context(&self) -> bool {
525        let ctx = unsafe { sys::igGetCurrentContext() };
526        self.raw == ctx
527    }
528
529    /// Push a font onto the font stack
530    pub fn push_font(&mut self, font: &Font) {
531        let _guard = CTX_MUTEX.lock();
532        unsafe {
533            sys::igPushFont(font.raw(), 0.0);
534        }
535    }
536
537    /// Pop a font from the font stack
538    ///
539    /// This restores the previous font. Must be paired with a call to `push_font()`.
540    #[doc(alias = "PopFont")]
541    pub fn pop_font(&mut self) {
542        let _guard = CTX_MUTEX.lock();
543        unsafe {
544            sys::igPopFont();
545        }
546    }
547
548    /// Get the current font
549    #[doc(alias = "GetFont")]
550    pub fn current_font(&self) -> &Font {
551        let _guard = CTX_MUTEX.lock();
552        unsafe { Font::from_raw(sys::igGetFont() as *const _) }
553    }
554
555    /// Get the current font size
556    #[doc(alias = "GetFontSize")]
557    pub fn current_font_size(&self) -> f32 {
558        let _guard = CTX_MUTEX.lock();
559        unsafe { sys::igGetFontSize() }
560    }
561
562    /// Get the font atlas from the IO structure
563    pub fn font_atlas(&self) -> FontAtlas {
564        let _guard = CTX_MUTEX.lock();
565
566        // wasm32 import-style builds keep Dear ImGui state in a separate module
567        // and share linear memory. When the experimental font-atlas feature is
568        // enabled, we allow direct access to the atlas pointer, assuming the
569        // provider has been correctly configured via xtask.
570        #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
571        unsafe {
572            let io = sys::igGetIO_Nil();
573            let atlas_ptr = (*io).Fonts;
574            assert!(
575                !atlas_ptr.is_null(),
576                "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
577            );
578            FontAtlas::from_raw(atlas_ptr)
579        }
580
581        // Default wasm path: keep this API disabled to avoid accidental UB.
582        #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
583        {
584            panic!(
585                "font_atlas() is not supported on wasm32 targets without \
586                 `wasm-font-atlas-experimental` feature; \
587                 see docs/WASM.md for current limitations."
588            );
589        }
590
591        #[cfg(not(target_arch = "wasm32"))]
592        unsafe {
593            let io = sys::igGetIO_Nil();
594            let atlas_ptr = (*io).Fonts;
595            FontAtlas::from_raw(atlas_ptr)
596        }
597    }
598
599    /// Get a mutable reference to the font atlas from the IO structure
600    pub fn font_atlas_mut(&mut self) -> FontAtlas {
601        let _guard = CTX_MUTEX.lock();
602
603        // wasm32 import-style builds keep Dear ImGui state in a separate module
604        // and share linear memory. When the experimental font-atlas feature is
605        // enabled, we allow direct access to the atlas pointer, assuming the
606        // provider has been correctly configured via xtask.
607        #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
608        unsafe {
609            let io = sys::igGetIO_Nil();
610            let atlas_ptr = (*io).Fonts;
611            assert!(
612                !atlas_ptr.is_null(),
613                "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
614            );
615            return FontAtlas::from_raw(atlas_ptr);
616        }
617
618        // Default wasm path: keep this API disabled to avoid accidental UB.
619        #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
620        {
621            panic!(
622                "font_atlas_mut()/fonts() are not supported on wasm32 targets yet; \
623                 enable `wasm-font-atlas-experimental` to opt-in for experiments."
624            );
625        }
626
627        #[cfg(not(target_arch = "wasm32"))]
628        unsafe {
629            let io = sys::igGetIO_Nil();
630            let atlas_ptr = (*io).Fonts;
631            FontAtlas::from_raw(atlas_ptr)
632        }
633    }
634
635    /// Returns the font atlas (alias for font_atlas_mut)
636    ///
637    /// This provides compatibility with imgui-rs naming convention
638    pub fn fonts(&mut self) -> FontAtlas {
639        self.font_atlas_mut()
640    }
641
642    /// Attempts to clone the interior shared font atlas **if it exists**.
643    pub fn clone_shared_font_atlas(&mut self) -> Option<SharedFontAtlas> {
644        self.shared_font_atlas.clone()
645    }
646
647    /// Loads settings from a string slice containing settings in .Ini file format
648    #[doc(alias = "LoadIniSettingsFromMemory")]
649    pub fn load_ini_settings(&mut self, data: &str) {
650        let _guard = CTX_MUTEX.lock();
651        unsafe {
652            sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len());
653        }
654    }
655
656    /// Saves settings to a mutable string buffer in .Ini file format
657    #[doc(alias = "SaveIniSettingsToMemory")]
658    pub fn save_ini_settings(&mut self, buf: &mut String) {
659        let _guard = CTX_MUTEX.lock();
660        unsafe {
661            let mut out_ini_size: usize = 0;
662            let data_ptr = sys::igSaveIniSettingsToMemory(&mut out_ini_size as *mut usize);
663            if data_ptr.is_null() || out_ini_size == 0 {
664                return;
665            }
666
667            let mut bytes = std::slice::from_raw_parts(data_ptr as *const u8, out_ini_size);
668            if bytes.last() == Some(&0) {
669                bytes = &bytes[..bytes.len().saturating_sub(1)];
670            }
671            buf.push_str(&String::from_utf8_lossy(bytes));
672        }
673    }
674
675    /// Loads settings from a `.ini` file on disk.
676    ///
677    /// This is a convenience wrapper over `ImGui::LoadIniSettingsFromDisk`.
678    ///
679    /// Note: this is not available on `wasm32` targets.
680    #[cfg(not(target_arch = "wasm32"))]
681    #[doc(alias = "LoadIniSettingsFromDisk")]
682    pub fn load_ini_settings_from_disk<P: Into<PathBuf>>(
683        &mut self,
684        filename: P,
685    ) -> crate::error::ImGuiResult<()> {
686        use crate::error::SafeStringConversion;
687        let _guard = CTX_MUTEX.lock();
688        let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
689        unsafe { sys::igLoadIniSettingsFromDisk(cstr.as_ptr()) }
690        Ok(())
691    }
692
693    /// Saves settings to a `.ini` file on disk.
694    ///
695    /// This is a convenience wrapper over `ImGui::SaveIniSettingsToDisk`.
696    ///
697    /// Note: this is not available on `wasm32` targets.
698    #[cfg(not(target_arch = "wasm32"))]
699    #[doc(alias = "SaveIniSettingsToDisk")]
700    pub fn save_ini_settings_to_disk<P: Into<PathBuf>>(
701        &mut self,
702        filename: P,
703    ) -> crate::error::ImGuiResult<()> {
704        use crate::error::SafeStringConversion;
705        let _guard = CTX_MUTEX.lock();
706        let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
707        unsafe { sys::igSaveIniSettingsToDisk(cstr.as_ptr()) }
708        Ok(())
709    }
710
711    /// Returns the current clipboard text, if available.
712    ///
713    /// This calls Dear ImGui's clipboard callbacks (configured via
714    /// [`Context::set_clipboard_backend`]). When no backend is installed, this returns `None`.
715    ///
716    /// Note: returned data is copied into a new `String`.
717    #[doc(alias = "GetClipboardText")]
718    pub fn clipboard_text(&self) -> Option<String> {
719        let _guard = CTX_MUTEX.lock();
720        unsafe {
721            let ptr = sys::igGetClipboardText();
722            if ptr.is_null() {
723                return None;
724            }
725            Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
726        }
727    }
728
729    /// Sets the clipboard text.
730    ///
731    /// This calls Dear ImGui's clipboard callbacks (configured via
732    /// [`Context::set_clipboard_backend`]). If no backend is installed, this is a no-op.
733    ///
734    /// Interior NUL bytes are sanitized to `?` to match other scratch-string helpers.
735    #[doc(alias = "SetClipboardText")]
736    pub fn set_clipboard_text(&self, text: impl AsRef<str>) {
737        let _guard = CTX_MUTEX.lock();
738        unsafe {
739            sys::igSetClipboardText(self.ui.scratch_txt(text.as_ref()));
740        }
741    }
742
743    /// Sets the clipboard backend used for clipboard operations
744    pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
745        let _guard = CTX_MUTEX.lock();
746
747        let clipboard_ctx: Box<UnsafeCell<_>> =
748            Box::new(UnsafeCell::new(ClipboardContext::new(backend)));
749
750        // On native/desktop targets, register clipboard callbacks in ImGui PlatformIO
751        // so ImGui can call back into Rust for copy/paste.
752        //
753        // On wasm32 (import-style build), function pointers cannot safely cross the
754        // module boundary between the Rust main module and the cimgui provider. We
755        // therefore keep the backend alive on the Rust side but do not hook it into
756        // ImGui's PlatformIO yet; clipboard integration for web will need a dedicated
757        // design using JS bindings.
758        #[cfg(not(target_arch = "wasm32"))]
759        unsafe {
760            let platform_io = sys::igGetPlatformIO_Nil();
761            if platform_io.is_null() {
762                panic!("Context::set_clipboard_backend() requires an active ImGui context");
763            }
764            (*platform_io).Platform_SetClipboardTextFn = Some(crate::clipboard::set_clipboard_text);
765            (*platform_io).Platform_GetClipboardTextFn = Some(crate::clipboard::get_clipboard_text);
766            (*platform_io).Platform_ClipboardUserData = clipboard_ctx.get() as *mut _;
767        }
768
769        self.clipboard_ctx = clipboard_ctx;
770    }
771}
772
773impl Drop for Context {
774    fn drop(&mut self) {
775        let _guard = CTX_MUTEX.lock();
776        unsafe {
777            if !self.raw.is_null() {
778                if sys::igGetCurrentContext() == self.raw {
779                    clear_current_context();
780                }
781                sys::igDestroyContext(self.raw);
782            }
783        }
784    }
785}
786
787/// A suspended Dear ImGui context
788///
789/// A suspended context retains its state, but is not usable without activating it first.
790#[derive(Debug)]
791pub struct SuspendedContext(Context);
792
793impl SuspendedContext {
794    /// Tries to create a new suspended Dear ImGui context
795    pub fn try_create() -> crate::error::ImGuiResult<Self> {
796        Self::try_create_internal(None)
797    }
798
799    /// Tries to create a new suspended Dear ImGui context with a shared font atlas
800    pub fn try_create_with_shared_font_atlas(
801        shared_font_atlas: SharedFontAtlas,
802    ) -> crate::error::ImGuiResult<Self> {
803        Self::try_create_internal(Some(shared_font_atlas))
804    }
805
806    /// Creates a new suspended Dear ImGui context (panics on error)
807    pub fn create() -> Self {
808        Self::try_create().expect("Failed to create Dear ImGui context")
809    }
810
811    /// Creates a new suspended Dear ImGui context with a shared font atlas (panics on error)
812    pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
813        Self::try_create_with_shared_font_atlas(shared_font_atlas)
814            .expect("Failed to create Dear ImGui context")
815    }
816
817    // removed legacy create_or_panic variants (use create()/try_create())
818
819    fn try_create_internal(
820        mut shared_font_atlas: Option<SharedFontAtlas>,
821    ) -> crate::error::ImGuiResult<Self> {
822        let _guard = CTX_MUTEX.lock();
823
824        let shared_font_atlas_ptr = match &mut shared_font_atlas {
825            Some(atlas) => atlas.as_ptr_mut(),
826            None => ptr::null_mut(),
827        };
828
829        let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
830        if raw.is_null() {
831            return Err(crate::error::ImGuiError::ContextCreation {
832                reason: "ImGui_CreateContext returned null".to_string(),
833            });
834        }
835
836        let ctx = Context {
837            raw,
838            shared_font_atlas,
839            ini_filename: None,
840            log_filename: None,
841            platform_name: None,
842            renderer_name: None,
843            clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
844            ui: crate::ui::Ui::new(),
845        };
846
847        // If the context was activated during creation, deactivate it
848        if ctx.is_current_context() {
849            clear_current_context();
850        }
851
852        Ok(SuspendedContext(ctx))
853    }
854
855    /// Attempts to activate this suspended context
856    ///
857    /// If there is no active context, this suspended context is activated and `Ok` is returned.
858    /// If there is already an active context, nothing happens and `Err` is returned.
859    pub fn activate(self) -> Result<Context, SuspendedContext> {
860        let _guard = CTX_MUTEX.lock();
861        if no_current_context() {
862            unsafe {
863                sys::igSetCurrentContext(self.0.raw);
864            }
865            Ok(self.0)
866        } else {
867            Err(self)
868        }
869    }
870}
871
872/// RAII token returned by `Context::register_user_texture_token()`.
873///
874/// On drop, this unregisters the corresponding `ImTextureData*` from ImGui's internal user texture
875/// list.
876///
877/// # Safety
878/// - The referenced `ImTextureData` must remain alive while the token exists.
879/// - The token must be dropped before the `ImGuiContext` is destroyed.
880#[derive(Debug)]
881pub struct RegisteredUserTexture {
882    ctx: *mut sys::ImGuiContext,
883    tex: *mut sys::ImTextureData,
884}
885
886impl Drop for RegisteredUserTexture {
887    fn drop(&mut self) {
888        if self.ctx.is_null() || self.tex.is_null() {
889            return;
890        }
891
892        let _guard = CTX_MUTEX.lock();
893        unsafe {
894            // Best-effort: temporarily bind the context so we can call Unregister in cases where
895            // multiple contexts are used via activate/suspend.
896            let prev = sys::igGetCurrentContext();
897            if prev != self.ctx {
898                sys::igSetCurrentContext(self.ctx);
899            }
900            sys::igUnregisterUserTexture(self.tex);
901            if prev != self.ctx {
902                sys::igSetCurrentContext(prev);
903            }
904        }
905    }
906}
907
908// Dear ImGui is not thread-safe. The Context must not be sent or shared across
909// threads. If you need multi-threaded rendering, capture render data via
910// OwnedDrawData and move that to another thread for rendering.