Skip to main content

dear_implot3d/ui/
core.rs

1use std::marker::PhantomData;
2
3use crate::builder::Plot3DBuilder;
4use crate::{
5    Line3DFlags, Plot3DDataLayout, Plot3DFlags, Quad3DFlags, Scatter3DFlags, Triangle3DFlags,
6    imgui_sys, len_i32, plot3d_spec_from, sys,
7};
8use dear_imgui_rs::Ui;
9
10use super::binding::Plot3DContextBinding;
11
12/// Per-frame access helper mirroring `dear-implot`
13///
14/// This provides access to all 3D plotting functions. It is tied to the lifetime
15/// of the current ImGui frame and should be obtained via `Plot3DContext::get_plot_ui()`.
16///
17/// # Example
18///
19/// ```no_run
20/// use dear_implot3d::*;
21///
22/// # let plot_ui: Plot3DUi = todo!();
23/// if let Some(_token) = plot_ui.begin_plot("My 3D Plot").build() {
24///     plot_ui.setup_axes("X", "Y", "Z", Axis3DFlags::NONE, Axis3DFlags::NONE, Axis3DFlags::NONE);
25///
26///     let xs = [0.0, 1.0, 2.0];
27///     let ys = [0.0, 1.0, 0.0];
28///     let zs = [0.0, 0.5, 1.0];
29///     plot_ui.plot_line_f32("Line", &xs, &ys, &zs, Line3DFlags::NONE);
30/// }
31/// ```
32pub struct Plot3DUi<'ui> {
33    pub(crate) _ui: &'ui Ui,
34    pub(crate) binding: Plot3DContextBinding,
35    pub(crate) imgui_alive: Option<dear_imgui_rs::ContextAliveToken>,
36}
37
38impl<'ui> Plot3DUi<'ui> {
39    pub(crate) fn from_current(ui: &'ui Ui) -> Self {
40        let imgui_ctx_raw = unsafe { imgui_sys::igGetCurrentContext() };
41        assert!(
42            !imgui_ctx_raw.is_null(),
43            "dear-implot3d: Plot3DUi requires an active ImGui context"
44        );
45        let plot_ctx_raw = unsafe { sys::ImPlot3D_GetCurrentContext() };
46        assert!(
47            !plot_ctx_raw.is_null(),
48            "dear-implot3d: Plot3DUi requires an active ImPlot3D context"
49        );
50        Self {
51            _ui: ui,
52            binding: Plot3DContextBinding {
53                plot_ctx_raw,
54                imgui_ctx_raw,
55            },
56            imgui_alive: None,
57        }
58    }
59
60    pub(crate) fn bind(&self) {
61        if let Some(alive) = &self.imgui_alive {
62            assert!(
63                alive.is_alive(),
64                "dear-implot3d: ImGui context has been dropped"
65            );
66        }
67        self.binding.bind();
68    }
69
70    /// Builder to configure and begin a 3D plot
71    ///
72    /// Returns a `Plot3DBuilder` that allows you to configure the plot before calling `.build()`.
73    ///
74    /// # Example
75    ///
76    /// ```no_run
77    /// use dear_implot3d::*;
78    ///
79    /// # let plot_ui: Plot3DUi = todo!();
80    /// if let Some(_token) = plot_ui
81    ///     .begin_plot("My Plot")
82    ///     .size([600.0, 400.0])
83    ///     .flags(Plot3DFlags::NO_LEGEND)
84    ///     .build()
85    /// {
86    ///     // Plot content here
87    /// }
88    /// ```
89    pub fn begin_plot<S: AsRef<str>>(&self, title: S) -> Plot3DBuilder<'ui> {
90        self.bind();
91        Plot3DBuilder {
92            binding: self.binding,
93            imgui_alive: self.imgui_alive.clone(),
94            title: title.as_ref().into(),
95            size: None,
96            flags: Plot3DFlags::empty(),
97            _lifetime: PhantomData,
98        }
99    }
100
101    /// Convenience: plot a simple 3D line (f32)
102    ///
103    /// This is a quick way to plot a line without using the builder pattern.
104    /// For more control, use the `plots::Line3D` builder.
105    ///
106    /// # Arguments
107    ///
108    /// * `label` - Label for the legend
109    /// * `xs` - X coordinates
110    /// * `ys` - Y coordinates
111    /// * `zs` - Z coordinates
112    /// * `flags` - Line flags (e.g., `Line3DFlags::SEGMENTS`, `Line3DFlags::LOOP`)
113    ///
114    /// # Example
115    ///
116    /// ```no_run
117    /// use dear_implot3d::*;
118    ///
119    /// # let plot_ui: Plot3DUi = todo!();
120    /// let xs = [0.0, 1.0, 2.0];
121    /// let ys = [0.0, 1.0, 0.0];
122    /// let zs = [0.0, 0.5, 1.0];
123    /// plot_ui.plot_line_f32("Line", &xs, &ys, &zs, Line3DFlags::NONE);
124    /// ```
125    pub fn plot_line_f32<S: AsRef<str>>(
126        &self,
127        label: S,
128        xs: &[f32],
129        ys: &[f32],
130        zs: &[f32],
131        flags: Line3DFlags,
132    ) {
133        self.bind();
134        if xs.len() != ys.len() || ys.len() != zs.len() {
135            return;
136        }
137        let Some(count) = len_i32(xs.len()) else {
138            return;
139        };
140        let label = label.as_ref();
141        if label.contains('\0') {
142            return;
143        }
144        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
145            let spec = plot3d_spec_from(flags.bits(), Plot3DDataLayout::DEFAULT);
146            sys::ImPlot3D_PlotLine_FloatPtr(
147                label_ptr,
148                xs.as_ptr(),
149                ys.as_ptr(),
150                zs.as_ptr(),
151                count,
152                spec,
153            );
154        })
155    }
156
157    /// Line plot (f32) with an explicit data layout.
158    pub fn plot_line_f32_raw<S: AsRef<str>>(
159        &self,
160        label: S,
161        xs: &[f32],
162        ys: &[f32],
163        zs: &[f32],
164        flags: Line3DFlags,
165        layout: Plot3DDataLayout,
166    ) {
167        self.bind();
168        if xs.len() != ys.len() || ys.len() != zs.len() {
169            return;
170        }
171        let Some(count) = len_i32(xs.len()) else {
172            return;
173        };
174        let label = label.as_ref();
175        if label.contains('\0') {
176            return;
177        }
178        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
179            let spec = plot3d_spec_from(flags.bits(), layout);
180            sys::ImPlot3D_PlotLine_FloatPtr(
181                label_ptr,
182                xs.as_ptr(),
183                ys.as_ptr(),
184                zs.as_ptr(),
185                count,
186                spec,
187            );
188        })
189    }
190
191    /// Convenience: plot a simple 3D line (f64)
192    pub fn plot_line_f64<S: AsRef<str>>(
193        &self,
194        label: S,
195        xs: &[f64],
196        ys: &[f64],
197        zs: &[f64],
198        flags: Line3DFlags,
199    ) {
200        self.bind();
201        if xs.len() != ys.len() || ys.len() != zs.len() {
202            return;
203        }
204        let Some(count) = len_i32(xs.len()) else {
205            return;
206        };
207        let label = label.as_ref();
208        if label.contains('\0') {
209            return;
210        }
211        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
212            let spec = plot3d_spec_from(flags.bits(), Plot3DDataLayout::DEFAULT);
213            sys::ImPlot3D_PlotLine_doublePtr(
214                label_ptr,
215                xs.as_ptr(),
216                ys.as_ptr(),
217                zs.as_ptr(),
218                count,
219                spec,
220            );
221        })
222    }
223
224    /// Line plot (f64) with an explicit data layout.
225    pub fn plot_line_f64_raw<S: AsRef<str>>(
226        &self,
227        label: S,
228        xs: &[f64],
229        ys: &[f64],
230        zs: &[f64],
231        flags: Line3DFlags,
232        layout: Plot3DDataLayout,
233    ) {
234        self.bind();
235        if xs.len() != ys.len() || ys.len() != zs.len() {
236            return;
237        }
238        let Some(count) = len_i32(xs.len()) else {
239            return;
240        };
241        let label = label.as_ref();
242        if label.contains('\0') {
243            return;
244        }
245        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
246            let spec = plot3d_spec_from(flags.bits(), layout);
247            sys::ImPlot3D_PlotLine_doublePtr(
248                label_ptr,
249                xs.as_ptr(),
250                ys.as_ptr(),
251                zs.as_ptr(),
252                count,
253                spec,
254            );
255        })
256    }
257
258    /// Convenience: plot a 3D scatter (f32)
259    pub fn plot_scatter_f32<S: AsRef<str>>(
260        &self,
261        label: S,
262        xs: &[f32],
263        ys: &[f32],
264        zs: &[f32],
265        flags: Scatter3DFlags,
266    ) {
267        self.bind();
268        if xs.len() != ys.len() || ys.len() != zs.len() {
269            return;
270        }
271        let Some(count) = len_i32(xs.len()) else {
272            return;
273        };
274        let label = label.as_ref();
275        if label.contains('\0') {
276            return;
277        }
278        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
279            let spec = plot3d_spec_from(flags.bits(), Plot3DDataLayout::DEFAULT);
280            sys::ImPlot3D_PlotScatter_FloatPtr(
281                label_ptr,
282                xs.as_ptr(),
283                ys.as_ptr(),
284                zs.as_ptr(),
285                count,
286                spec,
287            );
288        })
289    }
290
291    /// Scatter plot (f32) with an explicit data layout.
292    pub fn plot_scatter_f32_raw<S: AsRef<str>>(
293        &self,
294        label: S,
295        xs: &[f32],
296        ys: &[f32],
297        zs: &[f32],
298        flags: Scatter3DFlags,
299        layout: Plot3DDataLayout,
300    ) {
301        self.bind();
302        if xs.len() != ys.len() || ys.len() != zs.len() {
303            return;
304        }
305        let Some(count) = len_i32(xs.len()) else {
306            return;
307        };
308        let label = label.as_ref();
309        if label.contains('\0') {
310            return;
311        }
312        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
313            let spec = plot3d_spec_from(flags.bits(), layout);
314            sys::ImPlot3D_PlotScatter_FloatPtr(
315                label_ptr,
316                xs.as_ptr(),
317                ys.as_ptr(),
318                zs.as_ptr(),
319                count,
320                spec,
321            );
322        })
323    }
324
325    /// Convenience: plot a 3D scatter (f64)
326    pub fn plot_scatter_f64<S: AsRef<str>>(
327        &self,
328        label: S,
329        xs: &[f64],
330        ys: &[f64],
331        zs: &[f64],
332        flags: Scatter3DFlags,
333    ) {
334        self.bind();
335        if xs.len() != ys.len() || ys.len() != zs.len() {
336            return;
337        }
338        let Some(count) = len_i32(xs.len()) else {
339            return;
340        };
341        let label = label.as_ref();
342        if label.contains('\0') {
343            return;
344        }
345        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
346            let spec = plot3d_spec_from(flags.bits(), Plot3DDataLayout::DEFAULT);
347            sys::ImPlot3D_PlotScatter_doublePtr(
348                label_ptr,
349                xs.as_ptr(),
350                ys.as_ptr(),
351                zs.as_ptr(),
352                count,
353                spec,
354            );
355        })
356    }
357
358    /// Scatter plot (f64) with an explicit data layout.
359    pub fn plot_scatter_f64_raw<S: AsRef<str>>(
360        &self,
361        label: S,
362        xs: &[f64],
363        ys: &[f64],
364        zs: &[f64],
365        flags: Scatter3DFlags,
366        layout: Plot3DDataLayout,
367    ) {
368        self.bind();
369        if xs.len() != ys.len() || ys.len() != zs.len() {
370            return;
371        }
372        let Some(count) = len_i32(xs.len()) else {
373            return;
374        };
375        let label = label.as_ref();
376        if label.contains('\0') {
377            return;
378        }
379        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
380            let spec = plot3d_spec_from(flags.bits(), layout);
381            sys::ImPlot3D_PlotScatter_doublePtr(
382                label_ptr,
383                xs.as_ptr(),
384                ys.as_ptr(),
385                zs.as_ptr(),
386                count,
387                spec,
388            );
389        })
390    }
391
392    /// Convenience: plot triangles from interleaved xyz arrays (count must be multiple of 3)
393    pub fn plot_triangles_f32<S: AsRef<str>>(
394        &self,
395        label: S,
396        xs: &[f32],
397        ys: &[f32],
398        zs: &[f32],
399        flags: Triangle3DFlags,
400    ) {
401        self.bind();
402        if xs.len() != ys.len() || ys.len() != zs.len() {
403            return;
404        }
405        let Some(count) = len_i32(xs.len()) else {
406            return;
407        };
408        let label = label.as_ref();
409        if label.contains('\0') {
410            return;
411        }
412        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
413            let spec = plot3d_spec_from(flags.bits(), Plot3DDataLayout::DEFAULT);
414            sys::ImPlot3D_PlotTriangle_FloatPtr(
415                label_ptr,
416                xs.as_ptr(),
417                ys.as_ptr(),
418                zs.as_ptr(),
419                count,
420                spec,
421            );
422        })
423    }
424
425    pub fn plot_triangles_f32_raw<S: AsRef<str>>(
426        &self,
427        label: S,
428        xs: &[f32],
429        ys: &[f32],
430        zs: &[f32],
431        flags: Triangle3DFlags,
432        layout: Plot3DDataLayout,
433    ) {
434        self.bind();
435        if xs.len() != ys.len() || ys.len() != zs.len() {
436            return;
437        }
438        let Some(count) = len_i32(xs.len()) else {
439            return;
440        };
441        let label = label.as_ref();
442        if label.contains('\0') {
443            return;
444        }
445        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
446            let spec = plot3d_spec_from(flags.bits(), layout);
447            sys::ImPlot3D_PlotTriangle_FloatPtr(
448                label_ptr,
449                xs.as_ptr(),
450                ys.as_ptr(),
451                zs.as_ptr(),
452                count,
453                spec,
454            );
455        })
456    }
457
458    /// Convenience: plot quads from interleaved xyz arrays (count must be multiple of 4)
459    pub fn plot_quads_f32<S: AsRef<str>>(
460        &self,
461        label: S,
462        xs: &[f32],
463        ys: &[f32],
464        zs: &[f32],
465        flags: Quad3DFlags,
466    ) {
467        self.bind();
468        if xs.len() != ys.len() || ys.len() != zs.len() {
469            return;
470        }
471        let Some(count) = len_i32(xs.len()) else {
472            return;
473        };
474        let label = label.as_ref();
475        if label.contains('\0') {
476            return;
477        }
478        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
479            let spec = plot3d_spec_from(flags.bits(), Plot3DDataLayout::DEFAULT);
480            sys::ImPlot3D_PlotQuad_FloatPtr(
481                label_ptr,
482                xs.as_ptr(),
483                ys.as_ptr(),
484                zs.as_ptr(),
485                count,
486                spec,
487            );
488        })
489    }
490
491    pub fn plot_quads_f32_raw<S: AsRef<str>>(
492        &self,
493        label: S,
494        xs: &[f32],
495        ys: &[f32],
496        zs: &[f32],
497        flags: Quad3DFlags,
498        layout: Plot3DDataLayout,
499    ) {
500        self.bind();
501        if xs.len() != ys.len() || ys.len() != zs.len() {
502            return;
503        }
504        let Some(count) = len_i32(xs.len()) else {
505            return;
506        };
507        let label = label.as_ref();
508        if label.contains('\0') {
509            return;
510        }
511        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
512            let spec = plot3d_spec_from(flags.bits(), layout);
513            sys::ImPlot3D_PlotQuad_FloatPtr(
514                label_ptr,
515                xs.as_ptr(),
516                ys.as_ptr(),
517                zs.as_ptr(),
518                count,
519                spec,
520            );
521        })
522    }
523
524    /// Convenience: plot triangles from interleaved xyz arrays (f64)
525    pub fn plot_triangles_f64<S: AsRef<str>>(
526        &self,
527        label: S,
528        xs: &[f64],
529        ys: &[f64],
530        zs: &[f64],
531        flags: Triangle3DFlags,
532    ) {
533        self.bind();
534        if xs.len() != ys.len() || ys.len() != zs.len() {
535            return;
536        }
537        let Some(count) = len_i32(xs.len()) else {
538            return;
539        };
540        let label = label.as_ref();
541        if label.contains('\0') {
542            return;
543        }
544        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
545            let spec = plot3d_spec_from(flags.bits(), Plot3DDataLayout::DEFAULT);
546            sys::ImPlot3D_PlotTriangle_doublePtr(
547                label_ptr,
548                xs.as_ptr(),
549                ys.as_ptr(),
550                zs.as_ptr(),
551                count,
552                spec,
553            );
554        })
555    }
556
557    pub fn plot_triangles_f64_raw<S: AsRef<str>>(
558        &self,
559        label: S,
560        xs: &[f64],
561        ys: &[f64],
562        zs: &[f64],
563        flags: Triangle3DFlags,
564        layout: Plot3DDataLayout,
565    ) {
566        self.bind();
567        if xs.len() != ys.len() || ys.len() != zs.len() {
568            return;
569        }
570        let Some(count) = len_i32(xs.len()) else {
571            return;
572        };
573        let label = label.as_ref();
574        if label.contains('\0') {
575            return;
576        }
577        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
578            let spec = plot3d_spec_from(flags.bits(), layout);
579            sys::ImPlot3D_PlotTriangle_doublePtr(
580                label_ptr,
581                xs.as_ptr(),
582                ys.as_ptr(),
583                zs.as_ptr(),
584                count,
585                spec,
586            );
587        })
588    }
589
590    /// Convenience: plot quads from interleaved xyz arrays (f64)
591    pub fn plot_quads_f64<S: AsRef<str>>(
592        &self,
593        label: S,
594        xs: &[f64],
595        ys: &[f64],
596        zs: &[f64],
597        flags: Quad3DFlags,
598    ) {
599        self.bind();
600        if xs.len() != ys.len() || ys.len() != zs.len() {
601            return;
602        }
603        let Some(count) = len_i32(xs.len()) else {
604            return;
605        };
606        let label = label.as_ref();
607        if label.contains('\0') {
608            return;
609        }
610        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
611            let spec = plot3d_spec_from(flags.bits(), Plot3DDataLayout::DEFAULT);
612            sys::ImPlot3D_PlotQuad_doublePtr(
613                label_ptr,
614                xs.as_ptr(),
615                ys.as_ptr(),
616                zs.as_ptr(),
617                count,
618                spec,
619            );
620        })
621    }
622
623    pub fn plot_quads_f64_raw<S: AsRef<str>>(
624        &self,
625        label: S,
626        xs: &[f64],
627        ys: &[f64],
628        zs: &[f64],
629        flags: Quad3DFlags,
630        layout: Plot3DDataLayout,
631    ) {
632        self.bind();
633        if xs.len() != ys.len() || ys.len() != zs.len() {
634            return;
635        }
636        let Some(count) = len_i32(xs.len()) else {
637            return;
638        };
639        let label = label.as_ref();
640        if label.contains('\0') {
641            return;
642        }
643        dear_imgui_rs::with_scratch_txt(label, |label_ptr| unsafe {
644            let spec = plot3d_spec_from(flags.bits(), layout);
645            sys::ImPlot3D_PlotQuad_doublePtr(
646                label_ptr,
647                xs.as_ptr(),
648                ys.as_ptr(),
649                zs.as_ptr(),
650                count,
651                spec,
652            );
653        })
654    }
655}