Skip to main content

dear_implot3d/
context.rs

1use crate::sys;
2use crate::ui::{Plot3DContextBinding, Plot3DUi};
3use dear_imgui_rs::{Context, ContextBinding, 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_binding: ContextBinding,
26    pub(crate) owns_context: bool,
27}
28
29impl Plot3DContext {
30    pub(crate) fn binding(&self) -> Plot3DContextBinding {
31        Plot3DContextBinding {
32            plot_ctx_raw: self.raw,
33            imgui_binding: self.imgui_binding.clone(),
34        }
35    }
36
37    /// Try to create a new ImPlot3D context.
38    ///
39    /// This should be called once after creating your ImGui context.
40    pub fn try_create(imgui: &Context) -> dear_imgui_rs::ImGuiResult<Self> {
41        let imgui_binding = imgui.binding();
42        let ctx = imgui_binding.with_bound_context(|| unsafe {
43            let prev_plot = sys::ImPlot3D_GetCurrentContext();
44            let ctx = sys::ImPlot3D_CreateContext();
45            if sys::ImPlot3D_GetCurrentContext() != prev_plot {
46                sys::ImPlot3D_SetCurrentContext(prev_plot);
47            }
48            ctx
49        });
50        if ctx.is_null() {
51            return Err(dear_imgui_rs::ImGuiError::context_creation(
52                "ImPlot3D_CreateContext returned null",
53            ));
54        }
55
56        Ok(Self {
57            raw: ctx,
58            imgui_binding,
59            owns_context: true,
60        })
61    }
62
63    /// Create a new ImPlot3D context (panics on error).
64    pub fn create(imgui: &Context) -> Self {
65        Self::try_create(imgui).expect("Failed to create ImPlot3D context")
66    }
67
68    /// Set this context as the current ImPlot3D context.
69    /// Get a raw pointer to the current ImPlot3D style
70    ///
71    /// This is an advanced function for direct style manipulation.
72    /// Prefer using the safe style functions in the `style` module.
73    /// Get the raw ImPlot3D context pointer.
74    ///
75    /// # Safety
76    ///
77    /// The caller must ensure the pointer is used safely and not stored beyond the lifetime of
78    /// this context wrapper.
79    pub unsafe fn raw(&self) -> *mut sys::ImPlot3DContext {
80        self.raw
81    }
82
83    /// Get a per-frame plotting interface
84    ///
85    /// Call this once per frame to get access to plotting functions.
86    /// The returned `Plot3DUi` is tied to the lifetime of the `Ui` frame.
87    pub fn get_plot_ui<'ui>(&self, ui: &'ui Ui) -> Plot3DUi<'ui> {
88        assert_eq!(
89            ui.context_id(),
90            self.imgui_binding.id(),
91            "dear-implot3d: Plot3DContext::get_plot_ui() requires a Ui from the owning ImGui context"
92        );
93        Plot3DUi {
94            _ui: ui,
95            binding: self.binding(),
96        }
97    }
98}
99
100impl Drop for Plot3DContext {
101    fn drop(&mut self) {
102        if !self.owns_context || self.raw.is_null() {
103            return;
104        }
105
106        let _ = self.imgui_binding.try_with_bound_context(|| unsafe {
107            let prev_plot = sys::ImPlot3D_GetCurrentContext();
108            let restore_plot = if prev_plot == self.raw {
109                std::ptr::null_mut()
110            } else {
111                prev_plot
112            };
113            sys::ImPlot3D_DestroyContext(self.raw);
114            sys::ImPlot3D_SetCurrentContext(restore_plot);
115        });
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::Plot3DContext;
122    use crate::{Context, sys};
123    use std::mem::{align_of, size_of};
124    use std::sync::{Mutex, OnceLock};
125
126    fn test_guard() -> std::sync::MutexGuard<'static, ()> {
127        static GUARD: OnceLock<Mutex<()>> = OnceLock::new();
128        GUARD
129            .get_or_init(|| Mutex::new(()))
130            .lock()
131            .unwrap_or_else(|err| err.into_inner())
132    }
133
134    #[test]
135    fn ffi_layout_implot3d_point_is_3_f64() {
136        assert_eq!(size_of::<sys::ImPlot3DPoint>(), 3 * size_of::<f64>());
137        assert_eq!(align_of::<sys::ImPlot3DPoint>(), align_of::<f64>());
138    }
139
140    #[test]
141    fn plot3d_ui_binds_own_context() {
142        let _guard = test_guard();
143        let imgui = Context::create();
144        let plot_a = Plot3DContext::create(&imgui);
145        let raw_a = plot_a.raw;
146        let plot_b = Plot3DContext::create(&imgui);
147        let raw_b = plot_b.raw;
148
149        unsafe { sys::ImPlot3D_SetCurrentContext(raw_b) };
150
151        plot_a.binding().with_bound_context(|| {
152            assert_eq!(unsafe { sys::ImPlot3D_GetCurrentContext() }, raw_a);
153        });
154
155        assert_eq!(unsafe { sys::ImPlot3D_GetCurrentContext() }, raw_b);
156
157        drop(plot_b);
158        drop(plot_a);
159    }
160
161    #[test]
162    fn plot3d_tokens_bind_own_context_before_drop() {
163        let _guard = test_guard();
164        let mut imgui = Context::create();
165        {
166            let io = imgui.io_mut();
167            io.set_display_size([800.0, 600.0]);
168            io.set_delta_time(1.0 / 60.0);
169        }
170        imgui
171            .font_atlas()
172            .try_claim_legacy_renderer()
173            .expect("headless test requires the legacy font-atlas capability")
174            .build();
175        let plot_a = Plot3DContext::create(&imgui);
176        let plot_b = Plot3DContext::create(&imgui);
177        let raw_b = plot_b.raw;
178
179        {
180            let frame = imgui.begin_frame();
181            let plot_ui = plot_a.get_plot_ui(frame.ui());
182            let style = plot_ui.push_style_var_f32(crate::Plot3DStyleVar::FillAlpha, 0.5);
183            unsafe { sys::ImPlot3D_SetCurrentContext(raw_b) };
184            drop(style);
185            assert_eq!(unsafe { sys::ImPlot3D_GetCurrentContext() }, raw_b);
186
187            let token = plot_ui
188                .begin_plot("token")
189                .build()
190                .expect("failed to begin 3D plot");
191            unsafe { sys::ImPlot3D_SetCurrentContext(raw_b) };
192            drop(token);
193            assert_eq!(unsafe { sys::ImPlot3D_GetCurrentContext() }, raw_b);
194        }
195
196        drop(plot_b);
197        drop(plot_a);
198    }
199
200    #[test]
201    fn dropping_current_plot3d_context_clears_current_context() {
202        let _guard = test_guard();
203        let imgui = Context::create();
204        let plot = Plot3DContext::create(&imgui);
205        let raw = plot.raw;
206
207        unsafe { sys::ImPlot3D_SetCurrentContext(raw) };
208        drop(plot);
209
210        assert!(unsafe { sys::ImPlot3D_GetCurrentContext() }.is_null());
211    }
212
213    #[test]
214    fn dropping_non_current_plot3d_context_restores_previous_context() {
215        let _guard = test_guard();
216        let imgui = Context::create();
217        let plot_a = Plot3DContext::create(&imgui);
218        let plot_b = Plot3DContext::create(&imgui);
219        let raw_b = plot_b.raw;
220
221        unsafe { sys::ImPlot3D_SetCurrentContext(raw_b) };
222        drop(plot_a);
223
224        assert_eq!(unsafe { sys::ImPlot3D_GetCurrentContext() }, raw_b);
225        drop(plot_b);
226    }
227
228    #[test]
229    fn plot3d_ui_binds_owner_context() {
230        let _guard = test_guard();
231        let imgui_a = Context::create();
232        let plot_a = Plot3DContext::create(&imgui_a);
233        let imgui_a_raw = imgui_a.as_raw();
234        let suspended_a = imgui_a.suspend_or_panic();
235        let imgui_b = Context::create();
236
237        assert_eq!(
238            unsafe { dear_imgui_rs::sys::igGetCurrentContext() },
239            imgui_b.as_raw()
240        );
241        plot_a.binding().with_bound_context(|| {
242            assert_eq!(
243                unsafe { dear_imgui_rs::sys::igGetCurrentContext() },
244                imgui_a_raw
245            );
246        });
247        assert_eq!(
248            unsafe { dear_imgui_rs::sys::igGetCurrentContext() },
249            imgui_b.as_raw()
250        );
251        drop(suspended_a);
252        drop(plot_a);
253        drop(imgui_b);
254    }
255}