Skip to main content

dear_imgui_rs/context/
suspended.rs

1use std::panic::{self, AssertUnwindSafe};
2use std::ptr;
3
4use crate::clipboard::ClipboardContext;
5use crate::fonts::SharedFontAtlas;
6use crate::sys;
7
8use super::Context;
9use super::attachment::AttachmentRegistry;
10use super::binding::{
11    CTX_MUTEX, ContextBinding, ContextId, ContextState, clear_current_context, no_current_context,
12    set_current_context,
13};
14use super::frame::FrameLifecycleState;
15use super::snapshot_hub::SnapshotHub;
16use super::texture_registry::ManagedTextureRegistry;
17
18impl Context {
19    /// Suspends this context so another context can be the active context
20    pub fn suspend(self) -> SuspendedContext {
21        let _guard = CTX_MUTEX.lock();
22        assert!(
23            self.is_current_context(),
24            "context to be suspended is not the active context"
25        );
26        assert_ne!(
27            self.frame_lifecycle_state_unlocked(),
28            FrameLifecycleState::InFrame,
29            "cannot suspend a context while a Dear ImGui frame is open"
30        );
31        clear_current_context();
32        SuspendedContext(self)
33    }
34}
35
36/// A suspended Dear ImGui context
37///
38/// A suspended context retains its state, but is not usable without activating it first.
39#[derive(Debug)]
40pub struct SuspendedContext(pub(super) Context);
41
42impl SuspendedContext {
43    /// Returns the process-unique identity of this Context.
44    pub fn id(&self) -> ContextId {
45        self.0.id()
46    }
47
48    /// Runs a closure while this suspended Context is active.
49    ///
50    /// Any previously current Context is restored before this method returns. An open frame left
51    /// behind when the closure returns `Err` or panics is ended before propagating that outcome.
52    ///
53    /// # Panics
54    ///
55    /// Resumes any panic raised by the closure with its original payload. This method also panics
56    /// after ending the frame if the closure returns `Ok` while a Dear ImGui frame is still open.
57    pub fn try_with_active<T, E>(
58        &mut self,
59        f: impl FnOnce(&mut Context) -> Result<T, E>,
60    ) -> Result<T, E> {
61        let binding = self.0.binding();
62        binding.with_bound_context(|| {
63            let result = panic::catch_unwind(AssertUnwindSafe(|| f(&mut self.0)));
64
65            match result {
66                Ok(Ok(value)) => {
67                    if self.0.end_frame_for_teardown_unlocked() {
68                        panic!(
69                            "SuspendedContext::try_with_active(): closure returned Ok while a Dear ImGui frame was still open"
70                        );
71                    }
72                    Ok(value)
73                }
74                Ok(Err(error)) => {
75                    self.0.end_frame_for_teardown_unlocked();
76                    Err(error)
77                }
78                Err(payload) => {
79                    // Cleanup must not replace the closure's panic payload.
80                    let _ = panic::catch_unwind(AssertUnwindSafe(|| {
81                        self.0.end_frame_for_teardown_unlocked();
82                    }));
83                    panic::resume_unwind(payload)
84                }
85            }
86        })
87    }
88
89    /// Tries to create a new suspended Dear ImGui context
90    pub fn try_create() -> crate::error::ImGuiResult<Self> {
91        Self::try_create_internal(None)
92    }
93
94    /// Tries to create a new suspended Dear ImGui context with a shared font atlas.
95    ///
96    /// Multiple contexts may share the atlas while using legacy renderer-managed texture handling.
97    /// Once a managed renderer claims the atlas, registering another context returns
98    /// [`ImGuiError::SharedFontAtlasManaged`](crate::ImGuiError::SharedFontAtlasManaged).
99    /// If its prior managed Context was dropped without a committed renderer reset, this returns
100    /// [`ImGuiError::SharedFontAtlasRendererReleasePending`](crate::ImGuiError::SharedFontAtlasRendererReleasePending).
101    pub fn try_create_with_shared_font_atlas(
102        shared_font_atlas: SharedFontAtlas,
103    ) -> crate::error::ImGuiResult<Self> {
104        Self::try_create_internal(Some(shared_font_atlas))
105    }
106
107    /// Creates a new suspended Dear ImGui context (panics on error)
108    pub fn create() -> Self {
109        Self::try_create().expect("Failed to create Dear ImGui context")
110    }
111
112    /// Creates a new suspended Dear ImGui context with a shared font atlas (panics on error).
113    ///
114    /// This panics if a managed renderer has already claimed the atlas. Use
115    /// [`SuspendedContext::try_create_with_shared_font_atlas`] to handle ownership and
116    /// pending-release errors.
117    pub fn create_with_shared_font_atlas(shared_font_atlas: SharedFontAtlas) -> Self {
118        Self::try_create_with_shared_font_atlas(shared_font_atlas)
119            .expect("Failed to create Dear ImGui context")
120    }
121
122    // removed legacy create_or_panic variants (use create()/try_create())
123
124    fn try_create_internal(
125        shared_font_atlas: Option<SharedFontAtlas>,
126    ) -> crate::error::ImGuiResult<Self> {
127        let _guard = CTX_MUTEX.lock();
128        let previous_context = unsafe { sys::igGetCurrentContext() };
129
130        let shared_font_atlas_ptr = match &shared_font_atlas {
131            Some(atlas) => atlas.as_ptr(),
132            None => ptr::null_mut(),
133        };
134        crate::fonts::validate_font_atlas_context_registration(shared_font_atlas_ptr)?;
135
136        let id =
137            ContextId::allocate().ok_or_else(|| crate::error::ImGuiError::ContextCreation {
138                reason: "process Context identity space is exhausted".to_string(),
139            })?;
140
141        let raw = unsafe { sys::igCreateContext(shared_font_atlas_ptr) };
142        if raw.is_null() {
143            set_current_context(previous_context);
144            return Err(crate::error::ImGuiError::ContextCreation {
145                reason: "ImGui_CreateContext returned null".to_string(),
146            });
147        }
148
149        unsafe {
150            let io = sys::igGetIO_ContextPtr(raw);
151            assert!(
152                !io.is_null(),
153                "new ImGui context returned a null IO pointer"
154            );
155            crate::fonts::register_font_atlas_context((*io).Fonts, raw);
156        }
157
158        let state = ContextState::new(id, raw);
159        let texture_registry = ManagedTextureRegistry::new(id);
160        let ui = crate::ui::Ui::new(raw, ContextBinding::new(&state), texture_registry.clone());
161
162        let ctx = Context {
163            raw,
164            state,
165            attachments: AttachmentRegistry::default(),
166            snapshot_hub: SnapshotHub::new(id),
167            texture_registry,
168            shared_font_atlas,
169            ini_filename: None,
170            log_filename: None,
171            platform_name: None,
172            renderer_name: None,
173            clipboard_ctx: Box::new(ClipboardContext::dummy()),
174            ui,
175        };
176
177        if previous_context.is_null() {
178            clear_current_context();
179        } else {
180            set_current_context(previous_context);
181        }
182
183        Ok(SuspendedContext(ctx))
184    }
185
186    /// Attempts to activate this suspended context
187    ///
188    /// If there is no active context, this suspended context is activated and `Ok` is returned.
189    /// If there is already an active context, nothing happens and `Err` is returned.
190    pub fn activate(self) -> Result<Context, SuspendedContext> {
191        let _guard = CTX_MUTEX.lock();
192        if no_current_context() {
193            set_current_context(self.0.raw);
194            Ok(self.0)
195        } else {
196            Err(self)
197        }
198    }
199}