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