Skip to main content

dear_implot3d/
item_style.rs

1use crate::plots::Plot3D;
2use crate::{Plot3DDataLayout, sys};
3use dear_imgui_rs::texture::TextureRef;
4
5fn color4(rgba: [f32; 4]) -> sys::ImVec4_c {
6    sys::ImVec4_c {
7        x: rgba[0],
8        y: rgba[1],
9        z: rgba[2],
10        w: rgba[3],
11    }
12}
13
14/// Common style overrides for plot items backed by `ImPlot3DSpec`.
15#[derive(Debug, Clone, Copy, Default, PartialEq)]
16pub struct Plot3DItemStyle {
17    pub(crate) line_color: Option<sys::ImVec4_c>,
18    pub(crate) line_weight: Option<f32>,
19    pub(crate) fill_color: Option<sys::ImVec4_c>,
20    pub(crate) fill_alpha: Option<f32>,
21    pub(crate) marker: Option<sys::ImPlot3DMarker>,
22    pub(crate) marker_size: Option<f32>,
23    pub(crate) marker_line_color: Option<sys::ImVec4_c>,
24    pub(crate) marker_fill_color: Option<sys::ImVec4_c>,
25}
26
27impl Plot3DItemStyle {
28    /// Create an empty style override that keeps ImPlot3D defaults.
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    /// Override the plot item's line color.
34    pub fn with_line_color(mut self, color: [f32; 4]) -> Self {
35        self.line_color = Some(color4(color));
36        self
37    }
38
39    /// Override the plot item's line width in pixels.
40    pub fn with_line_weight(mut self, weight: f32) -> Self {
41        self.line_weight = Some(weight);
42        self
43    }
44
45    /// Override the plot item's fill color.
46    pub fn with_fill_color(mut self, color: [f32; 4]) -> Self {
47        self.fill_color = Some(color4(color));
48        self
49    }
50
51    /// Override the fill alpha multiplier used by filled regions and marker faces.
52    pub fn with_fill_alpha(mut self, alpha: f32) -> Self {
53        self.fill_alpha = Some(alpha);
54        self
55    }
56
57    /// Override the marker type.
58    pub fn with_marker(mut self, marker: crate::Marker3D) -> Self {
59        self.marker = Some(marker as sys::ImPlot3DMarker);
60        self
61    }
62
63    /// Override the marker size in pixels.
64    pub fn with_marker_size(mut self, size: f32) -> Self {
65        self.marker_size = Some(size);
66        self
67    }
68
69    /// Override the marker outline color.
70    pub fn with_marker_line_color(mut self, color: [f32; 4]) -> Self {
71        self.marker_line_color = Some(color4(color));
72        self
73    }
74
75    /// Override the marker fill color.
76    pub fn with_marker_fill_color(mut self, color: [f32; 4]) -> Self {
77        self.marker_fill_color = Some(color4(color));
78        self
79    }
80
81    pub(crate) fn apply_to_spec(self, spec: &mut sys::ImPlot3DSpec_c) {
82        if let Some(line_color) = self.line_color {
83            spec.LineColor = line_color;
84        }
85        if let Some(line_weight) = self.line_weight {
86            spec.LineWeight = line_weight;
87        }
88        if let Some(fill_color) = self.fill_color {
89            spec.FillColor = fill_color;
90        }
91        if let Some(fill_alpha) = self.fill_alpha {
92            spec.FillAlpha = fill_alpha;
93        }
94        if let Some(marker) = self.marker {
95            spec.Marker = marker;
96        }
97        if let Some(marker_size) = self.marker_size {
98            spec.MarkerSize = marker_size;
99        }
100        if let Some(marker_line_color) = self.marker_line_color {
101            spec.MarkerLineColor = marker_line_color;
102        }
103        if let Some(marker_fill_color) = self.marker_fill_color {
104            spec.MarkerFillColor = marker_fill_color;
105        }
106    }
107}
108
109pub(crate) fn plot3d_spec_with_style(
110    style: Plot3DItemStyle,
111    flags: u32,
112    layout: Plot3DDataLayout,
113) -> sys::ImPlot3DSpec_c {
114    let mut spec = crate::plot3d_spec_from(flags, layout);
115    style.apply_to_spec(&mut spec);
116    spec
117}
118
119struct ScopedNextPlot3DSpec {
120    previous: Option<sys::ImPlot3DSpec_c>,
121    active: bool,
122}
123
124impl ScopedNextPlot3DSpec {
125    fn restore_if_unused(&mut self) {
126        if !self.active {
127            return;
128        }
129
130        if crate::take_next_plot3d_spec().is_some() {
131            crate::set_next_plot3d_spec(self.previous.take());
132        }
133        self.active = false;
134    }
135}
136
137impl Drop for ScopedNextPlot3DSpec {
138    fn drop(&mut self) {
139        self.restore_if_unused();
140    }
141}
142
143fn with_scoped_next_plot3d_spec<R>(
144    style: Plot3DItemStyle,
145    item_flags: crate::Item3DFlags,
146    f: impl FnOnce() -> R,
147) -> R {
148    let previous = crate::take_next_plot3d_spec();
149    let mut spec = previous.unwrap_or_else(crate::default_plot3d_spec);
150    style.apply_to_spec(&mut spec);
151    spec.Flags = ((spec.Flags as u32) | item_flags.bits()) as sys::ImPlot3DItemFlags;
152    crate::set_next_plot3d_spec(Some(spec));
153
154    let mut guard = ScopedNextPlot3DSpec {
155        previous,
156        active: true,
157    };
158    let out = f();
159    guard.restore_if_unused();
160    out
161}
162
163/// Shared ImPlot3D item-style builder methods for plot builders backed by `ImPlot3DSpec`.
164pub trait Plot3DItemStyled: Sized {
165    /// The output type returned by style-building methods.
166    type Output;
167
168    fn map_style<F>(self, f: F) -> Self::Output
169    where
170        F: FnOnce(&mut Plot3DItemStyle);
171
172    /// Replace the entire item style override for this plot.
173    fn with_style(self, style: Plot3DItemStyle) -> Self::Output {
174        self.map_style(|current| *current = style)
175    }
176
177    /// Set the line color.
178    fn with_line_color(self, color: [f32; 4]) -> Self::Output {
179        self.map_style(|style| style.line_color = Some(color4(color)))
180    }
181
182    /// Set the line width in pixels.
183    fn with_line_weight(self, weight: f32) -> Self::Output {
184        self.map_style(|style| style.line_weight = Some(weight))
185    }
186
187    /// Set the fill color.
188    fn with_fill_color(self, color: [f32; 4]) -> Self::Output {
189        self.map_style(|style| style.fill_color = Some(color4(color)))
190    }
191
192    /// Set the fill alpha multiplier used for fills and marker faces.
193    fn with_fill_alpha(self, alpha: f32) -> Self::Output {
194        self.map_style(|style| style.fill_alpha = Some(alpha))
195    }
196
197    /// Set the marker type.
198    fn with_marker(self, marker: crate::Marker3D) -> Self::Output {
199        self.map_style(|style| style.marker = Some(marker as sys::ImPlot3DMarker))
200    }
201
202    /// Set the marker size in pixels.
203    fn with_marker_size(self, size: f32) -> Self::Output {
204        self.map_style(|style| style.marker_size = Some(size))
205    }
206
207    /// Set the marker outline color.
208    fn with_marker_line_color(self, color: [f32; 4]) -> Self::Output {
209        self.map_style(|style| style.marker_line_color = Some(color4(color)))
210    }
211
212    /// Set the marker fill color.
213    fn with_marker_fill_color(self, color: [f32; 4]) -> Self::Output {
214        self.map_style(|style| style.marker_fill_color = Some(color4(color)))
215    }
216}
217
218/// Shared ImPlot3D item-flag builder methods for plot builders backed by `ImPlot3DSpec`.
219pub trait Plot3DItemFlagged: Sized {
220    /// The output type returned by item-flag building methods.
221    type Output;
222
223    fn map_item_flags<F>(self, f: F) -> Self::Output
224    where
225        F: FnOnce(&mut crate::Item3DFlags);
226
227    /// Set common item flags such as `NO_LEGEND` / `NO_FIT`.
228    fn with_item_flags(self, flags: crate::Item3DFlags) -> Self::Output {
229        self.map_item_flags(|current| *current = flags)
230    }
231}
232
233/// Styled wrapper for plot types that do not store item-style state directly.
234pub struct StyledPlot3D<T> {
235    inner: T,
236    style: Plot3DItemStyle,
237    item_flags: crate::Item3DFlags,
238}
239
240impl<T> StyledPlot3D<T> {
241    /// Consume the wrapper and return the wrapped plot item.
242    pub fn into_inner(self) -> T {
243        self.inner
244    }
245
246    /// Borrow the wrapped plot item.
247    pub fn inner(&self) -> &T {
248        &self.inner
249    }
250}
251
252impl<T> Plot3DItemStyled for StyledPlot3D<T> {
253    type Output = Self;
254
255    fn map_style<F>(mut self, f: F) -> Self::Output
256    where
257        F: FnOnce(&mut Plot3DItemStyle),
258    {
259        f(&mut self.style);
260        self
261    }
262}
263
264impl<T> Plot3DItemFlagged for StyledPlot3D<T> {
265    type Output = Self;
266
267    fn map_item_flags<F>(mut self, f: F) -> Self::Output
268    where
269        F: FnOnce(&mut crate::Item3DFlags),
270    {
271        f(&mut self.item_flags);
272        self
273    }
274}
275
276impl<T> Plot3D for StyledPlot3D<T>
277where
278    T: Plot3D,
279{
280    fn label(&self) -> &str {
281        self.inner.label()
282    }
283
284    fn try_plot(&self, ui: &crate::Plot3DUi<'_>) -> Result<(), crate::plots::Plot3DError> {
285        with_scoped_next_plot3d_spec(self.style, self.item_flags, || self.inner.try_plot(ui))
286    }
287}
288
289macro_rules! impl_wrapped_plot3d_item_styled {
290    ($ty:ty) => {
291        impl Plot3DItemStyled for $ty {
292            type Output = StyledPlot3D<Self>;
293
294            fn map_style<F>(self, f: F) -> Self::Output
295            where
296                F: FnOnce(&mut Plot3DItemStyle),
297            {
298                let mut style = Plot3DItemStyle::default();
299                f(&mut style);
300                StyledPlot3D {
301                    inner: self,
302                    style,
303                    item_flags: crate::Item3DFlags::NONE,
304                }
305            }
306        }
307    };
308}
309
310macro_rules! impl_wrapped_plot3d_item_flagged {
311    ($ty:ty) => {
312        impl Plot3DItemFlagged for $ty {
313            type Output = StyledPlot3D<Self>;
314
315            fn map_item_flags<F>(self, f: F) -> Self::Output
316            where
317                F: FnOnce(&mut crate::Item3DFlags),
318            {
319                let mut item_flags = crate::Item3DFlags::NONE;
320                f(&mut item_flags);
321                StyledPlot3D {
322                    inner: self,
323                    style: Plot3DItemStyle::default(),
324                    item_flags,
325                }
326            }
327        }
328    };
329}
330
331impl_wrapped_plot3d_item_styled!(crate::plots::Line3D<'_>);
332impl_wrapped_plot3d_item_styled!(crate::plots::Scatter3D<'_>);
333impl_wrapped_plot3d_item_styled!(crate::plots::Surface3D<'_>);
334impl_wrapped_plot3d_item_styled!(crate::plots::Triangles3D<'_>);
335impl_wrapped_plot3d_item_styled!(crate::plots::Quads3D<'_>);
336impl_wrapped_plot3d_item_styled!(crate::plots::Mesh3D<'_>);
337impl_wrapped_plot3d_item_flagged!(crate::plots::Line3D<'_>);
338impl_wrapped_plot3d_item_flagged!(crate::plots::Scatter3D<'_>);
339impl_wrapped_plot3d_item_flagged!(crate::plots::Surface3D<'_>);
340impl_wrapped_plot3d_item_flagged!(crate::plots::Triangles3D<'_>);
341impl_wrapped_plot3d_item_flagged!(crate::plots::Quads3D<'_>);
342impl_wrapped_plot3d_item_flagged!(crate::plots::Mesh3D<'_>);
343
344impl<'a, T> Plot3DItemStyled for crate::plots::Image3DByAxes<'a, T>
345where
346    T: Into<TextureRef<'a>> + Copy,
347{
348    type Output = StyledPlot3D<Self>;
349
350    fn map_style<F>(self, f: F) -> Self::Output
351    where
352        F: FnOnce(&mut Plot3DItemStyle),
353    {
354        let mut style = Plot3DItemStyle::default();
355        f(&mut style);
356        StyledPlot3D {
357            inner: self,
358            style,
359            item_flags: crate::Item3DFlags::NONE,
360        }
361    }
362}
363
364impl<'a, T> Plot3DItemFlagged for crate::plots::Image3DByAxes<'a, T>
365where
366    T: Into<TextureRef<'a>> + Copy,
367{
368    type Output = StyledPlot3D<Self>;
369
370    fn map_item_flags<F>(self, f: F) -> Self::Output
371    where
372        F: FnOnce(&mut crate::Item3DFlags),
373    {
374        let mut item_flags = crate::Item3DFlags::NONE;
375        f(&mut item_flags);
376        StyledPlot3D {
377            inner: self,
378            style: Plot3DItemStyle::default(),
379            item_flags,
380        }
381    }
382}
383
384impl<'a, T> Plot3DItemStyled for crate::plots::Image3DByCorners<'a, T>
385where
386    T: Into<TextureRef<'a>> + Copy,
387{
388    type Output = StyledPlot3D<Self>;
389
390    fn map_style<F>(self, f: F) -> Self::Output
391    where
392        F: FnOnce(&mut Plot3DItemStyle),
393    {
394        let mut style = Plot3DItemStyle::default();
395        f(&mut style);
396        StyledPlot3D {
397            inner: self,
398            style,
399            item_flags: crate::Item3DFlags::NONE,
400        }
401    }
402}
403
404impl<'a, T> Plot3DItemFlagged for crate::plots::Image3DByCorners<'a, T>
405where
406    T: Into<TextureRef<'a>> + Copy,
407{
408    type Output = StyledPlot3D<Self>;
409
410    fn map_item_flags<F>(self, f: F) -> Self::Output
411    where
412        F: FnOnce(&mut crate::Item3DFlags),
413    {
414        let mut item_flags = crate::Item3DFlags::NONE;
415        f(&mut item_flags);
416        StyledPlot3D {
417            inner: self,
418            style: Plot3DItemStyle::default(),
419            item_flags,
420        }
421    }
422}
423
424macro_rules! impl_builder_plot3d_item_styled {
425    ($ty:ty) => {
426        impl Plot3DItemStyled for $ty {
427            type Output = Self;
428
429            fn map_style<F>(mut self, f: F) -> Self::Output
430            where
431                F: FnOnce(&mut Plot3DItemStyle),
432            {
433                f(&mut self.style);
434                self
435            }
436        }
437    };
438}
439
440macro_rules! impl_builder_plot3d_item_flagged {
441    ($ty:ty) => {
442        impl Plot3DItemFlagged for $ty {
443            type Output = Self;
444
445            fn map_item_flags<F>(mut self, f: F) -> Self::Output
446            where
447                F: FnOnce(&mut crate::Item3DFlags),
448            {
449                f(&mut self.item_flags);
450                self
451            }
452        }
453    };
454}
455
456impl_builder_plot3d_item_styled!(crate::Surface3DBuilder<'_>);
457impl_builder_plot3d_item_styled!(crate::Image3DByAxesBuilder<'_, '_>);
458impl_builder_plot3d_item_styled!(crate::Image3DByCornersBuilder<'_, '_>);
459impl_builder_plot3d_item_styled!(crate::Mesh3DBuilder<'_>);
460impl_builder_plot3d_item_flagged!(crate::Surface3DBuilder<'_>);
461impl_builder_plot3d_item_flagged!(crate::Image3DByAxesBuilder<'_, '_>);
462impl_builder_plot3d_item_flagged!(crate::Image3DByCornersBuilder<'_, '_>);
463impl_builder_plot3d_item_flagged!(crate::Mesh3DBuilder<'_>);
464
465#[cfg(test)]
466mod tests {
467    use super::{Plot3DItemStyle, plot3d_spec_with_style, with_scoped_next_plot3d_spec};
468    use crate::{
469        Item3DFlags, Marker3D, Plot3DDataLayout, Plot3DDataOffset, Plot3DDataStride,
470        default_plot3d_spec, set_next_plot3d_spec, take_next_plot3d_spec,
471    };
472
473    #[test]
474    fn plot3d_item_style_applies_fields() {
475        let style = Plot3DItemStyle::new()
476            .with_line_color([0.1, 0.2, 0.3, 0.4])
477            .with_line_weight(2.5)
478            .with_fill_color([0.5, 0.6, 0.7, 0.8])
479            .with_fill_alpha(0.35)
480            .with_marker(Marker3D::Auto)
481            .with_marker_size(6.0)
482            .with_marker_line_color([0.9, 0.1, 0.2, 1.0])
483            .with_marker_fill_color([0.3, 0.4, 0.5, 0.6]);
484
485        let layout =
486            Plot3DDataLayout::new(Plot3DDataOffset::samples(7), Plot3DDataStride::bytes(16));
487        let spec = plot3d_spec_with_style(style, 0, layout);
488
489        assert_eq!(spec.LineColor.x, 0.1);
490        assert_eq!(spec.LineColor.y, 0.2);
491        assert_eq!(spec.LineColor.z, 0.3);
492        assert_eq!(spec.LineColor.w, 0.4);
493        assert_eq!(spec.LineWeight, 2.5);
494        assert_eq!(spec.FillColor.x, 0.5);
495        assert_eq!(spec.FillColor.y, 0.6);
496        assert_eq!(spec.FillColor.z, 0.7);
497        assert_eq!(spec.FillColor.w, 0.8);
498        assert_eq!(spec.FillAlpha, 0.35);
499        assert_eq!(spec.Marker, crate::sys::ImPlot3DMarker_Auto as _);
500        assert_eq!(spec.MarkerSize, 6.0);
501        assert_eq!(spec.MarkerLineColor.x, 0.9);
502        assert_eq!(spec.MarkerFillColor.z, 0.5);
503        assert_eq!(spec.Offset, 7);
504        assert_eq!(spec.Stride, 16);
505    }
506
507    #[test]
508    fn scoped_next_spec_restores_previous_when_not_consumed() {
509        set_next_plot3d_spec(None);
510
511        let mut previous = default_plot3d_spec();
512        previous.FillAlpha = 0.25;
513        set_next_plot3d_spec(Some(previous));
514
515        let out = with_scoped_next_plot3d_spec(
516            Plot3DItemStyle::new().with_line_weight(2.0),
517            Item3DFlags::NONE,
518            || "no-plot",
519        );
520
521        assert_eq!(out, "no-plot");
522
523        let restored = take_next_plot3d_spec().expect("previous next spec should be restored");
524        assert_eq!(restored.FillAlpha, 0.25);
525        assert_eq!(restored.LineWeight, 1.0);
526
527        set_next_plot3d_spec(None);
528    }
529
530    #[test]
531    fn scoped_next_spec_merges_with_previous_when_consumed() {
532        set_next_plot3d_spec(None);
533
534        let mut previous = default_plot3d_spec();
535        previous.FillAlpha = 0.25;
536        set_next_plot3d_spec(Some(previous));
537
538        let consumed = with_scoped_next_plot3d_spec(
539            Plot3DItemStyle::new()
540                .with_line_weight(3.0)
541                .with_marker(Marker3D::Auto),
542            Item3DFlags::NO_LEGEND,
543            || {
544                let layout = Plot3DDataLayout::new(
545                    Plot3DDataOffset::samples(5),
546                    Plot3DDataStride::bytes(12),
547                );
548                crate::plot3d_spec_from(0, layout)
549            },
550        );
551
552        assert_eq!(consumed.FillAlpha, 0.25);
553        assert_eq!(consumed.LineWeight, 3.0);
554        assert_eq!(consumed.Marker, crate::sys::ImPlot3DMarker_Auto as _);
555        assert_eq!(consumed.Flags as u32, Item3DFlags::NO_LEGEND.bits(),);
556        assert_eq!(consumed.Offset, 5);
557        assert_eq!(consumed.Stride, 12);
558        assert!(take_next_plot3d_spec().is_none());
559    }
560
561    #[test]
562    fn scoped_next_spec_item_flags_merge_with_plot_flags() {
563        set_next_plot3d_spec(None);
564
565        let consumed = with_scoped_next_plot3d_spec(
566            Plot3DItemStyle::default(),
567            Item3DFlags::NO_LEGEND,
568            || {
569                crate::plot3d_spec_from(
570                    crate::Line3DFlags::SEGMENTS.bits(),
571                    Plot3DDataLayout::DEFAULT.with_stride(Plot3DDataStride::bytes(4)),
572                )
573            },
574        );
575
576        assert_eq!(
577            consumed.Flags as u32,
578            Item3DFlags::NO_LEGEND.bits() | crate::Line3DFlags::SEGMENTS.bits(),
579        );
580        assert!(take_next_plot3d_spec().is_none());
581    }
582
583    #[test]
584    fn scoped_next_spec_restores_previous_if_closure_panics() {
585        set_next_plot3d_spec(None);
586
587        let mut previous = default_plot3d_spec();
588        previous.FillAlpha = 0.25;
589        set_next_plot3d_spec(Some(previous));
590
591        let result = std::panic::catch_unwind(|| {
592            with_scoped_next_plot3d_spec(
593                Plot3DItemStyle::new().with_line_weight(2.0),
594                Item3DFlags::NO_LEGEND,
595                || panic!("boom"),
596            );
597        });
598
599        assert!(result.is_err());
600
601        let restored = take_next_plot3d_spec().expect("previous next spec should be restored");
602        assert_eq!(restored.FillAlpha, 0.25);
603        assert_eq!(restored.LineWeight, 1.0);
604
605        set_next_plot3d_spec(None);
606    }
607}