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