Skip to main content

dear_implot3d/
context.rs

1use crate::ui::{Plot3DContextBinding, Plot3DUi};
2use crate::{imgui_sys, sys};
3use dear_imgui_rs::{Context, Ui};
4
5/// Plot3D context wrapper
6///
7/// This manages the ImPlot3D context lifetime. Create one instance per application
8/// and keep it alive for the duration of your program.
9///
10/// # Example
11///
12/// ```no_run
13/// use dear_imgui_rs::*;
14/// use dear_implot3d::*;
15///
16/// let mut imgui_ctx = Context::create();
17/// let plot3d_ctx = Plot3DContext::create(&imgui_ctx);
18///
19/// // In your main loop:
20/// let ui = imgui_ctx.frame();
21/// let plot_ui = plot3d_ctx.get_plot_ui(&ui);
22/// ```
23pub struct Plot3DContext {
24    pub(crate) raw: *mut sys::ImPlot3DContext,
25    pub(crate) imgui_ctx_raw: *mut imgui_sys::ImGuiContext,
26    pub(crate) imgui_alive: Option<dear_imgui_rs::ContextAliveToken>,
27    pub(crate) owns_context: bool,
28}
29
30impl Plot3DContext {
31    pub(crate) fn binding(&self) -> Plot3DContextBinding {
32        Plot3DContextBinding {
33            plot_ctx_raw: self.raw,
34            imgui_ctx_raw: self.imgui_ctx_raw,
35        }
36    }
37
38    pub(crate) fn assert_imgui_alive(&self, caller: &str) {
39        if let Some(alive) = &self.imgui_alive {
40            assert!(alive.is_alive(), "{caller}: ImGui context has been dropped");
41        }
42    }
43
44    /// Try to create a new ImPlot3D context.
45    ///
46    /// This should be called once after creating your ImGui context.
47    pub fn try_create(imgui: &Context) -> dear_imgui_rs::ImGuiResult<Self> {
48        let imgui_ctx_raw = imgui.as_raw();
49        let imgui_alive = Some(imgui.alive_token());
50        let prev_imgui = unsafe { imgui_sys::igGetCurrentContext() };
51        let prev_plot = unsafe { sys::ImPlot3D_GetCurrentContext() };
52        unsafe {
53            if prev_imgui != imgui_ctx_raw {
54                imgui_sys::igSetCurrentContext(imgui_ctx_raw);
55            }
56            let ctx = sys::ImPlot3D_CreateContext();
57            if sys::ImPlot3D_GetCurrentContext() != prev_plot {
58                sys::ImPlot3D_SetCurrentContext(prev_plot);
59            }
60            if prev_imgui != imgui_ctx_raw {
61                imgui_sys::igSetCurrentContext(prev_imgui);
62            }
63            if ctx.is_null() {
64                return Err(dear_imgui_rs::ImGuiError::context_creation(
65                    "ImPlot3D_CreateContext returned null",
66                ));
67            }
68
69            Ok(Self {
70                raw: ctx,
71                imgui_ctx_raw,
72                imgui_alive,
73                owns_context: true,
74            })
75        }
76    }
77
78    /// Create a new ImPlot3D context (panics on error).
79    pub fn create(imgui: &Context) -> Self {
80        Self::try_create(imgui).expect("Failed to create ImPlot3D context")
81    }
82
83    /// Set this context as the current ImPlot3D context.
84    /// Get a raw pointer to the current ImPlot3D style
85    ///
86    /// This is an advanced function for direct style manipulation.
87    /// Prefer using the safe style functions in the `style` module.
88    /// Get the raw ImPlot3D context pointer.
89    ///
90    /// # Safety
91    ///
92    /// The caller must ensure the pointer is used safely and not stored beyond the lifetime of
93    /// this context wrapper.
94    pub unsafe fn raw(&self) -> *mut sys::ImPlot3DContext {
95        self.raw
96    }
97
98    /// Get a per-frame plotting interface
99    ///
100    /// Call this once per frame to get access to plotting functions.
101    /// The returned `Plot3DUi` is tied to the lifetime of the `Ui` frame.
102    pub fn get_plot_ui<'ui>(&self, ui: &'ui Ui) -> Plot3DUi<'ui> {
103        let ui_ctx_raw = ui.with_bound_context(|| unsafe { imgui_sys::igGetCurrentContext() });
104        assert_eq!(
105            ui_ctx_raw, self.imgui_ctx_raw,
106            "dear-implot3d: Plot3DContext::get_plot_ui() requires a Ui from the owning ImGui context"
107        );
108        Plot3DUi {
109            _ui: ui,
110            binding: self.binding(),
111            imgui_alive: self.imgui_alive.clone(),
112        }
113    }
114}
115
116impl Drop for Plot3DContext {
117    fn drop(&mut self) {
118        if !self.owns_context || self.raw.is_null() {
119            return;
120        }
121
122        if let Some(alive) = &self.imgui_alive {
123            if !alive.is_alive() {
124                // Avoid calling into ImGui allocators after the context has been dropped.
125                // Best-effort: leak the Plot3D context instead of risking UB.
126                return;
127            }
128        }
129
130        unsafe {
131            let prev_imgui = imgui_sys::igGetCurrentContext();
132            let prev_plot = sys::ImPlot3D_GetCurrentContext();
133            let restore_plot = if prev_plot == self.raw {
134                std::ptr::null_mut()
135            } else {
136                prev_plot
137            };
138            imgui_sys::igSetCurrentContext(self.imgui_ctx_raw);
139            sys::ImPlot3D_DestroyContext(self.raw);
140            sys::ImPlot3D_SetCurrentContext(restore_plot);
141            imgui_sys::igSetCurrentContext(prev_imgui);
142        }
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::Plot3DContext;
149    use crate::ui::Plot3DContextBinding;
150    use crate::{Context, sys};
151    use std::mem::{align_of, size_of};
152    use std::sync::{Mutex, OnceLock};
153
154    fn test_guard() -> std::sync::MutexGuard<'static, ()> {
155        static GUARD: OnceLock<Mutex<()>> = OnceLock::new();
156        GUARD
157            .get_or_init(|| Mutex::new(()))
158            .lock()
159            .unwrap_or_else(|err| err.into_inner())
160    }
161
162    #[test]
163    fn ffi_layout_implot3d_point_is_3_f64() {
164        assert_eq!(size_of::<sys::ImPlot3DPoint>(), 3 * size_of::<f64>());
165        assert_eq!(align_of::<sys::ImPlot3DPoint>(), align_of::<f64>());
166    }
167
168    #[test]
169    fn plot3d_ui_binds_own_context() {
170        let _guard = test_guard();
171        let imgui = Context::create();
172        let plot_a = Plot3DContext::create(&imgui);
173        let raw_a = plot_a.raw;
174        let plot_b = Plot3DContext::create(&imgui);
175        let raw_b = plot_b.raw;
176
177        unsafe { sys::ImPlot3D_SetCurrentContext(raw_b) };
178
179        {
180            let _guard = Plot3DContextBinding {
181                plot_ctx_raw: plot_a.raw,
182                imgui_ctx_raw: plot_a.imgui_ctx_raw,
183            }
184            .bind();
185
186            assert_eq!(unsafe { sys::ImPlot3D_GetCurrentContext() }, raw_a);
187        }
188
189        assert_eq!(unsafe { sys::ImPlot3D_GetCurrentContext() }, raw_b);
190
191        drop(plot_b);
192        drop(plot_a);
193    }
194
195    #[test]
196    fn plot3d_tokens_bind_own_context_before_drop() {
197        let _guard = test_guard();
198        let mut imgui = Context::create();
199        {
200            use dear_imgui_rs::BackendFlags;
201            let io = imgui.io_mut();
202            io.set_display_size([800.0, 600.0]);
203            io.set_delta_time(1.0 / 60.0);
204            io.set_backend_flags(io.backend_flags() | BackendFlags::RENDERER_HAS_TEXTURES);
205        }
206        let plot_a = Plot3DContext::create(&imgui);
207        let plot_b = Plot3DContext::create(&imgui);
208        let raw_b = plot_b.raw;
209
210        {
211            let ui = imgui.frame();
212            let plot_ui = plot_a.get_plot_ui(&ui);
213            let style = plot_ui.push_style_var_f32(crate::Plot3DStyleVar::FillAlpha, 0.5);
214            unsafe { sys::ImPlot3D_SetCurrentContext(raw_b) };
215            drop(style);
216            assert_eq!(unsafe { sys::ImPlot3D_GetCurrentContext() }, raw_b);
217
218            let token = plot_ui
219                .begin_plot("token")
220                .build()
221                .expect("failed to begin 3D plot");
222            unsafe { sys::ImPlot3D_SetCurrentContext(raw_b) };
223            drop(token);
224            assert_eq!(unsafe { sys::ImPlot3D_GetCurrentContext() }, raw_b);
225        }
226        let _ = imgui.render();
227
228        drop(plot_b);
229        drop(plot_a);
230    }
231
232    #[test]
233    fn dropping_current_plot3d_context_clears_current_context() {
234        let _guard = test_guard();
235        let imgui = Context::create();
236        let plot = Plot3DContext::create(&imgui);
237        let raw = plot.raw;
238
239        unsafe { sys::ImPlot3D_SetCurrentContext(raw) };
240        drop(plot);
241
242        assert!(unsafe { sys::ImPlot3D_GetCurrentContext() }.is_null());
243    }
244
245    #[test]
246    fn dropping_non_current_plot3d_context_restores_previous_context() {
247        let _guard = test_guard();
248        let imgui = Context::create();
249        let plot_a = Plot3DContext::create(&imgui);
250        let plot_b = Plot3DContext::create(&imgui);
251        let raw_b = plot_b.raw;
252
253        unsafe { sys::ImPlot3D_SetCurrentContext(raw_b) };
254        drop(plot_a);
255
256        assert_eq!(unsafe { sys::ImPlot3D_GetCurrentContext() }, raw_b);
257        drop(plot_b);
258    }
259
260    #[test]
261    fn plot3d_ui_binds_owner_context() {
262        let _guard = test_guard();
263        let imgui_a = Context::create();
264        let plot_a = Plot3DContext::create(&imgui_a);
265        let suspended_a = imgui_a.suspend();
266        let imgui_b = Context::create();
267
268        assert_eq!(
269            unsafe { dear_imgui_rs::sys::igGetCurrentContext() },
270            imgui_b.as_raw()
271        );
272        {
273            let _guard = Plot3DContextBinding {
274                plot_ctx_raw: plot_a.raw,
275                imgui_ctx_raw: plot_a.imgui_ctx_raw,
276            }
277            .bind();
278            assert_eq!(
279                unsafe { dear_imgui_rs::sys::igGetCurrentContext() },
280                plot_a.imgui_ctx_raw
281            );
282        }
283        assert_eq!(
284            unsafe { dear_imgui_rs::sys::igGetCurrentContext() },
285            imgui_b.as_raw()
286        );
287        drop(suspended_a);
288        drop(plot_a);
289        drop(imgui_b);
290    }
291}