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