1use crate::{AxisFlags, PlotCond, XAxis, YAxis, sys};
2use dear_imgui_rs::{
3 Context as ImGuiContext, Ui, with_scratch_txt, with_scratch_txt_slice, with_scratch_txt_two,
4};
5use dear_imgui_sys as imgui_sys;
6use std::os::raw::c_char;
7use std::{cell::RefCell, rc::Rc};
8
9pub struct PlotContext {
14 raw: *mut sys::ImPlotContext,
15}
16
17impl PlotContext {
18 pub fn try_create(_imgui_ctx: &ImGuiContext) -> dear_imgui_rs::ImGuiResult<Self> {
23 unsafe {
27 sys::ImPlot_SetImGuiContext(imgui_sys::igGetCurrentContext());
28 }
29
30 let raw = unsafe { sys::ImPlot_CreateContext() };
31 if raw.is_null() {
32 return Err(dear_imgui_rs::ImGuiError::context_creation(
33 "ImPlot_CreateContext returned null",
34 ));
35 }
36
37 unsafe {
39 sys::ImPlot_SetCurrentContext(raw);
40 }
41
42 Ok(Self { raw })
43 }
44
45 pub fn create(imgui_ctx: &ImGuiContext) -> Self {
47 Self::try_create(imgui_ctx).expect("Failed to create ImPlot context")
48 }
49
50 pub fn current() -> Option<Self> {
54 let raw = unsafe { sys::ImPlot_GetCurrentContext() };
55 if raw.is_null() {
56 None
57 } else {
58 Some(Self { raw })
59 }
60 }
61
62 pub fn set_as_current(&self) {
64 unsafe {
65 sys::ImPlot_SetCurrentContext(self.raw);
66 }
67 }
68
69 pub fn get_plot_ui<'ui>(&'ui self, ui: &'ui Ui) -> PlotUi<'ui> {
74 PlotUi { context: self, ui }
75 }
76
77 pub unsafe fn raw(&self) -> *mut sys::ImPlotContext {
84 self.raw
85 }
86}
87
88impl Drop for PlotContext {
89 fn drop(&mut self) {
90 if !self.raw.is_null() {
91 unsafe {
92 sys::ImPlot_DestroyContext(self.raw);
93 }
94 }
95 }
96}
97
98pub struct PlotUi<'ui> {
105 #[allow(dead_code)]
106 context: &'ui PlotContext,
107 #[allow(dead_code)]
108 ui: &'ui Ui,
109}
110
111impl<'ui> PlotUi<'ui> {
112 pub fn begin_plot(&self, title: &str) -> Option<PlotToken<'_>> {
117 let size = sys::ImVec2_c { x: -1.0, y: 0.0 };
118 if title.contains('\0') {
119 return None;
120 }
121 let started = with_scratch_txt(title, |ptr| unsafe { sys::ImPlot_BeginPlot(ptr, size, 0) });
122
123 if started {
124 Some(PlotToken::new())
125 } else {
126 None
127 }
128 }
129
130 pub fn begin_plot_with_size(&self, title: &str, size: [f32; 2]) -> Option<PlotToken<'_>> {
132 let plot_size = sys::ImVec2_c {
133 x: size[0],
134 y: size[1],
135 };
136 if title.contains('\0') {
137 return None;
138 }
139 let started = with_scratch_txt(title, |ptr| unsafe {
140 sys::ImPlot_BeginPlot(ptr, plot_size, 0)
141 });
142
143 if started {
144 Some(PlotToken::new())
145 } else {
146 None
147 }
148 }
149
150 pub fn plot_line(&self, label: &str, x_data: &[f64], y_data: &[f64]) {
154 if x_data.len() != y_data.len() {
155 return; }
157 let count = match i32::try_from(x_data.len()) {
158 Ok(v) => v,
159 Err(_) => return,
160 };
161
162 let label = if label.contains('\0') { "" } else { label };
163 with_scratch_txt(label, |ptr| unsafe {
164 sys::ImPlot_PlotLine_doublePtrdoublePtr(
165 ptr,
166 x_data.as_ptr(),
167 y_data.as_ptr(),
168 count,
169 0,
170 0,
171 0,
172 );
173 })
174 }
175
176 pub fn plot_scatter(&self, label: &str, x_data: &[f64], y_data: &[f64]) {
178 if x_data.len() != y_data.len() {
179 return; }
181 let count = match i32::try_from(x_data.len()) {
182 Ok(v) => v,
183 Err(_) => return,
184 };
185
186 let label = if label.contains('\0') { "" } else { label };
187 with_scratch_txt(label, |ptr| unsafe {
188 sys::ImPlot_PlotScatter_doublePtrdoublePtr(
189 ptr,
190 x_data.as_ptr(),
191 y_data.as_ptr(),
192 count,
193 0,
194 0,
195 0,
196 );
197 })
198 }
199
200 pub fn is_plot_hovered(&self) -> bool {
202 unsafe { sys::ImPlot_IsPlotHovered() }
203 }
204
205 pub fn get_plot_mouse_pos(&self, y_axis: Option<crate::YAxisChoice>) -> sys::ImPlotPoint {
207 let y_axis_i32 = crate::y_axis_choice_option_to_i32(y_axis);
208 let y_axis = match y_axis_i32 {
209 0 => 3,
210 1 => 4,
211 2 => 5,
212 _ => 3,
213 };
214 unsafe { sys::ImPlot_GetPlotMousePos(0, y_axis) }
215 }
216
217 pub fn get_plot_mouse_pos_axes(&self, x_axis: XAxis, y_axis: YAxis) -> sys::ImPlotPoint {
219 unsafe { sys::ImPlot_GetPlotMousePos(x_axis as i32, y_axis as i32) }
220 }
221
222 pub fn set_axes(&self, x_axis: XAxis, y_axis: YAxis) {
224 unsafe { sys::ImPlot_SetAxes(x_axis as i32, y_axis as i32) }
225 }
226
227 pub fn setup_x_axis(&self, axis: XAxis, label: Option<&str>, flags: AxisFlags) {
229 let label = label.filter(|s| !s.contains('\0'));
230 match label {
231 Some(label) => with_scratch_txt(label, |ptr| unsafe {
232 sys::ImPlot_SetupAxis(
233 axis as sys::ImAxis,
234 ptr,
235 flags.bits() as sys::ImPlotAxisFlags,
236 )
237 }),
238 None => unsafe {
239 sys::ImPlot_SetupAxis(
240 axis as sys::ImAxis,
241 std::ptr::null(),
242 flags.bits() as sys::ImPlotAxisFlags,
243 )
244 },
245 }
246 }
247
248 pub fn setup_y_axis(&self, axis: YAxis, label: Option<&str>, flags: AxisFlags) {
250 let label = label.filter(|s| !s.contains('\0'));
251 match label {
252 Some(label) => with_scratch_txt(label, |ptr| unsafe {
253 sys::ImPlot_SetupAxis(
254 axis as sys::ImAxis,
255 ptr,
256 flags.bits() as sys::ImPlotAxisFlags,
257 )
258 }),
259 None => unsafe {
260 sys::ImPlot_SetupAxis(
261 axis as sys::ImAxis,
262 std::ptr::null(),
263 flags.bits() as sys::ImPlotAxisFlags,
264 )
265 },
266 }
267 }
268
269 pub fn setup_x_axis_limits(&self, axis: XAxis, min: f64, max: f64, cond: PlotCond) {
271 unsafe {
272 sys::ImPlot_SetupAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
273 }
274 }
275
276 pub fn setup_y_axis_limits(&self, axis: YAxis, min: f64, max: f64, cond: PlotCond) {
278 unsafe {
279 sys::ImPlot_SetupAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
280 }
281 }
282
283 pub fn setup_axis_links(
285 &self,
286 axis: i32,
287 link_min: Option<&mut f64>,
288 link_max: Option<&mut f64>,
289 ) {
290 let pmin = link_min.map_or(std::ptr::null_mut(), |r| r as *mut f64);
291 let pmax = link_max.map_or(std::ptr::null_mut(), |r| r as *mut f64);
292 unsafe { sys::ImPlot_SetupAxisLinks(axis, pmin, pmax) }
293 }
294
295 pub fn setup_axes(
297 &self,
298 x_label: Option<&str>,
299 y_label: Option<&str>,
300 x_flags: AxisFlags,
301 y_flags: AxisFlags,
302 ) {
303 let x_label = x_label.filter(|s| !s.contains('\0'));
304 let y_label = y_label.filter(|s| !s.contains('\0'));
305
306 match (x_label, y_label) {
307 (Some(x_label), Some(y_label)) => {
308 with_scratch_txt_two(x_label, y_label, |xp, yp| unsafe {
309 sys::ImPlot_SetupAxes(
310 xp,
311 yp,
312 x_flags.bits() as sys::ImPlotAxisFlags,
313 y_flags.bits() as sys::ImPlotAxisFlags,
314 )
315 })
316 }
317 (Some(x_label), None) => with_scratch_txt(x_label, |xp| unsafe {
318 sys::ImPlot_SetupAxes(
319 xp,
320 std::ptr::null(),
321 x_flags.bits() as sys::ImPlotAxisFlags,
322 y_flags.bits() as sys::ImPlotAxisFlags,
323 )
324 }),
325 (None, Some(y_label)) => with_scratch_txt(y_label, |yp| unsafe {
326 sys::ImPlot_SetupAxes(
327 std::ptr::null(),
328 yp,
329 x_flags.bits() as sys::ImPlotAxisFlags,
330 y_flags.bits() as sys::ImPlotAxisFlags,
331 )
332 }),
333 (None, None) => unsafe {
334 sys::ImPlot_SetupAxes(
335 std::ptr::null(),
336 std::ptr::null(),
337 x_flags.bits() as sys::ImPlotAxisFlags,
338 y_flags.bits() as sys::ImPlotAxisFlags,
339 )
340 },
341 }
342 }
343
344 pub fn setup_axes_limits(
346 &self,
347 x_min: f64,
348 x_max: f64,
349 y_min: f64,
350 y_max: f64,
351 cond: PlotCond,
352 ) {
353 unsafe { sys::ImPlot_SetupAxesLimits(x_min, x_max, y_min, y_max, cond as sys::ImPlotCond) }
354 }
355
356 pub fn setup_finish(&self) {
358 unsafe { sys::ImPlot_SetupFinish() }
359 }
360
361 pub fn set_next_x_axis_limits(&self, axis: XAxis, min: f64, max: f64, cond: PlotCond) {
363 unsafe {
364 sys::ImPlot_SetNextAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
365 }
366 }
367
368 pub fn set_next_y_axis_limits(&self, axis: YAxis, min: f64, max: f64, cond: PlotCond) {
370 unsafe {
371 sys::ImPlot_SetNextAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
372 }
373 }
374
375 pub fn set_next_axis_links(
377 &self,
378 axis: i32,
379 link_min: Option<&mut f64>,
380 link_max: Option<&mut f64>,
381 ) {
382 let pmin = link_min.map_or(std::ptr::null_mut(), |r| r as *mut f64);
383 let pmax = link_max.map_or(std::ptr::null_mut(), |r| r as *mut f64);
384 unsafe { sys::ImPlot_SetNextAxisLinks(axis, pmin, pmax) }
385 }
386
387 pub fn set_next_axes_limits(
389 &self,
390 x_min: f64,
391 x_max: f64,
392 y_min: f64,
393 y_max: f64,
394 cond: PlotCond,
395 ) {
396 unsafe {
397 sys::ImPlot_SetNextAxesLimits(x_min, x_max, y_min, y_max, cond as sys::ImPlotCond)
398 }
399 }
400
401 pub fn set_next_axes_to_fit(&self) {
403 unsafe { sys::ImPlot_SetNextAxesToFit() }
404 }
405
406 pub fn set_next_axis_to_fit(&self, axis: i32) {
408 unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) }
409 }
410
411 pub fn set_next_x_axis_to_fit(&self, axis: XAxis) {
413 unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) }
414 }
415
416 pub fn set_next_y_axis_to_fit(&self, axis: YAxis) {
418 unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) }
419 }
420
421 pub fn setup_x_axis_ticks_positions(
425 &self,
426 axis: XAxis,
427 values: &[f64],
428 labels: Option<&[&str]>,
429 keep_default: bool,
430 ) {
431 let count = match i32::try_from(values.len()) {
432 Ok(v) => v,
433 Err(_) => return,
434 };
435 if let Some(labels) = labels {
436 if labels.len() != values.len() {
437 return;
438 }
439 let cleaned: Vec<&str> = labels
440 .iter()
441 .map(|&s| if s.contains('\0') { "" } else { s })
442 .collect();
443 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
444 sys::ImPlot_SetupAxisTicks_doublePtr(
445 axis as sys::ImAxis,
446 values.as_ptr(),
447 count,
448 ptrs.as_ptr() as *const *const c_char,
449 keep_default,
450 )
451 })
452 } else {
453 unsafe {
454 sys::ImPlot_SetupAxisTicks_doublePtr(
455 axis as sys::ImAxis,
456 values.as_ptr(),
457 count,
458 std::ptr::null(),
459 keep_default,
460 )
461 }
462 }
463 }
464
465 pub fn setup_y_axis_ticks_positions(
469 &self,
470 axis: YAxis,
471 values: &[f64],
472 labels: Option<&[&str]>,
473 keep_default: bool,
474 ) {
475 let count = match i32::try_from(values.len()) {
476 Ok(v) => v,
477 Err(_) => return,
478 };
479 if let Some(labels) = labels {
480 if labels.len() != values.len() {
481 return;
482 }
483 let cleaned: Vec<&str> = labels
484 .iter()
485 .map(|&s| if s.contains('\0') { "" } else { s })
486 .collect();
487 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
488 sys::ImPlot_SetupAxisTicks_doublePtr(
489 axis as sys::ImAxis,
490 values.as_ptr(),
491 count,
492 ptrs.as_ptr() as *const *const c_char,
493 keep_default,
494 )
495 })
496 } else {
497 unsafe {
498 sys::ImPlot_SetupAxisTicks_doublePtr(
499 axis as sys::ImAxis,
500 values.as_ptr(),
501 count,
502 std::ptr::null(),
503 keep_default,
504 )
505 }
506 }
507 }
508
509 pub fn setup_x_axis_ticks_range(
513 &self,
514 axis: XAxis,
515 v_min: f64,
516 v_max: f64,
517 n_ticks: i32,
518 labels: Option<&[&str]>,
519 keep_default: bool,
520 ) {
521 if n_ticks <= 0 {
522 return;
523 }
524 if let Some(labels) = labels {
525 let Ok(ticks_usize) = usize::try_from(n_ticks) else {
526 return;
527 };
528 if labels.len() != ticks_usize {
529 return;
530 }
531 let cleaned: Vec<&str> = labels
532 .iter()
533 .map(|&s| if s.contains('\0') { "" } else { s })
534 .collect();
535 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
536 sys::ImPlot_SetupAxisTicks_double(
537 axis as sys::ImAxis,
538 v_min,
539 v_max,
540 n_ticks,
541 ptrs.as_ptr() as *const *const c_char,
542 keep_default,
543 )
544 })
545 } else {
546 unsafe {
547 sys::ImPlot_SetupAxisTicks_double(
548 axis as sys::ImAxis,
549 v_min,
550 v_max,
551 n_ticks,
552 std::ptr::null(),
553 keep_default,
554 )
555 }
556 }
557 }
558
559 pub fn setup_y_axis_ticks_range(
563 &self,
564 axis: YAxis,
565 v_min: f64,
566 v_max: f64,
567 n_ticks: i32,
568 labels: Option<&[&str]>,
569 keep_default: bool,
570 ) {
571 if n_ticks <= 0 {
572 return;
573 }
574 if let Some(labels) = labels {
575 let Ok(ticks_usize) = usize::try_from(n_ticks) else {
576 return;
577 };
578 if labels.len() != ticks_usize {
579 return;
580 }
581 let cleaned: Vec<&str> = labels
582 .iter()
583 .map(|&s| if s.contains('\0') { "" } else { s })
584 .collect();
585 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
586 sys::ImPlot_SetupAxisTicks_double(
587 axis as sys::ImAxis,
588 v_min,
589 v_max,
590 n_ticks,
591 ptrs.as_ptr() as *const *const c_char,
592 keep_default,
593 )
594 })
595 } else {
596 unsafe {
597 sys::ImPlot_SetupAxisTicks_double(
598 axis as sys::ImAxis,
599 v_min,
600 v_max,
601 n_ticks,
602 std::ptr::null(),
603 keep_default,
604 )
605 }
606 }
607 }
608
609 pub fn setup_x_axis_format(&self, axis: XAxis, fmt: &str) {
611 if fmt.contains('\0') {
612 return;
613 }
614 with_scratch_txt(fmt, |ptr| unsafe {
615 sys::ImPlot_SetupAxisFormat_Str(axis as sys::ImAxis, ptr)
616 })
617 }
618
619 pub fn setup_y_axis_format(&self, axis: YAxis, fmt: &str) {
621 if fmt.contains('\0') {
622 return;
623 }
624 with_scratch_txt(fmt, |ptr| unsafe {
625 sys::ImPlot_SetupAxisFormat_Str(axis as sys::ImAxis, ptr)
626 })
627 }
628
629 pub fn setup_x_axis_scale(&self, axis: XAxis, scale: sys::ImPlotScale) {
631 unsafe { sys::ImPlot_SetupAxisScale_PlotScale(axis as sys::ImAxis, scale) }
632 }
633
634 pub fn setup_y_axis_scale(&self, axis: YAxis, scale: sys::ImPlotScale) {
636 unsafe { sys::ImPlot_SetupAxisScale_PlotScale(axis as sys::ImAxis, scale) }
637 }
638
639 pub fn setup_axis_limits_constraints(&self, axis: i32, v_min: f64, v_max: f64) {
641 unsafe { sys::ImPlot_SetupAxisLimitsConstraints(axis as sys::ImAxis, v_min, v_max) }
642 }
643
644 pub fn setup_axis_zoom_constraints(&self, axis: i32, z_min: f64, z_max: f64) {
646 unsafe { sys::ImPlot_SetupAxisZoomConstraints(axis as sys::ImAxis, z_min, z_max) }
647 }
648
649 pub fn setup_x_axis_format_closure<F>(&self, axis: XAxis, f: F) -> AxisFormatterToken
654 where
655 F: Fn(f64) -> String + Send + Sync + 'static,
656 {
657 AxisFormatterToken::new(axis as sys::ImAxis, f)
658 }
659
660 pub fn setup_y_axis_format_closure<F>(&self, axis: YAxis, f: F) -> AxisFormatterToken
664 where
665 F: Fn(f64) -> String + Send + Sync + 'static,
666 {
667 AxisFormatterToken::new(axis as sys::ImAxis, f)
668 }
669
670 pub fn setup_x_axis_transform_closure<FW, INV>(
675 &self,
676 axis: XAxis,
677 forward: FW,
678 inverse: INV,
679 ) -> AxisTransformToken
680 where
681 FW: Fn(f64) -> f64 + Send + Sync + 'static,
682 INV: Fn(f64) -> f64 + Send + Sync + 'static,
683 {
684 AxisTransformToken::new(axis as sys::ImAxis, forward, inverse)
685 }
686
687 pub fn setup_y_axis_transform_closure<FW, INV>(
689 &self,
690 axis: YAxis,
691 forward: FW,
692 inverse: INV,
693 ) -> AxisTransformToken
694 where
695 FW: Fn(f64) -> f64 + Send + Sync + 'static,
696 INV: Fn(f64) -> f64 + Send + Sync + 'static,
697 {
698 AxisTransformToken::new(axis as sys::ImAxis, forward, inverse)
699 }
700}
701
702#[derive(Default)]
715struct PlotScopeStorage {
716 formatters: Vec<Box<FormatterHolder>>,
717 transforms: Vec<Box<TransformHolder>>,
718}
719
720thread_local! {
721 static PLOT_SCOPE_STACK: RefCell<Vec<PlotScopeStorage>> = const { RefCell::new(Vec::new()) };
722}
723
724fn with_plot_scope_storage<T>(f: impl FnOnce(&mut PlotScopeStorage) -> T) -> Option<T> {
725 PLOT_SCOPE_STACK.with(|stack| {
726 let mut stack = stack.borrow_mut();
727 stack.last_mut().map(f)
728 })
729}
730
731pub(crate) struct PlotScopeGuard {
732 _not_send_or_sync: std::marker::PhantomData<Rc<()>>,
733}
734
735impl PlotScopeGuard {
736 pub(crate) fn new() -> Self {
737 PLOT_SCOPE_STACK.with(|stack| stack.borrow_mut().push(PlotScopeStorage::default()));
738 Self {
739 _not_send_or_sync: std::marker::PhantomData,
740 }
741 }
742}
743
744impl Drop for PlotScopeGuard {
745 fn drop(&mut self) {
746 PLOT_SCOPE_STACK.with(|stack| {
747 let popped = stack.borrow_mut().pop();
748 debug_assert!(popped.is_some(), "dear-implot: plot scope stack underflow");
749 });
750 }
751}
752
753struct FormatterHolder {
756 func: Box<dyn Fn(f64) -> String + Send + Sync + 'static>,
757}
758
759#[must_use]
760pub struct AxisFormatterToken {
761 _private: (),
762}
763
764impl AxisFormatterToken {
765 fn new<F>(axis: sys::ImAxis, f: F) -> Self
766 where
767 F: Fn(f64) -> String + Send + Sync + 'static,
768 {
769 let configured = with_plot_scope_storage(|storage| {
770 let holder = Box::new(FormatterHolder { func: Box::new(f) });
771 let user = &*holder as *const FormatterHolder as *mut std::os::raw::c_void;
772 storage.formatters.push(holder);
773 unsafe {
774 sys::ImPlot_SetupAxisFormat_PlotFormatter(
775 axis as sys::ImAxis,
776 Some(formatter_thunk),
777 user,
778 )
779 }
780 })
781 .is_some();
782
783 debug_assert!(
784 configured,
785 "dear-implot: axis formatter closure must be set within an active plot"
786 );
787
788 Self { _private: () }
789 }
790}
791
792impl Drop for AxisFormatterToken {
793 fn drop(&mut self) {
794 }
796}
797
798unsafe extern "C" fn formatter_thunk(
799 value: f64,
800 buff: *mut std::os::raw::c_char,
801 size: std::os::raw::c_int,
802 user_data: *mut std::os::raw::c_void,
803) -> std::os::raw::c_int {
804 if user_data.is_null() || buff.is_null() || size <= 0 {
805 return 0;
806 }
807 let holder = unsafe { &*(user_data as *const FormatterHolder) };
809 let s = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.func)(value))) {
810 Ok(v) => v,
811 Err(_) => {
812 eprintln!("dear-implot: panic in axis formatter callback");
813 std::process::abort();
814 }
815 };
816 let bytes = s.as_bytes();
817 let max = (size - 1).max(0) as usize;
818 let n = bytes.len().min(max);
819
820 unsafe {
824 std::ptr::copy_nonoverlapping(bytes.as_ptr(), buff as *mut u8, n);
825 *buff.add(n) = 0;
826 }
827 n as std::os::raw::c_int
828}
829
830struct TransformHolder {
833 forward: Box<dyn Fn(f64) -> f64 + Send + Sync + 'static>,
834 inverse: Box<dyn Fn(f64) -> f64 + Send + Sync + 'static>,
835}
836
837#[must_use]
838pub struct AxisTransformToken {
839 _private: (),
840}
841
842impl AxisTransformToken {
843 fn new<FW, INV>(axis: sys::ImAxis, forward: FW, inverse: INV) -> Self
844 where
845 FW: Fn(f64) -> f64 + Send + Sync + 'static,
846 INV: Fn(f64) -> f64 + Send + Sync + 'static,
847 {
848 let configured = with_plot_scope_storage(|storage| {
849 let holder = Box::new(TransformHolder {
850 forward: Box::new(forward),
851 inverse: Box::new(inverse),
852 });
853 let user = &*holder as *const TransformHolder as *mut std::os::raw::c_void;
854 storage.transforms.push(holder);
855 unsafe {
856 sys::ImPlot_SetupAxisScale_PlotTransform(
857 axis as sys::ImAxis,
858 Some(transform_forward_thunk),
859 Some(transform_inverse_thunk),
860 user,
861 )
862 }
863 })
864 .is_some();
865
866 debug_assert!(
867 configured,
868 "dear-implot: axis transform closure must be set within an active plot"
869 );
870
871 Self { _private: () }
872 }
873}
874
875impl Drop for AxisTransformToken {
876 fn drop(&mut self) {
877 }
879}
880
881unsafe extern "C" fn transform_forward_thunk(
882 value: f64,
883 user_data: *mut std::os::raw::c_void,
884) -> f64 {
885 if user_data.is_null() {
886 return value;
887 }
888 let holder = unsafe { &*(user_data as *const TransformHolder) };
889 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.forward)(value))) {
890 Ok(v) => v,
891 Err(_) => {
892 eprintln!("dear-implot: panic in axis transform (forward) callback");
893 std::process::abort();
894 }
895 }
896}
897
898unsafe extern "C" fn transform_inverse_thunk(
899 value: f64,
900 user_data: *mut std::os::raw::c_void,
901) -> f64 {
902 if user_data.is_null() {
903 return value;
904 }
905 let holder = unsafe { &*(user_data as *const TransformHolder) };
906 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.inverse)(value))) {
907 Ok(v) => v,
908 Err(_) => {
909 eprintln!("dear-implot: panic in axis transform (inverse) callback");
910 std::process::abort();
911 }
912 }
913}
914
915pub struct PlotToken<'ui> {
919 _scope: PlotScopeGuard,
920 _lifetime: std::marker::PhantomData<&'ui ()>,
921}
922
923impl<'ui> PlotToken<'ui> {
924 pub(crate) fn new() -> Self {
926 Self {
927 _scope: PlotScopeGuard::new(),
928 _lifetime: std::marker::PhantomData,
929 }
930 }
931
932 pub fn end(self) {
937 }
939}
940
941impl<'ui> Drop for PlotToken<'ui> {
942 fn drop(&mut self) {
943 unsafe {
944 sys::ImPlot_EndPlot();
945 }
946 }
947}