Skip to main content

dear_implot/
style.rs

1// Style and theming for plots
2
3use crate::sys;
4use dear_imgui_rs::{with_scratch_txt, with_scratch_txt_two};
5use std::borrow::Cow;
6use std::marker::PhantomData;
7use std::os::raw::c_char;
8use std::rc::Rc;
9
10use crate::Colormap;
11
12/// Style variables that can be modified
13#[repr(i32)]
14#[derive(Copy, Clone, Debug, PartialEq, Eq)]
15pub enum StyleVar {
16    PlotDefaultSize = sys::ImPlotStyleVar_PlotDefaultSize as i32,
17    PlotMinSize = sys::ImPlotStyleVar_PlotMinSize as i32,
18    PlotBorderSize = sys::ImPlotStyleVar_PlotBorderSize as i32,
19    MinorAlpha = sys::ImPlotStyleVar_MinorAlpha as i32,
20    MajorTickLen = sys::ImPlotStyleVar_MajorTickLen as i32,
21    MinorTickLen = sys::ImPlotStyleVar_MinorTickLen as i32,
22    MajorTickSize = sys::ImPlotStyleVar_MajorTickSize as i32,
23    MinorTickSize = sys::ImPlotStyleVar_MinorTickSize as i32,
24    MajorGridSize = sys::ImPlotStyleVar_MajorGridSize as i32,
25    MinorGridSize = sys::ImPlotStyleVar_MinorGridSize as i32,
26    PlotPadding = sys::ImPlotStyleVar_PlotPadding as i32,
27    LabelPadding = sys::ImPlotStyleVar_LabelPadding as i32,
28    LegendPadding = sys::ImPlotStyleVar_LegendPadding as i32,
29    LegendInnerPadding = sys::ImPlotStyleVar_LegendInnerPadding as i32,
30    LegendSpacing = sys::ImPlotStyleVar_LegendSpacing as i32,
31    MousePosPadding = sys::ImPlotStyleVar_MousePosPadding as i32,
32    AnnotationPadding = sys::ImPlotStyleVar_AnnotationPadding as i32,
33    FitPadding = sys::ImPlotStyleVar_FitPadding as i32,
34    DigitalPadding = sys::ImPlotStyleVar_DigitalPadding as i32,
35    DigitalSpacing = sys::ImPlotStyleVar_DigitalSpacing as i32,
36}
37
38/// Token for managing style variable changes
39pub struct StyleVarToken {
40    was_popped: bool,
41    _not_send_or_sync: PhantomData<Rc<()>>,
42}
43
44impl StyleVarToken {
45    /// Pop this style variable from the stack
46    pub fn pop(mut self) {
47        if self.was_popped {
48            panic!("Attempted to pop a style var token twice.");
49        }
50        self.was_popped = true;
51        unsafe {
52            sys::ImPlot_PopStyleVar(1);
53        }
54    }
55}
56
57impl Drop for StyleVarToken {
58    fn drop(&mut self) {
59        if !self.was_popped {
60            unsafe {
61                sys::ImPlot_PopStyleVar(1);
62            }
63        }
64    }
65}
66
67/// Token for managing style color changes
68pub struct StyleColorToken {
69    was_popped: bool,
70    _not_send_or_sync: PhantomData<Rc<()>>,
71}
72
73impl StyleColorToken {
74    /// Pop this style color from the stack
75    pub fn pop(mut self) {
76        if self.was_popped {
77            panic!("Attempted to pop a style color token twice.");
78        }
79        self.was_popped = true;
80        unsafe {
81            sys::ImPlot_PopStyleColor(1);
82        }
83    }
84}
85
86impl Drop for StyleColorToken {
87    fn drop(&mut self) {
88        if !self.was_popped {
89            unsafe {
90                sys::ImPlot_PopStyleColor(1);
91            }
92        }
93    }
94}
95
96/// Runtime ImPlot colormap index.
97#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
98#[repr(transparent)]
99pub struct ColormapIndex(pub(crate) i32);
100
101impl ColormapIndex {
102    #[inline]
103    pub const fn new(index: usize) -> Option<Self> {
104        if index <= i32::MAX as usize {
105            Some(Self(index as i32))
106        } else {
107            None
108        }
109    }
110
111    #[inline]
112    pub const fn get(self) -> usize {
113        self.0 as usize
114    }
115
116    #[inline]
117    pub const fn raw(self) -> i32 {
118        self.0
119    }
120
121    #[inline]
122    pub(crate) const fn from_raw(raw: i32) -> Option<Self> {
123        if raw >= 0 { Some(Self(raw)) } else { None }
124    }
125}
126
127impl From<Colormap> for ColormapIndex {
128    #[inline]
129    fn from(value: Colormap) -> Self {
130        value.index()
131    }
132}
133
134impl From<usize> for ColormapIndex {
135    #[inline]
136    fn from(value: usize) -> Self {
137        Self::new(value).expect("colormap index exceeded ImPlot's i32 range")
138    }
139}
140
141/// Selected colormap for helpers that may use either the current style colormap or an explicit one.
142#[derive(Copy, Clone, Debug, PartialEq, Eq)]
143pub enum ColormapSelection {
144    Current,
145    Index(ColormapIndex),
146}
147
148impl ColormapSelection {
149    #[inline]
150    pub(crate) const fn raw(self) -> i32 {
151        match self {
152            Self::Current => crate::IMPLOT_AUTO,
153            Self::Index(index) => index.raw(),
154        }
155    }
156}
157
158impl From<Colormap> for ColormapSelection {
159    #[inline]
160    fn from(value: Colormap) -> Self {
161        Self::Index(value.index())
162    }
163}
164
165impl From<ColormapIndex> for ColormapSelection {
166    #[inline]
167    fn from(value: ColormapIndex) -> Self {
168        Self::Index(value)
169    }
170}
171
172impl From<Option<ColormapIndex>> for ColormapSelection {
173    #[inline]
174    fn from(value: Option<ColormapIndex>) -> Self {
175        value.map_or(Self::Current, Self::Index)
176    }
177}
178
179/// Zero-based color entry inside the active or selected colormap.
180#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
181#[repr(transparent)]
182pub struct ColormapColorIndex(i32);
183
184impl ColormapColorIndex {
185    #[inline]
186    pub const fn new(index: usize) -> Option<Self> {
187        Self::from_usize(index)
188    }
189
190    #[inline]
191    pub const fn from_usize(index: usize) -> Option<Self> {
192        if index <= i32::MAX as usize {
193            Some(Self(index as i32))
194        } else {
195            None
196        }
197    }
198
199    #[inline]
200    pub const fn get(self) -> usize {
201        self.0 as usize
202    }
203
204    #[inline]
205    pub const fn raw(self) -> i32 {
206        self.0
207    }
208}
209
210impl From<usize> for ColormapColorIndex {
211    #[inline]
212    fn from(value: usize) -> Self {
213        Self::new(value).expect("colormap color index exceeded ImPlot's i32 range")
214    }
215}
216
217/// Token for managing colormap changes.
218#[must_use]
219pub struct ColormapToken {
220    was_popped: bool,
221    _not_send_or_sync: PhantomData<Rc<()>>,
222}
223
224impl ColormapToken {
225    /// Pop this colormap from the stack.
226    pub fn pop(mut self) {
227        if self.was_popped {
228            panic!("Attempted to pop an ImPlot colormap token twice.");
229        }
230        self.was_popped = true;
231        unsafe {
232            sys::ImPlot_PopColormap(1);
233        }
234    }
235}
236
237impl Drop for ColormapToken {
238    fn drop(&mut self) {
239        if !self.was_popped {
240            unsafe {
241                sys::ImPlot_PopColormap(1);
242            }
243        }
244    }
245}
246
247/// One-shot array-backed item style overrides for the next plot submission.
248///
249/// This mirrors the new per-item array fields added to `ImPlotSpec` without storing
250/// borrowed pointers beyond the closure passed to [`with_next_plot_item_array_style`].
251#[derive(Debug, Clone, Default, PartialEq)]
252pub struct PlotItemArrayStyle<'a> {
253    line_colors: Option<Cow<'a, [u32]>>,
254    fill_colors: Option<Cow<'a, [u32]>>,
255    marker_sizes: Option<Cow<'a, [f32]>>,
256    marker_line_colors: Option<Cow<'a, [u32]>>,
257    marker_fill_colors: Option<Cow<'a, [u32]>>,
258}
259
260impl<'a> PlotItemArrayStyle<'a> {
261    /// Create an empty array-style override.
262    pub fn new() -> Self {
263        Self::default()
264    }
265
266    /// Override per-index line colors using Dear ImGui packed colors (`ImU32` / ABGR).
267    pub fn with_line_colors(mut self, colors: &'a [u32]) -> Self {
268        self.line_colors = Some(Cow::Borrowed(colors));
269        self
270    }
271
272    /// Override per-index fill colors using Dear ImGui packed colors (`ImU32` / ABGR).
273    pub fn with_fill_colors(mut self, colors: &'a [u32]) -> Self {
274        self.fill_colors = Some(Cow::Borrowed(colors));
275        self
276    }
277
278    /// Override per-index marker sizes in pixels.
279    pub fn with_marker_sizes(mut self, sizes: &'a [f32]) -> Self {
280        self.marker_sizes = Some(Cow::Borrowed(sizes));
281        self
282    }
283
284    /// Override per-index marker outline colors using Dear ImGui packed colors (`ImU32` / ABGR).
285    pub fn with_marker_line_colors(mut self, colors: &'a [u32]) -> Self {
286        self.marker_line_colors = Some(Cow::Borrowed(colors));
287        self
288    }
289
290    /// Override per-index marker fill colors using Dear ImGui packed colors (`ImU32` / ABGR).
291    pub fn with_marker_fill_colors(mut self, colors: &'a [u32]) -> Self {
292        self.marker_fill_colors = Some(Cow::Borrowed(colors));
293        self
294    }
295
296    fn apply_to_spec(&self, spec: &mut sys::ImPlotSpec_c) {
297        spec.LineColors = self
298            .line_colors
299            .as_ref()
300            .map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
301        spec.FillColors = self
302            .fill_colors
303            .as_ref()
304            .map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
305        spec.MarkerSizes = self
306            .marker_sizes
307            .as_ref()
308            .map_or(std::ptr::null_mut(), |sizes| sizes.as_ptr() as *mut _);
309        spec.MarkerLineColors = self
310            .marker_line_colors
311            .as_ref()
312            .map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
313        spec.MarkerFillColors = self
314            .marker_fill_colors
315            .as_ref()
316            .map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
317    }
318}
319
320/// Apply array-backed item styling to the next plot submission executed inside `f`.
321///
322/// This is intentionally closure-scoped so borrowed slices stay valid for the entire
323/// duration of the next plot call and cannot leak into later frames accidentally.
324pub fn with_next_plot_item_array_style<'a, R>(
325    style: PlotItemArrayStyle<'a>,
326    f: impl FnOnce() -> R,
327) -> R {
328    let previous = crate::plots::take_next_plot_spec();
329    let mut spec = previous.unwrap_or_else(crate::plots::default_plot_spec);
330    style.apply_to_spec(&mut spec);
331    crate::plots::set_next_plot_spec(Some(spec));
332
333    let out = f();
334
335    if crate::plots::take_next_plot_spec().is_some() {
336        crate::plots::set_next_plot_spec(previous);
337    }
338
339    out
340}
341
342/// Push a float style variable to the stack
343pub fn push_style_var_f32(var: StyleVar, value: f32) -> StyleVarToken {
344    unsafe {
345        sys::ImPlot_PushStyleVar_Float(var as sys::ImPlotStyleVar, value);
346    }
347    StyleVarToken {
348        was_popped: false,
349        _not_send_or_sync: PhantomData,
350    }
351}
352
353/// Push a Vec2 style variable to the stack
354pub fn push_style_var_vec2(var: StyleVar, value: [f32; 2]) -> StyleVarToken {
355    unsafe {
356        sys::ImPlot_PushStyleVar_Vec2(
357            var as sys::ImPlotStyleVar,
358            sys::ImVec2_c {
359                x: value[0],
360                y: value[1],
361            },
362        );
363    }
364    StyleVarToken {
365        was_popped: false,
366        _not_send_or_sync: PhantomData,
367    }
368}
369
370/// Push a style color to the stack
371pub fn push_style_color(element: crate::PlotColorElement, color: [f32; 4]) -> StyleColorToken {
372    unsafe {
373        // Convert color to ImU32 format (RGBA)
374        let r = (color[0] * 255.0) as u32;
375        let g = (color[1] * 255.0) as u32;
376        let b = (color[2] * 255.0) as u32;
377        let a = (color[3] * 255.0) as u32;
378        let color_u32 = (a << 24) | (b << 16) | (g << 8) | r;
379
380        sys::ImPlot_PushStyleColor_U32(element as sys::ImPlotCol, color_u32);
381    }
382    StyleColorToken {
383        was_popped: false,
384        _not_send_or_sync: PhantomData,
385    }
386}
387
388/// Push a colormap to the stack
389pub fn push_colormap(cmap: impl Into<ColormapIndex>) -> ColormapToken {
390    unsafe {
391        sys::ImPlot_PushColormap_PlotColormap(cmap.into().raw());
392    }
393    ColormapToken {
394        was_popped: false,
395        _not_send_or_sync: PhantomData,
396    }
397}
398
399/// Push a colormap by name to the stack.
400pub fn push_colormap_name(name: &str) -> ColormapToken {
401    assert!(!name.contains('\0'), "colormap name contained NUL");
402    with_scratch_txt(name, |ptr| unsafe { sys::ImPlot_PushColormap_Str(ptr) });
403    ColormapToken {
404        was_popped: false,
405        _not_send_or_sync: PhantomData,
406    }
407}
408
409/// Add a custom colormap from colors. The colors are copied by ImPlot.
410pub fn add_colormap(name: &str, colors: &[[f32; 4]], qualitative: bool) -> ColormapIndex {
411    assert!(!name.contains('\0'), "colormap name contained NUL");
412    assert!(
413        colors.len() > 1,
414        "colormap must contain at least two colors"
415    );
416    assert!(
417        colors
418            .iter()
419            .flatten()
420            .all(|component| component.is_finite()),
421        "colormap colors must be finite"
422    );
423    let count = i32::try_from(colors.len()).expect("colormap contained too many colors");
424    let colors: Vec<sys::ImVec4> = colors
425        .iter()
426        .map(|color| sys::ImVec4 {
427            x: color[0],
428            y: color[1],
429            z: color[2],
430            w: color[3],
431        })
432        .collect();
433    let index = with_scratch_txt(name, |ptr| unsafe {
434        sys::ImPlot_AddColormap_Vec4Ptr(ptr, colors.as_ptr(), count, qualitative)
435    });
436    ColormapIndex::from_raw(index).expect("ImPlot returned a negative colormap index")
437}
438
439fn colormap_count_from_i32(raw: i32, caller: &str) -> usize {
440    assert!(raw >= 0, "{caller} returned a negative colormap count");
441    usize::try_from(raw).expect("non-negative colormap count must fit usize")
442}
443
444/// Return the number of available colormaps.
445pub fn colormap_count() -> usize {
446    colormap_count_from_i32(
447        unsafe { sys::ImPlot_GetColormapCount() },
448        "colormap_count()",
449    )
450}
451
452/// Return a colormap name, or an empty string if the index is invalid for the current context.
453pub fn colormap_name(index: impl Into<ColormapIndex>) -> String {
454    unsafe {
455        let p = sys::ImPlot_GetColormapName(index.into().raw());
456        if p.is_null() {
457            return String::new();
458        }
459        std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
460    }
461}
462
463/// Look up a colormap index by its name.
464pub fn colormap_index_by_name(name: &str) -> Option<ColormapIndex> {
465    if name.contains('\0') {
466        return None;
467    }
468    let index = with_scratch_txt(name, |ptr| unsafe { sys::ImPlot_GetColormapIndex(ptr) });
469    ColormapIndex::from_raw(index)
470}
471
472/// Return the number of color entries in a colormap.
473pub fn colormap_size(index: impl Into<ColormapIndex>) -> usize {
474    colormap_count_from_i32(
475        unsafe { sys::ImPlot_GetColormapSize(index.into().raw()) },
476        "colormap_size()",
477    )
478}
479
480/// Return the current default colormap stored in the ImPlot style.
481pub fn get_style_colormap_index() -> Option<ColormapIndex> {
482    unsafe {
483        let style = sys::ImPlot_GetStyle();
484        if style.is_null() {
485            return None;
486        }
487        ColormapIndex::from_raw((*style).Colormap)
488    }
489}
490
491/// Return the current default colormap name.
492pub fn get_style_colormap_name() -> Option<String> {
493    let idx = get_style_colormap_index()?;
494    let count = colormap_count();
495    if idx.get() >= count {
496        return None;
497    }
498    Some(colormap_name(idx))
499}
500
501/// Permanently set the default colormap used by ImPlot.
502pub fn set_style_colormap(index: impl Into<ColormapIndex>) {
503    unsafe {
504        let style = sys::ImPlot_GetStyle();
505        if !style.is_null() {
506            let count = colormap_count();
507            if count > 0 {
508                let index = index.into().get();
509                let idx = index.min(count - 1);
510                (*style).Colormap = ColormapIndex::from(idx).raw();
511            }
512        }
513    }
514}
515
516/// Permanently set the default colormap by name. Invalid names are ignored.
517pub fn set_style_colormap_by_name(name: &str) {
518    if let Some(idx) = colormap_index_by_name(name) {
519        set_style_colormap(idx);
520    }
521}
522
523/// Return a color from the active colormap.
524pub fn get_colormap_color(index: ColormapColorIndex) -> [f32; 4] {
525    unsafe {
526        let out = sys::ImPlot_GetColormapColor(index.raw(), crate::IMPLOT_AUTO);
527        [out.x, out.y, out.z, out.w]
528    }
529}
530
531/// Return a color from a selected colormap.
532pub fn get_colormap_color_from(
533    index: ColormapColorIndex,
534    cmap: impl Into<ColormapIndex>,
535) -> [f32; 4] {
536    unsafe {
537        let out = sys::ImPlot_GetColormapColor(index.raw(), cmap.into().raw());
538        [out.x, out.y, out.z, out.w]
539    }
540}
541
542/// Sample the active colormap at `t` in `[0, 1]`.
543pub fn sample_colormap(t: f32) -> [f32; 4] {
544    assert!(
545        (0.0..=1.0).contains(&t),
546        "sample_colormap t must be between 0 and 1"
547    );
548    unsafe {
549        let out = sys::ImPlot_SampleColormap(t, crate::IMPLOT_AUTO);
550        [out.x, out.y, out.z, out.w]
551    }
552}
553
554/// Sample a selected colormap at `t` in `[0, 1]`.
555pub fn sample_colormap_from(t: f32, cmap: impl Into<ColormapSelection>) -> [f32; 4] {
556    assert!(
557        (0.0..=1.0).contains(&t),
558        "sample_colormap t must be between 0 and 1"
559    );
560    unsafe {
561        let out = sys::ImPlot_SampleColormap(t, cmap.into().raw());
562        [out.x, out.y, out.z, out.w]
563    }
564}
565
566/// Return the next color from the current colormap and advance the plot color cursor.
567pub fn next_colormap_color() -> [f32; 4] {
568    unsafe {
569        let out = sys::ImPlot_NextColormapColor();
570        [out.x, out.y, out.z, out.w]
571    }
572}
573
574// Style editor / selectors and input-map helpers
575
576/// Show the ImPlot style editor window
577pub fn show_style_editor() {
578    unsafe { sys::ImPlot_ShowStyleEditor(std::ptr::null_mut()) }
579}
580
581/// Show the ImPlot style selector combo; returns true if selection changed
582pub fn show_style_selector(label: &str) -> bool {
583    let label = if label.contains('\0') { "" } else { label };
584    with_scratch_txt(label, |ptr| unsafe { sys::ImPlot_ShowStyleSelector(ptr) })
585}
586
587/// Show the ImPlot colormap selector combo; returns true if selection changed
588pub fn show_colormap_selector(label: &str) -> bool {
589    let label = if label.contains('\0') { "" } else { label };
590    with_scratch_txt(label, |ptr| unsafe {
591        sys::ImPlot_ShowColormapSelector(ptr)
592    })
593}
594
595/// Show the ImPlot input-map selector combo; returns true if selection changed
596pub fn show_input_map_selector(label: &str) -> bool {
597    let label = if label.contains('\0') { "" } else { label };
598    with_scratch_txt(label, |ptr| unsafe {
599        sys::ImPlot_ShowInputMapSelector(ptr)
600    })
601}
602
603/// Map input to defaults
604pub fn map_input_default() {
605    unsafe { sys::ImPlot_MapInputDefault(sys::ImPlot_GetInputMap()) }
606}
607
608/// Map input to reversed scheme
609pub fn map_input_reverse() {
610    unsafe { sys::ImPlot_MapInputReverse(sys::ImPlot_GetInputMap()) }
611}
612
613// Colormap widgets
614
615/// Draw a colormap scale widget
616pub fn colormap_scale(
617    label: &str,
618    scale_min: f64,
619    scale_max: f64,
620    height: f32,
621    cmap: impl Into<ColormapSelection>,
622) {
623    assert!(
624        scale_min.is_finite(),
625        "colormap_scale scale_min must be finite"
626    );
627    assert!(
628        scale_max.is_finite(),
629        "colormap_scale scale_max must be finite"
630    );
631    assert!(height.is_finite(), "colormap_scale height must be finite");
632    let label = if label.contains('\0') { "" } else { label };
633    let size = sys::ImVec2_c { x: 0.0, y: height };
634    let fmt_ptr: *const c_char = std::ptr::null();
635    let flags = sys::ImPlotColormapScaleFlags_None as sys::ImPlotColormapScaleFlags;
636    let cmap = cmap.into().raw();
637    with_scratch_txt(label, |ptr| unsafe {
638        sys::ImPlot_ColormapScale(ptr, scale_min, scale_max, size, fmt_ptr, flags, cmap)
639    })
640}
641
642/// Draw a colormap slider; returns true if selection changed
643pub fn colormap_slider(
644    label: &str,
645    t: &mut f32,
646    out_color: Option<&mut [f32; 4]>,
647    format: Option<&str>,
648    cmap: impl Into<ColormapSelection>,
649) -> bool {
650    assert!(t.is_finite(), "colormap_slider t must be finite");
651    let label = if label.contains('\0') { "" } else { label };
652    let format = format.filter(|s| !s.contains('\0'));
653    let cmap = cmap.into().raw();
654    let mut out = sys::ImVec4 {
655        x: 0.0,
656        y: 0.0,
657        z: 0.0,
658        w: 0.0,
659    };
660    let out_ptr = if out_color.is_some() {
661        &mut out as *mut sys::ImVec4
662    } else {
663        std::ptr::null_mut()
664    };
665
666    let changed = match format {
667        Some(fmt) => with_scratch_txt_two(label, fmt, |label_ptr, fmt_ptr| unsafe {
668            sys::ImPlot_ColormapSlider(label_ptr, t as *mut f32, out_ptr, fmt_ptr, cmap)
669        }),
670        None => with_scratch_txt(label, |label_ptr| unsafe {
671            sys::ImPlot_ColormapSlider(label_ptr, t as *mut f32, out_ptr, std::ptr::null(), cmap)
672        }),
673    };
674
675    if let Some(out_color) = out_color {
676        *out_color = [out.x, out.y, out.z, out.w];
677    }
678    changed
679}
680
681/// Draw a colormap picker button; returns true if clicked
682pub fn colormap_button(label: &str, size: [f32; 2], cmap: impl Into<ColormapSelection>) -> bool {
683    assert!(
684        size[0].is_finite() && size[1].is_finite(),
685        "colormap_button size must be finite"
686    );
687    let label = if label.contains('\0') { "" } else { label };
688    let sz = sys::ImVec2_c {
689        x: size[0],
690        y: size[1],
691    };
692    let cmap = cmap.into().raw();
693    with_scratch_txt(label, |ptr| unsafe {
694        sys::ImPlot_ColormapButton(ptr, sz, cmap)
695    })
696}
697
698#[cfg(test)]
699mod tests {
700    use super::{
701        Colormap, ColormapColorIndex, ColormapIndex, ColormapSelection, PlotItemArrayStyle,
702        with_next_plot_item_array_style,
703    };
704    use crate::plots::{PlotDataLayout, PlotDataOffset, PlotDataStride};
705
706    #[test]
707    fn colormap_indices_reject_negative_values() {
708        assert_eq!(ColormapIndex::from_raw(-1), None);
709        assert_eq!(ColormapIndex::new(0).map(ColormapIndex::raw), Some(0));
710        assert_eq!(ColormapIndex::new(0).map(ColormapIndex::get), Some(0));
711        assert_eq!(ColormapIndex::new(i32::MAX as usize + 1), None);
712        assert_eq!(
713            ColormapIndex::from(Colormap::Viridis).raw(),
714            crate::sys::ImPlotColormap_Viridis
715        );
716        assert_eq!(ColormapSelection::Current.raw(), crate::IMPLOT_AUTO);
717        assert_eq!(
718            ColormapSelection::from(Colormap::Viridis).raw(),
719            crate::sys::ImPlotColormap_Viridis
720        );
721
722        assert_eq!(
723            ColormapColorIndex::new(0).map(ColormapColorIndex::get),
724            Some(0)
725        );
726        assert_eq!(
727            ColormapColorIndex::from_usize(i32::MAX as usize).map(ColormapColorIndex::raw),
728            Some(i32::MAX)
729        );
730        assert_eq!(ColormapColorIndex::from_usize(i32::MAX as usize + 1), None);
731    }
732
733    #[test]
734    #[should_panic(expected = "test returned a negative colormap count")]
735    fn colormap_count_conversion_rejects_negative_ffi_values() {
736        let _ = super::colormap_count_from_i32(-1, "test");
737    }
738
739    #[test]
740    #[should_panic(expected = "sample_colormap t must be between 0 and 1")]
741    fn sample_colormap_rejects_out_of_range_t_before_ffi() {
742        let _ = super::sample_colormap(-0.1);
743    }
744
745    #[test]
746    fn next_plot_item_array_style_is_consumed_by_next_spec() {
747        let line_colors = [0x01020304u32, 0x05060708];
748        let fill_colors = [0x11121314u32];
749        let marker_sizes = [2.0f32, 4.0, 8.0];
750
751        with_next_plot_item_array_style(
752            PlotItemArrayStyle::new()
753                .with_line_colors(&line_colors)
754                .with_fill_colors(&fill_colors)
755                .with_marker_sizes(&marker_sizes),
756            || {
757                let layout =
758                    PlotDataLayout::new(PlotDataOffset::samples(3), PlotDataStride::bytes(16));
759                let spec = crate::plots::plot_spec_from(7, layout);
760                assert_eq!(spec.Flags, 7);
761                assert_eq!(spec.Offset, 3);
762                assert_eq!(spec.Stride, 16);
763                assert_eq!(spec.LineColors, line_colors.as_ptr() as *mut _);
764                assert_eq!(spec.FillColors, fill_colors.as_ptr() as *mut _);
765                assert_eq!(spec.MarkerSizes, marker_sizes.as_ptr() as *mut _);
766            },
767        );
768
769        let spec = crate::plots::plot_spec_from(0, PlotDataLayout::DEFAULT);
770        assert!(spec.LineColors.is_null());
771        assert!(spec.FillColors.is_null());
772        assert!(spec.MarkerSizes.is_null());
773    }
774
775    #[test]
776    fn next_plot_item_array_style_is_restored_if_unused() {
777        let line_colors = [0xAABBCCDDu32];
778
779        with_next_plot_item_array_style(
780            PlotItemArrayStyle::new().with_line_colors(&line_colors),
781            || {},
782        );
783
784        let spec = crate::plots::plot_spec_from(0, PlotDataLayout::DEFAULT);
785        assert!(spec.LineColors.is_null());
786    }
787}