1use std::cell::RefCell;
2
3use crate::{imgui_sys, sys};
4
5pub(crate) fn len_i32(len: usize) -> Option<i32> {
6 i32::try_from(len).ok()
7}
8
9pub(crate) fn axis_tick_count_to_i32(caller: &str, count: usize) -> i32 {
10 assert!(count > 0, "{caller} n_ticks must be positive");
11 i32::try_from(count)
12 .unwrap_or_else(|_| panic!("{caller} n_ticks exceeded ImPlot3D's i32 range"))
13}
14
15pub(crate) const IMPLOT3D_AUTO: i32 = -1;
16
17thread_local! {
18 static NEXT_PLOT3D_SPEC: RefCell<Option<sys::ImPlot3DSpec_c>> = RefCell::new(None);
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26pub struct Plot3DDataOffset(i32);
27
28impl Plot3DDataOffset {
29 pub const ZERO: Self = Self(0);
31
32 #[inline]
34 pub const fn samples(offset: i32) -> Self {
35 Self(offset)
36 }
37
38 #[inline]
39 pub(crate) const fn raw(self) -> i32 {
40 self.0
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct Plot3DDataStride(i32);
50
51impl Plot3DDataStride {
52 pub const AUTO: Self = Self(IMPLOT3D_AUTO);
54
55 #[inline]
59 pub fn bytes(bytes: usize) -> Self {
60 assert!(
61 bytes > 0,
62 "Plot3DDataStride::bytes() requires a non-zero stride"
63 );
64 let bytes = i32::try_from(bytes)
65 .expect("Plot3DDataStride::bytes() stride exceeded ImPlot3D's int range");
66 Self(bytes)
67 }
68
69 #[inline]
71 pub fn for_type<T>() -> Self {
72 Self::bytes(std::mem::size_of::<T>())
73 }
74
75 #[inline]
76 pub(crate) const fn raw(self) -> i32 {
77 self.0
78 }
79}
80
81impl Default for Plot3DDataStride {
82 fn default() -> Self {
83 Self::AUTO
84 }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
89pub struct Plot3DDataLayout {
90 offset: Plot3DDataOffset,
91 stride: Plot3DDataStride,
92}
93
94impl Plot3DDataLayout {
95 pub const DEFAULT: Self = Self {
97 offset: Plot3DDataOffset::ZERO,
98 stride: Plot3DDataStride::AUTO,
99 };
100
101 #[inline]
103 pub const fn new(offset: Plot3DDataOffset, stride: Plot3DDataStride) -> Self {
104 Self { offset, stride }
105 }
106
107 #[inline]
109 pub const fn with_offset(mut self, offset: Plot3DDataOffset) -> Self {
110 self.offset = offset;
111 self
112 }
113
114 #[inline]
116 pub const fn with_stride(mut self, stride: Plot3DDataStride) -> Self {
117 self.stride = stride;
118 self
119 }
120
121 #[inline]
122 pub(crate) const fn raw_offset(self) -> i32 {
123 self.offset.raw()
124 }
125
126 #[inline]
127 pub(crate) const fn raw_stride(self) -> i32 {
128 self.stride.raw()
129 }
130}
131
132pub(crate) fn update_next_plot3d_spec(f: impl FnOnce(&mut sys::ImPlot3DSpec_c)) {
133 NEXT_PLOT3D_SPEC.with(|cell| {
134 let mut guard = cell.borrow_mut();
135 let mut spec = guard.take().unwrap_or_else(default_plot3d_spec);
136 f(&mut spec);
137 *guard = Some(spec);
138 })
139}
140
141pub(crate) fn take_next_plot3d_spec() -> Option<sys::ImPlot3DSpec_c> {
142 NEXT_PLOT3D_SPEC.with(|cell| cell.borrow_mut().take())
143}
144
145pub(crate) fn set_next_plot3d_spec(spec: Option<sys::ImPlot3DSpec_c>) {
146 NEXT_PLOT3D_SPEC.with(|cell| {
147 *cell.borrow_mut() = spec;
148 })
149}
150
151pub(crate) fn default_plot3d_spec() -> sys::ImPlot3DSpec_c {
152 let auto_col = sys::ImVec4_c {
153 x: 0.0,
154 y: 0.0,
155 z: 0.0,
156 w: -1.0,
157 };
158
159 sys::ImPlot3DSpec_c {
160 LineColor: auto_col,
161 LineColors: std::ptr::null_mut(),
162 LineWeight: 1.0,
163 FillColor: auto_col,
164 FillColors: std::ptr::null_mut(),
165 FillAlpha: -1.0,
166 Marker: sys::ImPlot3DMarker_Auto as _,
167 MarkerSize: -1.0,
168 MarkerSizes: std::ptr::null_mut(),
169 MarkerLineColor: auto_col,
170 MarkerLineColors: std::ptr::null_mut(),
171 MarkerFillColor: auto_col,
172 MarkerFillColors: std::ptr::null_mut(),
173 Offset: 0,
174 Stride: IMPLOT3D_AUTO,
175 Flags: sys::ImPlot3DItemFlags_None as _,
176 }
177}
178
179pub(crate) fn plot3d_spec_from(flags: u32, layout: Plot3DDataLayout) -> sys::ImPlot3DSpec_c {
180 let mut spec = take_next_plot3d_spec().unwrap_or_else(default_plot3d_spec);
181 spec.Flags = ((spec.Flags as u32) | flags) as sys::ImPlot3DItemFlags;
182 spec.Offset = layout.raw_offset();
183 spec.Stride = layout.raw_stride();
184 spec
185}
186
187pub(crate) trait ImVec2Ctor {
188 fn from_xy(x: f32, y: f32) -> Self;
189}
190
191impl ImVec2Ctor for sys::ImVec2_c {
192 fn from_xy(x: f32, y: f32) -> Self {
193 Self { x, y }
194 }
195}
196
197impl ImVec2Ctor for imgui_sys::ImVec2_c {
198 fn from_xy(x: f32, y: f32) -> Self {
199 Self { x, y }
200 }
201}
202
203#[inline]
204pub(crate) fn imvec2<T: ImVec2Ctor>(x: f32, y: f32) -> T {
205 T::from_xy(x, y)
206}
207
208pub(crate) trait ImVec4Ctor {
209 fn from_xyzw(x: f32, y: f32, z: f32, w: f32) -> Self;
210}
211
212impl ImVec4Ctor for sys::ImVec4_c {
213 fn from_xyzw(x: f32, y: f32, z: f32, w: f32) -> Self {
214 Self { x, y, z, w }
215 }
216}
217
218impl ImVec4Ctor for imgui_sys::ImVec4_c {
219 fn from_xyzw(x: f32, y: f32, z: f32, w: f32) -> Self {
220 Self { x, y, z, w }
221 }
222}
223
224#[inline]
225pub(crate) fn imvec4<T: ImVec4Ctor>(x: f32, y: f32, z: f32, w: f32) -> T {
226 T::from_xyzw(x, y, z, w)
227}
228
229#[cfg(test)]
230mod tests {
231 use super::{
232 Plot3DDataLayout, Plot3DDataOffset, Plot3DDataStride, axis_tick_count_to_i32,
233 plot3d_spec_from,
234 };
235
236 #[test]
237 fn data_layout_allows_signed_sample_offsets() {
238 let layout = Plot3DDataLayout::DEFAULT.with_offset(Plot3DDataOffset::samples(-8));
239 let spec = plot3d_spec_from(0, layout);
240 assert_eq!(spec.Offset, -8);
241 assert_eq!(spec.Stride, super::IMPLOT3D_AUTO);
242 }
243
244 #[test]
245 fn data_stride_bytes_are_positive() {
246 let stride = Plot3DDataStride::bytes(16);
247 let layout = Plot3DDataLayout::DEFAULT.with_stride(stride);
248 let spec = plot3d_spec_from(0, layout);
249 assert_eq!(spec.Stride, 16);
250 }
251
252 #[test]
253 #[should_panic(expected = "requires a non-zero stride")]
254 fn zero_data_stride_panics_before_ffi() {
255 let _ = Plot3DDataStride::bytes(0);
256 }
257
258 #[test]
259 fn axis_ticks_range_count_is_checked_before_ffi() {
260 assert_eq!(axis_tick_count_to_i32("test", 1), 1);
261 assert_eq!(axis_tick_count_to_i32("test", i32::MAX as usize), i32::MAX);
262
263 assert!(
264 std::panic::catch_unwind(|| axis_tick_count_to_i32("test", 0)).is_err(),
265 "zero tick counts must not cross the safe API boundary"
266 );
267 assert!(
268 std::panic::catch_unwind(|| {
269 axis_tick_count_to_i32("test", i32::MAX as usize + 1);
270 })
271 .is_err(),
272 "oversized tick counts must not cross the safe API boundary"
273 );
274 }
275}