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 imgui_ctx_raw: *mut imgui_sys::ImGuiContext,
16 imgui_alive: Option<dear_imgui_rs::ContextAliveToken>,
17}
18
19impl PlotContext {
20 pub fn try_create(imgui_ctx: &ImGuiContext) -> dear_imgui_rs::ImGuiResult<Self> {
25 let imgui_ctx_raw = imgui_ctx.as_raw();
26 let imgui_alive = Some(imgui_ctx.alive_token());
27 assert_eq!(
28 unsafe { imgui_sys::igGetCurrentContext() },
29 imgui_ctx_raw,
30 "dear-implot: PlotContext must be created with the currently-active ImGui context"
31 );
32
33 unsafe { sys::ImPlot_SetImGuiContext(imgui_ctx_raw) };
37
38 let raw = unsafe { sys::ImPlot_CreateContext() };
39 if raw.is_null() {
40 return Err(dear_imgui_rs::ImGuiError::context_creation(
41 "ImPlot_CreateContext returned null",
42 ));
43 }
44
45 unsafe { sys::ImPlot_SetCurrentContext(raw) };
47
48 Ok(Self {
49 raw,
50 imgui_ctx_raw,
51 imgui_alive,
52 })
53 }
54
55 pub fn create(imgui_ctx: &ImGuiContext) -> Self {
57 Self::try_create(imgui_ctx).expect("Failed to create ImPlot context")
58 }
59
60 pub fn current() -> Option<Self> {
64 let raw = unsafe { sys::ImPlot_GetCurrentContext() };
65 if raw.is_null() {
66 None
67 } else {
68 Some(Self {
69 raw,
70 imgui_ctx_raw: unsafe { imgui_sys::igGetCurrentContext() },
71 imgui_alive: None,
72 })
73 }
74 }
75
76 pub fn set_as_current(&self) {
78 if let Some(alive) = &self.imgui_alive {
79 assert!(
80 alive.is_alive(),
81 "dear-implot: ImGui context has been dropped"
82 );
83 unsafe { sys::ImPlot_SetImGuiContext(self.imgui_ctx_raw) };
84 }
85 unsafe {
86 sys::ImPlot_SetCurrentContext(self.raw);
87 }
88 }
89
90 pub fn get_plot_ui<'ui>(&'ui self, ui: &'ui Ui) -> PlotUi<'ui> {
95 if let Some(alive) = &self.imgui_alive {
96 assert!(
97 alive.is_alive(),
98 "dear-implot: ImGui context has been dropped"
99 );
100 assert_eq!(
101 unsafe { imgui_sys::igGetCurrentContext() },
102 self.imgui_ctx_raw,
103 "dear-implot: PlotUi must be used with the currently-active ImGui context"
104 );
105 }
106 self.set_as_current();
107 PlotUi { context: self, ui }
108 }
109
110 pub unsafe fn raw(&self) -> *mut sys::ImPlotContext {
117 self.raw
118 }
119}
120
121impl Drop for PlotContext {
122 fn drop(&mut self) {
123 if !self.raw.is_null() {
124 if let Some(alive) = &self.imgui_alive {
125 if !alive.is_alive() {
126 return;
129 }
130 unsafe { sys::ImPlot_SetImGuiContext(self.imgui_ctx_raw) };
131 }
132 unsafe {
133 if sys::ImPlot_GetCurrentContext() == self.raw {
134 sys::ImPlot_SetCurrentContext(std::ptr::null_mut());
135 }
136 sys::ImPlot_DestroyContext(self.raw);
137 }
138 }
139 }
140}
141
142pub struct PlotUi<'ui> {
149 #[allow(dead_code)]
150 context: &'ui PlotContext,
151 #[allow(dead_code)]
152 ui: &'ui Ui,
153}
154
155impl<'ui> PlotUi<'ui> {
156 pub fn begin_plot(&self, title: &str) -> Option<PlotToken<'_>> {
161 let size = sys::ImVec2_c { x: -1.0, y: 0.0 };
162 if title.contains('\0') {
163 return None;
164 }
165 let started = with_scratch_txt(title, |ptr| unsafe { sys::ImPlot_BeginPlot(ptr, size, 0) });
166
167 if started {
168 Some(PlotToken::new())
169 } else {
170 None
171 }
172 }
173
174 pub fn begin_plot_with_size(&self, title: &str, size: [f32; 2]) -> Option<PlotToken<'_>> {
176 let plot_size = sys::ImVec2_c {
177 x: size[0],
178 y: size[1],
179 };
180 if title.contains('\0') {
181 return None;
182 }
183 let started = with_scratch_txt(title, |ptr| unsafe {
184 sys::ImPlot_BeginPlot(ptr, plot_size, 0)
185 });
186
187 if started {
188 Some(PlotToken::new())
189 } else {
190 None
191 }
192 }
193
194 pub fn plot_line(&self, label: &str, x_data: &[f64], y_data: &[f64]) {
198 if x_data.len() != y_data.len() {
199 return; }
201 let count = match i32::try_from(x_data.len()) {
202 Ok(v) => v,
203 Err(_) => return,
204 };
205
206 let label = if label.contains('\0') { "" } else { label };
207 with_scratch_txt(label, |ptr| unsafe {
208 let spec = crate::plots::plot_spec_from(0, 0, std::mem::size_of::<f64>() as i32);
209 sys::ImPlot_PlotLine_doublePtrdoublePtr(
210 ptr,
211 x_data.as_ptr(),
212 y_data.as_ptr(),
213 count,
214 spec,
215 );
216 })
217 }
218
219 pub fn plot_scatter(&self, label: &str, x_data: &[f64], y_data: &[f64]) {
221 if x_data.len() != y_data.len() {
222 return; }
224 let count = match i32::try_from(x_data.len()) {
225 Ok(v) => v,
226 Err(_) => return,
227 };
228
229 let label = if label.contains('\0') { "" } else { label };
230 with_scratch_txt(label, |ptr| unsafe {
231 let spec = crate::plots::plot_spec_from(0, 0, std::mem::size_of::<f64>() as i32);
232 sys::ImPlot_PlotScatter_doublePtrdoublePtr(
233 ptr,
234 x_data.as_ptr(),
235 y_data.as_ptr(),
236 count,
237 spec,
238 );
239 })
240 }
241
242 pub fn is_plot_hovered(&self) -> bool {
244 unsafe { sys::ImPlot_IsPlotHovered() }
245 }
246
247 pub fn get_plot_mouse_pos(&self, y_axis: Option<crate::YAxisChoice>) -> sys::ImPlotPoint {
249 let y_axis_i32 = crate::y_axis_choice_option_to_i32(y_axis);
250 let y_axis = match y_axis_i32 {
251 0 => 3,
252 1 => 4,
253 2 => 5,
254 _ => 3,
255 };
256 unsafe { sys::ImPlot_GetPlotMousePos(0, y_axis) }
257 }
258
259 pub fn get_plot_mouse_pos_axes(&self, x_axis: XAxis, y_axis: YAxis) -> sys::ImPlotPoint {
261 unsafe { sys::ImPlot_GetPlotMousePos(x_axis as i32, y_axis as i32) }
262 }
263
264 pub fn set_axes(&self, x_axis: XAxis, y_axis: YAxis) {
266 unsafe { sys::ImPlot_SetAxes(x_axis as i32, y_axis as i32) }
267 }
268
269 pub fn setup_x_axis(&self, axis: XAxis, label: Option<&str>, flags: AxisFlags) {
271 let label = label.filter(|s| !s.contains('\0'));
272 match label {
273 Some(label) => with_scratch_txt(label, |ptr| unsafe {
274 sys::ImPlot_SetupAxis(
275 axis as sys::ImAxis,
276 ptr,
277 flags.bits() as sys::ImPlotAxisFlags,
278 )
279 }),
280 None => unsafe {
281 sys::ImPlot_SetupAxis(
282 axis as sys::ImAxis,
283 std::ptr::null(),
284 flags.bits() as sys::ImPlotAxisFlags,
285 )
286 },
287 }
288 }
289
290 pub fn setup_y_axis(&self, axis: YAxis, label: Option<&str>, flags: AxisFlags) {
292 let label = label.filter(|s| !s.contains('\0'));
293 match label {
294 Some(label) => with_scratch_txt(label, |ptr| unsafe {
295 sys::ImPlot_SetupAxis(
296 axis as sys::ImAxis,
297 ptr,
298 flags.bits() as sys::ImPlotAxisFlags,
299 )
300 }),
301 None => unsafe {
302 sys::ImPlot_SetupAxis(
303 axis as sys::ImAxis,
304 std::ptr::null(),
305 flags.bits() as sys::ImPlotAxisFlags,
306 )
307 },
308 }
309 }
310
311 pub fn setup_x_axis_limits(&self, axis: XAxis, min: f64, max: f64, cond: PlotCond) {
313 unsafe {
314 sys::ImPlot_SetupAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
315 }
316 }
317
318 pub fn setup_y_axis_limits(&self, axis: YAxis, min: f64, max: f64, cond: PlotCond) {
320 unsafe {
321 sys::ImPlot_SetupAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
322 }
323 }
324
325 pub fn setup_axis_links(
327 &self,
328 axis: i32,
329 link_min: Option<&mut f64>,
330 link_max: Option<&mut f64>,
331 ) {
332 let pmin = link_min.map_or(std::ptr::null_mut(), |r| r as *mut f64);
333 let pmax = link_max.map_or(std::ptr::null_mut(), |r| r as *mut f64);
334 unsafe { sys::ImPlot_SetupAxisLinks(axis, pmin, pmax) }
335 }
336
337 pub fn setup_axes(
339 &self,
340 x_label: Option<&str>,
341 y_label: Option<&str>,
342 x_flags: AxisFlags,
343 y_flags: AxisFlags,
344 ) {
345 let x_label = x_label.filter(|s| !s.contains('\0'));
346 let y_label = y_label.filter(|s| !s.contains('\0'));
347
348 match (x_label, y_label) {
349 (Some(x_label), Some(y_label)) => {
350 with_scratch_txt_two(x_label, y_label, |xp, yp| unsafe {
351 sys::ImPlot_SetupAxes(
352 xp,
353 yp,
354 x_flags.bits() as sys::ImPlotAxisFlags,
355 y_flags.bits() as sys::ImPlotAxisFlags,
356 )
357 })
358 }
359 (Some(x_label), None) => with_scratch_txt(x_label, |xp| unsafe {
360 sys::ImPlot_SetupAxes(
361 xp,
362 std::ptr::null(),
363 x_flags.bits() as sys::ImPlotAxisFlags,
364 y_flags.bits() as sys::ImPlotAxisFlags,
365 )
366 }),
367 (None, Some(y_label)) => with_scratch_txt(y_label, |yp| unsafe {
368 sys::ImPlot_SetupAxes(
369 std::ptr::null(),
370 yp,
371 x_flags.bits() as sys::ImPlotAxisFlags,
372 y_flags.bits() as sys::ImPlotAxisFlags,
373 )
374 }),
375 (None, None) => unsafe {
376 sys::ImPlot_SetupAxes(
377 std::ptr::null(),
378 std::ptr::null(),
379 x_flags.bits() as sys::ImPlotAxisFlags,
380 y_flags.bits() as sys::ImPlotAxisFlags,
381 )
382 },
383 }
384 }
385
386 pub fn setup_axes_limits(
388 &self,
389 x_min: f64,
390 x_max: f64,
391 y_min: f64,
392 y_max: f64,
393 cond: PlotCond,
394 ) {
395 unsafe { sys::ImPlot_SetupAxesLimits(x_min, x_max, y_min, y_max, cond as sys::ImPlotCond) }
396 }
397
398 pub fn setup_finish(&self) {
400 unsafe { sys::ImPlot_SetupFinish() }
401 }
402
403 pub fn set_next_x_axis_limits(&self, axis: XAxis, min: f64, max: f64, cond: PlotCond) {
405 unsafe {
406 sys::ImPlot_SetNextAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
407 }
408 }
409
410 pub fn set_next_y_axis_limits(&self, axis: YAxis, min: f64, max: f64, cond: PlotCond) {
412 unsafe {
413 sys::ImPlot_SetNextAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
414 }
415 }
416
417 pub fn set_next_axis_links(
419 &self,
420 axis: i32,
421 link_min: Option<&mut f64>,
422 link_max: Option<&mut f64>,
423 ) {
424 let pmin = link_min.map_or(std::ptr::null_mut(), |r| r as *mut f64);
425 let pmax = link_max.map_or(std::ptr::null_mut(), |r| r as *mut f64);
426 unsafe { sys::ImPlot_SetNextAxisLinks(axis, pmin, pmax) }
427 }
428
429 pub fn set_next_axes_limits(
431 &self,
432 x_min: f64,
433 x_max: f64,
434 y_min: f64,
435 y_max: f64,
436 cond: PlotCond,
437 ) {
438 unsafe {
439 sys::ImPlot_SetNextAxesLimits(x_min, x_max, y_min, y_max, cond as sys::ImPlotCond)
440 }
441 }
442
443 pub fn set_next_axes_to_fit(&self) {
445 unsafe { sys::ImPlot_SetNextAxesToFit() }
446 }
447
448 pub fn set_next_axis_to_fit(&self, axis: i32) {
450 unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) }
451 }
452
453 pub fn set_next_x_axis_to_fit(&self, axis: XAxis) {
455 unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) }
456 }
457
458 pub fn set_next_y_axis_to_fit(&self, axis: YAxis) {
460 unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) }
461 }
462
463 pub fn setup_x_axis_ticks_positions(
467 &self,
468 axis: XAxis,
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_y_axis_ticks_positions(
511 &self,
512 axis: YAxis,
513 values: &[f64],
514 labels: Option<&[&str]>,
515 keep_default: bool,
516 ) {
517 let count = match i32::try_from(values.len()) {
518 Ok(v) => v,
519 Err(_) => return,
520 };
521 if let Some(labels) = labels {
522 if labels.len() != values.len() {
523 return;
524 }
525 let cleaned: Vec<&str> = labels
526 .iter()
527 .map(|&s| if s.contains('\0') { "" } else { s })
528 .collect();
529 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
530 sys::ImPlot_SetupAxisTicks_doublePtr(
531 axis as sys::ImAxis,
532 values.as_ptr(),
533 count,
534 ptrs.as_ptr() as *const *const c_char,
535 keep_default,
536 )
537 })
538 } else {
539 unsafe {
540 sys::ImPlot_SetupAxisTicks_doublePtr(
541 axis as sys::ImAxis,
542 values.as_ptr(),
543 count,
544 std::ptr::null(),
545 keep_default,
546 )
547 }
548 }
549 }
550
551 pub fn setup_x_axis_ticks_range(
555 &self,
556 axis: XAxis,
557 v_min: f64,
558 v_max: f64,
559 n_ticks: i32,
560 labels: Option<&[&str]>,
561 keep_default: bool,
562 ) {
563 if n_ticks <= 0 {
564 return;
565 }
566 if let Some(labels) = labels {
567 let Ok(ticks_usize) = usize::try_from(n_ticks) else {
568 return;
569 };
570 if labels.len() != ticks_usize {
571 return;
572 }
573 let cleaned: Vec<&str> = labels
574 .iter()
575 .map(|&s| if s.contains('\0') { "" } else { s })
576 .collect();
577 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
578 sys::ImPlot_SetupAxisTicks_double(
579 axis as sys::ImAxis,
580 v_min,
581 v_max,
582 n_ticks,
583 ptrs.as_ptr() as *const *const c_char,
584 keep_default,
585 )
586 })
587 } else {
588 unsafe {
589 sys::ImPlot_SetupAxisTicks_double(
590 axis as sys::ImAxis,
591 v_min,
592 v_max,
593 n_ticks,
594 std::ptr::null(),
595 keep_default,
596 )
597 }
598 }
599 }
600
601 pub fn setup_y_axis_ticks_range(
605 &self,
606 axis: YAxis,
607 v_min: f64,
608 v_max: f64,
609 n_ticks: i32,
610 labels: Option<&[&str]>,
611 keep_default: bool,
612 ) {
613 if n_ticks <= 0 {
614 return;
615 }
616 if let Some(labels) = labels {
617 let Ok(ticks_usize) = usize::try_from(n_ticks) else {
618 return;
619 };
620 if labels.len() != ticks_usize {
621 return;
622 }
623 let cleaned: Vec<&str> = labels
624 .iter()
625 .map(|&s| if s.contains('\0') { "" } else { s })
626 .collect();
627 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
628 sys::ImPlot_SetupAxisTicks_double(
629 axis as sys::ImAxis,
630 v_min,
631 v_max,
632 n_ticks,
633 ptrs.as_ptr() as *const *const c_char,
634 keep_default,
635 )
636 })
637 } else {
638 unsafe {
639 sys::ImPlot_SetupAxisTicks_double(
640 axis as sys::ImAxis,
641 v_min,
642 v_max,
643 n_ticks,
644 std::ptr::null(),
645 keep_default,
646 )
647 }
648 }
649 }
650
651 pub fn setup_x_axis_format(&self, axis: XAxis, fmt: &str) {
653 if fmt.contains('\0') {
654 return;
655 }
656 with_scratch_txt(fmt, |ptr| unsafe {
657 sys::ImPlot_SetupAxisFormat_Str(axis as sys::ImAxis, ptr)
658 })
659 }
660
661 pub fn setup_y_axis_format(&self, axis: YAxis, fmt: &str) {
663 if fmt.contains('\0') {
664 return;
665 }
666 with_scratch_txt(fmt, |ptr| unsafe {
667 sys::ImPlot_SetupAxisFormat_Str(axis as sys::ImAxis, ptr)
668 })
669 }
670
671 pub fn setup_x_axis_scale(&self, axis: XAxis, scale: sys::ImPlotScale) {
673 unsafe { sys::ImPlot_SetupAxisScale_PlotScale(axis as sys::ImAxis, scale) }
674 }
675
676 pub fn setup_y_axis_scale(&self, axis: YAxis, scale: sys::ImPlotScale) {
678 unsafe { sys::ImPlot_SetupAxisScale_PlotScale(axis as sys::ImAxis, scale) }
679 }
680
681 pub fn setup_axis_limits_constraints(&self, axis: i32, v_min: f64, v_max: f64) {
683 unsafe { sys::ImPlot_SetupAxisLimitsConstraints(axis as sys::ImAxis, v_min, v_max) }
684 }
685
686 pub fn setup_axis_zoom_constraints(&self, axis: i32, z_min: f64, z_max: f64) {
688 unsafe { sys::ImPlot_SetupAxisZoomConstraints(axis as sys::ImAxis, z_min, z_max) }
689 }
690
691 pub fn setup_x_axis_format_closure<F>(&self, axis: XAxis, f: F) -> AxisFormatterToken
696 where
697 F: Fn(f64) -> String + Send + Sync + 'static,
698 {
699 AxisFormatterToken::new(axis as sys::ImAxis, f)
700 }
701
702 pub fn setup_y_axis_format_closure<F>(&self, axis: YAxis, f: F) -> AxisFormatterToken
706 where
707 F: Fn(f64) -> String + Send + Sync + 'static,
708 {
709 AxisFormatterToken::new(axis as sys::ImAxis, f)
710 }
711
712 pub fn setup_x_axis_transform_closure<FW, INV>(
717 &self,
718 axis: XAxis,
719 forward: FW,
720 inverse: INV,
721 ) -> AxisTransformToken
722 where
723 FW: Fn(f64) -> f64 + Send + Sync + 'static,
724 INV: Fn(f64) -> f64 + Send + Sync + 'static,
725 {
726 AxisTransformToken::new(axis as sys::ImAxis, forward, inverse)
727 }
728
729 pub fn setup_y_axis_transform_closure<FW, INV>(
731 &self,
732 axis: YAxis,
733 forward: FW,
734 inverse: INV,
735 ) -> AxisTransformToken
736 where
737 FW: Fn(f64) -> f64 + Send + Sync + 'static,
738 INV: Fn(f64) -> f64 + Send + Sync + 'static,
739 {
740 AxisTransformToken::new(axis as sys::ImAxis, forward, inverse)
741 }
742}
743
744#[derive(Default)]
757struct PlotScopeStorage {
758 formatters: Vec<Box<FormatterHolder>>,
759 transforms: Vec<Box<TransformHolder>>,
760}
761
762thread_local! {
763 static PLOT_SCOPE_STACK: RefCell<Vec<PlotScopeStorage>> = const { RefCell::new(Vec::new()) };
764}
765
766fn with_plot_scope_storage<T>(f: impl FnOnce(&mut PlotScopeStorage) -> T) -> Option<T> {
767 PLOT_SCOPE_STACK.with(|stack| {
768 let mut stack = stack.borrow_mut();
769 stack.last_mut().map(f)
770 })
771}
772
773pub(crate) struct PlotScopeGuard {
774 _not_send_or_sync: std::marker::PhantomData<Rc<()>>,
775}
776
777impl PlotScopeGuard {
778 pub(crate) fn new() -> Self {
779 PLOT_SCOPE_STACK.with(|stack| stack.borrow_mut().push(PlotScopeStorage::default()));
780 Self {
781 _not_send_or_sync: std::marker::PhantomData,
782 }
783 }
784}
785
786impl Drop for PlotScopeGuard {
787 fn drop(&mut self) {
788 PLOT_SCOPE_STACK.with(|stack| {
789 let popped = stack.borrow_mut().pop();
790 debug_assert!(popped.is_some(), "dear-implot: plot scope stack underflow");
791 });
792 }
793}
794
795struct FormatterHolder {
798 func: Box<dyn Fn(f64) -> String + Send + Sync + 'static>,
799}
800
801#[must_use]
802pub struct AxisFormatterToken {
803 _private: (),
804}
805
806impl AxisFormatterToken {
807 fn new<F>(axis: sys::ImAxis, f: F) -> Self
808 where
809 F: Fn(f64) -> String + Send + Sync + 'static,
810 {
811 let configured = with_plot_scope_storage(|storage| {
812 let holder = Box::new(FormatterHolder { func: Box::new(f) });
813 let user = &*holder as *const FormatterHolder as *mut std::os::raw::c_void;
814 storage.formatters.push(holder);
815 unsafe {
816 sys::ImPlot_SetupAxisFormat_PlotFormatter(
817 axis as sys::ImAxis,
818 Some(formatter_thunk),
819 user,
820 )
821 }
822 })
823 .is_some();
824
825 debug_assert!(
826 configured,
827 "dear-implot: axis formatter closure must be set within an active plot"
828 );
829
830 Self { _private: () }
831 }
832}
833
834impl Drop for AxisFormatterToken {
835 fn drop(&mut self) {
836 }
838}
839
840unsafe extern "C" fn formatter_thunk(
841 value: f64,
842 buff: *mut std::os::raw::c_char,
843 size: std::os::raw::c_int,
844 user_data: *mut std::os::raw::c_void,
845) -> std::os::raw::c_int {
846 if user_data.is_null() || buff.is_null() || size <= 0 {
847 return 0;
848 }
849 let holder = unsafe { &*(user_data as *const FormatterHolder) };
851 let s = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.func)(value))) {
852 Ok(v) => v,
853 Err(_) => {
854 eprintln!("dear-implot: panic in axis formatter callback");
855 std::process::abort();
856 }
857 };
858 let bytes = s.as_bytes();
859 let max = (size - 1).max(0) as usize;
860 let n = bytes.len().min(max);
861
862 unsafe {
866 std::ptr::copy_nonoverlapping(bytes.as_ptr(), buff as *mut u8, n);
867 *buff.add(n) = 0;
868 }
869 n as std::os::raw::c_int
870}
871
872struct TransformHolder {
875 forward: Box<dyn Fn(f64) -> f64 + Send + Sync + 'static>,
876 inverse: Box<dyn Fn(f64) -> f64 + Send + Sync + 'static>,
877}
878
879#[must_use]
880pub struct AxisTransformToken {
881 _private: (),
882}
883
884impl AxisTransformToken {
885 fn new<FW, INV>(axis: sys::ImAxis, forward: FW, inverse: INV) -> Self
886 where
887 FW: Fn(f64) -> f64 + Send + Sync + 'static,
888 INV: Fn(f64) -> f64 + Send + Sync + 'static,
889 {
890 let configured = with_plot_scope_storage(|storage| {
891 let holder = Box::new(TransformHolder {
892 forward: Box::new(forward),
893 inverse: Box::new(inverse),
894 });
895 let user = &*holder as *const TransformHolder as *mut std::os::raw::c_void;
896 storage.transforms.push(holder);
897 unsafe {
898 sys::ImPlot_SetupAxisScale_PlotTransform(
899 axis as sys::ImAxis,
900 Some(transform_forward_thunk),
901 Some(transform_inverse_thunk),
902 user,
903 )
904 }
905 })
906 .is_some();
907
908 debug_assert!(
909 configured,
910 "dear-implot: axis transform closure must be set within an active plot"
911 );
912
913 Self { _private: () }
914 }
915}
916
917impl Drop for AxisTransformToken {
918 fn drop(&mut self) {
919 }
921}
922
923unsafe extern "C" fn transform_forward_thunk(
924 value: f64,
925 user_data: *mut std::os::raw::c_void,
926) -> f64 {
927 if user_data.is_null() {
928 return value;
929 }
930 let holder = unsafe { &*(user_data as *const TransformHolder) };
931 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.forward)(value))) {
932 Ok(v) => v,
933 Err(_) => {
934 eprintln!("dear-implot: panic in axis transform (forward) callback");
935 std::process::abort();
936 }
937 }
938}
939
940unsafe extern "C" fn transform_inverse_thunk(
941 value: f64,
942 user_data: *mut std::os::raw::c_void,
943) -> f64 {
944 if user_data.is_null() {
945 return value;
946 }
947 let holder = unsafe { &*(user_data as *const TransformHolder) };
948 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.inverse)(value))) {
949 Ok(v) => v,
950 Err(_) => {
951 eprintln!("dear-implot: panic in axis transform (inverse) callback");
952 std::process::abort();
953 }
954 }
955}
956
957pub struct PlotToken<'ui> {
961 _scope: PlotScopeGuard,
962 _lifetime: std::marker::PhantomData<&'ui ()>,
963}
964
965impl<'ui> PlotToken<'ui> {
966 pub(crate) fn new() -> Self {
968 Self {
969 _scope: PlotScopeGuard::new(),
970 _lifetime: std::marker::PhantomData,
971 }
972 }
973
974 pub fn end(self) {
979 }
981}
982
983impl<'ui> Drop for PlotToken<'ui> {
984 fn drop(&mut self) {
985 unsafe {
986 sys::ImPlot_EndPlot();
987 }
988 }
989}