dear_implot/
utils.rs

1// Utility functions for ImPlot
2
3use crate::{XAxis, YAxis, compat_ffi, sys};
4use dear_imgui_rs::with_scratch_txt;
5
6/// Check if the plot area is hovered
7pub fn is_plot_hovered() -> bool {
8    unsafe { sys::ImPlot_IsPlotHovered() }
9}
10
11/// Check if any subplots area is hovered
12pub fn is_subplots_hovered() -> bool {
13    unsafe { sys::ImPlot_IsSubplotsHovered() }
14}
15
16/// Check if a legend entry is hovered
17pub fn is_legend_entry_hovered(label: &str) -> bool {
18    let label = if label.contains('\0') { "" } else { label };
19    with_scratch_txt(label, |ptr| unsafe {
20        sys::ImPlot_IsLegendEntryHovered(ptr)
21    })
22}
23
24/// Get the mouse position in plot coordinates
25pub fn get_plot_mouse_position(y_axis_choice: Option<crate::YAxisChoice>) -> sys::ImPlotPoint {
26    let x_axis = 0; // ImAxis_X1
27    let y_axis = match y_axis_choice {
28        Some(crate::YAxisChoice::First) => 3,  // ImAxis_Y1
29        Some(crate::YAxisChoice::Second) => 4, // ImAxis_Y2
30        Some(crate::YAxisChoice::Third) => 5,  // ImAxis_Y3
31        None => 3,                             // Default to Y1
32    };
33    unsafe { sys::ImPlot_GetPlotMousePos(x_axis as sys::ImAxis, y_axis as sys::ImAxis) }
34}
35
36/// Get the mouse position in plot coordinates for specific axes
37pub fn get_plot_mouse_position_axes(x_axis: XAxis, y_axis: YAxis) -> sys::ImPlotPoint {
38    unsafe { sys::ImPlot_GetPlotMousePos(x_axis as sys::ImAxis, y_axis as sys::ImAxis) }
39}
40
41/// Convert pixels to plot coordinates
42pub fn pixels_to_plot(
43    pixel_position: [f32; 2],
44    y_axis_choice: Option<crate::YAxisChoice>,
45) -> sys::ImPlotPoint {
46    // Map absolute pixel coordinates to plot coordinates using current plot's axes
47    let y_index = match y_axis_choice {
48        Some(crate::YAxisChoice::First) => 0,
49        Some(crate::YAxisChoice::Second) => 1,
50        Some(crate::YAxisChoice::Third) => 2,
51        None => 0,
52    };
53    unsafe {
54        let plot = sys::ImPlot_GetCurrentPlot();
55        if plot.is_null() {
56            return sys::ImPlotPoint { x: 0.0, y: 0.0 };
57        }
58        let x_axis_ptr = sys::ImPlotPlot_XAxis_Nil(plot, 0);
59        let y_axis_ptr = sys::ImPlotPlot_YAxis_Nil(plot, y_index);
60        let x = sys::ImPlotAxis_PixelsToPlot(x_axis_ptr, pixel_position[0]);
61        let y = sys::ImPlotAxis_PixelsToPlot(y_axis_ptr, pixel_position[1]);
62        sys::ImPlotPoint { x, y }
63    }
64}
65
66/// Convert pixels to plot coordinates for specific axes
67pub fn pixels_to_plot_axes(
68    pixel_position: [f32; 2],
69    x_axis: XAxis,
70    y_axis: YAxis,
71) -> sys::ImPlotPoint {
72    unsafe {
73        let plot = sys::ImPlot_GetCurrentPlot();
74        if plot.is_null() {
75            return sys::ImPlotPoint { x: 0.0, y: 0.0 };
76        }
77        let x_axis_ptr = sys::ImPlotPlot_XAxis_Nil(plot, x_axis as i32);
78        let y_axis_ptr = sys::ImPlotPlot_YAxis_Nil(plot, y_axis.to_index());
79        let x = sys::ImPlotAxis_PixelsToPlot(x_axis_ptr, pixel_position[0]);
80        let y = sys::ImPlotAxis_PixelsToPlot(y_axis_ptr, pixel_position[1]);
81        sys::ImPlotPoint { x, y }
82    }
83}
84
85/// Convert plot coordinates to pixels
86pub fn plot_to_pixels(
87    plot_position: sys::ImPlotPoint,
88    y_axis_choice: Option<crate::YAxisChoice>,
89) -> [f32; 2] {
90    let y_index = match y_axis_choice {
91        Some(crate::YAxisChoice::First) => 0,
92        Some(crate::YAxisChoice::Second) => 1,
93        Some(crate::YAxisChoice::Third) => 2,
94        None => 0,
95    };
96    unsafe {
97        let plot = sys::ImPlot_GetCurrentPlot();
98        if plot.is_null() {
99            return [0.0, 0.0];
100        }
101        let x_axis_ptr = sys::ImPlotPlot_XAxis_Nil(plot, 0);
102        let y_axis_ptr = sys::ImPlotPlot_YAxis_Nil(plot, y_index);
103        let px = sys::ImPlotAxis_PlotToPixels(x_axis_ptr, plot_position.x);
104        let py = sys::ImPlotAxis_PlotToPixels(y_axis_ptr, plot_position.y);
105        [px, py]
106    }
107}
108
109/// Convert plot coordinates to pixels for specific axes
110pub fn plot_to_pixels_axes(
111    plot_position: sys::ImPlotPoint,
112    x_axis: XAxis,
113    y_axis: YAxis,
114) -> [f32; 2] {
115    unsafe {
116        let plot = sys::ImPlot_GetCurrentPlot();
117        if plot.is_null() {
118            return [0.0, 0.0];
119        }
120        let x_axis_ptr = sys::ImPlotPlot_XAxis_Nil(plot, x_axis as i32);
121        let y_axis_ptr = sys::ImPlotPlot_YAxis_Nil(plot, y_axis.to_index());
122        let px = sys::ImPlotAxis_PlotToPixels(x_axis_ptr, plot_position.x);
123        let py = sys::ImPlotAxis_PlotToPixels(y_axis_ptr, plot_position.y);
124        [px, py]
125    }
126}
127
128/// Get the current plot limits
129pub fn get_plot_limits(
130    _x_axis_choice: Option<crate::YAxisChoice>,
131    y_axis_choice: Option<crate::YAxisChoice>,
132) -> sys::ImPlotRect {
133    let x_axis = 0; // ImAxis_X1
134    let y_axis = match y_axis_choice {
135        Some(crate::YAxisChoice::First) => 3,  // ImAxis_Y1
136        Some(crate::YAxisChoice::Second) => 4, // ImAxis_Y2
137        Some(crate::YAxisChoice::Third) => 5,  // ImAxis_Y3
138        None => 3,                             // Default to Y1
139    };
140    unsafe { sys::ImPlot_GetPlotLimits(x_axis, y_axis) }
141}
142
143/// Whether a plot has an active selection region
144pub fn is_plot_selected() -> bool {
145    unsafe { sys::ImPlot_IsPlotSelected() }
146}
147
148/// Get the current plot selection rectangle for specific axes
149pub fn get_plot_selection_axes(x_axis: XAxis, y_axis: YAxis) -> Option<sys::ImPlotRect> {
150    if !is_plot_selected() {
151        return None;
152    }
153    let rect = unsafe { sys::ImPlot_GetPlotSelection(x_axis as i32, y_axis as i32) };
154    Some(rect)
155}
156
157/// Draw a simple round annotation marker at (x,y)
158pub fn annotation_point(
159    x: f64,
160    y: f64,
161    color: [f32; 4],
162    pixel_offset: [f32; 2],
163    clamp: bool,
164    round: bool,
165) {
166    let col = sys::ImVec4_c {
167        x: color[0],
168        y: color[1],
169        z: color[2],
170        w: color[3],
171    };
172    let off = sys::ImVec2_c {
173        x: pixel_offset[0],
174        y: pixel_offset[1],
175    };
176    unsafe { sys::ImPlot_Annotation_Bool(x, y, col, off, clamp, round) }
177}
178
179/// Draw a text annotation at (x,y) using the non-variadic `ImPlot_Annotation_Str0` API.
180///
181/// This avoids calling the C variadic (`...`) entrypoint, which is not supported on some targets
182/// (e.g. wasm32 via import-style bindings).
183pub fn annotation_text(
184    x: f64,
185    y: f64,
186    color: [f32; 4],
187    pixel_offset: [f32; 2],
188    clamp: bool,
189    text: &str,
190) {
191    let col = sys::ImVec4_c {
192        x: color[0],
193        y: color[1],
194        z: color[2],
195        w: color[3],
196    };
197    let off = sys::ImVec2_c {
198        x: pixel_offset[0],
199        y: pixel_offset[1],
200    };
201    assert!(!text.contains('\0'), "text contained NUL");
202    with_scratch_txt(text, |ptr| unsafe {
203        compat_ffi::ImPlot_Annotation_Str0(x, y, col, off, clamp, ptr)
204    })
205}
206
207/// Tag the X axis at position x with a tick-like mark
208pub fn tag_x(x: f64, color: [f32; 4], round: bool) {
209    let col = sys::ImVec4_c {
210        x: color[0],
211        y: color[1],
212        z: color[2],
213        w: color[3],
214    };
215    unsafe { sys::ImPlot_TagX_Bool(x, col, round) }
216}
217
218/// Tag the X axis at position x with a text label using the non-variadic `ImPlot_TagX_Str0` API.
219pub fn tag_x_text(x: f64, color: [f32; 4], text: &str) {
220    let col = sys::ImVec4_c {
221        x: color[0],
222        y: color[1],
223        z: color[2],
224        w: color[3],
225    };
226    assert!(!text.contains('\0'), "text contained NUL");
227    with_scratch_txt(text, |ptr| unsafe {
228        compat_ffi::ImPlot_TagX_Str0(x, col, ptr)
229    })
230}
231
232/// Tag the Y axis at position y with a tick-like mark
233pub fn tag_y(y: f64, color: [f32; 4], round: bool) {
234    let col = sys::ImVec4_c {
235        x: color[0],
236        y: color[1],
237        z: color[2],
238        w: color[3],
239    };
240    unsafe { sys::ImPlot_TagY_Bool(y, col, round) }
241}
242
243/// Tag the Y axis at position y with a text label using the non-variadic `ImPlot_TagY_Str0` API.
244pub fn tag_y_text(y: f64, color: [f32; 4], text: &str) {
245    let col = sys::ImVec4_c {
246        x: color[0],
247        y: color[1],
248        z: color[2],
249        w: color[3],
250    };
251    assert!(!text.contains('\0'), "text contained NUL");
252    with_scratch_txt(text, |ptr| unsafe {
253        compat_ffi::ImPlot_TagY_Str0(y, col, ptr)
254    })
255}
256
257/// Get the current plot limits for specific axes
258pub fn get_plot_limits_axes(x_axis: XAxis, y_axis: YAxis) -> sys::ImPlotRect {
259    unsafe { sys::ImPlot_GetPlotLimits(x_axis as i32, y_axis as i32) }
260}
261
262/// Check if an axis is hovered
263pub fn is_axis_hovered(axis: i32) -> bool {
264    unsafe { sys::ImPlot_IsAxisHovered(axis) }
265}
266
267/// Check if the X axis is hovered
268pub fn is_plot_x_axis_hovered() -> bool {
269    is_axis_hovered(XAxis::X1 as i32)
270}
271
272/// Check if a specific X axis is hovered
273pub fn is_plot_x_axis_hovered_axis(x_axis: XAxis) -> bool {
274    is_axis_hovered(x_axis as i32)
275}
276
277/// Check if a Y axis is hovered
278pub fn is_plot_y_axis_hovered(y_axis_choice: Option<crate::YAxisChoice>) -> bool {
279    let y_axis = match y_axis_choice {
280        Some(crate::YAxisChoice::First) => 3,  // ImAxis_Y1
281        Some(crate::YAxisChoice::Second) => 4, // ImAxis_Y2
282        Some(crate::YAxisChoice::Third) => 5,  // ImAxis_Y3
283        None => 3,                             // Default to Y1
284    };
285    is_axis_hovered(y_axis)
286}
287
288/// Check if a specific Y axis is hovered
289pub fn is_plot_y_axis_hovered_axis(y_axis: YAxis) -> bool {
290    is_axis_hovered(y_axis as i32)
291}
292
293/// Show the ImPlot demo window (requires sys demo symbols to be linked)
294#[cfg(feature = "demo")]
295pub fn show_demo_window(show: &mut bool) {
296    unsafe { sys::ImPlot_ShowDemoWindow(show) }
297}
298
299/// Stub when demo feature is disabled
300#[cfg(not(feature = "demo"))]
301pub fn show_demo_window(_show: &mut bool) {}
302
303/// Show the built-in user guide for ImPlot
304pub fn show_user_guide() {
305    unsafe { sys::ImPlot_ShowUserGuide() }
306}
307
308/// Show the metrics window (pass &mut bool for open state)
309pub fn show_metrics_window(open: &mut bool) {
310    unsafe { sys::ImPlot_ShowMetricsWindow(open as *mut bool) }
311}
312
313/// Get current plot position (top-left) in pixels
314pub fn get_plot_pos() -> [f32; 2] {
315    let out = unsafe { crate::compat_ffi::ImPlot_GetPlotPos() };
316    [out.x, out.y]
317}
318
319/// Get current plot size in pixels
320pub fn get_plot_size() -> [f32; 2] {
321    let out = unsafe { crate::compat_ffi::ImPlot_GetPlotSize() };
322    [out.x, out.y]
323}
324
325/// Get the underlying ImDrawList for the current plot (unsafe pointer)
326pub fn get_plot_draw_list() -> *mut sys::ImDrawList {
327    unsafe { sys::ImPlot_GetPlotDrawList() }
328}
329
330/// Push plot clip rect
331pub fn push_plot_clip_rect(expand: f32) {
332    unsafe { sys::ImPlot_PushPlotClipRect(expand) }
333}
334
335/// Pop plot clip rect
336pub fn pop_plot_clip_rect() {
337    unsafe { sys::ImPlot_PopPlotClipRect() }
338}
339
340/// Result of a drag interaction
341#[derive(Debug, Clone, Copy, Default)]
342pub struct DragResult {
343    /// True if the underlying value changed this frame
344    pub changed: bool,
345    /// True if it was clicked this frame
346    pub clicked: bool,
347    /// True if hovered this frame
348    pub hovered: bool,
349    /// True if held/active this frame
350    pub held: bool,
351}
352
353fn color4(rgba: [f32; 4]) -> sys::ImVec4_c {
354    sys::ImVec4_c {
355        x: rgba[0],
356        y: rgba[1],
357        z: rgba[2],
358        w: rgba[3],
359    }
360}
361
362/// Draggable point with result flags
363pub fn drag_point(
364    id: i32,
365    x: &mut f64,
366    y: &mut f64,
367    color: [f32; 4],
368    size: f32,
369    flags: crate::DragToolFlags,
370) -> DragResult {
371    let mut clicked = false;
372    let mut hovered = false;
373    let mut held = false;
374    let changed = unsafe {
375        sys::ImPlot_DragPoint(
376            id,
377            x as *mut f64,
378            y as *mut f64,
379            color4(color),
380            size,
381            flags.bits() as i32,
382            &mut clicked as *mut bool,
383            &mut hovered as *mut bool,
384            &mut held as *mut bool,
385        )
386    };
387    DragResult {
388        changed,
389        clicked,
390        hovered,
391        held,
392    }
393}
394
395/// Draggable vertical line at x
396pub fn drag_line_x(
397    id: i32,
398    x: &mut f64,
399    color: [f32; 4],
400    thickness: f32,
401    flags: crate::DragToolFlags,
402) -> DragResult {
403    let mut clicked = false;
404    let mut hovered = false;
405    let mut held = false;
406    let changed = unsafe {
407        sys::ImPlot_DragLineX(
408            id,
409            x as *mut f64,
410            color4(color),
411            thickness,
412            flags.bits() as i32,
413            &mut clicked as *mut bool,
414            &mut hovered as *mut bool,
415            &mut held as *mut bool,
416        )
417    };
418    DragResult {
419        changed,
420        clicked,
421        hovered,
422        held,
423    }
424}
425
426/// Draggable horizontal line at y
427pub fn drag_line_y(
428    id: i32,
429    y: &mut f64,
430    color: [f32; 4],
431    thickness: f32,
432    flags: crate::DragToolFlags,
433) -> DragResult {
434    let mut clicked = false;
435    let mut hovered = false;
436    let mut held = false;
437    let changed = unsafe {
438        sys::ImPlot_DragLineY(
439            id,
440            y as *mut f64,
441            color4(color),
442            thickness,
443            flags.bits() as i32,
444            &mut clicked as *mut bool,
445            &mut hovered as *mut bool,
446            &mut held as *mut bool,
447        )
448    };
449    DragResult {
450        changed,
451        clicked,
452        hovered,
453        held,
454    }
455}