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
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
139pub trait Plot3DItemStyled: Sized {
141 type Output;
143
144 fn map_style<F>(self, f: F) -> Self::Output
145 where
146 F: FnOnce(&mut Plot3DItemStyle);
147
148 fn with_style(self, style: Plot3DItemStyle) -> Self::Output {
150 self.map_style(|current| *current = style)
151 }
152
153 fn with_line_color(self, color: [f32; 4]) -> Self::Output {
155 self.map_style(|style| style.line_color = Some(color4(color)))
156 }
157
158 fn with_line_weight(self, weight: f32) -> Self::Output {
160 self.map_style(|style| style.line_weight = Some(weight))
161 }
162
163 fn with_fill_color(self, color: [f32; 4]) -> Self::Output {
165 self.map_style(|style| style.fill_color = Some(color4(color)))
166 }
167
168 fn with_fill_alpha(self, alpha: f32) -> Self::Output {
170 self.map_style(|style| style.fill_alpha = Some(alpha))
171 }
172
173 fn with_marker(self, marker: crate::Marker3D) -> Self::Output {
175 self.map_style(|style| style.marker = Some(marker as sys::ImPlot3DMarker))
176 }
177
178 fn with_marker_size(self, size: f32) -> Self::Output {
180 self.map_style(|style| style.marker_size = Some(size))
181 }
182
183 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 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
194pub trait Plot3DItemFlagged: Sized {
196 type Output;
198
199 fn map_item_flags<F>(self, f: F) -> Self::Output
200 where
201 F: FnOnce(&mut crate::Item3DFlags);
202
203 fn with_item_flags(self, flags: crate::Item3DFlags) -> Self::Output {
205 self.map_item_flags(|current| *current = flags)
206 }
207}
208
209pub struct StyledPlot3D<T> {
211 inner: T,
212 style: Plot3DItemStyle,
213 item_flags: crate::Item3DFlags,
214}
215
216impl<T> StyledPlot3D<T> {
217 pub fn into_inner(self) -> T {
219 self.inner
220 }
221
222 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}