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
119fn with_scoped_next_plot3d_spec<R>(
120    style: Plot3DItemStyle,
121    item_flags: crate::Item3DFlags,
122    f: impl FnOnce() -> R,
123) -> R {
124    let previous = crate::take_next_plot3d_spec();
125    let mut spec = previous.unwrap_or_else(crate::default_plot3d_spec);
126    style.apply_to_spec(&mut spec);
127    spec.Flags = ((spec.Flags as u32) | item_flags.bits()) as sys::ImPlot3DItemFlags;
128    crate::set_next_plot3d_spec(Some(spec));
129
130    let out = f();
131
132    if crate::take_next_plot3d_spec().is_some() {
133        crate::set_next_plot3d_spec(previous);
134    }
135
136    out
137}
138
139/// Shared ImPlot3D item-style builder methods for plot builders backed by `ImPlot3DSpec`.
140pub trait Plot3DItemStyled: Sized {
141    /// The output type returned by style-building methods.
142    type Output;
143
144    fn map_style<F>(self, f: F) -> Self::Output
145    where
146        F: FnOnce(&mut Plot3DItemStyle);
147
148    /// Replace the entire item style override for this plot.
149    fn with_style(self, style: Plot3DItemStyle) -> Self::Output {
150        self.map_style(|current| *current = style)
151    }
152
153    /// Set the line color.
154    fn with_line_color(self, color: [f32; 4]) -> Self::Output {
155        self.map_style(|style| style.line_color = Some(color4(color)))
156    }
157
158    /// Set the line width in pixels.
159    fn with_line_weight(self, weight: f32) -> Self::Output {
160        self.map_style(|style| style.line_weight = Some(weight))
161    }
162
163    /// Set the fill color.
164    fn with_fill_color(self, color: [f32; 4]) -> Self::Output {
165        self.map_style(|style| style.fill_color = Some(color4(color)))
166    }
167
168    /// Set the fill alpha multiplier used for fills and marker faces.
169    fn with_fill_alpha(self, alpha: f32) -> Self::Output {
170        self.map_style(|style| style.fill_alpha = Some(alpha))
171    }
172
173    /// Set the marker type.
174    fn with_marker(self, marker: crate::Marker3D) -> Self::Output {
175        self.map_style(|style| style.marker = Some(marker as sys::ImPlot3DMarker))
176    }
177
178    /// Set the marker size in pixels.
179    fn with_marker_size(self, size: f32) -> Self::Output {
180        self.map_style(|style| style.marker_size = Some(size))
181    }
182
183    /// Set the marker outline color.
184    fn with_marker_line_color(self, color: [f32; 4]) -> Self::Output {
185        self.map_style(|style| style.marker_line_color = Some(color4(color)))
186    }
187
188    /// Set the marker fill color.
189    fn with_marker_fill_color(self, color: [f32; 4]) -> Self::Output {
190        self.map_style(|style| style.marker_fill_color = Some(color4(color)))
191    }
192}
193
194/// Shared ImPlot3D item-flag builder methods for plot builders backed by `ImPlot3DSpec`.
195pub trait Plot3DItemFlagged: Sized {
196    /// The output type returned by item-flag building methods.
197    type Output;
198
199    fn map_item_flags<F>(self, f: F) -> Self::Output
200    where
201        F: FnOnce(&mut crate::Item3DFlags);
202
203    /// Set common item flags such as `NO_LEGEND` / `NO_FIT`.
204    fn with_item_flags(self, flags: crate::Item3DFlags) -> Self::Output {
205        self.map_item_flags(|current| *current = flags)
206    }
207}
208
209/// Styled wrapper for plot types that do not store item-style state directly.
210pub struct StyledPlot3D<T> {
211    inner: T,
212    style: Plot3DItemStyle,
213    item_flags: crate::Item3DFlags,
214}
215
216impl<T> StyledPlot3D<T> {
217    /// Consume the wrapper and return the wrapped plot item.
218    pub fn into_inner(self) -> T {
219        self.inner
220    }
221
222    /// Borrow the wrapped plot item.
223    pub fn inner(&self) -> &T {
224        &self.inner
225    }
226}
227
228impl<T> Plot3DItemStyled for StyledPlot3D<T> {
229    type Output = Self;
230
231    fn map_style<F>(mut self, f: F) -> Self::Output
232    where
233        F: FnOnce(&mut Plot3DItemStyle),
234    {
235        f(&mut self.style);
236        self
237    }
238}
239
240impl<T> Plot3DItemFlagged for StyledPlot3D<T> {
241    type Output = Self;
242
243    fn map_item_flags<F>(mut self, f: F) -> Self::Output
244    where
245        F: FnOnce(&mut crate::Item3DFlags),
246    {
247        f(&mut self.item_flags);
248        self
249    }
250}
251
252impl<T> Plot3D for StyledPlot3D<T>
253where
254    T: Plot3D,
255{
256    fn label(&self) -> &str {
257        self.inner.label()
258    }
259
260    fn try_plot(&self, ui: &crate::Plot3DUi<'_>) -> Result<(), crate::plots::Plot3DError> {
261        with_scoped_next_plot3d_spec(self.style, self.item_flags, || self.inner.try_plot(ui))
262    }
263}
264
265macro_rules! impl_wrapped_plot3d_item_styled {
266    ($ty:ty) => {
267        impl Plot3DItemStyled for $ty {
268            type Output = StyledPlot3D<Self>;
269
270            fn map_style<F>(self, f: F) -> Self::Output
271            where
272                F: FnOnce(&mut Plot3DItemStyle),
273            {
274                let mut style = Plot3DItemStyle::default();
275                f(&mut style);
276                StyledPlot3D {
277                    inner: self,
278                    style,
279                    item_flags: crate::Item3DFlags::NONE,
280                }
281            }
282        }
283    };
284}
285
286macro_rules! impl_wrapped_plot3d_item_flagged {
287    ($ty:ty) => {
288        impl Plot3DItemFlagged for $ty {
289            type Output = StyledPlot3D<Self>;
290
291            fn map_item_flags<F>(self, f: F) -> Self::Output
292            where
293                F: FnOnce(&mut crate::Item3DFlags),
294            {
295                let mut item_flags = crate::Item3DFlags::NONE;
296                f(&mut item_flags);
297                StyledPlot3D {
298                    inner: self,
299                    style: Plot3DItemStyle::default(),
300                    item_flags,
301                }
302            }
303        }
304    };
305}
306
307impl_wrapped_plot3d_item_styled!(crate::plots::Line3D<'_>);
308impl_wrapped_plot3d_item_styled!(crate::plots::Scatter3D<'_>);
309impl_wrapped_plot3d_item_styled!(crate::plots::Surface3D<'_>);
310impl_wrapped_plot3d_item_styled!(crate::plots::Triangles3D<'_>);
311impl_wrapped_plot3d_item_styled!(crate::plots::Quads3D<'_>);
312impl_wrapped_plot3d_item_styled!(crate::plots::Mesh3D<'_>);
313impl_wrapped_plot3d_item_flagged!(crate::plots::Line3D<'_>);
314impl_wrapped_plot3d_item_flagged!(crate::plots::Scatter3D<'_>);
315impl_wrapped_plot3d_item_flagged!(crate::plots::Surface3D<'_>);
316impl_wrapped_plot3d_item_flagged!(crate::plots::Triangles3D<'_>);
317impl_wrapped_plot3d_item_flagged!(crate::plots::Quads3D<'_>);
318impl_wrapped_plot3d_item_flagged!(crate::plots::Mesh3D<'_>);
319
320impl<'a, T> Plot3DItemStyled for crate::plots::Image3DByAxes<'a, T>
321where
322    T: Into<TextureRef<'a>> + Copy,
323{
324    type Output = StyledPlot3D<Self>;
325
326    fn map_style<F>(self, f: F) -> Self::Output
327    where
328        F: FnOnce(&mut Plot3DItemStyle),
329    {
330        let mut style = Plot3DItemStyle::default();
331        f(&mut style);
332        StyledPlot3D {
333            inner: self,
334            style,
335            item_flags: crate::Item3DFlags::NONE,
336        }
337    }
338}
339
340impl<'a, T> Plot3DItemFlagged for crate::plots::Image3DByAxes<'a, T>
341where
342    T: Into<TextureRef<'a>> + Copy,
343{
344    type Output = StyledPlot3D<Self>;
345
346    fn map_item_flags<F>(self, f: F) -> Self::Output
347    where
348        F: FnOnce(&mut crate::Item3DFlags),
349    {
350        let mut item_flags = crate::Item3DFlags::NONE;
351        f(&mut item_flags);
352        StyledPlot3D {
353            inner: self,
354            style: Plot3DItemStyle::default(),
355            item_flags,
356        }
357    }
358}
359
360impl<'a, T> Plot3DItemStyled for crate::plots::Image3DByCorners<'a, T>
361where
362    T: Into<TextureRef<'a>> + Copy,
363{
364    type Output = StyledPlot3D<Self>;
365
366    fn map_style<F>(self, f: F) -> Self::Output
367    where
368        F: FnOnce(&mut Plot3DItemStyle),
369    {
370        let mut style = Plot3DItemStyle::default();
371        f(&mut style);
372        StyledPlot3D {
373            inner: self,
374            style,
375            item_flags: crate::Item3DFlags::NONE,
376        }
377    }
378}
379
380impl<'a, T> Plot3DItemFlagged for crate::plots::Image3DByCorners<'a, T>
381where
382    T: Into<TextureRef<'a>> + Copy,
383{
384    type Output = StyledPlot3D<Self>;
385
386    fn map_item_flags<F>(self, f: F) -> Self::Output
387    where
388        F: FnOnce(&mut crate::Item3DFlags),
389    {
390        let mut item_flags = crate::Item3DFlags::NONE;
391        f(&mut item_flags);
392        StyledPlot3D {
393            inner: self,
394            style: Plot3DItemStyle::default(),
395            item_flags,
396        }
397    }
398}
399
400macro_rules! impl_builder_plot3d_item_styled {
401    ($ty:ty) => {
402        impl Plot3DItemStyled for $ty {
403            type Output = Self;
404
405            fn map_style<F>(mut self, f: F) -> Self::Output
406            where
407                F: FnOnce(&mut Plot3DItemStyle),
408            {
409                f(&mut self.style);
410                self
411            }
412        }
413    };
414}
415
416macro_rules! impl_builder_plot3d_item_flagged {
417    ($ty:ty) => {
418        impl Plot3DItemFlagged for $ty {
419            type Output = Self;
420
421            fn map_item_flags<F>(mut self, f: F) -> Self::Output
422            where
423                F: FnOnce(&mut crate::Item3DFlags),
424            {
425                f(&mut self.item_flags);
426                self
427            }
428        }
429    };
430}
431
432impl_builder_plot3d_item_styled!(crate::Surface3DBuilder<'_>);
433impl_builder_plot3d_item_styled!(crate::Image3DByAxesBuilder<'_, '_>);
434impl_builder_plot3d_item_styled!(crate::Image3DByCornersBuilder<'_, '_>);
435impl_builder_plot3d_item_styled!(crate::Mesh3DBuilder<'_>);
436impl_builder_plot3d_item_flagged!(crate::Surface3DBuilder<'_>);
437impl_builder_plot3d_item_flagged!(crate::Image3DByAxesBuilder<'_, '_>);
438impl_builder_plot3d_item_flagged!(crate::Image3DByCornersBuilder<'_, '_>);
439impl_builder_plot3d_item_flagged!(crate::Mesh3DBuilder<'_>);
440
441#[cfg(test)]
442mod tests {
443    use super::{Plot3DItemStyle, plot3d_spec_with_style, with_scoped_next_plot3d_spec};
444    use crate::{
445        Item3DFlags, Marker3D, Plot3DDataLayout, Plot3DDataOffset, Plot3DDataStride,
446        default_plot3d_spec, set_next_plot3d_spec, take_next_plot3d_spec,
447    };
448
449    #[test]
450    fn plot3d_item_style_applies_fields() {
451        let style = Plot3DItemStyle::new()
452            .with_line_color([0.1, 0.2, 0.3, 0.4])
453            .with_line_weight(2.5)
454            .with_fill_color([0.5, 0.6, 0.7, 0.8])
455            .with_fill_alpha(0.35)
456            .with_marker(Marker3D::Auto)
457            .with_marker_size(6.0)
458            .with_marker_line_color([0.9, 0.1, 0.2, 1.0])
459            .with_marker_fill_color([0.3, 0.4, 0.5, 0.6]);
460
461        let layout =
462            Plot3DDataLayout::new(Plot3DDataOffset::samples(7), Plot3DDataStride::bytes(16));
463        let spec = plot3d_spec_with_style(style, 0, layout);
464
465        assert_eq!(spec.LineColor.x, 0.1);
466        assert_eq!(spec.LineColor.y, 0.2);
467        assert_eq!(spec.LineColor.z, 0.3);
468        assert_eq!(spec.LineColor.w, 0.4);
469        assert_eq!(spec.LineWeight, 2.5);
470        assert_eq!(spec.FillColor.x, 0.5);
471        assert_eq!(spec.FillColor.y, 0.6);
472        assert_eq!(spec.FillColor.z, 0.7);
473        assert_eq!(spec.FillColor.w, 0.8);
474        assert_eq!(spec.FillAlpha, 0.35);
475        assert_eq!(spec.Marker, crate::sys::ImPlot3DMarker_Auto as _);
476        assert_eq!(spec.MarkerSize, 6.0);
477        assert_eq!(spec.MarkerLineColor.x, 0.9);
478        assert_eq!(spec.MarkerFillColor.z, 0.5);
479        assert_eq!(spec.Offset, 7);
480        assert_eq!(spec.Stride, 16);
481    }
482
483    #[test]
484    fn scoped_next_spec_restores_previous_when_not_consumed() {
485        set_next_plot3d_spec(None);
486
487        let mut previous = default_plot3d_spec();
488        previous.FillAlpha = 0.25;
489        set_next_plot3d_spec(Some(previous));
490
491        let out = with_scoped_next_plot3d_spec(
492            Plot3DItemStyle::new().with_line_weight(2.0),
493            Item3DFlags::NONE,
494            || "no-plot",
495        );
496
497        assert_eq!(out, "no-plot");
498
499        let restored = take_next_plot3d_spec().expect("previous next spec should be restored");
500        assert_eq!(restored.FillAlpha, 0.25);
501        assert_eq!(restored.LineWeight, 1.0);
502
503        set_next_plot3d_spec(None);
504    }
505
506    #[test]
507    fn scoped_next_spec_merges_with_previous_when_consumed() {
508        set_next_plot3d_spec(None);
509
510        let mut previous = default_plot3d_spec();
511        previous.FillAlpha = 0.25;
512        set_next_plot3d_spec(Some(previous));
513
514        let consumed = with_scoped_next_plot3d_spec(
515            Plot3DItemStyle::new()
516                .with_line_weight(3.0)
517                .with_marker(Marker3D::Auto),
518            Item3DFlags::NO_LEGEND,
519            || {
520                let layout = Plot3DDataLayout::new(
521                    Plot3DDataOffset::samples(5),
522                    Plot3DDataStride::bytes(12),
523                );
524                crate::plot3d_spec_from(0, layout)
525            },
526        );
527
528        assert_eq!(consumed.FillAlpha, 0.25);
529        assert_eq!(consumed.LineWeight, 3.0);
530        assert_eq!(consumed.Marker, crate::sys::ImPlot3DMarker_Auto as _);
531        assert_eq!(consumed.Flags as u32, Item3DFlags::NO_LEGEND.bits(),);
532        assert_eq!(consumed.Offset, 5);
533        assert_eq!(consumed.Stride, 12);
534        assert!(take_next_plot3d_spec().is_none());
535    }
536
537    #[test]
538    fn scoped_next_spec_item_flags_merge_with_plot_flags() {
539        set_next_plot3d_spec(None);
540
541        let consumed = with_scoped_next_plot3d_spec(
542            Plot3DItemStyle::default(),
543            Item3DFlags::NO_LEGEND,
544            || {
545                crate::plot3d_spec_from(
546                    crate::Line3DFlags::SEGMENTS.bits(),
547                    Plot3DDataLayout::DEFAULT.with_stride(Plot3DDataStride::bytes(4)),
548                )
549            },
550        );
551
552        assert_eq!(
553            consumed.Flags as u32,
554            Item3DFlags::NO_LEGEND.bits() | crate::Line3DFlags::SEGMENTS.bits(),
555        );
556        assert!(take_next_plot3d_spec().is_none());
557    }
558}