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