amdguid/
transform.rs

1use std::ops::RangeInclusive;
2
3use egui::{pos2, remap, Pos2, Rect, Vec2};
4
5use crate::items::Value;
6
7/// 2D bounding box of f64 precision.
8/// The range of data values we show.
9#[derive(Clone, Copy, Debug, serde::Deserialize, PartialEq, serde::Serialize)]
10pub struct Bounds {
11    pub min: [f64; 2],
12    pub max: [f64; 2],
13}
14
15impl Bounds {
16    pub const NOTHING: Self = Self {
17        min: [f64::INFINITY; 2],
18        max: [-f64::INFINITY; 2],
19    };
20
21    pub fn new_symmetrical(half_extent: f64) -> Self {
22        Self {
23            min: [-half_extent; 2],
24            max: [half_extent; 2],
25        }
26    }
27
28    pub fn is_finite(&self) -> bool {
29        self.min[0].is_finite()
30            && self.min[1].is_finite()
31            && self.max[0].is_finite()
32            && self.max[1].is_finite()
33    }
34
35    pub fn is_valid(&self) -> bool {
36        self.is_finite() && self.width() > 0.0 && self.height() > 0.0
37    }
38
39    pub fn width(&self) -> f64 {
40        self.max[0] - self.min[0]
41    }
42
43    pub fn height(&self) -> f64 {
44        self.max[1] - self.min[1]
45    }
46
47    pub fn extend_with(&mut self, value: &Value) {
48        self.extend_with_x(value.x);
49        self.extend_with_y(value.y);
50    }
51
52    /// Expand to include the given x coordinate
53    pub fn extend_with_x(&mut self, x: f64) {
54        self.min[0] = self.min[0].min(x);
55        self.max[0] = self.max[0].max(x);
56    }
57
58    /// Expand to include the given y coordinate
59    pub fn extend_with_y(&mut self, y: f64) {
60        self.min[1] = self.min[1].min(y);
61        self.max[1] = self.max[1].max(y);
62    }
63
64    pub fn expand_x(&mut self, pad: f64) {
65        self.min[0] -= pad;
66        self.max[0] += pad;
67    }
68
69    pub fn expand_y(&mut self, pad: f64) {
70        self.min[1] -= pad;
71        self.max[1] += pad;
72    }
73
74    pub fn merge(&mut self, other: &Bounds) {
75        self.min[0] = self.min[0].min(other.min[0]);
76        self.min[1] = self.min[1].min(other.min[1]);
77        self.max[0] = self.max[0].max(other.max[0]);
78        self.max[1] = self.max[1].max(other.max[1]);
79    }
80
81    pub fn translate_x(&mut self, delta: f64) {
82        self.min[0] += delta;
83        self.max[0] += delta;
84    }
85
86    pub fn translate_y(&mut self, delta: f64) {
87        self.min[1] += delta;
88        self.max[1] += delta;
89    }
90
91    pub fn translate(&mut self, delta: Vec2) {
92        self.translate_x(delta.x as f64);
93        self.translate_y(delta.y as f64);
94    }
95
96    pub fn add_relative_margin(&mut self, margin_fraction: Vec2) {
97        let width = self.width().max(0.0);
98        let height = self.height().max(0.0);
99        self.expand_x(margin_fraction.x as f64 * width);
100        self.expand_y(margin_fraction.y as f64 * height);
101    }
102
103    pub fn range_x(&self) -> RangeInclusive<f64> {
104        self.min[0]..=self.max[0]
105    }
106
107    pub fn make_x_symmetrical(&mut self) {
108        let x_abs = self.min[0].abs().max(self.max[0].abs());
109        self.min[0] = -x_abs;
110        self.max[0] = x_abs;
111    }
112
113    pub fn make_y_symmetrical(&mut self) {
114        let y_abs = self.min[1].abs().max(self.max[1].abs());
115        self.min[1] = -y_abs;
116        self.max[1] = y_abs;
117    }
118}
119
120/// Contains the screen rectangle and the plot bounds and provides methods to
121/// transform them.
122#[derive(Clone)]
123pub struct ScreenTransform {
124    /// The screen rectangle.
125    frame: Rect,
126    /// The plot bounds.
127    bounds: Bounds,
128    /// Whether to always center the x-range of the bounds.
129    x_centered: bool,
130    /// Whether to always center the y-range of the bounds.
131    y_centered: bool,
132}
133
134impl ScreenTransform {
135    pub fn new(frame: Rect, bounds: Bounds, x_centered: bool, y_centered: bool) -> Self {
136        Self {
137            frame,
138            bounds,
139            x_centered,
140            y_centered,
141        }
142    }
143
144    pub fn frame(&self) -> &Rect {
145        &self.frame
146    }
147
148    pub fn bounds(&self) -> &Bounds {
149        &self.bounds
150    }
151
152    pub fn translate_bounds(&mut self, mut delta_pos: Vec2) {
153        if self.x_centered {
154            delta_pos.x = 0.;
155        }
156        if self.y_centered {
157            delta_pos.y = 0.;
158        }
159        delta_pos.x *= self.dvalue_dpos()[0] as f32;
160        delta_pos.y *= self.dvalue_dpos()[1] as f32;
161        self.bounds.translate(delta_pos);
162    }
163
164    /// Zoom by a relative factor with the given screen position as center.
165    pub fn zoom(&mut self, zoom_factor: Vec2, center: Pos2) {
166        let center = self.value_from_position(center);
167
168        let mut new_bounds = self.bounds;
169        new_bounds.min[0] = center.x + (new_bounds.min[0] - center.x) / (zoom_factor.x as f64);
170        new_bounds.max[0] = center.x + (new_bounds.max[0] - center.x) / (zoom_factor.x as f64);
171        new_bounds.min[1] = center.y + (new_bounds.min[1] - center.y) / (zoom_factor.y as f64);
172        new_bounds.max[1] = center.y + (new_bounds.max[1] - center.y) / (zoom_factor.y as f64);
173
174        if new_bounds.is_valid() {
175            self.bounds = new_bounds;
176        }
177    }
178
179    pub fn position_from_value(&self, value: &Value) -> Pos2 {
180        let x = remap(
181            value.x,
182            self.bounds.min[0]..=self.bounds.max[0],
183            (self.frame.left() as f64)..=(self.frame.right() as f64),
184        );
185        let y = remap(
186            value.y,
187            self.bounds.min[1]..=self.bounds.max[1],
188            (self.frame.bottom() as f64)..=(self.frame.top() as f64), // negated y axis!
189        );
190        pos2(x as f32, y as f32)
191    }
192
193    pub fn value_from_position(&self, pos: Pos2) -> Value {
194        let x = remap(
195            pos.x as f64,
196            (self.frame.left() as f64)..=(self.frame.right() as f64),
197            self.bounds.min[0]..=self.bounds.max[0],
198        );
199        let y = remap(
200            pos.y as f64,
201            (self.frame.bottom() as f64)..=(self.frame.top() as f64), // negated y axis!
202            self.bounds.min[1]..=self.bounds.max[1],
203        );
204        Value::new(x, y)
205    }
206
207    /// delta position / delta value
208    pub fn dpos_dvalue_x(&self) -> f64 {
209        self.frame.width() as f64 / self.bounds.width()
210    }
211
212    /// delta position / delta value
213    pub fn dpos_dvalue_y(&self) -> f64 {
214        -self.frame.height() as f64 / self.bounds.height() // negated y axis!
215    }
216
217    /// delta position / delta value
218    pub fn dpos_dvalue(&self) -> [f64; 2] {
219        [self.dpos_dvalue_x(), self.dpos_dvalue_y()]
220    }
221
222    /// delta value / delta position
223    pub fn dvalue_dpos(&self) -> [f64; 2] {
224        [1.0 / self.dpos_dvalue_x(), 1.0 / self.dpos_dvalue_y()]
225    }
226
227    pub fn get_aspect(&self) -> f64 {
228        let rw = self.frame.width() as f64;
229        let rh = self.frame.height() as f64;
230        (self.bounds.width() / rw) / (self.bounds.height() / rh)
231    }
232
233    pub fn set_aspect(&mut self, aspect: f64) {
234        let epsilon = 1e-5;
235        let current_aspect = self.get_aspect();
236        if current_aspect < aspect - epsilon {
237            self.bounds
238                .expand_x((aspect / current_aspect - 1.0) * self.bounds.width() * 0.5);
239        } else if current_aspect > aspect + epsilon {
240            self.bounds
241                .expand_y((current_aspect / aspect - 1.0) * self.bounds.height() * 0.5);
242        }
243    }
244}