1use crate::sys;
4use crate::{FloatFormat, PlotContext, PlotContextBinding, PlotUi};
5use dear_imgui_rs::{with_scratch_txt, with_scratch_txt_two};
6use std::borrow::Cow;
7use std::marker::PhantomData;
8use std::rc::Rc;
9
10use crate::Colormap;
11
12const DEFAULT_COLORMAP_SCALE_FORMAT: &str = "%g";
13const DEFAULT_COLORMAP_SLIDER_FORMAT: &str = "";
14
15#[repr(i32)]
17#[derive(Copy, Clone, Debug, PartialEq, Eq)]
18pub enum StyleVar {
19 PlotDefaultSize = sys::ImPlotStyleVar_PlotDefaultSize as i32,
20 PlotMinSize = sys::ImPlotStyleVar_PlotMinSize as i32,
21 PlotBorderSize = sys::ImPlotStyleVar_PlotBorderSize as i32,
22 MinorAlpha = sys::ImPlotStyleVar_MinorAlpha as i32,
23 MajorTickLen = sys::ImPlotStyleVar_MajorTickLen as i32,
24 MinorTickLen = sys::ImPlotStyleVar_MinorTickLen as i32,
25 MajorTickSize = sys::ImPlotStyleVar_MajorTickSize as i32,
26 MinorTickSize = sys::ImPlotStyleVar_MinorTickSize as i32,
27 MajorGridSize = sys::ImPlotStyleVar_MajorGridSize as i32,
28 MinorGridSize = sys::ImPlotStyleVar_MinorGridSize as i32,
29 PlotPadding = sys::ImPlotStyleVar_PlotPadding as i32,
30 LabelPadding = sys::ImPlotStyleVar_LabelPadding as i32,
31 LegendPadding = sys::ImPlotStyleVar_LegendPadding as i32,
32 LegendInnerPadding = sys::ImPlotStyleVar_LegendInnerPadding as i32,
33 LegendSpacing = sys::ImPlotStyleVar_LegendSpacing as i32,
34 MousePosPadding = sys::ImPlotStyleVar_MousePosPadding as i32,
35 AnnotationPadding = sys::ImPlotStyleVar_AnnotationPadding as i32,
36 FitPadding = sys::ImPlotStyleVar_FitPadding as i32,
37 DigitalPadding = sys::ImPlotStyleVar_DigitalPadding as i32,
38 DigitalSpacing = sys::ImPlotStyleVar_DigitalSpacing as i32,
39}
40
41pub struct StyleVarToken<'ui> {
43 binding: PlotContextBinding,
44 was_popped: bool,
45 _lifetime: PhantomData<&'ui PlotUi<'ui>>,
46 _not_send_or_sync: PhantomData<Rc<()>>,
47}
48
49impl StyleVarToken<'_> {
50 pub fn pop(mut self) {
52 self.pop_inner();
53 }
54
55 fn pop_inner(&mut self) {
56 if self.was_popped {
57 panic!("Attempted to pop an ImPlot style var token twice.");
58 }
59 self.binding
60 .with_bound_context("dear-implot: StyleVarToken", || {
61 unsafe { sys::ImPlot_PopStyleVar(1) };
62 });
63 self.was_popped = true;
64 }
65}
66
67impl Drop for StyleVarToken<'_> {
68 fn drop(&mut self) {
69 if !self.was_popped {
70 let _ = self
71 .binding
72 .try_with_bound_context(|| unsafe { sys::ImPlot_PopStyleVar(1) });
73 self.was_popped = true;
74 }
75 }
76}
77
78pub struct StyleColorToken<'ui> {
80 binding: PlotContextBinding,
81 was_popped: bool,
82 _lifetime: PhantomData<&'ui PlotUi<'ui>>,
83 _not_send_or_sync: PhantomData<Rc<()>>,
84}
85
86impl StyleColorToken<'_> {
87 pub fn pop(mut self) {
89 self.pop_inner();
90 }
91
92 fn pop_inner(&mut self) {
93 if self.was_popped {
94 panic!("Attempted to pop an ImPlot style color token twice.");
95 }
96 self.binding
97 .with_bound_context("dear-implot: StyleColorToken", || {
98 unsafe { sys::ImPlot_PopStyleColor(1) };
99 });
100 self.was_popped = true;
101 }
102}
103
104impl Drop for StyleColorToken<'_> {
105 fn drop(&mut self) {
106 if !self.was_popped {
107 let _ = self
108 .binding
109 .try_with_bound_context(|| unsafe { sys::ImPlot_PopStyleColor(1) });
110 self.was_popped = true;
111 }
112 }
113}
114
115#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
117#[repr(transparent)]
118pub struct ColormapIndex(pub(crate) i32);
119
120impl ColormapIndex {
121 #[inline]
122 pub const fn new(index: usize) -> Option<Self> {
123 if index <= i32::MAX as usize {
124 Some(Self(index as i32))
125 } else {
126 None
127 }
128 }
129
130 #[inline]
131 pub const fn get(self) -> usize {
132 self.0 as usize
133 }
134
135 #[inline]
136 pub const fn raw(self) -> i32 {
137 self.0
138 }
139
140 #[inline]
141 pub(crate) const fn from_raw(raw: i32) -> Option<Self> {
142 if raw >= 0 { Some(Self(raw)) } else { None }
143 }
144}
145
146impl From<Colormap> for ColormapIndex {
147 #[inline]
148 fn from(value: Colormap) -> Self {
149 value.index()
150 }
151}
152
153impl From<usize> for ColormapIndex {
154 #[inline]
155 fn from(value: usize) -> Self {
156 Self::new(value).expect("colormap index exceeded ImPlot's i32 range")
157 }
158}
159
160#[derive(Copy, Clone, Debug, PartialEq, Eq)]
162pub enum ColormapSelection {
163 Current,
164 Index(ColormapIndex),
165}
166
167impl ColormapSelection {
168 #[inline]
169 pub(crate) const fn raw(self) -> i32 {
170 match self {
171 Self::Current => crate::IMPLOT_AUTO,
172 Self::Index(index) => index.raw(),
173 }
174 }
175}
176
177impl From<Colormap> for ColormapSelection {
178 #[inline]
179 fn from(value: Colormap) -> Self {
180 Self::Index(value.index())
181 }
182}
183
184impl From<ColormapIndex> for ColormapSelection {
185 #[inline]
186 fn from(value: ColormapIndex) -> Self {
187 Self::Index(value)
188 }
189}
190
191impl From<Option<ColormapIndex>> for ColormapSelection {
192 #[inline]
193 fn from(value: Option<ColormapIndex>) -> Self {
194 value.map_or(Self::Current, Self::Index)
195 }
196}
197
198#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
200#[repr(transparent)]
201pub struct ColormapColorIndex(i32);
202
203impl ColormapColorIndex {
204 #[inline]
205 pub const fn new(index: usize) -> Option<Self> {
206 Self::from_usize(index)
207 }
208
209 #[inline]
210 pub const fn from_usize(index: usize) -> Option<Self> {
211 if index <= i32::MAX as usize {
212 Some(Self(index as i32))
213 } else {
214 None
215 }
216 }
217
218 #[inline]
219 pub const fn get(self) -> usize {
220 self.0 as usize
221 }
222
223 #[inline]
224 pub const fn raw(self) -> i32 {
225 self.0
226 }
227}
228
229impl From<usize> for ColormapColorIndex {
230 #[inline]
231 fn from(value: usize) -> Self {
232 Self::new(value).expect("colormap color index exceeded ImPlot's i32 range")
233 }
234}
235
236#[must_use]
238pub struct ColormapToken<'ui> {
239 binding: PlotContextBinding,
240 was_popped: bool,
241 _lifetime: PhantomData<&'ui PlotUi<'ui>>,
242 _not_send_or_sync: PhantomData<Rc<()>>,
243}
244
245impl ColormapToken<'_> {
246 pub fn pop(mut self) {
248 self.pop_inner();
249 }
250
251 fn pop_inner(&mut self) {
252 if self.was_popped {
253 panic!("Attempted to pop an ImPlot colormap token twice.");
254 }
255 self.binding
256 .with_bound_context("dear-implot: ColormapToken", || {
257 unsafe { sys::ImPlot_PopColormap(1) };
258 });
259 self.was_popped = true;
260 }
261}
262
263impl Drop for ColormapToken<'_> {
264 fn drop(&mut self) {
265 if !self.was_popped {
266 let _ = self
267 .binding
268 .try_with_bound_context(|| unsafe { sys::ImPlot_PopColormap(1) });
269 self.was_popped = true;
270 }
271 }
272}
273
274#[derive(Debug, Clone, Default, PartialEq)]
279pub struct PlotItemArrayStyle<'a> {
280 line_colors: Option<Cow<'a, [u32]>>,
281 fill_colors: Option<Cow<'a, [u32]>>,
282 marker_sizes: Option<Cow<'a, [f32]>>,
283 marker_line_colors: Option<Cow<'a, [u32]>>,
284 marker_fill_colors: Option<Cow<'a, [u32]>>,
285}
286
287impl<'a> PlotItemArrayStyle<'a> {
288 pub fn new() -> Self {
290 Self::default()
291 }
292
293 pub fn with_line_colors(mut self, colors: &'a [u32]) -> Self {
295 self.line_colors = Some(Cow::Borrowed(colors));
296 self
297 }
298
299 pub fn with_fill_colors(mut self, colors: &'a [u32]) -> Self {
301 self.fill_colors = Some(Cow::Borrowed(colors));
302 self
303 }
304
305 pub fn with_marker_sizes(mut self, sizes: &'a [f32]) -> Self {
307 self.marker_sizes = Some(Cow::Borrowed(sizes));
308 self
309 }
310
311 pub fn with_marker_line_colors(mut self, colors: &'a [u32]) -> Self {
313 self.marker_line_colors = Some(Cow::Borrowed(colors));
314 self
315 }
316
317 pub fn with_marker_fill_colors(mut self, colors: &'a [u32]) -> Self {
319 self.marker_fill_colors = Some(Cow::Borrowed(colors));
320 self
321 }
322
323 fn apply_to_spec(&self, spec: &mut sys::ImPlotSpec_c) {
324 spec.LineColors = self
325 .line_colors
326 .as_ref()
327 .map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
328 spec.FillColors = self
329 .fill_colors
330 .as_ref()
331 .map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
332 spec.MarkerSizes = self
333 .marker_sizes
334 .as_ref()
335 .map_or(std::ptr::null_mut(), |sizes| sizes.as_ptr() as *mut _);
336 spec.MarkerLineColors = self
337 .marker_line_colors
338 .as_ref()
339 .map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
340 spec.MarkerFillColors = self
341 .marker_fill_colors
342 .as_ref()
343 .map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
344 }
345}
346
347struct ScopedNextPlotItemArrayStyle {
348 previous: Option<sys::ImPlotSpec_c>,
349 active: bool,
350}
351
352impl ScopedNextPlotItemArrayStyle {
353 fn restore_if_unused(&mut self) {
354 if !self.active {
355 return;
356 }
357
358 if crate::plots::take_next_plot_spec().is_some() {
359 crate::plots::set_next_plot_spec(self.previous.take());
360 }
361 self.active = false;
362 }
363}
364
365impl Drop for ScopedNextPlotItemArrayStyle {
366 fn drop(&mut self) {
367 self.restore_if_unused();
368 }
369}
370
371fn with_scoped_next_plot_item_array_style<'a, R>(
372 style: PlotItemArrayStyle<'a>,
373 f: impl FnOnce() -> R,
374) -> R {
375 let previous = crate::plots::take_next_plot_spec();
376 let mut spec = previous.unwrap_or_else(crate::plots::default_plot_spec);
377 style.apply_to_spec(&mut spec);
378 crate::plots::set_next_plot_spec(Some(spec));
379
380 let mut guard = ScopedNextPlotItemArrayStyle {
381 previous,
382 active: true,
383 };
384 let out = f();
385 guard.restore_if_unused();
386 out
387}
388
389impl<'ui> PlotUi<'ui> {
390 pub unsafe fn with_next_plot_item_array_style<'a, R>(
401 &self,
402 style: PlotItemArrayStyle<'a>,
403 f: impl FnOnce(&PlotUi<'ui>) -> R,
404 ) -> R {
405 self.with_bound_context(|| with_scoped_next_plot_item_array_style(style, || f(self)))
406 }
407
408 pub fn push_style_var_f32(&self, var: StyleVar, value: f32) -> StyleVarToken<'_> {
410 self.with_bound_context(|| {
411 unsafe {
412 sys::ImPlot_PushStyleVar_Float(var as sys::ImPlotStyleVar, value);
413 }
414 StyleVarToken {
415 binding: self.context.binding(),
416 was_popped: false,
417 _lifetime: PhantomData,
418 _not_send_or_sync: PhantomData,
419 }
420 })
421 }
422
423 pub fn push_style_var_vec2(&self, var: StyleVar, value: [f32; 2]) -> StyleVarToken<'_> {
425 self.with_bound_context(|| {
426 unsafe {
427 sys::ImPlot_PushStyleVar_Vec2(
428 var as sys::ImPlotStyleVar,
429 sys::ImVec2_c {
430 x: value[0],
431 y: value[1],
432 },
433 );
434 }
435 StyleVarToken {
436 binding: self.context.binding(),
437 was_popped: false,
438 _lifetime: PhantomData,
439 _not_send_or_sync: PhantomData,
440 }
441 })
442 }
443
444 pub fn push_style_color(
446 &self,
447 element: crate::PlotColorElement,
448 color: [f32; 4],
449 ) -> StyleColorToken<'_> {
450 self.with_bound_context(|| {
451 unsafe {
452 let r = (color[0] * 255.0) as u32;
454 let g = (color[1] * 255.0) as u32;
455 let b = (color[2] * 255.0) as u32;
456 let a = (color[3] * 255.0) as u32;
457 let color_u32 = (a << 24) | (b << 16) | (g << 8) | r;
458
459 sys::ImPlot_PushStyleColor_U32(element as sys::ImPlotCol, color_u32);
460 }
461 StyleColorToken {
462 binding: self.context.binding(),
463 was_popped: false,
464 _lifetime: PhantomData,
465 _not_send_or_sync: PhantomData,
466 }
467 })
468 }
469
470 pub fn push_colormap(&self, cmap: impl Into<ColormapIndex>) -> ColormapToken<'_> {
472 self.with_bound_context(|| {
473 unsafe {
474 sys::ImPlot_PushColormap_PlotColormap(cmap.into().raw());
475 }
476 ColormapToken {
477 binding: self.context.binding(),
478 was_popped: false,
479 _lifetime: PhantomData,
480 _not_send_or_sync: PhantomData,
481 }
482 })
483 }
484
485 pub fn push_colormap_name(&self, name: &str) -> ColormapToken<'_> {
487 assert!(!name.contains('\0'), "colormap name contained NUL");
488 self.with_bound_context(|| {
489 with_scratch_txt(name, |ptr| unsafe { sys::ImPlot_PushColormap_Str(ptr) });
490 ColormapToken {
491 binding: self.context.binding(),
492 was_popped: false,
493 _lifetime: PhantomData,
494 _not_send_or_sync: PhantomData,
495 }
496 })
497 }
498}
499
500fn colormap_count_from_i32(raw: i32, caller: &str) -> usize {
501 assert!(raw >= 0, "{caller} returned a negative colormap count");
502 usize::try_from(raw).expect("non-negative colormap count must fit usize")
503}
504
505fn assert_colormap_sample_t(t: f32) {
506 assert!(
507 (0.0..=1.0).contains(&t),
508 "sample_colormap t must be between 0 and 1"
509 );
510}
511
512impl PlotContext {
513 #[inline]
514 fn with_bound_style<R>(&self, caller: &str, f: impl FnOnce() -> R) -> R {
515 self.binding().with_bound_context(caller, || f())
516 }
517
518 pub fn add_colormap(
520 &self,
521 name: &str,
522 colors: &[[f32; 4]],
523 qualitative: bool,
524 ) -> ColormapIndex {
525 assert!(!name.contains('\0'), "colormap name contained NUL");
526 assert!(
527 colors.len() > 1,
528 "colormap must contain at least two colors"
529 );
530 assert!(
531 colors
532 .iter()
533 .flatten()
534 .all(|component| component.is_finite()),
535 "colormap colors must be finite"
536 );
537 let count = i32::try_from(colors.len()).expect("colormap contained too many colors");
538 let colors: Vec<sys::ImVec4> = colors
539 .iter()
540 .map(|color| sys::ImVec4 {
541 x: color[0],
542 y: color[1],
543 z: color[2],
544 w: color[3],
545 })
546 .collect();
547 let index = self.with_bound_style("dear-implot: PlotContext::add_colormap()", || {
548 with_scratch_txt(name, |ptr| unsafe {
549 sys::ImPlot_AddColormap_Vec4Ptr(ptr, colors.as_ptr(), count, qualitative)
550 })
551 });
552 ColormapIndex::from_raw(index).expect("ImPlot returned a negative colormap index")
553 }
554
555 pub fn colormap_count(&self) -> usize {
557 self.with_bound_style("dear-implot: PlotContext::colormap_count()", || {
558 colormap_count_from_i32(
559 unsafe { sys::ImPlot_GetColormapCount() },
560 "PlotContext::colormap_count()",
561 )
562 })
563 }
564
565 pub fn colormap_name(&self, index: impl Into<ColormapIndex>) -> String {
567 self.with_bound_style("dear-implot: PlotContext::colormap_name()", || unsafe {
568 let p = sys::ImPlot_GetColormapName(index.into().raw());
569 if p.is_null() {
570 return String::new();
571 }
572 std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
573 })
574 }
575
576 pub fn colormap_index_by_name(&self, name: &str) -> Option<ColormapIndex> {
578 if name.contains('\0') {
579 return None;
580 }
581 let index = self
582 .with_bound_style("dear-implot: PlotContext::colormap_index_by_name()", || {
583 with_scratch_txt(name, |ptr| unsafe { sys::ImPlot_GetColormapIndex(ptr) })
584 });
585 ColormapIndex::from_raw(index)
586 }
587
588 pub fn colormap_size(&self, index: impl Into<ColormapIndex>) -> usize {
590 self.with_bound_style("dear-implot: PlotContext::colormap_size()", || {
591 colormap_count_from_i32(
592 unsafe { sys::ImPlot_GetColormapSize(index.into().raw()) },
593 "PlotContext::colormap_size()",
594 )
595 })
596 }
597
598 pub fn style_colormap_index(&self) -> Option<ColormapIndex> {
600 self.with_bound_style(
601 "dear-implot: PlotContext::style_colormap_index()",
602 || unsafe {
603 let style = sys::ImPlot_GetStyle();
604 if style.is_null() {
605 return None;
606 }
607 ColormapIndex::from_raw((*style).Colormap)
608 },
609 )
610 }
611
612 pub fn style_colormap_name(&self) -> Option<String> {
614 let idx = self.style_colormap_index()?;
615 let count = self.colormap_count();
616 if idx.get() >= count {
617 return None;
618 }
619 Some(self.colormap_name(idx))
620 }
621
622 pub fn set_style_colormap(&self, index: impl Into<ColormapIndex>) {
624 self.with_bound_style(
625 "dear-implot: PlotContext::set_style_colormap()",
626 || unsafe {
627 let style = sys::ImPlot_GetStyle();
628 if !style.is_null() {
629 let count = colormap_count_from_i32(
630 sys::ImPlot_GetColormapCount(),
631 "PlotContext::set_style_colormap()",
632 );
633 if count > 0 {
634 let index = index.into().get();
635 let idx = index.min(count - 1);
636 (*style).Colormap = ColormapIndex::from(idx).raw();
637 }
638 }
639 },
640 )
641 }
642
643 pub fn set_style_colormap_by_name(&self, name: &str) {
645 if let Some(idx) = self.colormap_index_by_name(name) {
646 self.set_style_colormap(idx);
647 }
648 }
649
650 pub fn colormap_color(&self, index: ColormapColorIndex) -> [f32; 4] {
652 self.with_bound_style("dear-implot: PlotContext::colormap_color()", || unsafe {
653 let out = sys::ImPlot_GetColormapColor(index.raw(), crate::IMPLOT_AUTO);
654 [out.x, out.y, out.z, out.w]
655 })
656 }
657
658 pub fn colormap_color_from(
660 &self,
661 index: ColormapColorIndex,
662 cmap: impl Into<ColormapIndex>,
663 ) -> [f32; 4] {
664 self.with_bound_style(
665 "dear-implot: PlotContext::colormap_color_from()",
666 || unsafe {
667 let out = sys::ImPlot_GetColormapColor(index.raw(), cmap.into().raw());
668 [out.x, out.y, out.z, out.w]
669 },
670 )
671 }
672
673 pub fn sample_colormap(&self, t: f32) -> [f32; 4] {
675 assert_colormap_sample_t(t);
676 self.with_bound_style("dear-implot: PlotContext::sample_colormap()", || unsafe {
677 let out = sys::ImPlot_SampleColormap(t, crate::IMPLOT_AUTO);
678 [out.x, out.y, out.z, out.w]
679 })
680 }
681
682 pub fn sample_colormap_from(&self, t: f32, cmap: impl Into<ColormapSelection>) -> [f32; 4] {
684 assert_colormap_sample_t(t);
685 self.with_bound_style(
686 "dear-implot: PlotContext::sample_colormap_from()",
687 || unsafe {
688 let out = sys::ImPlot_SampleColormap(t, cmap.into().raw());
689 [out.x, out.y, out.z, out.w]
690 },
691 )
692 }
693
694 pub fn next_colormap_color(&self) -> [f32; 4] {
696 self.with_bound_style(
697 "dear-implot: PlotContext::next_colormap_color()",
698 || unsafe {
699 let out = sys::ImPlot_NextColormapColor();
700 [out.x, out.y, out.z, out.w]
701 },
702 )
703 }
704
705 pub fn map_input_default(&self) {
707 self.with_bound_style("dear-implot: PlotContext::map_input_default()", || unsafe {
708 sys::ImPlot_MapInputDefault(sys::ImPlot_GetInputMap())
709 })
710 }
711
712 pub fn map_input_reverse(&self) {
714 self.with_bound_style("dear-implot: PlotContext::map_input_reverse()", || unsafe {
715 sys::ImPlot_MapInputReverse(sys::ImPlot_GetInputMap())
716 })
717 }
718}
719
720impl PlotUi<'_> {
721 pub fn show_style_editor(&self) {
723 self.with_bound_context(|| unsafe { sys::ImPlot_ShowStyleEditor(std::ptr::null_mut()) })
724 }
725
726 pub fn show_style_selector(&self, label: &str) -> bool {
728 let label = if label.contains('\0') { "" } else { label };
729 self.with_bound_context(|| {
730 with_scratch_txt(label, |ptr| unsafe { sys::ImPlot_ShowStyleSelector(ptr) })
731 })
732 }
733
734 pub fn show_colormap_selector(&self, label: &str) -> bool {
736 let label = if label.contains('\0') { "" } else { label };
737 self.with_bound_context(|| {
738 with_scratch_txt(label, |ptr| unsafe {
739 sys::ImPlot_ShowColormapSelector(ptr)
740 })
741 })
742 }
743
744 pub fn show_input_map_selector(&self, label: &str) -> bool {
746 let label = if label.contains('\0') { "" } else { label };
747 self.with_bound_context(|| {
748 with_scratch_txt(label, |ptr| unsafe {
749 sys::ImPlot_ShowInputMapSelector(ptr)
750 })
751 })
752 }
753
754 pub fn colormap_scale(
756 &self,
757 label: &str,
758 scale_min: f64,
759 scale_max: f64,
760 height: f32,
761 cmap: impl Into<ColormapSelection>,
762 ) {
763 self.colormap_scale_impl(
764 label,
765 scale_min,
766 scale_max,
767 height,
768 DEFAULT_COLORMAP_SCALE_FORMAT,
769 cmap.into(),
770 );
771 }
772
773 pub fn colormap_scale_with_format(
775 &self,
776 label: &str,
777 scale_min: f64,
778 scale_max: f64,
779 height: f32,
780 format: &FloatFormat<'_>,
781 cmap: impl Into<ColormapSelection>,
782 ) {
783 self.colormap_scale_impl(
784 label,
785 scale_min,
786 scale_max,
787 height,
788 format.as_str(),
789 cmap.into(),
790 );
791 }
792
793 fn colormap_scale_impl(
794 &self,
795 label: &str,
796 scale_min: f64,
797 scale_max: f64,
798 height: f32,
799 format: &str,
800 cmap: ColormapSelection,
801 ) {
802 assert!(
803 scale_min.is_finite(),
804 "colormap_scale scale_min must be finite"
805 );
806 assert!(
807 scale_max.is_finite(),
808 "colormap_scale scale_max must be finite"
809 );
810 assert!(height.is_finite(), "colormap_scale height must be finite");
811 let label = if label.contains('\0') { "" } else { label };
812 let size = sys::ImVec2_c { x: 0.0, y: height };
813 let flags = sys::ImPlotColormapScaleFlags_None as sys::ImPlotColormapScaleFlags;
814 let cmap = cmap.raw();
815 self.with_bound_context(|| {
816 with_scratch_txt_two(label, format, |label_ptr, format_ptr| unsafe {
817 sys::ImPlot_ColormapScale(
818 label_ptr, scale_min, scale_max, size, format_ptr, flags, cmap,
819 )
820 })
821 })
822 }
823
824 pub fn colormap_slider(
826 &self,
827 label: &str,
828 t: &mut f32,
829 out_color: Option<&mut [f32; 4]>,
830 cmap: impl Into<ColormapSelection>,
831 ) -> bool {
832 self.colormap_slider_impl(
833 label,
834 t,
835 out_color,
836 DEFAULT_COLORMAP_SLIDER_FORMAT,
837 cmap.into(),
838 )
839 }
840
841 pub fn colormap_slider_with_format(
843 &self,
844 label: &str,
845 t: &mut f32,
846 out_color: Option<&mut [f32; 4]>,
847 format: &FloatFormat<'_>,
848 cmap: impl Into<ColormapSelection>,
849 ) -> bool {
850 self.colormap_slider_impl(label, t, out_color, format.as_str(), cmap.into())
851 }
852
853 fn colormap_slider_impl(
854 &self,
855 label: &str,
856 t: &mut f32,
857 out_color: Option<&mut [f32; 4]>,
858 format: &str,
859 cmap: ColormapSelection,
860 ) -> bool {
861 assert!(t.is_finite(), "colormap_slider t must be finite");
862 let label = if label.contains('\0') { "" } else { label };
863 let cmap = cmap.raw();
864 let mut out = sys::ImVec4 {
865 x: 0.0,
866 y: 0.0,
867 z: 0.0,
868 w: 0.0,
869 };
870 let out_ptr = if out_color.is_some() {
871 &mut out as *mut sys::ImVec4
872 } else {
873 std::ptr::null_mut()
874 };
875
876 self.with_bound_context(|| {
877 let changed = with_scratch_txt_two(label, format, |label_ptr, format_ptr| unsafe {
878 sys::ImPlot_ColormapSlider(label_ptr, t as *mut f32, out_ptr, format_ptr, cmap)
879 });
880
881 if let Some(out_color) = out_color {
882 *out_color = [out.x, out.y, out.z, out.w];
883 }
884 changed
885 })
886 }
887
888 pub fn colormap_button(
890 &self,
891 label: &str,
892 size: [f32; 2],
893 cmap: impl Into<ColormapSelection>,
894 ) -> bool {
895 assert!(
896 size[0].is_finite() && size[1].is_finite(),
897 "colormap_button size must be finite"
898 );
899 let label = if label.contains('\0') { "" } else { label };
900 let sz = sys::ImVec2_c {
901 x: size[0],
902 y: size[1],
903 };
904 let cmap = cmap.into().raw();
905 self.with_bound_context(|| {
906 with_scratch_txt(label, |ptr| unsafe {
907 sys::ImPlot_ColormapButton(ptr, sz, cmap)
908 })
909 })
910 }
911}
912
913#[cfg(test)]
914mod tests {
915 use super::{
916 Colormap, ColormapColorIndex, ColormapIndex, ColormapSelection, PlotItemArrayStyle,
917 with_scoped_next_plot_item_array_style,
918 };
919 use crate::plots::{
920 PlotDataLayout, PlotDataOffset, PlotDataStride, set_next_plot_spec, take_next_plot_spec,
921 };
922
923 #[test]
924 fn colormap_indices_reject_negative_values() {
925 assert_eq!(ColormapIndex::from_raw(-1), None);
926 assert_eq!(ColormapIndex::new(0).map(ColormapIndex::raw), Some(0));
927 assert_eq!(ColormapIndex::new(0).map(ColormapIndex::get), Some(0));
928 assert_eq!(ColormapIndex::new(i32::MAX as usize + 1), None);
929 assert_eq!(
930 ColormapIndex::from(Colormap::Viridis).raw(),
931 crate::sys::ImPlotColormap_Viridis
932 );
933 assert_eq!(ColormapSelection::Current.raw(), crate::IMPLOT_AUTO);
934 assert_eq!(
935 ColormapSelection::from(Colormap::Viridis).raw(),
936 crate::sys::ImPlotColormap_Viridis
937 );
938
939 assert_eq!(
940 ColormapColorIndex::new(0).map(ColormapColorIndex::get),
941 Some(0)
942 );
943 assert_eq!(
944 ColormapColorIndex::from_usize(i32::MAX as usize).map(ColormapColorIndex::raw),
945 Some(i32::MAX)
946 );
947 assert_eq!(ColormapColorIndex::from_usize(i32::MAX as usize + 1), None);
948 }
949
950 #[test]
951 #[should_panic(expected = "test returned a negative colormap count")]
952 fn colormap_count_conversion_rejects_negative_ffi_values() {
953 let _ = super::colormap_count_from_i32(-1, "test");
954 }
955
956 #[test]
957 #[should_panic(expected = "sample_colormap t must be between 0 and 1")]
958 fn sample_colormap_rejects_out_of_range_t_before_ffi() {
959 super::assert_colormap_sample_t(-0.1);
960 }
961
962 #[test]
963 fn next_plot_item_array_style_is_consumed_by_next_spec() {
964 let line_colors = [0x01020304u32, 0x05060708];
965 let fill_colors = [0x11121314u32];
966 let marker_sizes = [2.0f32, 4.0, 8.0];
967
968 with_scoped_next_plot_item_array_style(
969 PlotItemArrayStyle::new()
970 .with_line_colors(&line_colors)
971 .with_fill_colors(&fill_colors)
972 .with_marker_sizes(&marker_sizes),
973 || {
974 let layout =
975 PlotDataLayout::new(PlotDataOffset::samples(3), PlotDataStride::bytes(16));
976 let spec = crate::plots::plot_spec_from(7, layout);
977 assert_eq!(spec.Flags, 7);
978 assert_eq!(spec.Offset, 3);
979 assert_eq!(spec.Stride, 16);
980 assert_eq!(spec.LineColors, line_colors.as_ptr() as *mut _);
981 assert_eq!(spec.FillColors, fill_colors.as_ptr() as *mut _);
982 assert_eq!(spec.MarkerSizes, marker_sizes.as_ptr() as *mut _);
983 },
984 );
985
986 let spec = crate::plots::plot_spec_from(0, PlotDataLayout::DEFAULT);
987 assert!(spec.LineColors.is_null());
988 assert!(spec.FillColors.is_null());
989 assert!(spec.MarkerSizes.is_null());
990 }
991
992 #[test]
993 fn next_plot_item_array_style_is_restored_if_unused() {
994 let line_colors = [0xAABBCCDDu32];
995
996 with_scoped_next_plot_item_array_style(
997 PlotItemArrayStyle::new().with_line_colors(&line_colors),
998 || {},
999 );
1000
1001 let spec = crate::plots::plot_spec_from(0, PlotDataLayout::DEFAULT);
1002 assert!(spec.LineColors.is_null());
1003 }
1004
1005 #[test]
1006 fn next_plot_item_array_style_is_restored_if_closure_panics() {
1007 set_next_plot_spec(None);
1008 let line_colors = [0xAABBCCDDu32];
1009
1010 let result = std::panic::catch_unwind(|| {
1011 with_scoped_next_plot_item_array_style(
1012 PlotItemArrayStyle::new().with_line_colors(&line_colors),
1013 || panic!("boom"),
1014 );
1015 });
1016
1017 assert!(result.is_err());
1018 assert!(take_next_plot_spec().is_none());
1019 }
1020}