use crate::sys;
use crate::{PlotContext, PlotContextBinding, PlotUi};
use dear_imgui_rs::ContextAliveToken;
use dear_imgui_rs::{with_scratch_txt, with_scratch_txt_two};
use std::borrow::Cow;
use std::marker::PhantomData;
use std::os::raw::c_char;
use std::rc::Rc;
use crate::Colormap;
#[repr(i32)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum StyleVar {
PlotDefaultSize = sys::ImPlotStyleVar_PlotDefaultSize as i32,
PlotMinSize = sys::ImPlotStyleVar_PlotMinSize as i32,
PlotBorderSize = sys::ImPlotStyleVar_PlotBorderSize as i32,
MinorAlpha = sys::ImPlotStyleVar_MinorAlpha as i32,
MajorTickLen = sys::ImPlotStyleVar_MajorTickLen as i32,
MinorTickLen = sys::ImPlotStyleVar_MinorTickLen as i32,
MajorTickSize = sys::ImPlotStyleVar_MajorTickSize as i32,
MinorTickSize = sys::ImPlotStyleVar_MinorTickSize as i32,
MajorGridSize = sys::ImPlotStyleVar_MajorGridSize as i32,
MinorGridSize = sys::ImPlotStyleVar_MinorGridSize as i32,
PlotPadding = sys::ImPlotStyleVar_PlotPadding as i32,
LabelPadding = sys::ImPlotStyleVar_LabelPadding as i32,
LegendPadding = sys::ImPlotStyleVar_LegendPadding as i32,
LegendInnerPadding = sys::ImPlotStyleVar_LegendInnerPadding as i32,
LegendSpacing = sys::ImPlotStyleVar_LegendSpacing as i32,
MousePosPadding = sys::ImPlotStyleVar_MousePosPadding as i32,
AnnotationPadding = sys::ImPlotStyleVar_AnnotationPadding as i32,
FitPadding = sys::ImPlotStyleVar_FitPadding as i32,
DigitalPadding = sys::ImPlotStyleVar_DigitalPadding as i32,
DigitalSpacing = sys::ImPlotStyleVar_DigitalSpacing as i32,
}
pub struct StyleVarToken<'ui> {
binding: PlotContextBinding,
imgui_alive: Option<ContextAliveToken>,
was_popped: bool,
_lifetime: PhantomData<&'ui PlotUi<'ui>>,
_not_send_or_sync: PhantomData<Rc<()>>,
}
impl StyleVarToken<'_> {
pub fn pop(mut self) {
self.pop_inner();
}
fn pop_inner(&mut self) {
if self.was_popped {
panic!("Attempted to pop an ImPlot style var token twice.");
}
assert_imgui_alive(&self.imgui_alive, "dear-implot: StyleVarToken");
let _guard = self.binding.bind("dear-implot: StyleVarToken");
unsafe { sys::ImPlot_PopStyleVar(1) };
self.was_popped = true;
}
}
impl Drop for StyleVarToken<'_> {
fn drop(&mut self) {
if !self.was_popped {
self.pop_inner();
}
}
}
pub struct StyleColorToken<'ui> {
binding: PlotContextBinding,
imgui_alive: Option<ContextAliveToken>,
was_popped: bool,
_lifetime: PhantomData<&'ui PlotUi<'ui>>,
_not_send_or_sync: PhantomData<Rc<()>>,
}
impl StyleColorToken<'_> {
pub fn pop(mut self) {
self.pop_inner();
}
fn pop_inner(&mut self) {
if self.was_popped {
panic!("Attempted to pop an ImPlot style color token twice.");
}
assert_imgui_alive(&self.imgui_alive, "dear-implot: StyleColorToken");
let _guard = self.binding.bind("dear-implot: StyleColorToken");
unsafe { sys::ImPlot_PopStyleColor(1) };
self.was_popped = true;
}
}
impl Drop for StyleColorToken<'_> {
fn drop(&mut self) {
if !self.was_popped {
self.pop_inner();
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct ColormapIndex(pub(crate) i32);
impl ColormapIndex {
#[inline]
pub const fn new(index: usize) -> Option<Self> {
if index <= i32::MAX as usize {
Some(Self(index as i32))
} else {
None
}
}
#[inline]
pub const fn get(self) -> usize {
self.0 as usize
}
#[inline]
pub const fn raw(self) -> i32 {
self.0
}
#[inline]
pub(crate) const fn from_raw(raw: i32) -> Option<Self> {
if raw >= 0 { Some(Self(raw)) } else { None }
}
}
impl From<Colormap> for ColormapIndex {
#[inline]
fn from(value: Colormap) -> Self {
value.index()
}
}
impl From<usize> for ColormapIndex {
#[inline]
fn from(value: usize) -> Self {
Self::new(value).expect("colormap index exceeded ImPlot's i32 range")
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ColormapSelection {
Current,
Index(ColormapIndex),
}
impl ColormapSelection {
#[inline]
pub(crate) const fn raw(self) -> i32 {
match self {
Self::Current => crate::IMPLOT_AUTO,
Self::Index(index) => index.raw(),
}
}
}
impl From<Colormap> for ColormapSelection {
#[inline]
fn from(value: Colormap) -> Self {
Self::Index(value.index())
}
}
impl From<ColormapIndex> for ColormapSelection {
#[inline]
fn from(value: ColormapIndex) -> Self {
Self::Index(value)
}
}
impl From<Option<ColormapIndex>> for ColormapSelection {
#[inline]
fn from(value: Option<ColormapIndex>) -> Self {
value.map_or(Self::Current, Self::Index)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct ColormapColorIndex(i32);
impl ColormapColorIndex {
#[inline]
pub const fn new(index: usize) -> Option<Self> {
Self::from_usize(index)
}
#[inline]
pub const fn from_usize(index: usize) -> Option<Self> {
if index <= i32::MAX as usize {
Some(Self(index as i32))
} else {
None
}
}
#[inline]
pub const fn get(self) -> usize {
self.0 as usize
}
#[inline]
pub const fn raw(self) -> i32 {
self.0
}
}
impl From<usize> for ColormapColorIndex {
#[inline]
fn from(value: usize) -> Self {
Self::new(value).expect("colormap color index exceeded ImPlot's i32 range")
}
}
#[must_use]
pub struct ColormapToken<'ui> {
binding: PlotContextBinding,
imgui_alive: Option<ContextAliveToken>,
was_popped: bool,
_lifetime: PhantomData<&'ui PlotUi<'ui>>,
_not_send_or_sync: PhantomData<Rc<()>>,
}
impl ColormapToken<'_> {
pub fn pop(mut self) {
self.pop_inner();
}
fn pop_inner(&mut self) {
if self.was_popped {
panic!("Attempted to pop an ImPlot colormap token twice.");
}
assert_imgui_alive(&self.imgui_alive, "dear-implot: ColormapToken");
let _guard = self.binding.bind("dear-implot: ColormapToken");
unsafe { sys::ImPlot_PopColormap(1) };
self.was_popped = true;
}
}
impl Drop for ColormapToken<'_> {
fn drop(&mut self) {
if !self.was_popped {
self.pop_inner();
}
}
}
fn assert_imgui_alive(alive: &Option<ContextAliveToken>, caller: &str) {
if let Some(alive) = alive {
assert!(alive.is_alive(), "{caller}: ImGui context has been dropped");
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PlotItemArrayStyle<'a> {
line_colors: Option<Cow<'a, [u32]>>,
fill_colors: Option<Cow<'a, [u32]>>,
marker_sizes: Option<Cow<'a, [f32]>>,
marker_line_colors: Option<Cow<'a, [u32]>>,
marker_fill_colors: Option<Cow<'a, [u32]>>,
}
impl<'a> PlotItemArrayStyle<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn with_line_colors(mut self, colors: &'a [u32]) -> Self {
self.line_colors = Some(Cow::Borrowed(colors));
self
}
pub fn with_fill_colors(mut self, colors: &'a [u32]) -> Self {
self.fill_colors = Some(Cow::Borrowed(colors));
self
}
pub fn with_marker_sizes(mut self, sizes: &'a [f32]) -> Self {
self.marker_sizes = Some(Cow::Borrowed(sizes));
self
}
pub fn with_marker_line_colors(mut self, colors: &'a [u32]) -> Self {
self.marker_line_colors = Some(Cow::Borrowed(colors));
self
}
pub fn with_marker_fill_colors(mut self, colors: &'a [u32]) -> Self {
self.marker_fill_colors = Some(Cow::Borrowed(colors));
self
}
fn apply_to_spec(&self, spec: &mut sys::ImPlotSpec_c) {
spec.LineColors = self
.line_colors
.as_ref()
.map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
spec.FillColors = self
.fill_colors
.as_ref()
.map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
spec.MarkerSizes = self
.marker_sizes
.as_ref()
.map_or(std::ptr::null_mut(), |sizes| sizes.as_ptr() as *mut _);
spec.MarkerLineColors = self
.marker_line_colors
.as_ref()
.map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
spec.MarkerFillColors = self
.marker_fill_colors
.as_ref()
.map_or(std::ptr::null_mut(), |colors| colors.as_ptr() as *mut _);
}
}
struct ScopedNextPlotItemArrayStyle {
previous: Option<sys::ImPlotSpec_c>,
active: bool,
}
impl ScopedNextPlotItemArrayStyle {
fn restore_if_unused(&mut self) {
if !self.active {
return;
}
if crate::plots::take_next_plot_spec().is_some() {
crate::plots::set_next_plot_spec(self.previous.take());
}
self.active = false;
}
}
impl Drop for ScopedNextPlotItemArrayStyle {
fn drop(&mut self) {
self.restore_if_unused();
}
}
fn with_scoped_next_plot_item_array_style<'a, R>(
style: PlotItemArrayStyle<'a>,
f: impl FnOnce() -> R,
) -> R {
let previous = crate::plots::take_next_plot_spec();
let mut spec = previous.unwrap_or_else(crate::plots::default_plot_spec);
style.apply_to_spec(&mut spec);
crate::plots::set_next_plot_spec(Some(spec));
let mut guard = ScopedNextPlotItemArrayStyle {
previous,
active: true,
};
let out = f();
guard.restore_if_unused();
out
}
impl<'ui> PlotUi<'ui> {
pub fn with_next_plot_item_array_style<'a, R>(
&self,
style: PlotItemArrayStyle<'a>,
f: impl FnOnce(&PlotUi<'ui>) -> R,
) -> R {
let _guard = self.bind();
with_scoped_next_plot_item_array_style(style, || f(self))
}
pub fn push_style_var_f32(&self, var: StyleVar, value: f32) -> StyleVarToken<'_> {
let _guard = self.bind();
unsafe {
sys::ImPlot_PushStyleVar_Float(var as sys::ImPlotStyleVar, value);
}
StyleVarToken {
binding: self.context.binding(),
imgui_alive: self.context.imgui_alive_token(),
was_popped: false,
_lifetime: PhantomData,
_not_send_or_sync: PhantomData,
}
}
pub fn push_style_var_vec2(&self, var: StyleVar, value: [f32; 2]) -> StyleVarToken<'_> {
let _guard = self.bind();
unsafe {
sys::ImPlot_PushStyleVar_Vec2(
var as sys::ImPlotStyleVar,
sys::ImVec2_c {
x: value[0],
y: value[1],
},
);
}
StyleVarToken {
binding: self.context.binding(),
imgui_alive: self.context.imgui_alive_token(),
was_popped: false,
_lifetime: PhantomData,
_not_send_or_sync: PhantomData,
}
}
pub fn push_style_color(
&self,
element: crate::PlotColorElement,
color: [f32; 4],
) -> StyleColorToken<'_> {
let _guard = self.bind();
unsafe {
let r = (color[0] * 255.0) as u32;
let g = (color[1] * 255.0) as u32;
let b = (color[2] * 255.0) as u32;
let a = (color[3] * 255.0) as u32;
let color_u32 = (a << 24) | (b << 16) | (g << 8) | r;
sys::ImPlot_PushStyleColor_U32(element as sys::ImPlotCol, color_u32);
}
StyleColorToken {
binding: self.context.binding(),
imgui_alive: self.context.imgui_alive_token(),
was_popped: false,
_lifetime: PhantomData,
_not_send_or_sync: PhantomData,
}
}
pub fn push_colormap(&self, cmap: impl Into<ColormapIndex>) -> ColormapToken<'_> {
let _guard = self.bind();
unsafe {
sys::ImPlot_PushColormap_PlotColormap(cmap.into().raw());
}
ColormapToken {
binding: self.context.binding(),
imgui_alive: self.context.imgui_alive_token(),
was_popped: false,
_lifetime: PhantomData,
_not_send_or_sync: PhantomData,
}
}
pub fn push_colormap_name(&self, name: &str) -> ColormapToken<'_> {
assert!(!name.contains('\0'), "colormap name contained NUL");
let _guard = self.bind();
with_scratch_txt(name, |ptr| unsafe { sys::ImPlot_PushColormap_Str(ptr) });
ColormapToken {
binding: self.context.binding(),
imgui_alive: self.context.imgui_alive_token(),
was_popped: false,
_lifetime: PhantomData,
_not_send_or_sync: PhantomData,
}
}
}
fn colormap_count_from_i32(raw: i32, caller: &str) -> usize {
assert!(raw >= 0, "{caller} returned a negative colormap count");
usize::try_from(raw).expect("non-negative colormap count must fit usize")
}
fn assert_colormap_sample_t(t: f32) {
assert!(
(0.0..=1.0).contains(&t),
"sample_colormap t must be between 0 and 1"
);
}
impl PlotContext {
#[inline]
fn with_bound_style<R>(&self, caller: &str, f: impl FnOnce() -> R) -> R {
self.assert_imgui_alive();
let _guard = self.binding().bind(caller);
f()
}
pub fn add_colormap(
&self,
name: &str,
colors: &[[f32; 4]],
qualitative: bool,
) -> ColormapIndex {
assert!(!name.contains('\0'), "colormap name contained NUL");
assert!(
colors.len() > 1,
"colormap must contain at least two colors"
);
assert!(
colors
.iter()
.flatten()
.all(|component| component.is_finite()),
"colormap colors must be finite"
);
let count = i32::try_from(colors.len()).expect("colormap contained too many colors");
let colors: Vec<sys::ImVec4> = colors
.iter()
.map(|color| sys::ImVec4 {
x: color[0],
y: color[1],
z: color[2],
w: color[3],
})
.collect();
let index = self.with_bound_style("dear-implot: PlotContext::add_colormap()", || {
with_scratch_txt(name, |ptr| unsafe {
sys::ImPlot_AddColormap_Vec4Ptr(ptr, colors.as_ptr(), count, qualitative)
})
});
ColormapIndex::from_raw(index).expect("ImPlot returned a negative colormap index")
}
pub fn colormap_count(&self) -> usize {
self.with_bound_style("dear-implot: PlotContext::colormap_count()", || {
colormap_count_from_i32(
unsafe { sys::ImPlot_GetColormapCount() },
"PlotContext::colormap_count()",
)
})
}
pub fn colormap_name(&self, index: impl Into<ColormapIndex>) -> String {
self.with_bound_style("dear-implot: PlotContext::colormap_name()", || unsafe {
let p = sys::ImPlot_GetColormapName(index.into().raw());
if p.is_null() {
return String::new();
}
std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
})
}
pub fn colormap_index_by_name(&self, name: &str) -> Option<ColormapIndex> {
if name.contains('\0') {
return None;
}
let index = self
.with_bound_style("dear-implot: PlotContext::colormap_index_by_name()", || {
with_scratch_txt(name, |ptr| unsafe { sys::ImPlot_GetColormapIndex(ptr) })
});
ColormapIndex::from_raw(index)
}
pub fn colormap_size(&self, index: impl Into<ColormapIndex>) -> usize {
self.with_bound_style("dear-implot: PlotContext::colormap_size()", || {
colormap_count_from_i32(
unsafe { sys::ImPlot_GetColormapSize(index.into().raw()) },
"PlotContext::colormap_size()",
)
})
}
pub fn style_colormap_index(&self) -> Option<ColormapIndex> {
self.with_bound_style(
"dear-implot: PlotContext::style_colormap_index()",
|| unsafe {
let style = sys::ImPlot_GetStyle();
if style.is_null() {
return None;
}
ColormapIndex::from_raw((*style).Colormap)
},
)
}
pub fn style_colormap_name(&self) -> Option<String> {
let idx = self.style_colormap_index()?;
let count = self.colormap_count();
if idx.get() >= count {
return None;
}
Some(self.colormap_name(idx))
}
pub fn set_style_colormap(&self, index: impl Into<ColormapIndex>) {
self.with_bound_style(
"dear-implot: PlotContext::set_style_colormap()",
|| unsafe {
let style = sys::ImPlot_GetStyle();
if !style.is_null() {
let count = colormap_count_from_i32(
sys::ImPlot_GetColormapCount(),
"PlotContext::set_style_colormap()",
);
if count > 0 {
let index = index.into().get();
let idx = index.min(count - 1);
(*style).Colormap = ColormapIndex::from(idx).raw();
}
}
},
)
}
pub fn set_style_colormap_by_name(&self, name: &str) {
if let Some(idx) = self.colormap_index_by_name(name) {
self.set_style_colormap(idx);
}
}
pub fn colormap_color(&self, index: ColormapColorIndex) -> [f32; 4] {
self.with_bound_style("dear-implot: PlotContext::colormap_color()", || unsafe {
let out = sys::ImPlot_GetColormapColor(index.raw(), crate::IMPLOT_AUTO);
[out.x, out.y, out.z, out.w]
})
}
pub fn colormap_color_from(
&self,
index: ColormapColorIndex,
cmap: impl Into<ColormapIndex>,
) -> [f32; 4] {
self.with_bound_style(
"dear-implot: PlotContext::colormap_color_from()",
|| unsafe {
let out = sys::ImPlot_GetColormapColor(index.raw(), cmap.into().raw());
[out.x, out.y, out.z, out.w]
},
)
}
pub fn sample_colormap(&self, t: f32) -> [f32; 4] {
assert_colormap_sample_t(t);
self.with_bound_style("dear-implot: PlotContext::sample_colormap()", || unsafe {
let out = sys::ImPlot_SampleColormap(t, crate::IMPLOT_AUTO);
[out.x, out.y, out.z, out.w]
})
}
pub fn sample_colormap_from(&self, t: f32, cmap: impl Into<ColormapSelection>) -> [f32; 4] {
assert_colormap_sample_t(t);
self.with_bound_style(
"dear-implot: PlotContext::sample_colormap_from()",
|| unsafe {
let out = sys::ImPlot_SampleColormap(t, cmap.into().raw());
[out.x, out.y, out.z, out.w]
},
)
}
pub fn next_colormap_color(&self) -> [f32; 4] {
self.with_bound_style(
"dear-implot: PlotContext::next_colormap_color()",
|| unsafe {
let out = sys::ImPlot_NextColormapColor();
[out.x, out.y, out.z, out.w]
},
)
}
pub fn map_input_default(&self) {
self.with_bound_style("dear-implot: PlotContext::map_input_default()", || unsafe {
sys::ImPlot_MapInputDefault(sys::ImPlot_GetInputMap())
})
}
pub fn map_input_reverse(&self) {
self.with_bound_style("dear-implot: PlotContext::map_input_reverse()", || unsafe {
sys::ImPlot_MapInputReverse(sys::ImPlot_GetInputMap())
})
}
}
impl PlotUi<'_> {
pub fn show_style_editor(&self) {
let _guard = self.bind();
unsafe { sys::ImPlot_ShowStyleEditor(std::ptr::null_mut()) }
}
pub fn show_style_selector(&self, label: &str) -> bool {
let label = if label.contains('\0') { "" } else { label };
let _guard = self.bind();
with_scratch_txt(label, |ptr| unsafe { sys::ImPlot_ShowStyleSelector(ptr) })
}
pub fn show_colormap_selector(&self, label: &str) -> bool {
let label = if label.contains('\0') { "" } else { label };
let _guard = self.bind();
with_scratch_txt(label, |ptr| unsafe {
sys::ImPlot_ShowColormapSelector(ptr)
})
}
pub fn show_input_map_selector(&self, label: &str) -> bool {
let label = if label.contains('\0') { "" } else { label };
let _guard = self.bind();
with_scratch_txt(label, |ptr| unsafe {
sys::ImPlot_ShowInputMapSelector(ptr)
})
}
pub fn colormap_scale(
&self,
label: &str,
scale_min: f64,
scale_max: f64,
height: f32,
cmap: impl Into<ColormapSelection>,
) {
assert!(
scale_min.is_finite(),
"colormap_scale scale_min must be finite"
);
assert!(
scale_max.is_finite(),
"colormap_scale scale_max must be finite"
);
assert!(height.is_finite(), "colormap_scale height must be finite");
let label = if label.contains('\0') { "" } else { label };
let size = sys::ImVec2_c { x: 0.0, y: height };
let fmt_ptr: *const c_char = std::ptr::null();
let flags = sys::ImPlotColormapScaleFlags_None as sys::ImPlotColormapScaleFlags;
let cmap = cmap.into().raw();
let _guard = self.bind();
with_scratch_txt(label, |ptr| unsafe {
sys::ImPlot_ColormapScale(ptr, scale_min, scale_max, size, fmt_ptr, flags, cmap)
})
}
pub fn colormap_slider(
&self,
label: &str,
t: &mut f32,
out_color: Option<&mut [f32; 4]>,
format: Option<&str>,
cmap: impl Into<ColormapSelection>,
) -> bool {
assert!(t.is_finite(), "colormap_slider t must be finite");
let label = if label.contains('\0') { "" } else { label };
let format = format.filter(|s| !s.contains('\0'));
let cmap = cmap.into().raw();
let mut out = sys::ImVec4 {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
};
let out_ptr = if out_color.is_some() {
&mut out as *mut sys::ImVec4
} else {
std::ptr::null_mut()
};
let _guard = self.bind();
let changed = match format {
Some(fmt) => with_scratch_txt_two(label, fmt, |label_ptr, fmt_ptr| unsafe {
sys::ImPlot_ColormapSlider(label_ptr, t as *mut f32, out_ptr, fmt_ptr, cmap)
}),
None => with_scratch_txt(label, |label_ptr| unsafe {
sys::ImPlot_ColormapSlider(
label_ptr,
t as *mut f32,
out_ptr,
std::ptr::null(),
cmap,
)
}),
};
if let Some(out_color) = out_color {
*out_color = [out.x, out.y, out.z, out.w];
}
changed
}
pub fn colormap_button(
&self,
label: &str,
size: [f32; 2],
cmap: impl Into<ColormapSelection>,
) -> bool {
assert!(
size[0].is_finite() && size[1].is_finite(),
"colormap_button size must be finite"
);
let label = if label.contains('\0') { "" } else { label };
let sz = sys::ImVec2_c {
x: size[0],
y: size[1],
};
let cmap = cmap.into().raw();
let _guard = self.bind();
with_scratch_txt(label, |ptr| unsafe {
sys::ImPlot_ColormapButton(ptr, sz, cmap)
})
}
}
#[cfg(test)]
mod tests {
use super::{
Colormap, ColormapColorIndex, ColormapIndex, ColormapSelection, PlotItemArrayStyle,
with_scoped_next_plot_item_array_style,
};
use crate::plots::{
PlotDataLayout, PlotDataOffset, PlotDataStride, set_next_plot_spec, take_next_plot_spec,
};
#[test]
fn colormap_indices_reject_negative_values() {
assert_eq!(ColormapIndex::from_raw(-1), None);
assert_eq!(ColormapIndex::new(0).map(ColormapIndex::raw), Some(0));
assert_eq!(ColormapIndex::new(0).map(ColormapIndex::get), Some(0));
assert_eq!(ColormapIndex::new(i32::MAX as usize + 1), None);
assert_eq!(
ColormapIndex::from(Colormap::Viridis).raw(),
crate::sys::ImPlotColormap_Viridis
);
assert_eq!(ColormapSelection::Current.raw(), crate::IMPLOT_AUTO);
assert_eq!(
ColormapSelection::from(Colormap::Viridis).raw(),
crate::sys::ImPlotColormap_Viridis
);
assert_eq!(
ColormapColorIndex::new(0).map(ColormapColorIndex::get),
Some(0)
);
assert_eq!(
ColormapColorIndex::from_usize(i32::MAX as usize).map(ColormapColorIndex::raw),
Some(i32::MAX)
);
assert_eq!(ColormapColorIndex::from_usize(i32::MAX as usize + 1), None);
}
#[test]
#[should_panic(expected = "test returned a negative colormap count")]
fn colormap_count_conversion_rejects_negative_ffi_values() {
let _ = super::colormap_count_from_i32(-1, "test");
}
#[test]
#[should_panic(expected = "sample_colormap t must be between 0 and 1")]
fn sample_colormap_rejects_out_of_range_t_before_ffi() {
super::assert_colormap_sample_t(-0.1);
}
#[test]
fn next_plot_item_array_style_is_consumed_by_next_spec() {
let line_colors = [0x01020304u32, 0x05060708];
let fill_colors = [0x11121314u32];
let marker_sizes = [2.0f32, 4.0, 8.0];
with_scoped_next_plot_item_array_style(
PlotItemArrayStyle::new()
.with_line_colors(&line_colors)
.with_fill_colors(&fill_colors)
.with_marker_sizes(&marker_sizes),
|| {
let layout =
PlotDataLayout::new(PlotDataOffset::samples(3), PlotDataStride::bytes(16));
let spec = crate::plots::plot_spec_from(7, layout);
assert_eq!(spec.Flags, 7);
assert_eq!(spec.Offset, 3);
assert_eq!(spec.Stride, 16);
assert_eq!(spec.LineColors, line_colors.as_ptr() as *mut _);
assert_eq!(spec.FillColors, fill_colors.as_ptr() as *mut _);
assert_eq!(spec.MarkerSizes, marker_sizes.as_ptr() as *mut _);
},
);
let spec = crate::plots::plot_spec_from(0, PlotDataLayout::DEFAULT);
assert!(spec.LineColors.is_null());
assert!(spec.FillColors.is_null());
assert!(spec.MarkerSizes.is_null());
}
#[test]
fn next_plot_item_array_style_is_restored_if_unused() {
let line_colors = [0xAABBCCDDu32];
with_scoped_next_plot_item_array_style(
PlotItemArrayStyle::new().with_line_colors(&line_colors),
|| {},
);
let spec = crate::plots::plot_spec_from(0, PlotDataLayout::DEFAULT);
assert!(spec.LineColors.is_null());
}
#[test]
fn next_plot_item_array_style_is_restored_if_closure_panics() {
set_next_plot_spec(None);
let line_colors = [0xAABBCCDDu32];
let result = std::panic::catch_unwind(|| {
with_scoped_next_plot_item_array_style(
PlotItemArrayStyle::new().with_line_colors(&line_colors),
|| panic!("boom"),
);
});
assert!(result.is_err());
assert!(take_next_plot_spec().is_none());
}
}