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