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::{RefCell, 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
68#[derive(Clone)]
69struct UserTextureRegistration {
70    ctx: *mut sys::ImGuiContext,
71    tex: *mut sys::ImTextureData,
72    alive: Weak<()>,
73}
74
75thread_local! {
76    static USER_TEXTURE_REGISTRATIONS: RefCell<Vec<UserTextureRegistration>> = RefCell::new(Vec::new());
77}
78
79fn clear_current_context() {
80    unsafe {
81        sys::igSetCurrentContext(ptr::null_mut());
82    }
83}
84
85fn no_current_context() -> bool {
86    let ctx = unsafe { sys::igGetCurrentContext() };
87    ctx.is_null()
88}
89
90struct BoundContextGuard {
91    prev: *mut sys::ImGuiContext,
92    restore: bool,
93}
94
95impl BoundContextGuard {
96    fn bind(ctx: *mut sys::ImGuiContext) -> Self {
97        unsafe {
98            let prev = sys::igGetCurrentContext();
99            let restore = prev != ctx;
100            if restore {
101                sys::igSetCurrentContext(ctx);
102            }
103            Self { prev, restore }
104        }
105    }
106}
107
108impl Drop for BoundContextGuard {
109    fn drop(&mut self) {
110        if self.restore {
111            unsafe {
112                sys::igSetCurrentContext(self.prev);
113            }
114        }
115    }
116}
117
118fn with_bound_context<R>(ctx: *mut sys::ImGuiContext, f: impl FnOnce() -> R) -> R {
119    let _guard = BoundContextGuard::bind(ctx);
120    f()
121}
122
123fn prune_dead_user_texture_registrations(registrations: &mut Vec<UserTextureRegistration>) {
124    registrations.retain(|registration| registration.alive.upgrade().is_some());
125}
126
127fn is_user_texture_registered(ctx: *mut sys::ImGuiContext, tex: *mut sys::ImTextureData) -> bool {
128    USER_TEXTURE_REGISTRATIONS.with(|registrations| {
129        let mut registrations = registrations.borrow_mut();
130        prune_dead_user_texture_registrations(&mut registrations);
131        registrations
132            .iter()
133            .any(|registration| registration.ctx == ctx && registration.tex == tex)
134    })
135}
136
137fn track_user_texture_registration(
138    ctx: *mut sys::ImGuiContext,
139    tex: *mut sys::ImTextureData,
140    alive: Weak<()>,
141) {
142    USER_TEXTURE_REGISTRATIONS.with(|registrations| {
143        let mut registrations = registrations.borrow_mut();
144        prune_dead_user_texture_registrations(&mut registrations);
145        registrations.push(UserTextureRegistration { ctx, tex, alive });
146    });
147}
148
149fn take_user_texture_registration(
150    ctx: *mut sys::ImGuiContext,
151    tex: *mut sys::ImTextureData,
152) -> Option<UserTextureRegistration> {
153    USER_TEXTURE_REGISTRATIONS.with(|registrations| {
154        let mut registrations = registrations.borrow_mut();
155        prune_dead_user_texture_registrations(&mut registrations);
156        registrations
157            .iter()
158            .position(|registration| registration.ctx == ctx && registration.tex == tex)
159            .map(|index| registrations.remove(index))
160    })
161}
162
163fn unregister_user_texture_registration(registration: UserTextureRegistration) {
164    if registration.ctx.is_null()
165        || registration.tex.is_null()
166        || registration.alive.upgrade().is_none()
167    {
168        return;
169    }
170
171    unsafe {
172        with_bound_context(registration.ctx, || {
173            sys::igUnregisterUserTexture(registration.tex);
174        });
175    }
176}
177
178pub(crate) fn unregister_user_texture_from_all_contexts(tex: *mut sys::ImTextureData) {
179    if tex.is_null() {
180        return;
181    }
182
183    let registrations = USER_TEXTURE_REGISTRATIONS.with(|registrations| {
184        let mut registrations = registrations.borrow_mut();
185        let mut taken = Vec::new();
186        let mut index = 0;
187        while index < registrations.len() {
188            if registrations[index].alive.upgrade().is_none() {
189                registrations.remove(index);
190            } else if registrations[index].tex == tex {
191                taken.push(registrations.remove(index));
192            } else {
193                index += 1;
194            }
195        }
196        taken
197    });
198
199    let _guard = CTX_MUTEX.lock();
200    for registration in registrations {
201        unregister_user_texture_registration(registration);
202    }
203}
204
205fn unregister_user_textures_for_context(ctx: *mut sys::ImGuiContext) {
206    if ctx.is_null() {
207        return;
208    }
209
210    let registrations = USER_TEXTURE_REGISTRATIONS.with(|registrations| {
211        let mut registrations = registrations.borrow_mut();
212        let mut taken = Vec::new();
213        let mut index = 0;
214        while index < registrations.len() {
215            if registrations[index].alive.upgrade().is_none() || registrations[index].ctx == ctx {
216                let registration = registrations.remove(index);
217                if registration.ctx == ctx {
218                    taken.push(registration);
219                }
220            } else {
221                index += 1;
222            }
223        }
224        taken
225    });
226
227    for registration in registrations {
228        unregister_user_texture_registration(registration);
229    }
230}
231
232impl Context {
233    /// Tries to create a new active Dear ImGui context.
234    ///
235    /// Returns an error if another context is already active or creation fails.
236    pub fn try_create() -> crate::error::ImGuiResult<Context> {
237        Self::try_create_internal(None)
238    }
239
240    /// Tries to create a new active Dear ImGui context with a shared font atlas.
241    pub fn try_create_with_shared_font_atlas(
242        shared_font_atlas: SharedFontAtlas,
243    ) -> crate::error::ImGuiResult<Context> {
244        Self::try_create_internal(Some(shared_font_atlas))
245    }
246
247    /// Creates a new active Dear ImGui context (panics on error).
248    ///
249    /// This aligns with imgui-rs behavior. For fallible creation use `try_create()`.
250    pub fn create() -> Context {
251        Self::try_create().expect("Failed to create Dear ImGui context")
252    }
253
254    /// Creates a new active Dear ImGui context with a shared font atlas (panics on error).
255    pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Context {
256        Self::try_create_with_shared_font_atlas(shared_font_atlas)
257            .expect("Failed to create Dear ImGui context")
258    }
259
260    /// Returns the raw `ImGuiContext*` for FFI integrations.
261    pub fn as_raw(&self) -> *mut sys::ImGuiContext {
262        self.raw
263    }
264
265    /// Returns a token that can be used to check whether this context is still alive.
266    ///
267    /// Useful for extension crates that store raw pointers and need to avoid calling into FFI
268    /// after the owning `Context` has been dropped.
269    pub fn alive_token(&self) -> ContextAliveToken {
270        ContextAliveToken(Rc::downgrade(&self.alive))
271    }
272
273    // removed legacy create_or_panic variants (use create()/try_create())
274
275    fn io_ptr(&self, caller: &str) -> *mut sys::ImGuiIO {
276        let io = unsafe { sys::igGetIO_ContextPtr(self.raw) };
277        if io.is_null() {
278            panic!("{caller} requires a valid ImGui context");
279        }
280        io
281    }
282
283    fn platform_io_ptr(&self, caller: &str) -> *mut sys::ImGuiPlatformIO {
284        let pio = unsafe { sys::igGetPlatformIO_ContextPtr(self.raw) };
285        if pio.is_null() {
286            panic!("{caller} requires a valid ImGui context");
287        }
288        pio
289    }
290
291    fn assert_current_context(&self, caller: &str) {
292        assert!(
293            self.is_current_context(),
294            "{caller} requires this context to be current"
295        );
296    }
297
298    fn try_create_internal(
299        mut shared_font_atlas: Option<SharedFontAtlas>,
300    ) -> crate::error::ImGuiResult<Context> {
301        let _guard = CTX_MUTEX.lock();
302
303        if !no_current_context() {
304            return Err(crate::error::ImGuiError::ContextAlreadyActive);
305        }
306
307        let shared_font_atlas_ptr = match &mut shared_font_atlas {
308            Some(atlas) => atlas.as_ptr_mut(),
309            None => ptr::null_mut(),
310        };
311
312        // Create the actual ImGui context
313        let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
314        if raw.is_null() {
315            return Err(crate::error::ImGuiError::ContextCreation {
316                reason: "ImGui_CreateContext returned null".to_string(),
317            });
318        }
319
320        // Set it as the current context
321        unsafe {
322            sys::igSetCurrentContext(raw);
323        }
324
325        Ok(Context {
326            raw,
327            alive: Rc::new(()),
328            shared_font_atlas,
329            ini_filename: None,
330            log_filename: None,
331            platform_name: None,
332            renderer_name: None,
333            clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
334            ui: crate::ui::Ui::new(),
335        })
336    }
337
338    /// Returns a mutable reference to this context's IO object.
339    pub fn io_mut(&mut self) -> &mut Io {
340        let _guard = CTX_MUTEX.lock();
341        unsafe {
342            let io_ptr = self.io_ptr("Context::io_mut()");
343            &mut *(io_ptr as *mut Io)
344        }
345    }
346
347    /// Get shared access to this context's IO object.
348    pub fn io(&self) -> &crate::io::Io {
349        let _guard = CTX_MUTEX.lock();
350        unsafe {
351            let io_ptr = self.io_ptr("Context::io()");
352            &*(io_ptr as *const crate::io::Io)
353        }
354    }
355
356    /// Get access to the Style structure
357    pub fn style(&self) -> &crate::style::Style {
358        let _guard = CTX_MUTEX.lock();
359        unsafe {
360            with_bound_context(self.raw, || {
361                let style_ptr = sys::igGetStyle();
362                if style_ptr.is_null() {
363                    panic!("Context::style() requires a valid ImGui context");
364                }
365                &*(style_ptr as *const crate::style::Style)
366            })
367        }
368    }
369
370    /// Get mutable access to the Style structure
371    pub fn style_mut(&mut self) -> &mut crate::style::Style {
372        let _guard = CTX_MUTEX.lock();
373        unsafe {
374            with_bound_context(self.raw, || {
375                let style_ptr = sys::igGetStyle();
376                if style_ptr.is_null() {
377                    panic!("Context::style_mut() requires a valid ImGui context");
378                }
379                &mut *(style_ptr as *mut crate::style::Style)
380            })
381        }
382    }
383
384    /// Creates a new frame and returns a Ui object for building the interface.
385    ///
386    /// Note: you must update `io.DisplaySize` (and usually `io.DeltaTime`) before calling this,
387    /// unless you are using a platform backend that does it for you (e.g. `dear-imgui-winit`).
388    pub fn frame(&mut self) -> &mut crate::ui::Ui {
389        let _guard = CTX_MUTEX.lock();
390        self.assert_current_context("Context::frame()");
391
392        unsafe {
393            // Dear ImGui initializes DisplaySize to (-1, -1). Calling NewFrame() without a
394            // platform backend (or without setting DisplaySize manually) will trip an internal
395            // assertion and abort the process. Fail fast with a Rust panic to make the setup
396            // requirement obvious.
397            let io = sys::igGetIO_Nil();
398            if !io.is_null() && ((*io).DisplaySize.x < 0.0 || (*io).DisplaySize.y < 0.0) {
399                panic!(
400                    "Context::frame() called with invalid io.DisplaySize ({}, {}). \
401Set io.DisplaySize (and typically io.DeltaTime) before starting a frame. \
402If you are using a windowing/event-loop library, prefer a platform backend such as \
403dear-imgui-winit::WinitPlatform::prepare_frame().",
404                    (*io).DisplaySize.x,
405                    (*io).DisplaySize.y
406                );
407            }
408            sys::igNewFrame();
409        }
410        &mut self.ui
411    }
412
413    /// Create a new frame with a callback
414    pub fn frame_with<F, R>(&mut self, f: F) -> R
415    where
416        F: FnOnce(&crate::ui::Ui) -> R,
417    {
418        let ui = self.frame();
419        f(ui)
420    }
421
422    /// Renders the frame and returns a reference to the resulting draw data
423    ///
424    /// This finalizes the Dear ImGui frame and prepares all draw data for rendering.
425    /// The returned draw data contains all the information needed to render the frame.
426    pub fn render(&mut self) -> &crate::render::DrawData {
427        let _guard = CTX_MUTEX.lock();
428        self.assert_current_context("Context::render()");
429
430        unsafe {
431            sys::igRender();
432            let dd = sys::igGetDrawData();
433            if dd.is_null() {
434                panic!("Context::render() returned null draw data");
435            }
436            &*(dd as *const crate::render::DrawData)
437        }
438    }
439
440    /// Gets the draw data for the current frame
441    ///
442    /// This returns the draw data without calling render. Only valid after
443    /// `render()` has been called and before the next `new_frame()`.
444    pub fn draw_data(&self) -> Option<&crate::render::DrawData> {
445        let _guard = CTX_MUTEX.lock();
446        self.assert_current_context("Context::draw_data()");
447
448        unsafe {
449            let draw_data = sys::igGetDrawData();
450            if draw_data.is_null() {
451                None
452            } else {
453                let data = &*(draw_data as *const crate::render::DrawData);
454                if data.valid() { Some(data) } else { None }
455            }
456        }
457    }
458
459    /// Register a user-created texture in ImGui's global texture list (ImGui 1.92+).
460    ///
461    /// Dear ImGui builds `DrawData::textures()` from its internal `PlatformIO.Textures[]` list.
462    /// If you create an `OwnedTextureData` yourself, you must register
463    /// it for renderer backends (with `BackendFlags::RENDERER_HAS_TEXTURES`) to receive
464    /// Create/Update/Destroy requests automatically.
465    ///
466    /// Note: `RegisterUserTexture()` is currently an experimental ImGui API.
467    ///
468    /// The registration is tracked by this crate and will be removed automatically when the
469    /// `Context` or the `OwnedTextureData` is dropped.
470    pub fn register_user_texture(&mut self, texture: &mut crate::texture::OwnedTextureData) {
471        self.register_user_texture_ptr(texture.as_mut().as_raw_mut());
472    }
473
474    /// Register a borrowed/raw texture data pointer in ImGui's global texture list.
475    ///
476    /// Prefer [`Context::register_user_texture`] for `OwnedTextureData`.
477    ///
478    /// # Safety
479    /// The caller must guarantee that `texture` remains alive until it is unregistered, the
480    /// owning `Context` is dropped, or the texture owner unregisters it from all contexts before
481    /// destruction.
482    pub unsafe fn register_user_texture_raw(&mut self, texture: &mut crate::texture::TextureData) {
483        self.register_user_texture_ptr(texture.as_raw_mut());
484    }
485
486    fn register_user_texture_ptr(&mut self, texture: *mut sys::ImTextureData) {
487        let _guard = CTX_MUTEX.lock();
488        self.assert_current_context("Context::register_user_texture()");
489        assert!(
490            !texture.is_null(),
491            "Context::register_user_texture() received a null texture"
492        );
493        if is_user_texture_registered(self.raw, texture) {
494            return;
495        }
496        unsafe {
497            sys::igRegisterUserTexture(texture);
498        }
499        track_user_texture_registration(self.raw, texture, Rc::downgrade(&self.alive));
500    }
501
502    /// Register a user-created texture and return an RAII token which unregisters on drop.
503    ///
504    /// This is a convenience wrapper around `register_user_texture()`.
505    pub fn register_user_texture_token(
506        &mut self,
507        texture: &mut crate::texture::OwnedTextureData,
508    ) -> RegisteredUserTexture {
509        self.register_user_texture(texture);
510        RegisteredUserTexture {
511            ctx: self.raw,
512            tex: texture.as_mut().as_raw_mut(),
513            alive: Rc::downgrade(&self.alive),
514        }
515    }
516
517    /// Unregister a user texture previously registered with `register_user_texture()`.
518    ///
519    /// This removes the `ImTextureData*` from ImGui's internal texture list.
520    pub fn unregister_user_texture(&mut self, texture: &mut crate::texture::OwnedTextureData) {
521        self.unregister_user_texture_ptr(texture.as_mut().as_raw_mut());
522    }
523
524    /// Unregister a borrowed/raw user texture previously registered with
525    /// [`Context::register_user_texture_raw`].
526    ///
527    /// # Safety
528    /// The pointer must refer to the same live `TextureData` object that was previously
529    /// registered for this context.
530    pub unsafe fn unregister_user_texture_raw(
531        &mut self,
532        texture: &mut crate::texture::TextureData,
533    ) {
534        self.unregister_user_texture_ptr(texture.as_raw_mut());
535    }
536
537    fn unregister_user_texture_ptr(&mut self, texture: *mut sys::ImTextureData) {
538        let _guard = CTX_MUTEX.lock();
539        self.assert_current_context("Context::unregister_user_texture()");
540        assert!(
541            !texture.is_null(),
542            "Context::unregister_user_texture() received a null texture"
543        );
544        if let Some(registration) = take_user_texture_registration(self.raw, texture) {
545            unregister_user_texture_registration(registration);
546        }
547    }
548
549    /// Sets the INI filename for settings persistence
550    ///
551    /// # Errors
552    ///
553    /// Returns an error if the filename contains null bytes
554    pub fn set_ini_filename<P: Into<PathBuf>>(
555        &mut self,
556        filename: Option<P>,
557    ) -> crate::error::ImGuiResult<()> {
558        use crate::error::SafeStringConversion;
559        let _guard = CTX_MUTEX.lock();
560
561        self.ini_filename = match filename {
562            Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
563            None => None,
564        };
565
566        unsafe {
567            let io = self.io_ptr("Context::set_ini_filename()");
568            let ptr = self
569                .ini_filename
570                .as_ref()
571                .map(|s| s.as_ptr())
572                .unwrap_or(ptr::null());
573            (*io).IniFilename = ptr;
574        }
575        Ok(())
576    }
577
578    // removed legacy set_ini_filename_or_panic (use set_ini_filename())
579
580    /// Sets the log filename
581    ///
582    /// # Errors
583    ///
584    /// Returns an error if the filename contains null bytes
585    pub fn set_log_filename<P: Into<PathBuf>>(
586        &mut self,
587        filename: Option<P>,
588    ) -> crate::error::ImGuiResult<()> {
589        use crate::error::SafeStringConversion;
590        let _guard = CTX_MUTEX.lock();
591
592        self.log_filename = match filename {
593            Some(f) => Some(f.into().to_string_lossy().to_cstring_safe()?),
594            None => None,
595        };
596
597        unsafe {
598            let io = self.io_ptr("Context::set_log_filename()");
599            let ptr = self
600                .log_filename
601                .as_ref()
602                .map(|s| s.as_ptr())
603                .unwrap_or(ptr::null());
604            (*io).LogFilename = ptr;
605        }
606        Ok(())
607    }
608
609    // removed legacy set_log_filename_or_panic (use set_log_filename())
610
611    /// Sets the platform name
612    ///
613    /// # Errors
614    ///
615    /// Returns an error if the name contains null bytes
616    pub fn set_platform_name<S: Into<String>>(
617        &mut self,
618        name: Option<S>,
619    ) -> crate::error::ImGuiResult<()> {
620        use crate::error::SafeStringConversion;
621        let _guard = CTX_MUTEX.lock();
622
623        self.platform_name = match name {
624            Some(n) => Some(n.into().to_cstring_safe()?),
625            None => None,
626        };
627
628        unsafe {
629            let io = self.io_ptr("Context::set_platform_name()");
630            let ptr = self
631                .platform_name
632                .as_ref()
633                .map(|s| s.as_ptr())
634                .unwrap_or(ptr::null());
635            (*io).BackendPlatformName = ptr;
636        }
637        Ok(())
638    }
639
640    // removed legacy set_platform_name_or_panic (use set_platform_name())
641
642    /// Sets the renderer name
643    ///
644    /// # Errors
645    ///
646    /// Returns an error if the name contains null bytes
647    pub fn set_renderer_name<S: Into<String>>(
648        &mut self,
649        name: Option<S>,
650    ) -> crate::error::ImGuiResult<()> {
651        use crate::error::SafeStringConversion;
652        let _guard = CTX_MUTEX.lock();
653
654        self.renderer_name = match name {
655            Some(n) => Some(n.into().to_cstring_safe()?),
656            None => None,
657        };
658
659        unsafe {
660            let io = self.io_ptr("Context::set_renderer_name()");
661            let ptr = self
662                .renderer_name
663                .as_ref()
664                .map(|s| s.as_ptr())
665                .unwrap_or(ptr::null());
666            (*io).BackendRendererName = ptr;
667        }
668        Ok(())
669    }
670
671    // removed legacy set_renderer_name_or_panic (use set_renderer_name())
672
673    /// Get shared access to the platform IO.
674    ///
675    /// Note: `ImGuiPlatformIO` exists even when multi-viewport is disabled. We expose it
676    /// unconditionally so callers can use ImGui 1.92+ texture management via `PlatformIO.Textures[]`.
677    pub fn platform_io(&self) -> &crate::platform_io::PlatformIo {
678        let _guard = CTX_MUTEX.lock();
679        unsafe {
680            let pio = self.platform_io_ptr("Context::platform_io()");
681            crate::platform_io::PlatformIo::from_raw(pio)
682        }
683    }
684
685    /// Get mutable access to the platform IO.
686    ///
687    /// Note: `ImGuiPlatformIO` exists even when multi-viewport is disabled. We expose it
688    /// unconditionally so callers can use ImGui 1.92+ texture management via `PlatformIO.Textures[]`.
689    pub fn platform_io_mut(&mut self) -> &mut crate::platform_io::PlatformIo {
690        let _guard = CTX_MUTEX.lock();
691        unsafe {
692            let pio = self.platform_io_ptr("Context::platform_io_mut()");
693            crate::platform_io::PlatformIo::from_raw_mut(pio)
694        }
695    }
696
697    /// Returns a reference to the main Dear ImGui viewport.
698    ///
699    /// The returned reference is owned by this ImGui context and
700    /// must not be used after the context is destroyed.
701    #[doc(alias = "GetMainViewport")]
702    pub fn main_viewport(&mut self) -> &crate::platform_io::Viewport {
703        let _guard = CTX_MUTEX.lock();
704        unsafe {
705            with_bound_context(self.raw, || {
706                let ptr = sys::igGetMainViewport();
707                if ptr.is_null() {
708                    panic!("Context::main_viewport() requires a valid ImGui context");
709                }
710                crate::platform_io::Viewport::from_raw(ptr as *const sys::ImGuiViewport)
711            })
712        }
713    }
714
715    /// Enable multi-viewport support flags
716    #[cfg(feature = "multi-viewport")]
717    pub fn enable_multi_viewport(&mut self) {
718        // Enable viewport flags
719        crate::viewport_backend::utils::enable_viewport_flags(self.io_mut());
720    }
721
722    /// Update platform windows
723    ///
724    /// This function should be called every frame when multi-viewport is enabled.
725    /// It updates all platform windows and handles viewport management.
726    #[cfg(feature = "multi-viewport")]
727    pub fn update_platform_windows(&mut self) {
728        let _guard = CTX_MUTEX.lock();
729        unsafe {
730            with_bound_context(self.raw, || {
731                // Ensure main viewport is properly set up before updating platform windows
732                let main_viewport = sys::igGetMainViewport();
733                if !main_viewport.is_null() && (*main_viewport).PlatformHandle.is_null() {
734                    eprintln!(
735                        "update_platform_windows: main viewport not set up, setting it up now"
736                    );
737                    // The main viewport needs to be set up - this should be done by the backend
738                    // For now, we'll just log this and continue
739                }
740
741                sys::igUpdatePlatformWindows();
742            });
743        }
744    }
745
746    /// Render platform windows with default implementation
747    ///
748    /// This function renders all platform windows using the default implementation.
749    /// It calls the platform and renderer backends to render each viewport.
750    #[cfg(feature = "multi-viewport")]
751    pub fn render_platform_windows_default(&mut self) {
752        let _guard = CTX_MUTEX.lock();
753        unsafe {
754            with_bound_context(self.raw, || {
755                sys::igRenderPlatformWindowsDefault(std::ptr::null_mut(), std::ptr::null_mut());
756            });
757        }
758    }
759
760    /// Destroy all platform windows
761    ///
762    /// This function should be called during shutdown to properly clean up
763    /// all platform windows and their associated resources.
764    #[cfg(feature = "multi-viewport")]
765    pub fn destroy_platform_windows(&mut self) {
766        let _guard = CTX_MUTEX.lock();
767        unsafe {
768            with_bound_context(self.raw, || {
769                sys::igDestroyPlatformWindows();
770            });
771        }
772    }
773
774    /// Suspends this context so another context can be the active context
775    pub fn suspend(self) -> SuspendedContext {
776        let _guard = CTX_MUTEX.lock();
777        assert!(
778            self.is_current_context(),
779            "context to be suspended is not the active context"
780        );
781        clear_current_context();
782        SuspendedContext(self)
783    }
784
785    fn is_current_context(&self) -> bool {
786        let ctx = unsafe { sys::igGetCurrentContext() };
787        self.raw == ctx
788    }
789
790    /// Push a font onto the font stack
791    pub fn push_font(&mut self, font: &Font) {
792        let _guard = CTX_MUTEX.lock();
793        unsafe {
794            with_bound_context(self.raw, || {
795                sys::igPushFont(font.raw(), 0.0);
796            });
797        }
798    }
799
800    /// Pop a font from the font stack
801    ///
802    /// This restores the previous font. Must be paired with a call to `push_font()`.
803    #[doc(alias = "PopFont")]
804    pub fn pop_font(&mut self) {
805        let _guard = CTX_MUTEX.lock();
806        unsafe {
807            with_bound_context(self.raw, || {
808                sys::igPopFont();
809            });
810        }
811    }
812
813    /// Get the current font
814    #[doc(alias = "GetFont")]
815    pub fn current_font(&self) -> &Font {
816        let _guard = CTX_MUTEX.lock();
817        unsafe { with_bound_context(self.raw, || Font::from_raw(sys::igGetFont() as *const _)) }
818    }
819
820    /// Get the current font size
821    #[doc(alias = "GetFontSize")]
822    pub fn current_font_size(&self) -> f32 {
823        let _guard = CTX_MUTEX.lock();
824        unsafe { with_bound_context(self.raw, || sys::igGetFontSize()) }
825    }
826
827    /// Get the font atlas from the IO structure
828    pub fn font_atlas(&self) -> FontAtlas {
829        let _guard = CTX_MUTEX.lock();
830
831        // wasm32 import-style builds keep Dear ImGui state in a separate module
832        // and share linear memory. When the experimental font-atlas feature is
833        // enabled, we allow direct access to the atlas pointer, assuming the
834        // provider has been correctly configured via xtask.
835        #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
836        unsafe {
837            let io = self.io_ptr("Context::font_atlas()");
838            let atlas_ptr = (*io).Fonts;
839            assert!(
840                !atlas_ptr.is_null(),
841                "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
842            );
843            FontAtlas::from_raw(atlas_ptr)
844        }
845
846        // Default wasm path: keep this API disabled to avoid accidental UB.
847        #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
848        {
849            panic!(
850                "font_atlas() is not supported on wasm32 targets without \
851                 `wasm-font-atlas-experimental` feature; \
852                 see docs/WASM.md for current limitations."
853            );
854        }
855
856        #[cfg(not(target_arch = "wasm32"))]
857        unsafe {
858            let io = self.io_ptr("Context::font_atlas()");
859            let atlas_ptr = (*io).Fonts;
860            FontAtlas::from_raw(atlas_ptr)
861        }
862    }
863
864    /// Get a mutable reference to the font atlas from the IO structure
865    pub fn font_atlas_mut(&mut self) -> FontAtlas {
866        let _guard = CTX_MUTEX.lock();
867
868        // wasm32 import-style builds keep Dear ImGui state in a separate module
869        // and share linear memory. When the experimental font-atlas feature is
870        // enabled, we allow direct access to the atlas pointer, assuming the
871        // provider has been correctly configured via xtask.
872        #[cfg(all(target_arch = "wasm32", feature = "wasm-font-atlas-experimental"))]
873        unsafe {
874            let io = self.io_ptr("Context::font_atlas_mut()");
875            let atlas_ptr = (*io).Fonts;
876            assert!(
877                !atlas_ptr.is_null(),
878                "ImGui IO Fonts pointer is null on wasm; provider not initialized?"
879            );
880            return FontAtlas::from_raw(atlas_ptr);
881        }
882
883        // Default wasm path: keep this API disabled to avoid accidental UB.
884        #[cfg(all(target_arch = "wasm32", not(feature = "wasm-font-atlas-experimental")))]
885        {
886            panic!(
887                "font_atlas_mut()/fonts() are not supported on wasm32 targets yet; \
888                 enable `wasm-font-atlas-experimental` to opt-in for experiments."
889            );
890        }
891
892        #[cfg(not(target_arch = "wasm32"))]
893        unsafe {
894            let io = self.io_ptr("Context::font_atlas_mut()");
895            let atlas_ptr = (*io).Fonts;
896            FontAtlas::from_raw(atlas_ptr)
897        }
898    }
899
900    /// Returns the font atlas (alias for font_atlas_mut)
901    ///
902    /// This provides compatibility with imgui-rs naming convention
903    pub fn fonts(&mut self) -> FontAtlas {
904        self.font_atlas_mut()
905    }
906
907    /// Attempts to clone the interior shared font atlas **if it exists**.
908    pub fn clone_shared_font_atlas(&mut self) -> Option<SharedFontAtlas> {
909        self.shared_font_atlas.clone()
910    }
911
912    /// Loads settings from a string slice containing settings in .Ini file format
913    #[doc(alias = "LoadIniSettingsFromMemory")]
914    pub fn load_ini_settings(&mut self, data: &str) {
915        let _guard = CTX_MUTEX.lock();
916        unsafe {
917            with_bound_context(self.raw, || {
918                sys::igLoadIniSettingsFromMemory(data.as_ptr() as *const _, data.len());
919            });
920        }
921    }
922
923    /// Saves settings to a mutable string buffer in .Ini file format
924    #[doc(alias = "SaveIniSettingsToMemory")]
925    pub fn save_ini_settings(&mut self, buf: &mut String) {
926        let _guard = CTX_MUTEX.lock();
927        unsafe {
928            with_bound_context(self.raw, || {
929                let mut out_ini_size: usize = 0;
930                let data_ptr = sys::igSaveIniSettingsToMemory(&mut out_ini_size as *mut usize);
931                if data_ptr.is_null() || out_ini_size == 0 {
932                    return;
933                }
934
935                let mut bytes = std::slice::from_raw_parts(data_ptr as *const u8, out_ini_size);
936                if bytes.last() == Some(&0) {
937                    bytes = &bytes[..bytes.len().saturating_sub(1)];
938                }
939                buf.push_str(&String::from_utf8_lossy(bytes));
940            });
941        }
942    }
943
944    /// Loads settings from a `.ini` file on disk.
945    ///
946    /// This is a convenience wrapper over `ImGui::LoadIniSettingsFromDisk`.
947    ///
948    /// Note: this is not available on `wasm32` targets.
949    #[cfg(not(target_arch = "wasm32"))]
950    #[doc(alias = "LoadIniSettingsFromDisk")]
951    pub fn load_ini_settings_from_disk<P: Into<PathBuf>>(
952        &mut self,
953        filename: P,
954    ) -> crate::error::ImGuiResult<()> {
955        use crate::error::SafeStringConversion;
956        let _guard = CTX_MUTEX.lock();
957        let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
958        unsafe {
959            with_bound_context(self.raw, || {
960                sys::igLoadIniSettingsFromDisk(cstr.as_ptr());
961            });
962        }
963        Ok(())
964    }
965
966    /// Saves settings to a `.ini` file on disk.
967    ///
968    /// This is a convenience wrapper over `ImGui::SaveIniSettingsToDisk`.
969    ///
970    /// Note: this is not available on `wasm32` targets.
971    #[cfg(not(target_arch = "wasm32"))]
972    #[doc(alias = "SaveIniSettingsToDisk")]
973    pub fn save_ini_settings_to_disk<P: Into<PathBuf>>(
974        &mut self,
975        filename: P,
976    ) -> crate::error::ImGuiResult<()> {
977        use crate::error::SafeStringConversion;
978        let _guard = CTX_MUTEX.lock();
979        let cstr = filename.into().to_string_lossy().to_cstring_safe()?;
980        unsafe {
981            with_bound_context(self.raw, || {
982                sys::igSaveIniSettingsToDisk(cstr.as_ptr());
983            });
984        }
985        Ok(())
986    }
987
988    /// Returns the current clipboard text, if available.
989    ///
990    /// This calls Dear ImGui's clipboard callbacks (configured via
991    /// [`Context::set_clipboard_backend`]). When no backend is installed, this returns `None`.
992    ///
993    /// Note: returned data is copied into a new `String`.
994    #[doc(alias = "GetClipboardText")]
995    pub fn clipboard_text(&self) -> Option<String> {
996        let _guard = CTX_MUTEX.lock();
997        unsafe {
998            with_bound_context(self.raw, || {
999                let ptr = sys::igGetClipboardText();
1000                if ptr.is_null() {
1001                    return None;
1002                }
1003                Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
1004            })
1005        }
1006    }
1007
1008    /// Sets the clipboard text.
1009    ///
1010    /// This calls Dear ImGui's clipboard callbacks (configured via
1011    /// [`Context::set_clipboard_backend`]). If no backend is installed, this is a no-op.
1012    ///
1013    /// Interior NUL bytes are sanitized to `?` to match other scratch-string helpers.
1014    #[doc(alias = "SetClipboardText")]
1015    pub fn set_clipboard_text(&self, text: impl AsRef<str>) {
1016        let _guard = CTX_MUTEX.lock();
1017        unsafe {
1018            with_bound_context(self.raw, || {
1019                sys::igSetClipboardText(self.ui.scratch_txt(text.as_ref()));
1020            });
1021        }
1022    }
1023
1024    /// Sets the clipboard backend used for clipboard operations
1025    pub fn set_clipboard_backend<T: ClipboardBackend>(&mut self, backend: T) {
1026        let _guard = CTX_MUTEX.lock();
1027
1028        let clipboard_ctx: Box<UnsafeCell<_>> =
1029            Box::new(UnsafeCell::new(ClipboardContext::new(backend)));
1030
1031        // On native/desktop targets, register clipboard callbacks in ImGui PlatformIO
1032        // so ImGui can call back into Rust for copy/paste.
1033        //
1034        // On wasm32 (import-style build), function pointers cannot safely cross the
1035        // module boundary between the Rust main module and the cimgui provider. We
1036        // therefore keep the backend alive on the Rust side but do not hook it into
1037        // ImGui's PlatformIO yet; clipboard integration for web will need a dedicated
1038        // design using JS bindings.
1039        #[cfg(not(target_arch = "wasm32"))]
1040        unsafe {
1041            let platform_io = sys::igGetPlatformIO_ContextPtr(self.raw);
1042            if platform_io.is_null() {
1043                panic!("Context::set_clipboard_backend() requires a valid ImGui context");
1044            }
1045            (*platform_io).Platform_SetClipboardTextFn = Some(crate::clipboard::set_clipboard_text);
1046            (*platform_io).Platform_GetClipboardTextFn = Some(crate::clipboard::get_clipboard_text);
1047            (*platform_io).Platform_ClipboardUserData = clipboard_ctx.get() as *mut _;
1048        }
1049
1050        self.clipboard_ctx = clipboard_ctx;
1051    }
1052}
1053
1054impl Drop for Context {
1055    fn drop(&mut self) {
1056        let _guard = CTX_MUTEX.lock();
1057        unsafe {
1058            if !self.raw.is_null() {
1059                unregister_user_textures_for_context(self.raw);
1060                crate::platform_io::clear_typed_callbacks_for_context(self.raw);
1061                with_bound_context(self.raw, || {
1062                    crate::platform_io::clear_out_param_callbacks_for_current_context();
1063                });
1064                if sys::igGetCurrentContext() == self.raw {
1065                    clear_current_context();
1066                }
1067                sys::igDestroyContext(self.raw);
1068            }
1069        }
1070    }
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075    use super::{Context, with_bound_context};
1076
1077    #[test]
1078    fn platform_io_shared_and_mut_views_match() {
1079        let mut ctx = Context::create();
1080        let shared = ctx.platform_io().as_raw();
1081        let mutable = ctx.platform_io_mut().as_raw();
1082        assert_eq!(shared, mutable);
1083    }
1084
1085    #[test]
1086    fn with_bound_context_restores_previous_context_after_panic() {
1087        let ctx_a = Context::create();
1088        let raw_a = ctx_a.raw;
1089        let suspended_a = ctx_a.suspend();
1090        let ctx_b = Context::create();
1091        let raw_b = ctx_b.raw;
1092
1093        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1094            with_bound_context(raw_a, || panic!("forced panic while context is rebound"));
1095        }));
1096
1097        assert!(result.is_err());
1098        assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, raw_b);
1099
1100        drop(ctx_b);
1101        drop(suspended_a);
1102    }
1103
1104    #[test]
1105    fn io_and_platform_io_accessors_use_self_context_not_current_context() {
1106        let mut ctx_a = Context::create();
1107        let marker_a = std::ptr::NonNull::<u8>::dangling().as_ptr().cast();
1108        ctx_a.io_mut().set_backend_language_user_data(marker_a);
1109        let pio_a = ctx_a.platform_io().as_raw();
1110        let suspended_a = ctx_a.suspend();
1111
1112        let mut ctx_b = Context::create();
1113        let marker_b = std::ptr::NonNull::<u16>::dangling().as_ptr().cast();
1114        ctx_b.io_mut().set_backend_language_user_data(marker_b);
1115        let pio_b = ctx_b.platform_io().as_raw();
1116
1117        assert_ne!(marker_a, marker_b);
1118        assert_ne!(pio_a, pio_b);
1119
1120        let ctx_a = suspended_a.activate().expect_err("ctx_b is still active");
1121        assert_eq!(ctx_a.0.io().backend_language_user_data(), marker_a);
1122        assert_eq!(ctx_a.0.platform_io().as_raw(), pio_a);
1123        assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, ctx_b.raw);
1124
1125        drop(ctx_b);
1126        drop(ctx_a);
1127    }
1128
1129    #[test]
1130    fn style_and_main_viewport_accessors_use_self_context_not_current_context() {
1131        let mut ctx_a = Context::create();
1132        ctx_a.style_mut().set_alpha(0.25);
1133        let viewport_a = ctx_a.main_viewport().as_raw();
1134        let suspended_a = ctx_a.suspend();
1135
1136        let mut ctx_b = Context::create();
1137        ctx_b.style_mut().set_alpha(0.75);
1138        let viewport_b = ctx_b.main_viewport().as_raw();
1139
1140        assert_ne!(viewport_a, viewport_b);
1141
1142        let mut ctx_a = suspended_a.activate().expect_err("ctx_b is still active");
1143        assert_eq!(ctx_a.0.style().alpha(), 0.25);
1144        assert_eq!(ctx_a.0.main_viewport().as_raw(), viewport_a);
1145        assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, ctx_b.raw);
1146
1147        drop(ctx_b);
1148        drop(ctx_a);
1149    }
1150
1151    #[test]
1152    fn io_font_global_scale_uses_owner_context_not_current_context() {
1153        let mut ctx_a = Context::create();
1154        ctx_a.style_mut().set_font_scale_main(1.25);
1155        let suspended_a = ctx_a.suspend();
1156
1157        let mut ctx_b = Context::create();
1158        ctx_b.style_mut().set_font_scale_main(2.0);
1159
1160        let mut ctx_a = suspended_a.activate().expect_err("ctx_b is still active");
1161        assert_eq!(ctx_a.0.io().font_global_scale(), 1.25);
1162
1163        ctx_a.0.io_mut().set_font_global_scale(1.5);
1164
1165        assert_eq!(ctx_a.0.style().font_scale_main(), 1.5);
1166        assert_eq!(ctx_b.style().font_scale_main(), 2.0);
1167        assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, ctx_b.raw);
1168
1169        drop(ctx_b);
1170        drop(ctx_a);
1171    }
1172
1173    #[test]
1174    fn frame_lifecycle_requires_receiver_to_be_current_context() {
1175        let ctx_a = Context::create();
1176        let suspended_a = ctx_a.suspend();
1177        let ctx_b = Context::create();
1178
1179        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1180            let _ = suspended_a.0.draw_data();
1181        }));
1182
1183        assert!(result.is_err());
1184        assert_eq!(unsafe { crate::sys::igGetCurrentContext() }, ctx_b.raw);
1185
1186        drop(ctx_b);
1187        drop(suspended_a);
1188    }
1189
1190    #[cfg(feature = "multi-viewport")]
1191    #[test]
1192    fn platform_io_get_window_pos_and_size_setters_install_handlers() {
1193        unsafe extern "C" fn get_pos(
1194            _viewport: *mut crate::sys::ImGuiViewport,
1195            out_pos: *mut crate::sys::ImVec2,
1196        ) {
1197            if let Some(out_pos) = unsafe { out_pos.as_mut() } {
1198                *out_pos = crate::sys::ImVec2 { x: 10.0, y: 20.0 };
1199            }
1200        }
1201        unsafe extern "C" fn get_size(
1202            _viewport: *mut crate::sys::ImGuiViewport,
1203            out_size: *mut crate::sys::ImVec2,
1204        ) {
1205            if let Some(out_size) = unsafe { out_size.as_mut() } {
1206                *out_size = crate::sys::ImVec2 { x: 30.0, y: 40.0 };
1207            }
1208        }
1209        unsafe extern "C" fn get_scale(
1210            _viewport: *mut crate::sys::ImGuiViewport,
1211            out_scale: *mut crate::sys::ImVec2,
1212        ) {
1213            if let Some(out_scale) = unsafe { out_scale.as_mut() } {
1214                *out_scale = crate::sys::ImVec2 { x: 1.0, y: 2.0 };
1215            }
1216        }
1217        unsafe extern "C" fn get_insets(
1218            _viewport: *mut crate::sys::ImGuiViewport,
1219            out_insets: *mut crate::sys::ImVec4,
1220        ) {
1221            if let Some(out_insets) = unsafe { out_insets.as_mut() } {
1222                *out_insets = crate::sys::ImVec4::new(1.0, 2.0, 3.0, 4.0);
1223            }
1224        }
1225
1226        let mut ctx = Context::create();
1227
1228        {
1229            let pio = ctx.platform_io_mut();
1230            pio.set_platform_get_window_pos_raw(Some(get_pos));
1231            pio.set_platform_get_window_size_raw(Some(get_size));
1232            pio.set_platform_get_window_framebuffer_scale_raw(Some(get_scale));
1233            pio.set_platform_get_window_work_area_insets_raw(Some(get_insets));
1234
1235            let raw = unsafe { &*pio.as_raw() };
1236            assert!(raw.Platform_GetWindowPos.is_some());
1237            assert!(raw.Platform_GetWindowSize.is_some());
1238            assert!(raw.Platform_GetWindowFramebufferScale.is_some());
1239            assert!(raw.Platform_GetWindowWorkAreaInsets.is_some());
1240        }
1241        assert!(
1242            ctx.io().backend_language_user_data().is_null(),
1243            "PlatformIO out-param helpers must not occupy BackendLanguageUserData"
1244        );
1245
1246        let pio = ctx.platform_io_mut();
1247        pio.set_platform_get_window_pos_raw(None);
1248        pio.set_platform_get_window_size_raw(None);
1249        pio.set_platform_get_window_framebuffer_scale_raw(None);
1250        pio.set_platform_get_window_work_area_insets_raw(None);
1251
1252        let raw = unsafe { &*pio.as_raw() };
1253        assert!(raw.Platform_GetWindowPos.is_none());
1254        assert!(raw.Platform_GetWindowSize.is_none());
1255        assert!(raw.Platform_GetWindowFramebufferScale.is_none());
1256        assert!(raw.Platform_GetWindowWorkAreaInsets.is_none());
1257    }
1258
1259    #[test]
1260    fn registered_user_texture_token_survives_context_drop() {
1261        let mut ctx = Context::create();
1262        let mut texture = crate::texture::OwnedTextureData::new();
1263
1264        let token = ctx.register_user_texture_token(&mut texture);
1265        drop(ctx);
1266        drop(token);
1267        drop(texture);
1268    }
1269
1270    #[test]
1271    fn registered_user_texture_token_survives_texture_drop() {
1272        let mut ctx = Context::create();
1273        let token = {
1274            let mut texture = crate::texture::OwnedTextureData::new();
1275            ctx.register_user_texture_token(&mut texture)
1276        };
1277
1278        drop(token);
1279        drop(ctx);
1280    }
1281
1282    #[test]
1283    fn user_texture_registration_is_idempotent_and_unregister_is_noop_when_missing() {
1284        let mut ctx = Context::create();
1285        let mut texture = crate::texture::OwnedTextureData::new();
1286
1287        ctx.register_user_texture(&mut texture);
1288        ctx.register_user_texture(&mut texture);
1289        ctx.unregister_user_texture(&mut texture);
1290        ctx.unregister_user_texture(&mut texture);
1291    }
1292}
1293
1294/// A suspended Dear ImGui context
1295///
1296/// A suspended context retains its state, but is not usable without activating it first.
1297#[derive(Debug)]
1298pub struct SuspendedContext(Context);
1299
1300/// A weak token that indicates whether a `Context` is still alive.
1301#[derive(Clone, Debug)]
1302pub struct ContextAliveToken(Weak<()>);
1303
1304impl ContextAliveToken {
1305    /// Returns true if the originating `Context` has not been dropped.
1306    pub fn is_alive(&self) -> bool {
1307        self.0.upgrade().is_some()
1308    }
1309}
1310
1311impl SuspendedContext {
1312    /// Tries to create a new suspended Dear ImGui context
1313    pub fn try_create() -> crate::error::ImGuiResult<Self> {
1314        Self::try_create_internal(None)
1315    }
1316
1317    /// Tries to create a new suspended Dear ImGui context with a shared font atlas
1318    pub fn try_create_with_shared_font_atlas(
1319        shared_font_atlas: SharedFontAtlas,
1320    ) -> crate::error::ImGuiResult<Self> {
1321        Self::try_create_internal(Some(shared_font_atlas))
1322    }
1323
1324    /// Creates a new suspended Dear ImGui context (panics on error)
1325    pub fn create() -> Self {
1326        Self::try_create().expect("Failed to create Dear ImGui context")
1327    }
1328
1329    /// Creates a new suspended Dear ImGui context with a shared font atlas (panics on error)
1330    pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
1331        Self::try_create_with_shared_font_atlas(shared_font_atlas)
1332            .expect("Failed to create Dear ImGui context")
1333    }
1334
1335    // removed legacy create_or_panic variants (use create()/try_create())
1336
1337    fn try_create_internal(
1338        mut shared_font_atlas: Option<SharedFontAtlas>,
1339    ) -> crate::error::ImGuiResult<Self> {
1340        let _guard = CTX_MUTEX.lock();
1341
1342        let shared_font_atlas_ptr = match &mut shared_font_atlas {
1343            Some(atlas) => atlas.as_ptr_mut(),
1344            None => ptr::null_mut(),
1345        };
1346
1347        let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
1348        if raw.is_null() {
1349            return Err(crate::error::ImGuiError::ContextCreation {
1350                reason: "ImGui_CreateContext returned null".to_string(),
1351            });
1352        }
1353
1354        let ctx = Context {
1355            raw,
1356            alive: Rc::new(()),
1357            shared_font_atlas,
1358            ini_filename: None,
1359            log_filename: None,
1360            platform_name: None,
1361            renderer_name: None,
1362            clipboard_ctx: Box::new(UnsafeCell::new(ClipboardContext::dummy())),
1363            ui: crate::ui::Ui::new(),
1364        };
1365
1366        // If the context was activated during creation, deactivate it
1367        if ctx.is_current_context() {
1368            clear_current_context();
1369        }
1370
1371        Ok(SuspendedContext(ctx))
1372    }
1373
1374    /// Attempts to activate this suspended context
1375    ///
1376    /// If there is no active context, this suspended context is activated and `Ok` is returned.
1377    /// If there is already an active context, nothing happens and `Err` is returned.
1378    pub fn activate(self) -> Result<Context, SuspendedContext> {
1379        let _guard = CTX_MUTEX.lock();
1380        if no_current_context() {
1381            unsafe {
1382                sys::igSetCurrentContext(self.0.raw);
1383            }
1384            Ok(self.0)
1385        } else {
1386            Err(self)
1387        }
1388    }
1389}
1390
1391/// RAII token returned by `Context::register_user_texture_token()`.
1392///
1393/// On drop, this unregisters the corresponding `ImTextureData*` from ImGui's internal user texture
1394/// list.
1395#[derive(Debug)]
1396pub struct RegisteredUserTexture {
1397    ctx: *mut sys::ImGuiContext,
1398    tex: *mut sys::ImTextureData,
1399    alive: Weak<()>,
1400}
1401
1402impl Drop for RegisteredUserTexture {
1403    fn drop(&mut self) {
1404        if self.ctx.is_null() || self.tex.is_null() || self.alive.upgrade().is_none() {
1405            return;
1406        }
1407
1408        let _guard = CTX_MUTEX.lock();
1409        if let Some(registration) = take_user_texture_registration(self.ctx, self.tex) {
1410            unregister_user_texture_registration(registration);
1411        }
1412    }
1413}
1414
1415// Dear ImGui is not thread-safe. The Context must not be sent or shared across
1416// threads. If you need multi-threaded rendering, capture render data via
1417// OwnedDrawData and move that to another thread for rendering.