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 plot_polygon(&self, label: &str, x_data: &[f64], y_data: &[f64]) {
244 if x_data.len() != y_data.len() {
245 return;
246 }
247 let count = match i32::try_from(x_data.len()) {
248 Ok(v) => v,
249 Err(_) => return,
250 };
251
252 let label = if label.contains('\0') { "" } else { label };
253 with_scratch_txt(label, |ptr| unsafe {
254 let spec = crate::plots::plot_spec_from(0, 0, std::mem::size_of::<f64>() as i32);
255 sys::ImPlot_PlotPolygon_doublePtr(ptr, x_data.as_ptr(), y_data.as_ptr(), count, spec);
256 })
257 }
258
259 pub fn is_plot_hovered(&self) -> bool {
261 unsafe { sys::ImPlot_IsPlotHovered() }
262 }
263
264 pub fn get_plot_mouse_pos(&self, y_axis: Option<crate::YAxisChoice>) -> sys::ImPlotPoint {
266 let y_axis_i32 = crate::y_axis_choice_option_to_i32(y_axis);
267 let y_axis = match y_axis_i32 {
268 0 => 3,
269 1 => 4,
270 2 => 5,
271 _ => 3,
272 };
273 unsafe { sys::ImPlot_GetPlotMousePos(0, y_axis) }
274 }
275
276 pub fn get_plot_mouse_pos_axes(&self, x_axis: XAxis, y_axis: YAxis) -> sys::ImPlotPoint {
278 unsafe { sys::ImPlot_GetPlotMousePos(x_axis as i32, y_axis as i32) }
279 }
280
281 pub fn set_axes(&self, x_axis: XAxis, y_axis: YAxis) {
283 unsafe { sys::ImPlot_SetAxes(x_axis as i32, y_axis as i32) }
284 }
285
286 pub fn setup_x_axis(&self, axis: XAxis, label: Option<&str>, flags: AxisFlags) {
288 let label = label.filter(|s| !s.contains('\0'));
289 match label {
290 Some(label) => with_scratch_txt(label, |ptr| unsafe {
291 sys::ImPlot_SetupAxis(
292 axis as sys::ImAxis,
293 ptr,
294 flags.bits() as sys::ImPlotAxisFlags,
295 )
296 }),
297 None => unsafe {
298 sys::ImPlot_SetupAxis(
299 axis as sys::ImAxis,
300 std::ptr::null(),
301 flags.bits() as sys::ImPlotAxisFlags,
302 )
303 },
304 }
305 }
306
307 pub fn setup_y_axis(&self, axis: YAxis, label: Option<&str>, flags: AxisFlags) {
309 let label = label.filter(|s| !s.contains('\0'));
310 match label {
311 Some(label) => with_scratch_txt(label, |ptr| unsafe {
312 sys::ImPlot_SetupAxis(
313 axis as sys::ImAxis,
314 ptr,
315 flags.bits() as sys::ImPlotAxisFlags,
316 )
317 }),
318 None => unsafe {
319 sys::ImPlot_SetupAxis(
320 axis as sys::ImAxis,
321 std::ptr::null(),
322 flags.bits() as sys::ImPlotAxisFlags,
323 )
324 },
325 }
326 }
327
328 pub fn setup_x_axis_limits(&self, axis: XAxis, min: f64, max: f64, cond: PlotCond) {
330 unsafe {
331 sys::ImPlot_SetupAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
332 }
333 }
334
335 pub fn setup_y_axis_limits(&self, axis: YAxis, min: f64, max: f64, cond: PlotCond) {
337 unsafe {
338 sys::ImPlot_SetupAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
339 }
340 }
341
342 pub fn setup_axis_links(
344 &self,
345 axis: i32,
346 link_min: Option<&mut f64>,
347 link_max: Option<&mut f64>,
348 ) {
349 let pmin = link_min.map_or(std::ptr::null_mut(), |r| r as *mut f64);
350 let pmax = link_max.map_or(std::ptr::null_mut(), |r| r as *mut f64);
351 unsafe { sys::ImPlot_SetupAxisLinks(axis, pmin, pmax) }
352 }
353
354 pub fn setup_axes(
356 &self,
357 x_label: Option<&str>,
358 y_label: Option<&str>,
359 x_flags: AxisFlags,
360 y_flags: AxisFlags,
361 ) {
362 let x_label = x_label.filter(|s| !s.contains('\0'));
363 let y_label = y_label.filter(|s| !s.contains('\0'));
364
365 match (x_label, y_label) {
366 (Some(x_label), Some(y_label)) => {
367 with_scratch_txt_two(x_label, y_label, |xp, yp| unsafe {
368 sys::ImPlot_SetupAxes(
369 xp,
370 yp,
371 x_flags.bits() as sys::ImPlotAxisFlags,
372 y_flags.bits() as sys::ImPlotAxisFlags,
373 )
374 })
375 }
376 (Some(x_label), None) => with_scratch_txt(x_label, |xp| unsafe {
377 sys::ImPlot_SetupAxes(
378 xp,
379 std::ptr::null(),
380 x_flags.bits() as sys::ImPlotAxisFlags,
381 y_flags.bits() as sys::ImPlotAxisFlags,
382 )
383 }),
384 (None, Some(y_label)) => with_scratch_txt(y_label, |yp| unsafe {
385 sys::ImPlot_SetupAxes(
386 std::ptr::null(),
387 yp,
388 x_flags.bits() as sys::ImPlotAxisFlags,
389 y_flags.bits() as sys::ImPlotAxisFlags,
390 )
391 }),
392 (None, None) => unsafe {
393 sys::ImPlot_SetupAxes(
394 std::ptr::null(),
395 std::ptr::null(),
396 x_flags.bits() as sys::ImPlotAxisFlags,
397 y_flags.bits() as sys::ImPlotAxisFlags,
398 )
399 },
400 }
401 }
402
403 pub fn setup_axes_limits(
405 &self,
406 x_min: f64,
407 x_max: f64,
408 y_min: f64,
409 y_max: f64,
410 cond: PlotCond,
411 ) {
412 unsafe { sys::ImPlot_SetupAxesLimits(x_min, x_max, y_min, y_max, cond as sys::ImPlotCond) }
413 }
414
415 pub fn setup_finish(&self) {
417 unsafe { sys::ImPlot_SetupFinish() }
418 }
419
420 pub fn set_next_x_axis_limits(&self, axis: XAxis, min: f64, max: f64, cond: PlotCond) {
422 unsafe {
423 sys::ImPlot_SetNextAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
424 }
425 }
426
427 pub fn set_next_y_axis_limits(&self, axis: YAxis, min: f64, max: f64, cond: PlotCond) {
429 unsafe {
430 sys::ImPlot_SetNextAxisLimits(axis as sys::ImAxis, min, max, cond as sys::ImPlotCond)
431 }
432 }
433
434 pub fn set_next_axis_links(
436 &self,
437 axis: i32,
438 link_min: Option<&mut f64>,
439 link_max: Option<&mut f64>,
440 ) {
441 let pmin = link_min.map_or(std::ptr::null_mut(), |r| r as *mut f64);
442 let pmax = link_max.map_or(std::ptr::null_mut(), |r| r as *mut f64);
443 unsafe { sys::ImPlot_SetNextAxisLinks(axis, pmin, pmax) }
444 }
445
446 pub fn set_next_axes_limits(
448 &self,
449 x_min: f64,
450 x_max: f64,
451 y_min: f64,
452 y_max: f64,
453 cond: PlotCond,
454 ) {
455 unsafe {
456 sys::ImPlot_SetNextAxesLimits(x_min, x_max, y_min, y_max, cond as sys::ImPlotCond)
457 }
458 }
459
460 pub fn set_next_axes_to_fit(&self) {
462 unsafe { sys::ImPlot_SetNextAxesToFit() }
463 }
464
465 pub fn set_next_axis_to_fit(&self, axis: i32) {
467 unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) }
468 }
469
470 pub fn set_next_x_axis_to_fit(&self, axis: XAxis) {
472 unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) }
473 }
474
475 pub fn set_next_y_axis_to_fit(&self, axis: YAxis) {
477 unsafe { sys::ImPlot_SetNextAxisToFit(axis as sys::ImAxis) }
478 }
479
480 pub fn setup_x_axis_ticks_positions(
484 &self,
485 axis: XAxis,
486 values: &[f64],
487 labels: Option<&[&str]>,
488 keep_default: bool,
489 ) {
490 let count = match i32::try_from(values.len()) {
491 Ok(v) => v,
492 Err(_) => return,
493 };
494 if let Some(labels) = labels {
495 if labels.len() != values.len() {
496 return;
497 }
498 let cleaned: Vec<&str> = labels
499 .iter()
500 .map(|&s| if s.contains('\0') { "" } else { s })
501 .collect();
502 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
503 sys::ImPlot_SetupAxisTicks_doublePtr(
504 axis as sys::ImAxis,
505 values.as_ptr(),
506 count,
507 ptrs.as_ptr() as *const *const c_char,
508 keep_default,
509 )
510 })
511 } else {
512 unsafe {
513 sys::ImPlot_SetupAxisTicks_doublePtr(
514 axis as sys::ImAxis,
515 values.as_ptr(),
516 count,
517 std::ptr::null(),
518 keep_default,
519 )
520 }
521 }
522 }
523
524 pub fn setup_y_axis_ticks_positions(
528 &self,
529 axis: YAxis,
530 values: &[f64],
531 labels: Option<&[&str]>,
532 keep_default: bool,
533 ) {
534 let count = match i32::try_from(values.len()) {
535 Ok(v) => v,
536 Err(_) => return,
537 };
538 if let Some(labels) = labels {
539 if labels.len() != values.len() {
540 return;
541 }
542 let cleaned: Vec<&str> = labels
543 .iter()
544 .map(|&s| if s.contains('\0') { "" } else { s })
545 .collect();
546 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
547 sys::ImPlot_SetupAxisTicks_doublePtr(
548 axis as sys::ImAxis,
549 values.as_ptr(),
550 count,
551 ptrs.as_ptr() as *const *const c_char,
552 keep_default,
553 )
554 })
555 } else {
556 unsafe {
557 sys::ImPlot_SetupAxisTicks_doublePtr(
558 axis as sys::ImAxis,
559 values.as_ptr(),
560 count,
561 std::ptr::null(),
562 keep_default,
563 )
564 }
565 }
566 }
567
568 pub fn setup_x_axis_ticks_range(
572 &self,
573 axis: XAxis,
574 v_min: f64,
575 v_max: f64,
576 n_ticks: i32,
577 labels: Option<&[&str]>,
578 keep_default: bool,
579 ) {
580 if n_ticks <= 0 {
581 return;
582 }
583 if let Some(labels) = labels {
584 let Ok(ticks_usize) = usize::try_from(n_ticks) else {
585 return;
586 };
587 if labels.len() != ticks_usize {
588 return;
589 }
590 let cleaned: Vec<&str> = labels
591 .iter()
592 .map(|&s| if s.contains('\0') { "" } else { s })
593 .collect();
594 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
595 sys::ImPlot_SetupAxisTicks_double(
596 axis as sys::ImAxis,
597 v_min,
598 v_max,
599 n_ticks,
600 ptrs.as_ptr() as *const *const c_char,
601 keep_default,
602 )
603 })
604 } else {
605 unsafe {
606 sys::ImPlot_SetupAxisTicks_double(
607 axis as sys::ImAxis,
608 v_min,
609 v_max,
610 n_ticks,
611 std::ptr::null(),
612 keep_default,
613 )
614 }
615 }
616 }
617
618 pub fn setup_y_axis_ticks_range(
622 &self,
623 axis: YAxis,
624 v_min: f64,
625 v_max: f64,
626 n_ticks: i32,
627 labels: Option<&[&str]>,
628 keep_default: bool,
629 ) {
630 if n_ticks <= 0 {
631 return;
632 }
633 if let Some(labels) = labels {
634 let Ok(ticks_usize) = usize::try_from(n_ticks) else {
635 return;
636 };
637 if labels.len() != ticks_usize {
638 return;
639 }
640 let cleaned: Vec<&str> = labels
641 .iter()
642 .map(|&s| if s.contains('\0') { "" } else { s })
643 .collect();
644 with_scratch_txt_slice(&cleaned, |ptrs| unsafe {
645 sys::ImPlot_SetupAxisTicks_double(
646 axis as sys::ImAxis,
647 v_min,
648 v_max,
649 n_ticks,
650 ptrs.as_ptr() as *const *const c_char,
651 keep_default,
652 )
653 })
654 } else {
655 unsafe {
656 sys::ImPlot_SetupAxisTicks_double(
657 axis as sys::ImAxis,
658 v_min,
659 v_max,
660 n_ticks,
661 std::ptr::null(),
662 keep_default,
663 )
664 }
665 }
666 }
667
668 pub fn setup_x_axis_format(&self, axis: XAxis, fmt: &str) {
670 if fmt.contains('\0') {
671 return;
672 }
673 with_scratch_txt(fmt, |ptr| unsafe {
674 sys::ImPlot_SetupAxisFormat_Str(axis as sys::ImAxis, ptr)
675 })
676 }
677
678 pub fn setup_y_axis_format(&self, axis: YAxis, fmt: &str) {
680 if fmt.contains('\0') {
681 return;
682 }
683 with_scratch_txt(fmt, |ptr| unsafe {
684 sys::ImPlot_SetupAxisFormat_Str(axis as sys::ImAxis, ptr)
685 })
686 }
687
688 pub fn setup_x_axis_scale(&self, axis: XAxis, scale: sys::ImPlotScale) {
690 unsafe { sys::ImPlot_SetupAxisScale_PlotScale(axis as sys::ImAxis, scale) }
691 }
692
693 pub fn setup_y_axis_scale(&self, axis: YAxis, scale: sys::ImPlotScale) {
695 unsafe { sys::ImPlot_SetupAxisScale_PlotScale(axis as sys::ImAxis, scale) }
696 }
697
698 pub fn setup_axis_limits_constraints(&self, axis: i32, v_min: f64, v_max: f64) {
700 unsafe { sys::ImPlot_SetupAxisLimitsConstraints(axis as sys::ImAxis, v_min, v_max) }
701 }
702
703 pub fn setup_axis_zoom_constraints(&self, axis: i32, z_min: f64, z_max: f64) {
705 unsafe { sys::ImPlot_SetupAxisZoomConstraints(axis as sys::ImAxis, z_min, z_max) }
706 }
707
708 pub fn setup_x_axis_format_closure<F>(&self, axis: XAxis, f: F) -> AxisFormatterToken
713 where
714 F: Fn(f64) -> String + Send + Sync + 'static,
715 {
716 AxisFormatterToken::new(axis as sys::ImAxis, f)
717 }
718
719 pub fn setup_y_axis_format_closure<F>(&self, axis: YAxis, f: F) -> AxisFormatterToken
723 where
724 F: Fn(f64) -> String + Send + Sync + 'static,
725 {
726 AxisFormatterToken::new(axis as sys::ImAxis, f)
727 }
728
729 pub fn setup_x_axis_transform_closure<FW, INV>(
734 &self,
735 axis: XAxis,
736 forward: FW,
737 inverse: INV,
738 ) -> AxisTransformToken
739 where
740 FW: Fn(f64) -> f64 + Send + Sync + 'static,
741 INV: Fn(f64) -> f64 + Send + Sync + 'static,
742 {
743 AxisTransformToken::new(axis as sys::ImAxis, forward, inverse)
744 }
745
746 pub fn setup_y_axis_transform_closure<FW, INV>(
748 &self,
749 axis: YAxis,
750 forward: FW,
751 inverse: INV,
752 ) -> AxisTransformToken
753 where
754 FW: Fn(f64) -> f64 + Send + Sync + 'static,
755 INV: Fn(f64) -> f64 + Send + Sync + 'static,
756 {
757 AxisTransformToken::new(axis as sys::ImAxis, forward, inverse)
758 }
759}
760
761#[derive(Default)]
774struct PlotScopeStorage {
775 formatters: Vec<Box<FormatterHolder>>,
776 transforms: Vec<Box<TransformHolder>>,
777}
778
779thread_local! {
780 static PLOT_SCOPE_STACK: RefCell<Vec<PlotScopeStorage>> = const { RefCell::new(Vec::new()) };
781}
782
783fn with_plot_scope_storage<T>(f: impl FnOnce(&mut PlotScopeStorage) -> T) -> Option<T> {
784 PLOT_SCOPE_STACK.with(|stack| {
785 let mut stack = stack.borrow_mut();
786 stack.last_mut().map(f)
787 })
788}
789
790pub(crate) struct PlotScopeGuard {
791 _not_send_or_sync: std::marker::PhantomData<Rc<()>>,
792}
793
794impl PlotScopeGuard {
795 pub(crate) fn new() -> Self {
796 PLOT_SCOPE_STACK.with(|stack| stack.borrow_mut().push(PlotScopeStorage::default()));
797 Self {
798 _not_send_or_sync: std::marker::PhantomData,
799 }
800 }
801}
802
803impl Drop for PlotScopeGuard {
804 fn drop(&mut self) {
805 PLOT_SCOPE_STACK.with(|stack| {
806 let popped = stack.borrow_mut().pop();
807 debug_assert!(popped.is_some(), "dear-implot: plot scope stack underflow");
808 });
809 }
810}
811
812struct FormatterHolder {
815 func: Box<dyn Fn(f64) -> String + Send + Sync + 'static>,
816}
817
818#[must_use]
819pub struct AxisFormatterToken {
820 _private: (),
821}
822
823impl AxisFormatterToken {
824 fn new<F>(axis: sys::ImAxis, f: F) -> Self
825 where
826 F: Fn(f64) -> String + Send + Sync + 'static,
827 {
828 let configured = with_plot_scope_storage(|storage| {
829 let holder = Box::new(FormatterHolder { func: Box::new(f) });
830 let user = &*holder as *const FormatterHolder as *mut std::os::raw::c_void;
831 storage.formatters.push(holder);
832 unsafe {
833 sys::ImPlot_SetupAxisFormat_PlotFormatter(
834 axis as sys::ImAxis,
835 Some(formatter_thunk),
836 user,
837 )
838 }
839 })
840 .is_some();
841
842 debug_assert!(
843 configured,
844 "dear-implot: axis formatter closure must be set within an active plot"
845 );
846
847 Self { _private: () }
848 }
849}
850
851impl Drop for AxisFormatterToken {
852 fn drop(&mut self) {
853 }
855}
856
857unsafe extern "C" fn formatter_thunk(
858 value: f64,
859 buff: *mut std::os::raw::c_char,
860 size: std::os::raw::c_int,
861 user_data: *mut std::os::raw::c_void,
862) -> std::os::raw::c_int {
863 if user_data.is_null() || buff.is_null() || size <= 0 {
864 return 0;
865 }
866 let holder = unsafe { &*(user_data as *const FormatterHolder) };
868 let s = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.func)(value))) {
869 Ok(v) => v,
870 Err(_) => {
871 eprintln!("dear-implot: panic in axis formatter callback");
872 std::process::abort();
873 }
874 };
875 let bytes = s.as_bytes();
876 let max = (size - 1).max(0) as usize;
877 let n = bytes.len().min(max);
878
879 unsafe {
883 std::ptr::copy_nonoverlapping(bytes.as_ptr(), buff as *mut u8, n);
884 *buff.add(n) = 0;
885 }
886 n as std::os::raw::c_int
887}
888
889struct TransformHolder {
892 forward: Box<dyn Fn(f64) -> f64 + Send + Sync + 'static>,
893 inverse: Box<dyn Fn(f64) -> f64 + Send + Sync + 'static>,
894}
895
896#[must_use]
897pub struct AxisTransformToken {
898 _private: (),
899}
900
901impl AxisTransformToken {
902 fn new<FW, INV>(axis: sys::ImAxis, forward: FW, inverse: INV) -> Self
903 where
904 FW: Fn(f64) -> f64 + Send + Sync + 'static,
905 INV: Fn(f64) -> f64 + Send + Sync + 'static,
906 {
907 let configured = with_plot_scope_storage(|storage| {
908 let holder = Box::new(TransformHolder {
909 forward: Box::new(forward),
910 inverse: Box::new(inverse),
911 });
912 let user = &*holder as *const TransformHolder as *mut std::os::raw::c_void;
913 storage.transforms.push(holder);
914 unsafe {
915 sys::ImPlot_SetupAxisScale_PlotTransform(
916 axis as sys::ImAxis,
917 Some(transform_forward_thunk),
918 Some(transform_inverse_thunk),
919 user,
920 )
921 }
922 })
923 .is_some();
924
925 debug_assert!(
926 configured,
927 "dear-implot: axis transform closure must be set within an active plot"
928 );
929
930 Self { _private: () }
931 }
932}
933
934impl Drop for AxisTransformToken {
935 fn drop(&mut self) {
936 }
938}
939
940unsafe extern "C" fn transform_forward_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.forward)(value))) {
949 Ok(v) => v,
950 Err(_) => {
951 eprintln!("dear-implot: panic in axis transform (forward) callback");
952 std::process::abort();
953 }
954 }
955}
956
957unsafe extern "C" fn transform_inverse_thunk(
958 value: f64,
959 user_data: *mut std::os::raw::c_void,
960) -> f64 {
961 if user_data.is_null() {
962 return value;
963 }
964 let holder = unsafe { &*(user_data as *const TransformHolder) };
965 match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (holder.inverse)(value))) {
966 Ok(v) => v,
967 Err(_) => {
968 eprintln!("dear-implot: panic in axis transform (inverse) callback");
969 std::process::abort();
970 }
971 }
972}
973
974pub struct PlotToken<'ui> {
978 _scope: PlotScopeGuard,
979 _lifetime: std::marker::PhantomData<&'ui ()>,
980}
981
982impl<'ui> PlotToken<'ui> {
983 pub(crate) fn new() -> Self {
985 Self {
986 _scope: PlotScopeGuard::new(),
987 _lifetime: std::marker::PhantomData,
988 }
989 }
990
991 pub fn end(self) {
996 }
998}
999
1000impl<'ui> Drop for PlotToken<'ui> {
1001 fn drop(&mut self) {
1002 unsafe {
1003 sys::ImPlot_EndPlot();
1004 }
1005 }
1006}