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 let spec = crate::plots::plot_spec_from(0, 0, std::mem::size_of::<f64>() as i32);
165 sys::ImPlot_PlotLine_doublePtrdoublePtr(
166 ptr,
167 x_data.as_ptr(),
168 y_data.as_ptr(),
169 count,
170 spec,
171 );
172 })
173 }
174
175 pub fn plot_scatter(&self, label: &str, x_data: &[f64], y_data: &[f64]) {
177 if x_data.len() != y_data.len() {
178 return; }
180 let count = match i32::try_from(x_data.len()) {
181 Ok(v) => v,
182 Err(_) => return,
183 };
184
185 let label = if label.contains('\0') { "" } else { label };
186 with_scratch_txt(label, |ptr| unsafe {
187 let spec = crate::plots::plot_spec_from(0, 0, std::mem::size_of::<f64>() as i32);
188 sys::ImPlot_PlotScatter_doublePtrdoublePtr(
189 ptr,
190 x_data.as_ptr(),
191 y_data.as_ptr(),
192 count,
193 spec,
194 );
195 })
196 }
197
198 pub fn is_plot_hovered(&self) -> bool {
200 unsafe { sys::ImPlot_IsPlotHovered() }
201 }
202
203 pub fn get_plot_mouse_pos(&self, y_axis: Option<crate::YAxisChoice>) -> sys::ImPlotPoint {
205 let y_axis_i32 = crate::y_axis_choice_option_to_i32(y_axis);
206 let y_axis = match y_axis_i32 {
207 0 => 3,
208 1 => 4,
209 2 => 5,
210 _ => 3,
211 };
212 unsafe { sys::ImPlot_GetPlotMousePos(0, y_axis) }
213 }
214
215 pub fn get_plot_mouse_pos_axes(&self, x_axis: XAxis, y_axis: YAxis) -> sys::ImPlotPoint {
217 unsafe { sys::ImPlot_GetPlotMousePos(x_axis as i32, y_axis as i32) }
218 }
219
220 pub fn set_axes(&self, x_axis: XAxis, y_axis: YAxis) {
222 unsafe { sys::ImPlot_SetAxes(x_axis as i32, y_axis as i32) }
223 }
224
225 pub fn setup_x_axis(&self, axis: XAxis, label: Option<&str>, flags: AxisFlags) {
227 let label = label.filter(|s| !s.contains('\0'));
228 match label {
229 Some(label) => with_scratch_txt(label, |ptr| unsafe {
230 sys::ImPlot_SetupAxis(
231 axis as sys::ImAxis,
232 ptr,
233 flags.bits() as sys::ImPlotAxisFlags,
234 )
235 }),
236 None => unsafe {
237 sys::ImPlot_SetupAxis(
238 axis as sys::ImAxis,
239 std::ptr::null(),
240 flags.bits() as sys::ImPlotAxisFlags,
241 )
242 },
243 }
244 }
245
246 pub fn setup_y_axis(&self, axis: YAxis, label: Option<&str>, flags: AxisFlags) {
248 let label = label.filter(|s| !s.contains('\0'));
249 match label {
250 Some(label) => with_scratch_txt(label, |ptr| unsafe {
251 sys::ImPlot_SetupAxis(
252 axis as sys::ImAxis,
253 ptr,
254 flags.bits() as sys::ImPlotAxisFlags,
255 )
256 }),
257 None => unsafe {
258 sys::ImPlot_SetupAxis(
259 axis as sys::ImAxis,
260 std::ptr::null(),
261 flags.bits() as sys::ImPlotAxisFlags,
262 )
263 },
264 }
265 }
266
267 pub fn setup_x_axis_limits(&self, axis: XAxis, min: f64, max: f64, cond: PlotCond) {
269 unsafe {
270 sys::ImPlot_SetupAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
271 }
272 }
273
274 pub fn setup_y_axis_limits(&self, axis: YAxis, min: f64, max: f64, cond: PlotCond) {
276 unsafe {
277 sys::ImPlot_SetupAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
278 }
279 }
280
281 pub fn setup_axis_links(
283 &self,
284 axis: i32,
285 link_min: Option<&mut f64>,
286 link_max: Option<&mut f64>,
287 ) {
288 let pmin = link_min.map_or(std::ptr::null_mut(), |r| r as *mut f64);
289 let pmax = link_max.map_or(std::ptr::null_mut(), |r| r as *mut f64);
290 unsafe { sys::ImPlot_SetupAxisLinks(axis, pmin, pmax) }
291 }
292
293 pub fn setup_axes(
295 &self,
296 x_label: Option<&str>,
297 y_label: Option<&str>,
298 x_flags: AxisFlags,
299 y_flags: AxisFlags,
300 ) {
301 let x_label = x_label.filter(|s| !s.contains('\0'));
302 let y_label = y_label.filter(|s| !s.contains('\0'));
303
304 match (x_label, y_label) {
305 (Some(x_label), Some(y_label)) => {
306 with_scratch_txt_two(x_label, y_label, |xp, yp| unsafe {
307 sys::ImPlot_SetupAxes(
308 xp,
309 yp,
310 x_flags.bits() as sys::ImPlotAxisFlags,
311 y_flags.bits() as sys::ImPlotAxisFlags,
312 )
313 })
314 }
315 (Some(x_label), None) => with_scratch_txt(x_label, |xp| unsafe {
316 sys::ImPlot_SetupAxes(
317 xp,
318 std::ptr::null(),
319 x_flags.bits() as sys::ImPlotAxisFlags,
320 y_flags.bits() as sys::ImPlotAxisFlags,
321 )
322 }),
323 (None, Some(y_label)) => with_scratch_txt(y_label, |yp| unsafe {
324 sys::ImPlot_SetupAxes(
325 std::ptr::null(),
326 yp,
327 x_flags.bits() as sys::ImPlotAxisFlags,
328 y_flags.bits() as sys::ImPlotAxisFlags,
329 )
330 }),
331 (None, None) => unsafe {
332 sys::ImPlot_SetupAxes(
333 std::ptr::null(),
334 std::ptr::null(),
335 x_flags.bits() as sys::ImPlotAxisFlags,
336 y_flags.bits() as sys::ImPlotAxisFlags,
337 )
338 },
339 }
340 }
341
342 pub fn setup_axes_limits(
344 &self,
345 x_min: f64,
346 x_max: f64,
347 y_min: f64,
348 y_max: f64,
349 cond: PlotCond,
350 ) {
351 unsafe { sys::ImPlot_SetupAxesLimits(x_min, x_max, y_min, y_max, cond as sys::ImPlotCond) }
352 }
353
354 pub fn setup_finish(&self) {
356 unsafe { sys::ImPlot_SetupFinish() }
357 }
358
359 pub fn set_next_x_axis_limits(&self, axis: XAxis, min: f64, max: f64, cond: PlotCond) {
361 unsafe {
362 sys::ImPlot_SetNextAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
363 }
364 }
365
366 pub fn set_next_y_axis_limits(&self, axis: YAxis, min: f64, max: f64, cond: PlotCond) {
368 unsafe {
369 sys::ImPlot_SetNextAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
370 }
371 }
372
373 pub fn set_next_axis_links(
375 &self,
376 axis: i32,
377 link_min: Option<&mut f64>,
378 link_max: Option<&mut f64>,
379 ) {
380 let pmin = link_min.map_or(std::ptr::null_mut(), |r| r as *mut f64);
381 let pmax = link_max.map_or(std::ptr::null_mut(), |r| r as *mut f64);
382 unsafe { sys::ImPlot_SetNextAxisLinks(axis, pmin, pmax) }
383 }
384
385 pub fn set_next_axes_limits(
387 &self,
388 x_min: f64,
389 x_max: f64,
390 y_min: f64,
391 y_max: f64,
392 cond: PlotCond,
393 ) {
394 unsafe {
395 sys::ImPlot_SetNextAxesLimits(x_min, x_max, y_min, y_max, cond as sys::ImPlotCond)
396 }
397 }
398
399 pub fn set_next_axes_to_fit(&self) {
401 unsafe { sys::ImPlot_SetNextAxesToFit() }
402 }
403
404 pub fn set_next_axis_to_fit(&self, axis: i32) {
406 unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) }
407 }
408
409 pub fn set_next_x_axis_to_fit(&self, axis: XAxis) {
411 unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) }
412 }
413
414 pub fn set_next_y_axis_to_fit(&self, axis: YAxis) {
416 unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) }
417 }
418
419 pub fn setup_x_axis_ticks_positions(
423 &self,
424 axis: XAxis,
425 values: &[f64],
426 labels: Option<&[&str]>,
427 keep_default: bool,
428 ) {
429 let count = match i32::try_from(values.len()) {
430 Ok(v) => v,
431 Err(_) => return,
432 };
433 if let Some(labels) = labels {
434 if labels.len() != values.len() {
435 return;
436 }
437 let cleaned: Vec<&str> = labels
438 .iter()
439 .map(|&s| if s.contains('\0') { "" } else { s })
440 .collect();
441 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
442 sys::ImPlot_SetupAxisTicks_doublePtr(
443 axis as sys::ImAxis,
444 values.as_ptr(),
445 count,
446 ptrs.as_ptr() as *const *const c_char,
447 keep_default,
448 )
449 })
450 } else {
451 unsafe {
452 sys::ImPlot_SetupAxisTicks_doublePtr(
453 axis as sys::ImAxis,
454 values.as_ptr(),
455 count,
456 std::ptr::null(),
457 keep_default,
458 )
459 }
460 }
461 }
462
463 pub fn setup_y_axis_ticks_positions(
467 &self,
468 axis: YAxis,
469 values: &[f64],
470 labels: Option<&[&str]>,
471 keep_default: bool,
472 ) {
473 let count = match i32::try_from(values.len()) {
474 Ok(v) => v,
475 Err(_) => return,
476 };
477 if let Some(labels) = labels {
478 if labels.len() != values.len() {
479 return;
480 }
481 let cleaned: Vec<&str> = labels
482 .iter()
483 .map(|&s| if s.contains('\0') { "" } else { s })
484 .collect();
485 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
486 sys::ImPlot_SetupAxisTicks_doublePtr(
487 axis as sys::ImAxis,
488 values.as_ptr(),
489 count,
490 ptrs.as_ptr() as *const *const c_char,
491 keep_default,
492 )
493 })
494 } else {
495 unsafe {
496 sys::ImPlot_SetupAxisTicks_doublePtr(
497 axis as sys::ImAxis,
498 values.as_ptr(),
499 count,
500 std::ptr::null(),
501 keep_default,
502 )
503 }
504 }
505 }
506
507 pub fn setup_x_axis_ticks_range(
511 &self,
512 axis: XAxis,
513 v_min: f64,
514 v_max: f64,
515 n_ticks: i32,
516 labels: Option<&[&str]>,
517 keep_default: bool,
518 ) {
519 if n_ticks <= 0 {
520 return;
521 }
522 if let Some(labels) = labels {
523 let Ok(ticks_usize) = usize::try_from(n_ticks) else {
524 return;
525 };
526 if labels.len() != ticks_usize {
527 return;
528 }
529 let cleaned: Vec<&str> = labels
530 .iter()
531 .map(|&s| if s.contains('\0') { "" } else { s })
532 .collect();
533 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
534 sys::ImPlot_SetupAxisTicks_double(
535 axis as sys::ImAxis,
536 v_min,
537 v_max,
538 n_ticks,
539 ptrs.as_ptr() as *const *const c_char,
540 keep_default,
541 )
542 })
543 } else {
544 unsafe {
545 sys::ImPlot_SetupAxisTicks_double(
546 axis as sys::ImAxis,
547 v_min,
548 v_max,
549 n_ticks,
550 std::ptr::null(),
551 keep_default,
552 )
553 }
554 }
555 }
556
557 pub fn setup_y_axis_ticks_range(
561 &self,
562 axis: YAxis,
563 v_min: f64,
564 v_max: f64,
565 n_ticks: i32,
566 labels: Option<&[&str]>,
567 keep_default: bool,
568 ) {
569 if n_ticks <= 0 {
570 return;
571 }
572 if let Some(labels) = labels {
573 let Ok(ticks_usize) = usize::try_from(n_ticks) else {
574 return;
575 };
576 if labels.len() != ticks_usize {
577 return;
578 }
579 let cleaned: Vec<&str> = labels
580 .iter()
581 .map(|&s| if s.contains('\0') { "" } else { s })
582 .collect();
583 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
584 sys::ImPlot_SetupAxisTicks_double(
585 axis as sys::ImAxis,
586 v_min,
587 v_max,
588 n_ticks,
589 ptrs.as_ptr() as *const *const c_char,
590 keep_default,
591 )
592 })
593 } else {
594 unsafe {
595 sys::ImPlot_SetupAxisTicks_double(
596 axis as sys::ImAxis,
597 v_min,
598 v_max,
599 n_ticks,
600 std::ptr::null(),
601 keep_default,
602 )
603 }
604 }
605 }
606
607 pub fn setup_x_axis_format(&self, axis: XAxis, fmt: &str) {
609 if fmt.contains('\0') {
610 return;
611 }
612 with_scratch_txt(fmt, |ptr| unsafe {
613 sys::ImPlot_SetupAxisFormat_Str(axis as sys::ImAxis, ptr)
614 })
615 }
616
617 pub fn setup_y_axis_format(&self, axis: YAxis, fmt: &str) {
619 if fmt.contains('\0') {
620 return;
621 }
622 with_scratch_txt(fmt, |ptr| unsafe {
623 sys::ImPlot_SetupAxisFormat_Str(axis as sys::ImAxis, ptr)
624 })
625 }
626
627 pub fn setup_x_axis_scale(&self, axis: XAxis, scale: sys::ImPlotScale) {
629 unsafe { sys::ImPlot_SetupAxisScale_PlotScale(axis as sys::ImAxis, scale) }
630 }
631
632 pub fn setup_y_axis_scale(&self, axis: YAxis, scale: sys::ImPlotScale) {
634 unsafe { sys::ImPlot_SetupAxisScale_PlotScale(axis as sys::ImAxis, scale) }
635 }
636
637 pub fn setup_axis_limits_constraints(&self, axis: i32, v_min: f64, v_max: f64) {
639 unsafe { sys::ImPlot_SetupAxisLimitsConstraints(axis as sys::ImAxis, v_min, v_max) }
640 }
641
642 pub fn setup_axis_zoom_constraints(&self, axis: i32, z_min: f64, z_max: f64) {
644 unsafe { sys::ImPlot_SetupAxisZoomConstraints(axis as sys::ImAxis, z_min, z_max) }
645 }
646
647 pub fn setup_x_axis_format_closure<F>(&self, axis: XAxis, f: F) -> AxisFormatterToken
652 where
653 F: Fn(f64) -> String + Send + Sync + 'static,
654 {
655 AxisFormatterToken::new(axis as sys::ImAxis, f)
656 }
657
658 pub fn setup_y_axis_format_closure<F>(&self, axis: YAxis, f: F) -> AxisFormatterToken
662 where
663 F: Fn(f64) -> String + Send + Sync + 'static,
664 {
665 AxisFormatterToken::new(axis as sys::ImAxis, f)
666 }
667
668 pub fn setup_x_axis_transform_closure<FW, INV>(
673 &self,
674 axis: XAxis,
675 forward: FW,
676 inverse: INV,
677 ) -> AxisTransformToken
678 where
679 FW: Fn(f64) -> f64 + Send + Sync + 'static,
680 INV: Fn(f64) -> f64 + Send + Sync + 'static,
681 {
682 AxisTransformToken::new(axis as sys::ImAxis, forward, inverse)
683 }
684
685 pub fn setup_y_axis_transform_closure<FW, INV>(
687 &self,
688 axis: YAxis,
689 forward: FW,
690 inverse: INV,
691 ) -> AxisTransformToken
692 where
693 FW: Fn(f64) -> f64 + Send + Sync + 'static,
694 INV: Fn(f64) -> f64 + Send + Sync + 'static,
695 {
696 AxisTransformToken::new(axis as sys::ImAxis, forward, inverse)
697 }
698}
699
700#[derive(Default)]
713struct PlotScopeStorage {
714 formatters: Vec<Box<FormatterHolder>>,
715 transforms: Vec<Box<TransformHolder>>,
716}
717
718thread_local! {
719 static PLOT_SCOPE_STACK: RefCell<Vec<PlotScopeStorage>> = const { RefCell::new(Vec::new()) };
720}
721
722fn with_plot_scope_storage<T>(f: impl FnOnce(&mut PlotScopeStorage) -> T) -> Option<T> {
723 PLOT_SCOPE_STACK.with(|stack| {
724 let mut stack = stack.borrow_mut();
725 stack.last_mut().map(f)
726 })
727}
728
729pub(crate) struct PlotScopeGuard {
730 _not_send_or_sync: std::marker::PhantomData<Rc<()>>,
731}
732
733impl PlotScopeGuard {
734 pub(crate) fn new() -> Self {
735 PLOT_SCOPE_STACK.with(|stack| stack.borrow_mut().push(PlotScopeStorage::default()));
736 Self {
737 _not_send_or_sync: std::marker::PhantomData,
738 }
739 }
740}
741
742impl Drop for PlotScopeGuard {
743 fn drop(&mut self) {
744 PLOT_SCOPE_STACK.with(|stack| {
745 let popped = stack.borrow_mut().pop();
746 debug_assert!(popped.is_some(), "dear-implot: plot scope stack underflow");
747 });
748 }
749}
750
751struct FormatterHolder {
754 func: Box<dyn Fn(f64) -> String + Send + Sync + 'static>,
755}
756
757#[must_use]
758pub struct AxisFormatterToken {
759 _private: (),
760}
761
762impl AxisFormatterToken {
763 fn new<F>(axis: sys::ImAxis, f: F) -> Self
764 where
765 F: Fn(f64) -> String + Send + Sync + 'static,
766 {
767 let configured = with_plot_scope_storage(|storage| {
768 let holder = Box::new(FormatterHolder { func: Box::new(f) });
769 let user = &*holder as *const FormatterHolder as *mut std::os::raw::c_void;
770 storage.formatters.push(holder);
771 unsafe {
772 sys::ImPlot_SetupAxisFormat_PlotFormatter(
773 axis as sys::ImAxis,
774 Some(formatter_thunk),
775 user,
776 )
777 }
778 })
779 .is_some();
780
781 debug_assert!(
782 configured,
783 "dear-implot: axis formatter closure must be set within an active plot"
784 );
785
786 Self { _private: () }
787 }
788}
789
790impl Drop for AxisFormatterToken {
791 fn drop(&mut self) {
792 }
794}
795
796unsafe extern "C" fn formatter_thunk(
797 value: f64,
798 buff: *mut std::os::raw::c_char,
799 size: std::os::raw::c_int,
800 user_data: *mut std::os::raw::c_void,
801) -> std::os::raw::c_int {
802 if user_data.is_null() || buff.is_null() || size <= 0 {
803 return 0;
804 }
805 let holder = unsafe { &*(user_data as *const FormatterHolder) };
807 let s = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.func)(value))) {
808 Ok(v) => v,
809 Err(_) => {
810 eprintln!("dear-implot: panic in axis formatter callback");
811 std::process::abort();
812 }
813 };
814 let bytes = s.as_bytes();
815 let max = (size - 1).max(0) as usize;
816 let n = bytes.len().min(max);
817
818 unsafe {
822 std::ptr::copy_nonoverlapping(bytes.as_ptr(), buff as *mut u8, n);
823 *buff.add(n) = 0;
824 }
825 n as std::os::raw::c_int
826}
827
828struct TransformHolder {
831 forward: Box<dyn Fn(f64) -> f64 + Send + Sync + 'static>,
832 inverse: Box<dyn Fn(f64) -> f64 + Send + Sync + 'static>,
833}
834
835#[must_use]
836pub struct AxisTransformToken {
837 _private: (),
838}
839
840impl AxisTransformToken {
841 fn new<FW, INV>(axis: sys::ImAxis, forward: FW, inverse: INV) -> Self
842 where
843 FW: Fn(f64) -> f64 + Send + Sync + 'static,
844 INV: Fn(f64) -> f64 + Send + Sync + 'static,
845 {
846 let configured = with_plot_scope_storage(|storage| {
847 let holder = Box::new(TransformHolder {
848 forward: Box::new(forward),
849 inverse: Box::new(inverse),
850 });
851 let user = &*holder as *const TransformHolder as *mut std::os::raw::c_void;
852 storage.transforms.push(holder);
853 unsafe {
854 sys::ImPlot_SetupAxisScale_PlotTransform(
855 axis as sys::ImAxis,
856 Some(transform_forward_thunk),
857 Some(transform_inverse_thunk),
858 user,
859 )
860 }
861 })
862 .is_some();
863
864 debug_assert!(
865 configured,
866 "dear-implot: axis transform closure must be set within an active plot"
867 );
868
869 Self { _private: () }
870 }
871}
872
873impl Drop for AxisTransformToken {
874 fn drop(&mut self) {
875 }
877}
878
879unsafe extern "C" fn transform_forward_thunk(
880 value: f64,
881 user_data: *mut std::os::raw::c_void,
882) -> f64 {
883 if user_data.is_null() {
884 return value;
885 }
886 let holder = unsafe { &*(user_data as *const TransformHolder) };
887 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.forward)(value))) {
888 Ok(v) => v,
889 Err(_) => {
890 eprintln!("dear-implot: panic in axis transform (forward) callback");
891 std::process::abort();
892 }
893 }
894}
895
896unsafe extern "C" fn transform_inverse_thunk(
897 value: f64,
898 user_data: *mut std::os::raw::c_void,
899) -> f64 {
900 if user_data.is_null() {
901 return value;
902 }
903 let holder = unsafe { &*(user_data as *const TransformHolder) };
904 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.inverse)(value))) {
905 Ok(v) => v,
906 Err(_) => {
907 eprintln!("dear-implot: panic in axis transform (inverse) callback");
908 std::process::abort();
909 }
910 }
911}
912
913pub struct PlotToken<'ui> {
917 _scope: PlotScopeGuard,
918 _lifetime: std::marker::PhantomData<&'ui ()>,
919}
920
921impl<'ui> PlotToken<'ui> {
922 pub(crate) fn new() -> Self {
924 Self {
925 _scope: PlotScopeGuard::new(),
926 _lifetime: std::marker::PhantomData,
927 }
928 }
929
930 pub fn end(self) {
935 }
937}
938
939impl<'ui> Drop for PlotToken<'ui> {
940 fn drop(&mut self) {
941 unsafe {
942 sys::ImPlot_EndPlot();
943 }
944 }
945}