Skip to main content

dear_implot/
utils.rs

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