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#[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 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn with_line_color(mut self, color: [f32; 4]) -> Self {
35 self.line_color = Some(color4(color));
36 self
37 }
38
39 pub fn with_line_weight(mut self, weight: f32) -> Self {
41 self.line_weight = Some(weight);
42 self
43 }
44
45 pub fn with_fill_color(mut self, color: [f32; 4]) -> Self {
47 self.fill_color = Some(color4(color));
48 self
49 }
50
51 pub fn with_fill_alpha(mut self, alpha: f32) -> Self {
53 self.fill_alpha = Some(alpha);
54 self
55 }
56
57 pub fn with_marker(mut self, marker: crate::Marker3D) -> Self {
59 self.marker = Some(marker as sys::ImPlot3DMarker);
60 self
61 }
62
63 pub fn with_marker_size(mut self, size: f32) -> Self {
65 self.marker_size = Some(size);
66 self
67 }
68
69 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 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
163pub trait Plot3DItemStyled: Sized {
165 type Output;
167
168 fn map_style<F>(self, f: F) -> Self::Output
169 where
170 F: FnOnce(&mut Plot3DItemStyle);
171
172 fn with_style(self, style: Plot3DItemStyle) -> Self::Output {
174 self.map_style(|current| *current = style)
175 }
176
177 fn with_line_color(self, color: [f32; 4]) -> Self::Output {
179 self.map_style(|style| style.line_color = Some(color4(color)))
180 }
181
182 fn with_line_weight(self, weight: f32) -> Self::Output {
184 self.map_style(|style| style.line_weight = Some(weight))
185 }
186
187 fn with_fill_color(self, color: [f32; 4]) -> Self::Output {
189 self.map_style(|style| style.fill_color = Some(color4(color)))
190 }
191
192 fn with_fill_alpha(self, alpha: f32) -> Self::Output {
194 self.map_style(|style| style.fill_alpha = Some(alpha))
195 }
196
197 fn with_marker(self, marker: crate::Marker3D) -> Self::Output {
199 self.map_style(|style| style.marker = Some(marker as sys::ImPlot3DMarker))
200 }
201
202 fn with_marker_size(self, size: f32) -> Self::Output {
204 self.map_style(|style| style.marker_size = Some(size))
205 }
206
207 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 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
218pub trait Plot3DItemFlagged: Sized {
220 type Output;
222
223 fn map_item_flags<F>(self, f: F) -> Self::Output
224 where
225 F: FnOnce(&mut crate::Item3DFlags);
226
227 fn with_item_flags(self, flags: crate::Item3DFlags) -> Self::Output {
229 self.map_item_flags(|current| *current = flags)
230 }
231}
232
233pub struct StyledPlot3D<T> {
235 inner: T,
236 style: Plot3DItemStyle,
237 item_flags: crate::Item3DFlags,
238}
239
240impl<T> StyledPlot3D<T> {
241 pub fn into_inner(self) -> T {
243 self.inner
244 }
245
246 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}