1use crate::ui::{Plot3DContextBinding, Plot3DUi};
2use crate::{imgui_sys, sys};
3use dear_imgui_rs::{Context, Ui};
4
5pub 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 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 pub fn create(imgui: &Context) -> Self {
80 Self::try_create(imgui).expect("Failed to create ImPlot3D context")
81 }
82
83 pub unsafe fn raw(&self) -> *mut sys::ImPlot3DContext {
95 self.raw
96 }
97
98 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 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}