Skip to main content

dear_implot3d/
item_style.rs

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