dear_implot/context/
callbacks.rs1use super::ui::PlotUi;
2use crate::{XAxis, YAxis, sys};
3use std::{cell::RefCell, rc::Rc};
4
5impl<'ui> PlotUi<'ui> {
6 pub fn setup_x_axis_format_closure<F>(&self, axis: XAxis, f: F) -> AxisFormatterToken
11 where
12 F: Fn(f64) -> String + Send + Sync + 'static,
13 {
14 self.with_bound_context(|| AxisFormatterToken::new(axis as sys::ImAxis, f))
15 }
16
17 pub fn setup_y_axis_format_closure<F>(&self, axis: YAxis, f: F) -> AxisFormatterToken
21 where
22 F: Fn(f64) -> String + Send + Sync + 'static,
23 {
24 self.with_bound_context(|| AxisFormatterToken::new(axis as sys::ImAxis, f))
25 }
26
27 pub fn setup_x_axis_transform_closure<FW, INV>(
32 &self,
33 axis: XAxis,
34 forward: FW,
35 inverse: INV,
36 ) -> AxisTransformToken
37 where
38 FW: Fn(f64) -> f64 + Send + Sync + 'static,
39 INV: Fn(f64) -> f64 + Send + Sync + 'static,
40 {
41 self.with_bound_context(|| AxisTransformToken::new(axis as sys::ImAxis, forward, inverse))
42 }
43
44 pub fn setup_y_axis_transform_closure<FW, INV>(
46 &self,
47 axis: YAxis,
48 forward: FW,
49 inverse: INV,
50 ) -> AxisTransformToken
51 where
52 FW: Fn(f64) -> f64 + Send + Sync + 'static,
53 INV: Fn(f64) -> f64 + Send + Sync + 'static,
54 {
55 self.with_bound_context(|| AxisTransformToken::new(axis as sys::ImAxis, forward, inverse))
56 }
57}
58
59#[derive(Default)]
72struct PlotScopeStorage {
73 formatters: Vec<Box<FormatterHolder>>,
74 transforms: Vec<Box<TransformHolder>>,
75}
76
77thread_local! {
78 static PLOT_SCOPE_STACK: RefCell<Vec<PlotScopeStorage>> = const { RefCell::new(Vec::new()) };
79}
80
81fn with_plot_scope_storage<T>(f: impl FnOnce(&mut PlotScopeStorage) -> T) -> Option<T> {
82 PLOT_SCOPE_STACK.with(|stack| {
83 let mut stack = stack.borrow_mut();
84 stack.last_mut().map(f)
85 })
86}
87
88pub(crate) struct PlotScopeGuard {
89 _not_send_or_sync: std::marker::PhantomData<Rc<()>>,
90}
91
92impl PlotScopeGuard {
93 pub(crate) fn new() -> Self {
94 PLOT_SCOPE_STACK.with(|stack| stack.borrow_mut().push(PlotScopeStorage::default()));
95 Self {
96 _not_send_or_sync: std::marker::PhantomData,
97 }
98 }
99}
100
101impl Drop for PlotScopeGuard {
102 fn drop(&mut self) {
103 PLOT_SCOPE_STACK.with(|stack| {
104 let popped = stack.borrow_mut().pop();
105 debug_assert!(popped.is_some(), "dear-implot: plot scope stack underflow");
106 });
107 }
108}
109
110struct FormatterHolder {
113 func: Box<dyn Fn(f64) -> String + Send + Sync + 'static>,
114}
115
116#[must_use]
117pub struct AxisFormatterToken {
118 _private: (),
119 _not_send_or_sync: std::marker::PhantomData<Rc<()>>,
120}
121
122impl AxisFormatterToken {
123 fn new<F>(axis: sys::ImAxis, f: F) -> Self
124 where
125 F: Fn(f64) -> String + Send + Sync + 'static,
126 {
127 let configured = with_plot_scope_storage(|storage| {
128 let holder = Box::new(FormatterHolder { func: Box::new(f) });
129 let user = &*holder as *const FormatterHolder as *mut std::os::raw::c_void;
130 storage.formatters.push(holder);
131 unsafe {
132 sys::ImPlot_SetupAxisFormat_PlotFormatter(
133 axis as sys::ImAxis,
134 Some(formatter_thunk),
135 user,
136 )
137 }
138 })
139 .is_some();
140
141 debug_assert!(
142 configured,
143 "dear-implot: axis formatter closure must be set within an active plot"
144 );
145
146 Self {
147 _private: (),
148 _not_send_or_sync: std::marker::PhantomData,
149 }
150 }
151}
152
153impl Drop for AxisFormatterToken {
154 fn drop(&mut self) {
155 }
157}
158
159unsafe extern "C" fn formatter_thunk(
160 value: f64,
161 buff: *mut std::os::raw::c_char,
162 size: std::os::raw::c_int,
163 user_data: *mut std::os::raw::c_void,
164) -> std::os::raw::c_int {
165 if user_data.is_null() || buff.is_null() || size <= 0 {
166 return 0;
167 }
168 let holder = unsafe { &*(user_data as *const FormatterHolder) };
170 let s = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.func)(value))) {
171 Ok(v) => v,
172 Err(_) => {
173 eprintln!("dear-implot: panic in axis formatter callback");
174 std::process::abort();
175 }
176 };
177 let bytes = s.as_bytes();
178 let max = (size - 1).max(0) as usize;
179 let n = bytes.len().min(max);
180
181 unsafe {
185 std::ptr::copy_nonoverlapping(bytes.as_ptr(), buff as *mut u8, n);
186 *buff.add(n) = 0;
187 }
188 n as std::os::raw::c_int
189}
190
191struct TransformHolder {
194 forward: Box<dyn Fn(f64) -> f64 + Send + Sync + 'static>,
195 inverse: Box<dyn Fn(f64) -> f64 + Send + Sync + 'static>,
196}
197
198#[must_use]
199pub struct AxisTransformToken {
200 _private: (),
201 _not_send_or_sync: std::marker::PhantomData<Rc<()>>,
202}
203
204impl AxisTransformToken {
205 fn new<FW, INV>(axis: sys::ImAxis, forward: FW, inverse: INV) -> Self
206 where
207 FW: Fn(f64) -> f64 + Send + Sync + 'static,
208 INV: Fn(f64) -> f64 + Send + Sync + 'static,
209 {
210 let configured = with_plot_scope_storage(|storage| {
211 let holder = Box::new(TransformHolder {
212 forward: Box::new(forward),
213 inverse: Box::new(inverse),
214 });
215 let user = &*holder as *const TransformHolder as *mut std::os::raw::c_void;
216 storage.transforms.push(holder);
217 unsafe {
218 sys::ImPlot_SetupAxisScale_PlotTransform(
219 axis as sys::ImAxis,
220 Some(transform_forward_thunk),
221 Some(transform_inverse_thunk),
222 user,
223 )
224 }
225 })
226 .is_some();
227
228 debug_assert!(
229 configured,
230 "dear-implot: axis transform closure must be set within an active plot"
231 );
232
233 Self {
234 _private: (),
235 _not_send_or_sync: std::marker::PhantomData,
236 }
237 }
238}
239
240impl Drop for AxisTransformToken {
241 fn drop(&mut self) {
242 }
244}
245
246unsafe extern "C" fn transform_forward_thunk(
247 value: f64,
248 user_data: *mut std::os::raw::c_void,
249) -> f64 {
250 if user_data.is_null() {
251 return value;
252 }
253 let holder = unsafe { &*(user_data as *const TransformHolder) };
254 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.forward)(value))) {
255 Ok(v) => v,
256 Err(_) => {
257 eprintln!("dear-implot: panic in axis transform (forward) callback");
258 std::process::abort();
259 }
260 }
261}
262
263unsafe extern "C" fn transform_inverse_thunk(
264 value: f64,
265 user_data: *mut std::os::raw::c_void,
266) -> f64 {
267 if user_data.is_null() {
268 return value;
269 }
270 let holder = unsafe { &*(user_data as *const TransformHolder) };
271 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.inverse)(value))) {
272 Ok(v) => v,
273 Err(_) => {
274 eprintln!("dear-implot: panic in axis transform (inverse) callback");
275 std::process::abort();
276 }
277 }
278}