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