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