1use crate::{Axis, PlotUi, XAxis, YAxis, sys};
4use dear_imgui_rs::with_scratch_txt;
5use std::{borrow::Cow, fmt};
6
7fn literal_printf_format(text: &str) -> Cow<'_, str> {
8 if text.contains('%') {
9 Cow::Owned(text.replace('%', "%%"))
10 } else {
11 Cow::Borrowed(text)
12 }
13}
14
15fn assert_finite_f64(caller: &str, name: &str, value: f64) {
16 assert!(value.is_finite(), "{caller} {name} must be finite");
17}
18
19fn assert_finite_vec2(caller: &str, name: &str, value: [f32; 2]) {
20 assert!(
21 value[0].is_finite() && value[1].is_finite(),
22 "{caller} {name} must be finite"
23 );
24}
25
26fn assert_finite_color(caller: &str, name: &str, value: [f32; 4]) {
27 assert!(
28 value.iter().all(|component| component.is_finite()),
29 "{caller} {name} must be finite"
30 );
31}
32
33fn assert_finite_point(caller: &str, name: &str, value: sys::ImPlotPoint) {
34 assert!(
35 value.x.is_finite() && value.y.is_finite(),
36 "{caller} {name} must be finite"
37 );
38}
39
40impl PlotUi<'_> {
41 pub fn is_subplots_hovered(&self) -> bool {
43 self.with_bound_context(|| unsafe { sys::ImPlot_IsSubplotsHovered() })
44 }
45
46 pub fn is_legend_entry_hovered(&self, label: &str) -> bool {
48 let label = if label.contains('\0') { "" } else { label };
49 self.with_bound_context(|| {
50 with_scratch_txt(label, |ptr| unsafe {
51 sys::ImPlot_IsLegendEntryHovered(ptr)
52 })
53 })
54 }
55
56 pub fn plot_mouse_position(
58 &self,
59 y_axis_choice: Option<crate::YAxisChoice>,
60 ) -> sys::ImPlotPoint {
61 let x_axis = 0; let y_axis = match y_axis_choice {
63 Some(crate::YAxisChoice::First) => 3, Some(crate::YAxisChoice::Second) => 4, Some(crate::YAxisChoice::Third) => 5, None => 3, };
68 self.with_bound_context(|| unsafe {
69 sys::ImPlot_GetPlotMousePos(x_axis as sys::ImAxis, y_axis as sys::ImAxis)
70 })
71 }
72
73 pub fn plot_mouse_position_axes(&self, x_axis: XAxis, y_axis: YAxis) -> sys::ImPlotPoint {
75 self.with_bound_context(|| unsafe {
76 sys::ImPlot_GetPlotMousePos(x_axis as sys::ImAxis, y_axis as sys::ImAxis)
77 })
78 }
79
80 pub fn pixels_to_plot(
82 &self,
83 pixel_position: [f32; 2],
84 y_axis_choice: Option<crate::YAxisChoice>,
85 ) -> sys::ImPlotPoint {
86 assert_finite_vec2("PlotUi::pixels_to_plot()", "pixel_position", pixel_position);
87 let y_index = match y_axis_choice {
89 Some(crate::YAxisChoice::First) => 0,
90 Some(crate::YAxisChoice::Second) => 1,
91 Some(crate::YAxisChoice::Third) => 2,
92 None => 0,
93 };
94 self.with_bound_context(|| unsafe {
95 let plot = sys::ImPlot_GetCurrentPlot();
96 if plot.is_null() {
97 return sys::ImPlotPoint { x: 0.0, y: 0.0 };
98 }
99 let x_axis_ptr = sys::ImPlotPlot_XAxis_Nil(plot, 0);
100 let y_axis_ptr = sys::ImPlotPlot_YAxis_Nil(plot, y_index);
101 let x = sys::ImPlotAxis_PixelsToPlot(x_axis_ptr, pixel_position[0]);
102 let y = sys::ImPlotAxis_PixelsToPlot(y_axis_ptr, pixel_position[1]);
103 sys::ImPlotPoint { x, y }
104 })
105 }
106
107 pub fn pixels_to_plot_axes(
109 &self,
110 pixel_position: [f32; 2],
111 x_axis: XAxis,
112 y_axis: YAxis,
113 ) -> sys::ImPlotPoint {
114 assert_finite_vec2(
115 "PlotUi::pixels_to_plot_axes()",
116 "pixel_position",
117 pixel_position,
118 );
119 self.with_bound_context(|| unsafe {
120 let plot = sys::ImPlot_GetCurrentPlot();
121 if plot.is_null() {
122 return sys::ImPlotPoint { x: 0.0, y: 0.0 };
123 }
124 let x_axis_ptr = sys::ImPlotPlot_XAxis_Nil(plot, x_axis as i32);
125 let y_axis_ptr = sys::ImPlotPlot_YAxis_Nil(plot, y_axis.to_index());
126 let x = sys::ImPlotAxis_PixelsToPlot(x_axis_ptr, pixel_position[0]);
127 let y = sys::ImPlotAxis_PixelsToPlot(y_axis_ptr, pixel_position[1]);
128 sys::ImPlotPoint { x, y }
129 })
130 }
131
132 pub fn plot_to_pixels(
134 &self,
135 plot_position: sys::ImPlotPoint,
136 y_axis_choice: Option<crate::YAxisChoice>,
137 ) -> [f32; 2] {
138 assert_finite_point("PlotUi::plot_to_pixels()", "plot_position", plot_position);
139 let y_index = match y_axis_choice {
140 Some(crate::YAxisChoice::First) => 0,
141 Some(crate::YAxisChoice::Second) => 1,
142 Some(crate::YAxisChoice::Third) => 2,
143 None => 0,
144 };
145 self.with_bound_context(|| unsafe {
146 let plot = sys::ImPlot_GetCurrentPlot();
147 if plot.is_null() {
148 return [0.0, 0.0];
149 }
150 let x_axis_ptr = sys::ImPlotPlot_XAxis_Nil(plot, 0);
151 let y_axis_ptr = sys::ImPlotPlot_YAxis_Nil(plot, y_index);
152 let px = sys::ImPlotAxis_PlotToPixels(x_axis_ptr, plot_position.x);
153 let py = sys::ImPlotAxis_PlotToPixels(y_axis_ptr, plot_position.y);
154 [px, py]
155 })
156 }
157
158 pub fn plot_to_pixels_axes(
160 &self,
161 plot_position: sys::ImPlotPoint,
162 x_axis: XAxis,
163 y_axis: YAxis,
164 ) -> [f32; 2] {
165 assert_finite_point(
166 "PlotUi::plot_to_pixels_axes()",
167 "plot_position",
168 plot_position,
169 );
170 self.with_bound_context(|| unsafe {
171 let plot = sys::ImPlot_GetCurrentPlot();
172 if plot.is_null() {
173 return [0.0, 0.0];
174 }
175 let x_axis_ptr = sys::ImPlotPlot_XAxis_Nil(plot, x_axis as i32);
176 let y_axis_ptr = sys::ImPlotPlot_YAxis_Nil(plot, y_axis.to_index());
177 let px = sys::ImPlotAxis_PlotToPixels(x_axis_ptr, plot_position.x);
178 let py = sys::ImPlotAxis_PlotToPixels(y_axis_ptr, plot_position.y);
179 [px, py]
180 })
181 }
182
183 pub fn plot_limits(
185 &self,
186 _x_axis_choice: Option<crate::YAxisChoice>,
187 y_axis_choice: Option<crate::YAxisChoice>,
188 ) -> sys::ImPlotRect {
189 let x_axis = 0; let y_axis = match y_axis_choice {
191 Some(crate::YAxisChoice::First) => 3, Some(crate::YAxisChoice::Second) => 4, Some(crate::YAxisChoice::Third) => 5, None => 3, };
196 self.with_bound_context(|| unsafe { sys::ImPlot_GetPlotLimits(x_axis, y_axis) })
197 }
198
199 pub fn is_plot_selected(&self) -> bool {
201 self.with_bound_context(|| unsafe { sys::ImPlot_IsPlotSelected() })
202 }
203
204 pub fn plot_selection_axes(&self, x_axis: XAxis, y_axis: YAxis) -> Option<sys::ImPlotRect> {
206 if !self.is_plot_selected() {
207 return None;
208 }
209 self.with_bound_context(|| {
210 let rect = unsafe { sys::ImPlot_GetPlotSelection(x_axis as i32, y_axis as i32) };
211 Some(rect)
212 })
213 }
214
215 pub fn annotation_point(
217 &self,
218 x: f64,
219 y: f64,
220 color: [f32; 4],
221 pixel_offset: [f32; 2],
222 clamp: bool,
223 round: bool,
224 ) {
225 assert_finite_f64("PlotUi::annotation_point()", "x", x);
226 assert_finite_f64("PlotUi::annotation_point()", "y", y);
227 assert_finite_color("PlotUi::annotation_point()", "color", color);
228 assert_finite_vec2("PlotUi::annotation_point()", "pixel_offset", pixel_offset);
229 let col = sys::ImVec4_c {
230 x: color[0],
231 y: color[1],
232 z: color[2],
233 w: color[3],
234 };
235 let off = sys::ImVec2_c {
236 x: pixel_offset[0],
237 y: pixel_offset[1],
238 };
239 self.with_bound_context(|| unsafe {
240 sys::ImPlot_Annotation_Bool(x, y, col, off, clamp, round)
241 })
242 }
243
244 pub fn annotation_text(
249 &self,
250 x: f64,
251 y: f64,
252 color: [f32; 4],
253 pixel_offset: [f32; 2],
254 clamp: bool,
255 text: &str,
256 ) {
257 assert_finite_f64("PlotUi::annotation_text()", "x", x);
258 assert_finite_f64("PlotUi::annotation_text()", "y", y);
259 assert_finite_color("PlotUi::annotation_text()", "color", color);
260 assert_finite_vec2("PlotUi::annotation_text()", "pixel_offset", pixel_offset);
261 let col = sys::ImVec4_c {
262 x: color[0],
263 y: color[1],
264 z: color[2],
265 w: color[3],
266 };
267 let off = sys::ImVec2_c {
268 x: pixel_offset[0],
269 y: pixel_offset[1],
270 };
271 assert!(!text.contains('\0'), "text contained NUL");
272 let text = literal_printf_format(text);
273 self.with_bound_context(|| {
274 with_scratch_txt(text.as_ref(), |ptr| unsafe {
275 sys::ImPlot_Annotation_Str0(x, y, col, off, clamp, ptr)
276 })
277 })
278 }
279
280 pub fn tag_x(&self, x: f64, color: [f32; 4], round: bool) {
282 assert_finite_f64("PlotUi::tag_x()", "x", x);
283 assert_finite_color("PlotUi::tag_x()", "color", color);
284 let col = sys::ImVec4_c {
285 x: color[0],
286 y: color[1],
287 z: color[2],
288 w: color[3],
289 };
290 self.with_bound_context(|| unsafe { sys::ImPlot_TagX_Bool(x, col, round) })
291 }
292
293 pub fn tag_x_text(&self, x: f64, color: [f32; 4], text: &str) {
295 assert_finite_f64("PlotUi::tag_x_text()", "x", x);
296 assert_finite_color("PlotUi::tag_x_text()", "color", color);
297 let col = sys::ImVec4_c {
298 x: color[0],
299 y: color[1],
300 z: color[2],
301 w: color[3],
302 };
303 assert!(!text.contains('\0'), "text contained NUL");
304 let text = literal_printf_format(text);
305 self.with_bound_context(|| {
306 with_scratch_txt(text.as_ref(), |ptr| unsafe {
307 sys::ImPlot_TagX_Str0(x, col, ptr)
308 })
309 })
310 }
311
312 pub fn tag_y(&self, y: f64, color: [f32; 4], round: bool) {
314 assert_finite_f64("PlotUi::tag_y()", "y", y);
315 assert_finite_color("PlotUi::tag_y()", "color", color);
316 let col = sys::ImVec4_c {
317 x: color[0],
318 y: color[1],
319 z: color[2],
320 w: color[3],
321 };
322 self.with_bound_context(|| unsafe { sys::ImPlot_TagY_Bool(y, col, round) })
323 }
324
325 pub fn tag_y_text(&self, y: f64, color: [f32; 4], text: &str) {
327 assert_finite_f64("PlotUi::tag_y_text()", "y", y);
328 assert_finite_color("PlotUi::tag_y_text()", "color", color);
329 let col = sys::ImVec4_c {
330 x: color[0],
331 y: color[1],
332 z: color[2],
333 w: color[3],
334 };
335 assert!(!text.contains('\0'), "text contained NUL");
336 let text = literal_printf_format(text);
337 self.with_bound_context(|| {
338 with_scratch_txt(text.as_ref(), |ptr| unsafe {
339 sys::ImPlot_TagY_Str0(y, col, ptr)
340 })
341 })
342 }
343
344 pub fn plot_limits_axes(&self, x_axis: XAxis, y_axis: YAxis) -> sys::ImPlotRect {
346 self.with_bound_context(|| unsafe {
347 sys::ImPlot_GetPlotLimits(x_axis as i32, y_axis as i32)
348 })
349 }
350
351 pub fn is_axis_hovered(&self, axis: Axis) -> bool {
353 self.with_bound_context(|| unsafe { sys::ImPlot_IsAxisHovered(axis.to_sys()) })
354 }
355
356 pub unsafe fn is_axis_hovered_unchecked(&self, axis: sys::ImAxis) -> bool {
363 self.with_bound_context(|| unsafe { sys::ImPlot_IsAxisHovered(axis) })
364 }
365
366 pub fn is_plot_x_axis_hovered(&self) -> bool {
368 self.is_axis_hovered(Axis::X1)
369 }
370
371 pub fn is_plot_x_axis_hovered_axis(&self, x_axis: XAxis) -> bool {
373 self.is_axis_hovered(x_axis.into())
374 }
375
376 pub fn is_plot_y_axis_hovered(&self, y_axis_choice: Option<crate::YAxisChoice>) -> bool {
378 let axis = match y_axis_choice {
379 Some(crate::YAxisChoice::First) | None => Axis::Y1,
380 Some(crate::YAxisChoice::Second) => Axis::Y2,
381 Some(crate::YAxisChoice::Third) => Axis::Y3,
382 };
383 self.is_axis_hovered(axis)
384 }
385
386 pub fn is_plot_y_axis_hovered_axis(&self, y_axis: YAxis) -> bool {
388 self.is_axis_hovered(y_axis.into())
389 }
390
391 #[cfg(feature = "demo")]
393 pub fn show_demo_window(&self, show: &mut bool) {
394 self.with_bound_context(|| unsafe { sys::ImPlot_ShowDemoWindow(show) })
395 }
396
397 #[cfg(not(feature = "demo"))]
399 pub fn show_demo_window(&self, _show: &mut bool) {}
400
401 pub fn show_user_guide(&self) {
403 self.with_bound_context(|| unsafe { sys::ImPlot_ShowUserGuide() })
404 }
405
406 pub fn show_metrics_window(&self, open: &mut bool) {
408 self.with_bound_context(|| unsafe { sys::ImPlot_ShowMetricsWindow(open as *mut bool) })
409 }
410
411 pub fn plot_pos(&self) -> [f32; 2] {
413 self.with_bound_context(|| {
414 let out = unsafe { sys::ImPlot_GetPlotPos() };
415 [out.x, out.y]
416 })
417 }
418
419 pub fn plot_size(&self) -> [f32; 2] {
421 self.with_bound_context(|| {
422 let out = unsafe { sys::ImPlot_GetPlotSize() };
423 [out.x, out.y]
424 })
425 }
426}
427
428#[derive(Debug, Clone, Copy, Default)]
430pub struct DragResult {
431 pub changed: bool,
433 pub clicked: bool,
435 pub hovered: bool,
437 pub held: bool,
439}
440
441#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
446#[repr(transparent)]
447pub struct DragToolId(i32);
448
449impl DragToolId {
450 #[inline]
452 pub const fn new(id: i32) -> Self {
453 Self(id)
454 }
455
456 #[inline]
458 pub const fn raw(self) -> i32 {
459 self.0
460 }
461}
462
463impl From<i32> for DragToolId {
464 #[inline]
465 fn from(value: i32) -> Self {
466 Self::new(value)
467 }
468}
469
470impl From<DragToolId> for i32 {
471 #[inline]
472 fn from(value: DragToolId) -> Self {
473 value.raw()
474 }
475}
476
477impl fmt::Display for DragToolId {
478 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
479 self.0.fmt(f)
480 }
481}
482
483fn color4(rgba: [f32; 4]) -> sys::ImVec4_c {
484 sys::ImVec4_c {
485 x: rgba[0],
486 y: rgba[1],
487 z: rgba[2],
488 w: rgba[3],
489 }
490}
491
492impl PlotUi<'_> {
493 pub fn drag_point(
495 &self,
496 id: DragToolId,
497 x: &mut f64,
498 y: &mut f64,
499 color: [f32; 4],
500 size: f32,
501 flags: crate::DragToolFlags,
502 ) -> DragResult {
503 let mut clicked = false;
504 let mut hovered = false;
505 let mut held = false;
506 self.with_bound_context(|| {
507 let changed = unsafe {
508 sys::ImPlot_DragPoint(
509 id.raw(),
510 x as *mut f64,
511 y as *mut f64,
512 color4(color),
513 size,
514 flags.bits() as i32,
515 &mut clicked as *mut bool,
516 &mut hovered as *mut bool,
517 &mut held as *mut bool,
518 )
519 };
520 DragResult {
521 changed,
522 clicked,
523 hovered,
524 held,
525 }
526 })
527 }
528
529 pub fn drag_line_x(
531 &self,
532 id: DragToolId,
533 x: &mut f64,
534 color: [f32; 4],
535 thickness: f32,
536 flags: crate::DragToolFlags,
537 ) -> DragResult {
538 let mut clicked = false;
539 let mut hovered = false;
540 let mut held = false;
541 self.with_bound_context(|| {
542 let changed = unsafe {
543 sys::ImPlot_DragLineX(
544 id.raw(),
545 x as *mut f64,
546 color4(color),
547 thickness,
548 flags.bits() as i32,
549 &mut clicked as *mut bool,
550 &mut hovered as *mut bool,
551 &mut held as *mut bool,
552 )
553 };
554 DragResult {
555 changed,
556 clicked,
557 hovered,
558 held,
559 }
560 })
561 }
562
563 pub fn drag_line_y(
565 &self,
566 id: DragToolId,
567 y: &mut f64,
568 color: [f32; 4],
569 thickness: f32,
570 flags: crate::DragToolFlags,
571 ) -> DragResult {
572 let mut clicked = false;
573 let mut hovered = false;
574 let mut held = false;
575 self.with_bound_context(|| {
576 let changed = unsafe {
577 sys::ImPlot_DragLineY(
578 id.raw(),
579 y as *mut f64,
580 color4(color),
581 thickness,
582 flags.bits() as i32,
583 &mut clicked as *mut bool,
584 &mut hovered as *mut bool,
585 &mut held as *mut bool,
586 )
587 };
588 DragResult {
589 changed,
590 clicked,
591 hovered,
592 held,
593 }
594 })
595 }
596}
597
598#[cfg(test)]
599mod tests {
600 use super::{DragToolId, literal_printf_format};
601 use std::borrow::Cow;
602
603 #[test]
604 fn literal_plot_text_borrows_strings_without_percent_signs() {
605 let text = literal_printf_format("ordinary label");
606
607 assert!(matches!(text, Cow::Borrowed("ordinary label")));
608 }
609
610 #[test]
611 fn literal_plot_text_escapes_every_printf_directive() {
612 let text = literal_printf_format("100% complete: %s %n %%");
613
614 assert_eq!(text, "100%% complete: %%s %%n %%%%");
615 assert!(matches!(text, Cow::Owned(_)));
616 }
617
618 #[test]
619 fn drag_tool_id_round_trips_raw_values() {
620 let id = DragToolId::new(-7);
621 assert_eq!(id.raw(), -7);
622 assert_eq!(i32::from(id), -7);
623
624 let other = DragToolId::from(120482);
625 assert_eq!(other.raw(), 120482);
626 assert_eq!(other.to_string(), "120482");
627 }
628}