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#[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 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
140pub trait Plot3DItemStyled: Sized {
142 type Output;
144
145 fn map_style<F>(self, f: F) -> Self::Output
146 where
147 F: FnOnce(&mut Plot3DItemStyle);
148
149 fn with_style(self, style: Plot3DItemStyle) -> Self::Output {
151 self.map_style(|current| *current = style)
152 }
153
154 fn with_line_color(self, color: [f32; 4]) -> Self::Output {
156 self.map_style(|style| style.line_color = Some(color4(color)))
157 }
158
159 fn with_line_weight(self, weight: f32) -> Self::Output {
161 self.map_style(|style| style.line_weight = Some(weight))
162 }
163
164 fn with_fill_color(self, color: [f32; 4]) -> Self::Output {
166 self.map_style(|style| style.fill_color = Some(color4(color)))
167 }
168
169 fn with_fill_alpha(self, alpha: f32) -> Self::Output {
171 self.map_style(|style| style.fill_alpha = Some(alpha))
172 }
173
174 fn with_marker(self, marker: crate::Marker3D) -> Self::Output {
176 self.map_style(|style| style.marker = Some(marker as sys::ImPlot3DMarker))
177 }
178
179 fn with_marker_size(self, size: f32) -> Self::Output {
181 self.map_style(|style| style.marker_size = Some(size))
182 }
183
184 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 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
195pub trait Plot3DItemFlagged: Sized {
197 type Output;
199
200 fn map_item_flags<F>(self, f: F) -> Self::Output
201 where
202 F: FnOnce(&mut crate::Item3DFlags);
203
204 fn with_item_flags(self, flags: crate::Item3DFlags) -> Self::Output {
206 self.map_item_flags(|current| *current = flags)
207 }
208}
209
210pub struct StyledPlot3D<T> {
212 inner: T,
213 style: Plot3DItemStyle,
214 item_flags: crate::Item3DFlags,
215}
216
217impl<T> StyledPlot3D<T> {
218 pub fn into_inner(self) -> T {
220 self.inner
221 }
222
223 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}