Skip to main content

dear_implot/context/
ui.rs

1use super::core::PlotContext;
2use super::token::PlotToken;
3use super::validation::assert_finite_vec2;
4use crate::{XAxis, YAxis, sys};
5use dear_imgui_rs::{Ui, with_scratch_txt};
6
7/// A temporary reference for building plots
8///
9/// This struct ensures that plots can only be created when both ImGui and ImPlot
10/// contexts are available and properly set up.
11pub struct PlotUi<'ui> {
12    #[allow(dead_code)]
13    pub(crate) context: &'ui PlotContext,
14    #[allow(dead_code)]
15    pub(crate) ui: &'ui Ui,
16}
17
18impl<'ui> PlotUi<'ui> {
19    #[inline]
20    pub(crate) fn with_bound_context<R>(&self, f: impl FnOnce() -> R) -> R {
21        self.context
22            .binding()
23            .with_bound_context("dear-implot: PlotUi", f)
24    }
25
26    /// Begin a new plot with the given title
27    ///
28    /// Returns a PlotToken if the plot was successfully started.
29    /// The plot will be automatically ended when the token is dropped.
30    pub fn begin_plot(&self, title: &str) -> Option<PlotToken<'_>> {
31        let size = sys::ImVec2_c { x: -1.0, y: 0.0 };
32        if title.contains('\0') {
33            return None;
34        }
35        self.with_bound_context(|| {
36            let started =
37                with_scratch_txt(title, |ptr| unsafe { sys::ImPlot_BeginPlot(ptr, size, 0) });
38
39            if started {
40                Some(PlotToken::new(self.context.binding(), self.ui))
41            } else {
42                None
43            }
44        })
45    }
46
47    /// Begin a plot with custom size
48    pub fn begin_plot_with_size(&self, title: &str, size: [f32; 2]) -> Option<PlotToken<'_>> {
49        assert_finite_vec2("PlotUi::begin_plot_with_size()", "size", size);
50        let plot_size = sys::ImVec2_c {
51            x: size[0],
52            y: size[1],
53        };
54        if title.contains('\0') {
55            return None;
56        }
57        self.with_bound_context(|| {
58            let started = with_scratch_txt(title, |ptr| unsafe {
59                sys::ImPlot_BeginPlot(ptr, plot_size, 0)
60            });
61
62            if started {
63                Some(PlotToken::new(self.context.binding(), self.ui))
64            } else {
65                None
66            }
67        })
68    }
69
70    /// Plot a line with the given label and data
71    ///
72    /// This is a convenience method that can be called within a plot.
73    pub fn plot_line(&self, label: &str, x_data: &[f64], y_data: &[f64]) {
74        if x_data.len() != y_data.len() {
75            return; // Data length mismatch
76        }
77        let count = match i32::try_from(x_data.len()) {
78            Ok(v) => v,
79            Err(_) => return,
80        };
81
82        let label = if label.contains('\0') { "" } else { label };
83        self.with_bound_context(|| {
84            with_scratch_txt(label, |ptr| unsafe {
85                let spec = crate::plots::plot_spec_from(0, crate::plots::PlotDataLayout::DEFAULT);
86                sys::ImPlot_PlotLine_doublePtrdoublePtr(
87                    ptr,
88                    x_data.as_ptr(),
89                    y_data.as_ptr(),
90                    count,
91                    spec,
92                );
93            })
94        })
95    }
96
97    /// Plot a scatter plot with the given label and data
98    pub fn plot_scatter(&self, label: &str, x_data: &[f64], y_data: &[f64]) {
99        if x_data.len() != y_data.len() {
100            return; // Data length mismatch
101        }
102        let count = match i32::try_from(x_data.len()) {
103            Ok(v) => v,
104            Err(_) => return,
105        };
106
107        let label = if label.contains('\0') { "" } else { label };
108        self.with_bound_context(|| {
109            with_scratch_txt(label, |ptr| unsafe {
110                let spec = crate::plots::plot_spec_from(0, crate::plots::PlotDataLayout::DEFAULT);
111                sys::ImPlot_PlotScatter_doublePtrdoublePtr(
112                    ptr,
113                    x_data.as_ptr(),
114                    y_data.as_ptr(),
115                    count,
116                    spec,
117                );
118            })
119        })
120    }
121
122    /// Plot a polygon with the given label and vertex data.
123    pub fn plot_polygon(&self, label: &str, x_data: &[f64], y_data: &[f64]) {
124        if x_data.len() != y_data.len() {
125            return;
126        }
127        let count = match i32::try_from(x_data.len()) {
128            Ok(v) => v,
129            Err(_) => return,
130        };
131
132        let label = if label.contains('\0') { "" } else { label };
133        self.with_bound_context(|| {
134            with_scratch_txt(label, |ptr| unsafe {
135                let spec = crate::plots::plot_spec_from(0, crate::plots::PlotDataLayout::DEFAULT);
136                sys::ImPlot_PlotPolygon_doublePtr(
137                    ptr,
138                    x_data.as_ptr(),
139                    y_data.as_ptr(),
140                    count,
141                    spec,
142                );
143            })
144        })
145    }
146
147    /// Check if the plot area is hovered
148    pub fn is_plot_hovered(&self) -> bool {
149        self.with_bound_context(|| unsafe { sys::ImPlot_IsPlotHovered() })
150    }
151
152    /// Get the mouse position in plot coordinates
153    pub fn get_plot_mouse_pos(&self, y_axis: Option<crate::YAxisChoice>) -> sys::ImPlotPoint {
154        let y_axis_i32 = crate::y_axis_choice_option_to_i32(y_axis);
155        let y_axis = match y_axis_i32 {
156            0 => 3,
157            1 => 4,
158            2 => 5,
159            _ => 3,
160        };
161        self.with_bound_context(|| unsafe { sys::ImPlot_GetPlotMousePos(0, y_axis) })
162    }
163
164    /// Get the mouse position in plot coordinates for specific axes
165    pub fn get_plot_mouse_pos_axes(&self, x_axis: XAxis, y_axis: YAxis) -> sys::ImPlotPoint {
166        self.with_bound_context(|| unsafe {
167            sys::ImPlot_GetPlotMousePos(x_axis as i32, y_axis as i32)
168        })
169    }
170
171    /// Set current axes for subsequent plot submissions
172    pub fn set_axes(&self, x_axis: XAxis, y_axis: YAxis) {
173        self.with_bound_context(|| unsafe { sys::ImPlot_SetAxes(x_axis as i32, y_axis as i32) })
174    }
175}