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 fn try_create(imgui: &Context) -> dear_imgui_rs::ImGuiResult<Self> {
35 let imgui_ctx_raw = imgui.as_raw();
36 let imgui_alive = Some(imgui.alive_token());
37 assert_eq!(
38 unsafe { imgui_sys::igGetCurrentContext() },
39 imgui_ctx_raw,
40 "dear-implot3d: Plot3DContext must be created with the currently-active ImGui context"
41 );
42 unsafe {
43 let ctx = sys::ImPlot3D_CreateContext();
44 if ctx.is_null() {
45 return Err(dear_imgui_rs::ImGuiError::context_creation(
46 "ImPlot3D_CreateContext returned null",
47 ));
48 }
49
50 sys::ImPlot3D_SetCurrentContext(ctx);
52 Ok(Self {
53 raw: ctx,
54 imgui_ctx_raw,
55 imgui_alive,
56 owns_context: true,
57 })
58 }
59 }
60
61 pub fn create(imgui: &Context) -> Self {
63 Self::try_create(imgui).expect("Failed to create ImPlot3D context")
64 }
65
66 pub unsafe fn current() -> Option<Self> {
76 let raw = unsafe { sys::ImPlot3D_GetCurrentContext() };
77 if raw.is_null() {
78 None
79 } else {
80 Some(Self {
81 raw,
82 imgui_ctx_raw: unsafe { imgui_sys::igGetCurrentContext() },
83 imgui_alive: None,
84 owns_context: false,
85 })
86 }
87 }
88
89 pub fn set_as_current(&self) {
91 self.assert_imgui_alive();
92 self.binding().bind();
93 }
94
95 pub fn raw_style_mut() -> *mut sys::ImPlot3DStyle {
100 unsafe { sys::ImPlot3D_GetStyle() }
101 }
102
103 pub unsafe fn raw(&self) -> *mut sys::ImPlot3DContext {
110 self.raw
111 }
112
113 pub fn get_plot_ui<'ui>(&self, ui: &'ui Ui) -> Plot3DUi<'ui> {
118 self.set_as_current();
119 Plot3DUi {
120 _ui: ui,
121 binding: Plot3DContextBinding {
122 plot_ctx_raw: self.raw,
123 imgui_ctx_raw: self.imgui_ctx_raw,
124 },
125 imgui_alive: self.imgui_alive.clone(),
126 }
127 }
128
129 fn assert_imgui_alive(&self) {
130 if let Some(alive) = &self.imgui_alive {
131 assert!(
132 alive.is_alive(),
133 "dear-implot3d: ImGui context has been dropped"
134 );
135 }
136 }
137
138 fn binding(&self) -> Plot3DContextBinding {
139 Plot3DContextBinding {
140 plot_ctx_raw: self.raw,
141 imgui_ctx_raw: self.imgui_ctx_raw,
142 }
143 }
144}
145
146impl Drop for Plot3DContext {
147 fn drop(&mut self) {
148 if !self.owns_context || self.raw.is_null() {
149 return;
150 }
151
152 if let Some(alive) = &self.imgui_alive {
153 if !alive.is_alive() {
154 return;
157 }
158 }
159
160 unsafe {
161 let prev_imgui = imgui_sys::igGetCurrentContext();
162 imgui_sys::igSetCurrentContext(self.imgui_ctx_raw);
163
164 if sys::ImPlot3D_GetCurrentContext() == self.raw {
165 sys::ImPlot3D_SetCurrentContext(std::ptr::null_mut());
166 }
167 sys::ImPlot3D_DestroyContext(self.raw);
168
169 imgui_sys::igSetCurrentContext(prev_imgui);
170 }
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::Plot3DContext;
177 use crate::ui::Plot3DContextBinding;
178 use crate::{Context, sys};
179 use std::mem::{align_of, size_of};
180 use std::ptr;
181 use std::sync::{Mutex, OnceLock};
182
183 fn test_guard() -> std::sync::MutexGuard<'static, ()> {
184 static GUARD: OnceLock<Mutex<()>> = OnceLock::new();
185 GUARD.get_or_init(|| Mutex::new(())).lock().unwrap()
186 }
187
188 #[test]
189 fn ffi_layout_implot3d_point_is_3_f64() {
190 assert_eq!(size_of::<sys::ImPlot3DPoint>(), 3 * size_of::<f64>());
191 assert_eq!(align_of::<sys::ImPlot3DPoint>(), align_of::<f64>());
192 }
193
194 #[test]
195 fn plot3d_ui_binds_own_context() {
196 let _guard = test_guard();
197 let imgui = Context::create();
198 let plot_a = Plot3DContext::create(&imgui);
199 let raw_a = plot_a.raw;
200 let plot_b = Plot3DContext::create(&imgui);
201 let raw_b = plot_b.raw;
202
203 unsafe { sys::ImPlot3D_SetCurrentContext(raw_b) };
204
205 Plot3DContextBinding {
206 plot_ctx_raw: plot_a.raw,
207 imgui_ctx_raw: plot_a.imgui_ctx_raw,
208 }
209 .bind();
210
211 assert_eq!(unsafe { sys::ImPlot3D_GetCurrentContext() }, raw_a);
212 }
213
214 #[test]
215 fn plot3d_current_returns_none_without_current_context() {
216 let _guard = test_guard();
217 let _imgui = Context::create();
218
219 unsafe { sys::ImPlot3D_SetCurrentContext(ptr::null_mut()) };
220 assert!(unsafe { Plot3DContext::current() }.is_none());
221 }
222
223 #[test]
224 fn plot3d_current_wrapper_reports_current_context() {
225 let _guard = test_guard();
226 let imgui = Context::create();
227 let plot = Plot3DContext::create(&imgui);
228 let raw = plot.raw;
229
230 let current =
231 unsafe { Plot3DContext::current() }.expect("expected current ImPlot3D context");
232
233 assert_eq!(unsafe { current.raw() }, raw);
234 }
235
236 #[test]
237 fn plot3d_ui_rejects_wrong_imgui_context() {
238 let _guard = test_guard();
239 let imgui_a = Context::create();
240 let plot_a = Plot3DContext::create(&imgui_a);
241 let suspended_a = imgui_a.suspend();
242 let imgui_b = Context::create();
243
244 let previous = unsafe { dear_imgui_rs::sys::igGetCurrentContext() };
245 struct RestoreCurrentContext(*mut dear_imgui_rs::sys::ImGuiContext);
246 impl Drop for RestoreCurrentContext {
247 fn drop(&mut self) {
248 unsafe { dear_imgui_rs::sys::igSetCurrentContext(self.0) };
249 }
250 }
251 let _restore = RestoreCurrentContext(previous);
252
253 assert_eq!(
254 unsafe { dear_imgui_rs::sys::igGetCurrentContext() },
255 imgui_b.as_raw()
256 );
257 let panic = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
258 Plot3DContextBinding {
259 plot_ctx_raw: plot_a.raw,
260 imgui_ctx_raw: plot_a.imgui_ctx_raw,
261 }
262 .bind();
263 }))
264 .expect_err("expected wrong ImGui context to panic");
265
266 let message = panic
267 .downcast_ref::<String>()
268 .map(String::as_str)
269 .or_else(|| panic.downcast_ref::<&'static str>().copied())
270 .unwrap_or("");
271 assert!(message.contains("Plot3DUi must be used with the currently-active ImGui context"));
272 drop(plot_a);
273 drop(_restore);
274 drop(imgui_b);
275 drop(suspended_a);
276 }
277}