gpui_liveplot/
interaction.rs1use crate::geom::{Point, ScreenPoint, ScreenRect};
7use crate::series::SeriesId;
8use crate::transform::Transform;
9use crate::view::{Range, Viewport};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub(crate) enum HitRegion {
14 Plot,
16 XAxis,
18 YAxis,
20 Outside,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq)]
26pub(crate) struct PlotRegions {
27 pub(crate) plot: ScreenRect,
29 pub(crate) x_axis: ScreenRect,
31 pub(crate) y_axis: ScreenRect,
33}
34
35impl PlotRegions {
36 pub(crate) fn hit_test(&self, point: ScreenPoint) -> HitRegion {
38 if contains(self.plot, point) {
39 HitRegion::Plot
40 } else if contains(self.x_axis, point) {
41 HitRegion::XAxis
42 } else if contains(self.y_axis, point) {
43 HitRegion::YAxis
44 } else {
45 HitRegion::Outside
46 }
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct Pin {
56 pub series_id: SeriesId,
58 pub point_index: usize,
60}
61
62pub(crate) fn toggle_pin(pins: &mut Vec<Pin>, pin: Pin) -> bool {
64 if let Some(index) = pins.iter().position(|existing| *existing == pin) {
65 pins.swap_remove(index);
66 false
67 } else {
68 pins.push(pin);
69 true
70 }
71}
72
73pub(crate) fn pan_viewport(
75 viewport: Viewport,
76 delta_pixels: ScreenPoint,
77 transform: &Transform,
78) -> Option<Viewport> {
79 let origin = transform.screen_to_data(ScreenPoint::new(0.0, 0.0))?;
80 let shifted = transform.screen_to_data(ScreenPoint::new(delta_pixels.x, delta_pixels.y))?;
81 let dx = shifted.x - origin.x;
82 let dy = shifted.y - origin.y;
83 Some(Viewport::new(
84 Range::new(viewport.x.min - dx, viewport.x.max - dx),
85 Range::new(viewport.y.min - dy, viewport.y.max - dy),
86 ))
87}
88
89pub(crate) fn zoom_viewport(
91 viewport: Viewport,
92 center: Point,
93 factor_x: f64,
94 factor_y: f64,
95) -> Viewport {
96 let x_min = center.x + (viewport.x.min - center.x) * factor_x;
97 let x_max = center.x + (viewport.x.max - center.x) * factor_x;
98 let y_min = center.y + (viewport.y.min - center.y) * factor_y;
99 let y_max = center.y + (viewport.y.max - center.y) * factor_y;
100 Viewport::new(Range::new(x_min, x_max), Range::new(y_min, y_max))
101}
102
103pub(crate) fn zoom_to_rect(
105 viewport: Viewport,
106 rect: ScreenRect,
107 transform: &Transform,
108) -> Option<Viewport> {
109 if rect.width().abs() < 2.0 || rect.height().abs() < 2.0 {
110 return Some(viewport);
111 }
112 let data_min = transform.screen_to_data(rect.min)?;
113 let data_max = transform.screen_to_data(rect.max)?;
114 Some(Viewport::new(
115 Range::new(data_min.x, data_max.x),
116 Range::new(data_min.y, data_max.y),
117 ))
118}
119
120pub(crate) fn zoom_factor_from_drag(delta_pixels: f32, axis_pixels: f32) -> f64 {
122 if axis_pixels <= 0.0 {
123 return 1.0;
124 }
125 let normalized = delta_pixels as f64 / axis_pixels as f64;
126 (1.0 - normalized).clamp(0.1, 10.0)
127}
128
129fn contains(rect: ScreenRect, point: ScreenPoint) -> bool {
130 point.x >= rect.min.x && point.x <= rect.max.x && point.y >= rect.min.y && point.y <= rect.max.y
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn hit_test_regions() {
139 let regions = PlotRegions {
140 plot: ScreenRect::new(ScreenPoint::new(0.0, 0.0), ScreenPoint::new(10.0, 10.0)),
141 x_axis: ScreenRect::new(ScreenPoint::new(0.0, 10.0), ScreenPoint::new(10.0, 12.0)),
142 y_axis: ScreenRect::new(ScreenPoint::new(-2.0, 0.0), ScreenPoint::new(0.0, 10.0)),
143 };
144 assert_eq!(
145 regions.hit_test(ScreenPoint::new(5.0, 5.0)),
146 HitRegion::Plot
147 );
148 assert_eq!(
149 regions.hit_test(ScreenPoint::new(5.0, 11.0)),
150 HitRegion::XAxis
151 );
152 assert_eq!(
153 regions.hit_test(ScreenPoint::new(-1.0, 5.0)),
154 HitRegion::YAxis
155 );
156 }
157}