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