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 shared 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(&self) -> &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!("Context::platform_io() requires an active ImGui context");
475            }
476            crate::platform_io::PlatformIo::from_raw(pio)
477        }
478    }
479
480    /// Get mutable access to the platform IO.
481    ///
482    /// Note: `ImGuiPlatformIO` exists even when multi-viewport is disabled. We expose it
483    /// unconditionally so callers can use ImGui 1.92+ texture management via `PlatformIO.Textures[]`.
484    pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
485        let _guard = CTX_MUTEX.lock();
486        unsafe {
487            let pio = sys::igGetPlatformIO_Nil();
488            if pio.is_null() {
489                panic!("igGetPlatformIO_Nil() returned null");
490            }
491            crate::platform_io::PlatformIo::from_raw_mut(pio)
492        }
493    }
494
495    /// Returns a reference to the main Dear ImGui viewport.
496    ///
497    /// The returned reference is owned by the currently active ImGui context and
498    /// must not be used after the context is destroyed.
499    #[doc(alias = "GetMainViewport")]
500    pub fn main_viewport(&mut self) -> &crate::platform_io::Viewport {
501        let _guard = CTX_MUTEX.lock();
502        unsafe {
503            let ptr = sys::igGetMainViewport();
504            if ptr.is_null() {
505                panic!("Context::main_viewport() requires an active ImGui context");
506            }
507            crate::platform_io::Viewport::from_raw(ptr as *const sys::ImGuiViewport)
508        }
509    }
510
511    /// Enable multi-viewport support flags
512    #[cfg(feature = "multi-viewport")]
513    pub fn enable_multi_viewport(&mut self) {
514        // Enable viewport flags
515        crate::viewport_backend::utils::enable_viewport_flags(self.io_mut());
516    }
517
518    /// Update platform windows
519    ///
520    /// This function should be called every frame when multi-viewport is enabled.
521    /// It updates all platform windows and handles viewport management.
522    #[cfg(feature = "multi-viewport")]
523    pub fn update_platform_windows(&mut self) {
524        let _guard = CTX_MUTEX.lock();
525        unsafe {
526            // Ensure main viewport is properly set up before updating platform windows
527            let main_viewport = sys::igGetMainViewport();
528            if !main_viewport.is_null() && (*main_viewport).PlatformHandle.is_null() {
529                eprintln!("update_platform_windows: main viewport not set up, setting it up now");
530                // The main viewport needs to be set up - this should be done by the backend
531                // For now, we'll just log this and continue
532            }
533
534            sys::igUpdatePlatformWindows();
535        }
536    }
537
538    /// Render platform windows with default implementation
539    ///
540    /// This function renders all platform windows using the default implementation.
541    /// It calls the platform and renderer backends to render each viewport.
542    #[cfg(feature = "multi-viewport")]
543    pub fn render_platform_windows_default(&mut self) {
544        let _guard = CTX_MUTEX.lock();
545        unsafe {
546            sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
547        }
548    }
549
550    /// Destroy all platform windows
551    ///
552    /// This function should be called during shutdown to properly clean up
553    /// all platform windows and their associated resources.
554    #[cfg(feature = "multi-viewport")]
555    pub fn destroy_platform_windows(&mut self) {
556        let _guard = CTX_MUTEX.lock();
557        unsafe {
558            sys::igDestroyPlatformWindows();
559        }
560    }
561
562    /// Suspends this context so another context can be the active context
563    pub fn suspend(self) -> SuspendedContext {
564        let _guard = CTX_MUTEX.lock();
565        assert!(
566            self.is_current_context(),
567            "context to be suspended is not the active context"
568        );
569        clear_current_context();
570        SuspendedContext(self)
571    }
572
573    fn is_current_context(&self) -> bool {
574        let ctx = unsafe { sys::igGetCurrentContext() };
575        self.raw == ctx
576    }
577
578    /// Push a font onto the font stack
579    pub fn push_font(&mut self, font: &Font) {
580        let _guard = CTX_MUTEX.lock();
581        unsafe {
582            sys::igPushFont(font.raw(), 0.0);
583        }
584    }
585
586    /// Pop a font from the font stack
587    ///
588    /// This restores the previous font. Must be paired with a call to `push_font()`.
589    #[doc(alias = "PopFont")]
590    pub fn pop_font(&mut self) {
591        let _guard = CTX_MUTEX.lock();
592        unsafe {
593            sys::igPopFont();
594        }
595    }
596
597    /// Get the current font
598    #[doc(alias = "GetFont")]
599    pub fn current_font(&self) -> &Font {
600        let _guard = CTX_MUTEX.lock();
601        unsafe { Font::from_raw(sys::igGetFont() as *const _) }
602    }
603
604    /// Get the current font size
605    #[doc(alias = "GetFontSize")]
606    pub fn current_font_size(&self) -> f32 {
607        let _guard = CTX_MUTEX.lock();
608        unsafe { sys::igGetFontSize() }
609    }
610
611    /// Get the font atlas from the IO structure
612    pub fn font_atlas(&self) -> FontAtlas {
613        let _guard = CTX_MUTEX.lock();
614
615        // wasm32 import-style builds keep Dear ImGui state in a separate module
616        // and share linear memory. When the experimental font-atlas feature is
617        // enabled, we allow direct access to the atlas pointer, assuming the
618        // provider has been correctly configured via xtask.
619        #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
620        unsafe {
621            let io = sys::igGetIO_Nil();
622            let atlas_ptr = (*io).Fonts;
623            assert!(
624                !atlas_ptr.is_null(),
625                "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
626            );
627            FontAtlas::from_raw(atlas_ptr)
628        }
629
630        // Default wasm path: keep this API disabled to avoid accidental UB.
631        #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
632        {
633            panic!(
634                "font_atlas() is not supported on wasm32 targets without \
635                 `wasm-font-atlas-experimental` feature; \
636                 see docs/WASM.md for current limitations."
637            );
638        }
639
640        #[cfg(not(target_arch = "wasm32"))]
641        unsafe {
642            let io = sys::igGetIO_Nil();
643            let atlas_ptr = (*io).Fonts;
644            FontAtlas::from_raw(atlas_ptr)
645        }
646    }
647
648    /// Get a mutable reference to the font atlas from the IO structure
649    pub fn font_atlas_mut(&mut self) -> FontAtlas {
650        let _guard = CTX_MUTEX.lock();
651
652        // wasm32 import-style builds keep Dear ImGui state in a separate module
653        // and share linear memory. When the experimental font-atlas feature is
654        // enabled, we allow direct access to the atlas pointer, assuming the
655        // provider has been correctly configured via xtask.
656        #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
657        unsafe {
658            let io = sys::igGetIO_Nil();
659            let atlas_ptr = (*io).Fonts;
660            assert!(
661                !atlas_ptr.is_null(),
662                "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
663            );
664            return FontAtlas::from_raw(atlas_ptr);
665        }
666
667        // Default wasm path: keep this API disabled to avoid accidental UB.
668        #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
669        {
670            panic!(
671                "font_atlas_mut()/fonts() are not supported on wasm32 targets yet; \
672                 enable `wasm-font-atlas-experimental` to opt-in for experiments."
673            );
674        }
675
676        #[cfg(not(target_arch = "wasm32"))]
677        unsafe {
678            let io = sys::igGetIO_Nil();
679            let atlas_ptr = (*io).Fonts;
680            FontAtlas::from_raw(atlas_ptr)
681        }
682    }
683
684    /// Returns the font atlas (alias for font_atlas_mut)
685    ///
686    /// This provides compatibility with imgui-rs naming convention
687    pub fn fonts(&mut self) -> FontAtlas {
688        self.font_atlas_mut()
689    }
690
691    /// Attempts to clone the interior shared font atlas **if it exists**.
692    pub fn clone_shared_font_atlas(&mut self) -> Option<SharedFontAtlas> {
693        self.shared_font_atlas.clone()
694    }
695
696    /// Loads settings from a string slice containing settings in .Ini file format
697    #[doc(alias = "LoadIniSettingsFromMemory")]
698    pub fn load_ini_settings(&mut self, data: &str) {
699        let _guard = CTX_MUTEX.lock();
700        unsafe {
701            sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len());
702        }
703    }
704
705    /// Saves settings to a mutable string buffer in .Ini file format
706    #[doc(alias = "SaveIniSettingsToMemory")]
707    pub fn save_ini_settings(&mut self, buf: &mut String) {
708        let _guard = CTX_MUTEX.lock();
709        unsafe {
710            let mut out_ini_size: usize = 0;
711            let data_ptr = sys::igSaveIniSettingsToMemory(&mut out_ini_size as *mut usize);
712            if data_ptr.is_null() || out_ini_size == 0 {
713                return;
714            }
715
716            let mut bytes = std::slice::from_raw_parts(data_ptr as *const u8, out_ini_size);
717            if bytes.last() == Some(&0) {
718                bytes = &bytes[..bytes.len().saturating_sub(1)];
719            }
720            buf.push_str(&String::from_utf8_lossy(bytes));
721        }
722    }
723
724    /// Loads settings from a `.ini` file on disk.
725    ///
726    /// This is a convenience wrapper over `ImGui::LoadIniSettingsFromDisk`.
727    ///
728    /// Note: this is not available on `wasm32` targets.
729    #[cfg(not(target_arch = "wasm32"))]
730    #[doc(alias = "LoadIniSettingsFromDisk")]
731    pub fn load_ini_settings_from_disk<P: Into<PathBuf>>(
732        &mut self,
733        filename: P,
734    ) -> crate::error::ImGuiResult<()> {
735        use crate::error::SafeStringConversion;
736        let _guard = CTX_MUTEX.lock();
737        let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
738        unsafe { sys::igLoadIniSettingsFromDisk(cstr.as_ptr()) }
739        Ok(())
740    }
741
742    /// Saves settings to a `.ini` file on disk.
743    ///
744    /// This is a convenience wrapper over `ImGui::SaveIniSettingsToDisk`.
745    ///
746    /// Note: this is not available on `wasm32` targets.
747    #[cfg(not(target_arch = "wasm32"))]
748    #[doc(alias = "SaveIniSettingsToDisk")]
749    pub fn save_ini_settings_to_disk<P: Into<PathBuf>>(
750        &mut self,
751        filename: P,
752    ) -> crate::error::ImGuiResult<()> {
753        use crate::error::SafeStringConversion;
754        let _guard = CTX_MUTEX.lock();
755        let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
756        unsafe { sys::igSaveIniSettingsToDisk(cstr.as_ptr()) }
757        Ok(())
758    }
759
760    /// Returns the current clipboard text, if available.
761    ///
762    /// This calls Dear ImGui's clipboard callbacks (configured via
763    /// [`Context::set_clipboard_backend`]). When no backend is installed, this returns `None`.
764    ///
765    /// Note: returned data is copied into a new `String`.
766    #[doc(alias = "GetClipboardText")]
767    pub fn clipboard_text(&self) -> Option<String> {
768        let _guard = CTX_MUTEX.lock();
769        unsafe {
770            let ptr = sys::igGetClipboardText();
771            if ptr.is_null() {
772                return None;
773            }
774            Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
775        }
776    }
777
778    /// Sets the clipboard text.
779    ///
780    /// This calls Dear ImGui's clipboard callbacks (configured via
781    /// [`Context::set_clipboard_backend`]). If no backend is installed, this is a no-op.
782    ///
783    /// Interior NUL bytes are sanitized to `?` to match other scratch-string helpers.
784    #[doc(alias = "SetClipboardText")]
785    pub fn set_clipboard_text(&self, text: impl AsRef<str>) {
786        let _guard = CTX_MUTEX.lock();
787        unsafe {
788            sys::igSetClipboardText(self.ui.scratch_txt(text.as_ref()));
789        }
790    }
791
792    /// Sets the clipboard backend used for clipboard operations
793    pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
794        let _guard = CTX_MUTEX.lock();
795
796        let clipboard_ctx: Box<UnsafeCell<_>> =
797            Box::new(UnsafeCell::new(ClipboardContext::new(backend)));
798
799        // On native/desktop targets, register clipboard callbacks in ImGui PlatformIO
800        // so ImGui can call back into Rust for copy/paste.
801        //
802        // On wasm32 (import-style build), function pointers cannot safely cross the
803        // module boundary between the Rust main module and the cimgui provider. We
804        // therefore keep the backend alive on the Rust side but do not hook it into
805        // ImGui's PlatformIO yet; clipboard integration for web will need a dedicated
806        // design using JS bindings.
807        #[cfg(not(target_arch = "wasm32"))]
808        unsafe {
809            let platform_io = sys::igGetPlatformIO_Nil();
810            if platform_io.is_null() {
811                panic!("Context::set_clipboard_backend() requires an active ImGui context");
812            }
813            (*platform_io).Platform_SetClipboardTextFn = Some(crate::clipboard::set_clipboard_text);
814            (*platform_io).Platform_GetClipboardTextFn = Some(crate::clipboard::get_clipboard_text);
815            (*platform_io).Platform_ClipboardUserData = clipboard_ctx.get() as *mut _;
816        }
817
818        self.clipboard_ctx = clipboard_ctx;
819    }
820}
821
822impl Drop for Context {
823    fn drop(&mut self) {
824        let _guard = CTX_MUTEX.lock();
825        unsafe {
826            if !self.raw.is_null() {
827                if sys::igGetCurrentContext() == self.raw {
828                    clear_current_context();
829                }
830                sys::igDestroyContext(self.raw);
831            }
832        }
833    }
834}
835
836#[cfg(test)]
837mod tests {
838    use super::Context;
839
840    #[test]
841    fn platform_io_shared_and_mut_views_match() {
842        let mut ctx = Context::create();
843        let shared = ctx.platform_io().as_raw();
844        let mutable = ctx.platform_io_mut().as_raw();
845        assert_eq!(shared, mutable);
846    }
847
848    #[cfg(feature = "multi-viewport")]
849    #[test]
850    fn platform_io_get_window_pos_and_size_setters_install_handlers() {
851        unsafe extern "C" fn get_pos(
852            _viewport: *mut crate::sys::ImGuiViewport,
853        ) -> crate::sys::ImVec2 {
854            crate::sys::ImVec2 { x: 10.0, y: 20.0 }
855        }
856        unsafe extern "C" fn get_size(
857            _viewport: *mut crate::sys::ImGuiViewport,
858        ) -> crate::sys::ImVec2 {
859            crate::sys::ImVec2 { x: 30.0, y: 40.0 }
860        }
861
862        let mut ctx = Context::create();
863        let pio = ctx.platform_io_mut();
864
865        pio.set_platform_get_window_pos_raw(Some(get_pos));
866        pio.set_platform_get_window_size_raw(Some(get_size));
867
868        let raw = unsafe { &*pio.as_raw() };
869        assert!(raw.Platform_GetWindowPos.is_some());
870        assert!(raw.Platform_GetWindowSize.is_some());
871
872        pio.set_platform_get_window_pos_raw(None);
873        pio.set_platform_get_window_size_raw(None);
874
875        let raw = unsafe { &*pio.as_raw() };
876        assert!(raw.Platform_GetWindowPos.is_none());
877        assert!(raw.Platform_GetWindowSize.is_none());
878    }
879}
880
881/// A suspended Dear ImGui context
882///
883/// A suspended context retains its state, but is not usable without activating it first.
884#[derive(Debug)]
885pub struct SuspendedContext(Context);
886
887/// A weak token that indicates whether a `Context` is still alive.
888#[derive(Clone, Debug)]
889pub struct ContextAliveToken(Weak<()>);
890
891impl ContextAliveToken {
892    /// Returns true if the originating `Context` has not been dropped.
893    pub fn is_alive(&self) -> bool {
894        self.0.upgrade().is_some()
895    }
896}
897
898impl SuspendedContext {
899    /// Tries to create a new suspended Dear ImGui context
900    pub fn try_create() -> crate::error::ImGuiResult<Self> {
901        Self::try_create_internal(None)
902    }
903
904    /// Tries to create a new suspended Dear ImGui context with a shared font atlas
905    pub fn try_create_with_shared_font_atlas(
906        shared_font_atlas: SharedFontAtlas,
907    ) -> crate::error::ImGuiResult<Self> {
908        Self::try_create_internal(Some(shared_font_atlas))
909    }
910
911    /// Creates a new suspended Dear ImGui context (panics on error)
912    pub fn create() -> Self {
913        Self::try_create().expect("Failed to create Dear ImGui context")
914    }
915
916    /// Creates a new suspended Dear ImGui context with a shared font atlas (panics on error)
917    pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
918        Self::try_create_with_shared_font_atlas(shared_font_atlas)
919            .expect("Failed to create Dear ImGui context")
920    }
921
922    // removed legacy create_or_panic variants (use create()/try_create())
923
924    fn try_create_internal(
925        mut shared_font_atlas: Option<SharedFontAtlas>,
926    ) -> crate::error::ImGuiResult<Self> {
927        let _guard = CTX_MUTEX.lock();
928
929        let shared_font_atlas_ptr = match &mut shared_font_atlas {
930            Some(atlas) => atlas.as_ptr_mut(),
931            None => ptr::null_mut(),
932        };
933
934        let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
935        if raw.is_null() {
936            return Err(crate::error::ImGuiError::ContextCreation {
937                reason: "ImGui_CreateContext returned null".to_string(),
938            });
939        }
940
941        let ctx = Context {
942            raw,
943            alive: Rc::new(()),
944            shared_font_atlas,
945            ini_filename: None,
946            log_filename: None,
947            platform_name: None,
948            renderer_name: None,
949            clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
950            ui: crate::ui::Ui::new(),
951        };
952
953        // If the context was activated during creation, deactivate it
954        if ctx.is_current_context() {
955            clear_current_context();
956        }
957
958        Ok(SuspendedContext(ctx))
959    }
960
961    /// Attempts to activate this suspended context
962    ///
963    /// If there is no active context, this suspended context is activated and `Ok` is returned.
964    /// If there is already an active context, nothing happens and `Err` is returned.
965    pub fn activate(self) -> Result<Context, SuspendedContext> {
966        let _guard = CTX_MUTEX.lock();
967        if no_current_context() {
968            unsafe {
969                sys::igSetCurrentContext(self.0.raw);
970            }
971            Ok(self.0)
972        } else {
973            Err(self)
974        }
975    }
976}
977
978/// RAII token returned by `Context::register_user_texture_token()`.
979///
980/// On drop, this unregisters the corresponding `ImTextureData*` from ImGui's internal user texture
981/// list.
982///
983/// # Safety
984/// - The referenced `ImTextureData` must remain alive while the token exists.
985/// - The token must be dropped before the `ImGuiContext` is destroyed.
986#[derive(Debug)]
987pub struct RegisteredUserTexture {
988    ctx: *mut sys::ImGuiContext,
989    tex: *mut sys::ImTextureData,
990}
991
992impl Drop for RegisteredUserTexture {
993    fn drop(&mut self) {
994        if self.ctx.is_null() || self.tex.is_null() {
995            return;
996        }
997
998        let _guard = CTX_MUTEX.lock();
999        unsafe {
1000            // Best-effort: temporarily bind the context so we can call Unregister in cases where
1001            // multiple contexts are used via activate/suspend.
1002            let prev = sys::igGetCurrentContext();
1003            if prev != self.ctx {
1004                sys::igSetCurrentContext(self.ctx);
1005            }
1006            sys::igUnregisterUserTexture(self.tex);
1007            if prev != self.ctx {
1008                sys::igSetCurrentContext(prev);
1009            }
1010        }
1011    }
1012}
1013
1014// Dear ImGui is not thread-safe. The Context must not be sent or shared across
1015// threads. If you need multi-threaded rendering, capture render data via
1016// OwnedDrawData and move that to another thread for rendering.