widgetry/
screen_geom.rs

1use serde::{Deserialize, Serialize};
2
3use geom::{trim_f64, Polygon, Pt2D};
4
5use crate::{Canvas, EdgeInsets};
6
7/// ScreenPt is in units of logical pixels, as opposed to physical pixels.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub struct ScreenPt {
10    pub x: f64,
11    pub y: f64,
12}
13
14impl ScreenPt {
15    pub fn new(x: f64, y: f64) -> ScreenPt {
16        ScreenPt { x, y }
17    }
18
19    // The geom layer operates in map-space, but currently reusing lots of geom abstractions for
20    // screen-space.
21    pub fn to_pt(self) -> Pt2D {
22        Pt2D::new(self.x, self.y)
23    }
24
25    pub fn zero() -> Self {
26        Self { x: 0.0, y: 0.0 }
27    }
28
29    pub fn translated(&self, x: f64, y: f64) -> Self {
30        Self {
31            x: self.x + x,
32            y: self.y + y,
33        }
34    }
35}
36
37impl From<winit::dpi::LogicalPosition<f64>> for ScreenPt {
38    fn from(lp: winit::dpi::LogicalPosition<f64>) -> ScreenPt {
39        ScreenPt { x: lp.x, y: lp.y }
40    }
41}
42
43/// ScreenRectangle is in units of logical pixels, as opposed to physical pixels.
44#[derive(Clone, Debug)]
45pub struct ScreenRectangle {
46    pub x1: f64,
47    pub y1: f64,
48    pub x2: f64,
49    pub y2: f64,
50}
51
52impl ScreenRectangle {
53    pub fn top_left(top_left: ScreenPt, dims: ScreenDims) -> ScreenRectangle {
54        ScreenRectangle {
55            x1: top_left.x,
56            y1: top_left.y,
57            x2: top_left.x + dims.width,
58            y2: top_left.y + dims.height,
59        }
60    }
61
62    pub fn placeholder() -> ScreenRectangle {
63        ScreenRectangle {
64            x1: 0.0,
65            y1: 0.0,
66            x2: 0.0,
67            y2: 0.0,
68        }
69    }
70
71    pub fn contains(&self, pt: ScreenPt) -> bool {
72        pt.x >= self.x1 && pt.x <= self.x2 && pt.y >= self.y1 && pt.y <= self.y2
73    }
74
75    pub fn pt_to_percent(&self, pt: ScreenPt) -> Option<(f64, f64)> {
76        if self.contains(pt) {
77            Some((
78                (pt.x - self.x1) / self.width(),
79                (pt.y - self.y1) / self.height(),
80            ))
81        } else {
82            None
83        }
84    }
85    pub fn percent_to_pt(&self, x: f64, y: f64) -> ScreenPt {
86        ScreenPt::new(self.x1 + x * self.width(), self.y1 + y * self.height())
87    }
88
89    // TODO Remove these in favor of dims()
90    pub fn width(&self) -> f64 {
91        self.x2 - self.x1
92    }
93
94    pub fn height(&self) -> f64 {
95        self.y2 - self.y1
96    }
97
98    pub fn dims(&self) -> ScreenDims {
99        ScreenDims::new(self.x2 - self.x1, self.y2 - self.y1)
100    }
101
102    pub fn center(&self) -> ScreenPt {
103        ScreenPt::new((self.x1 + self.x2) / 2.0, (self.y1 + self.y2) / 2.0)
104    }
105
106    pub fn to_polygon(&self) -> Polygon {
107        Polygon::rectangle(self.width(), self.height()).translate(self.x1, self.y1)
108    }
109}
110
111// REVIEW: Rename to something shorter? e.g. Dims / Size
112/// ScreenDims is in units of logical pixels, as opposed to physical pixels.
113#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
114pub struct ScreenDims {
115    pub width: f64,
116    pub height: f64,
117}
118
119impl ScreenDims {
120    pub fn new(width: f64, height: f64) -> ScreenDims {
121        ScreenDims {
122            width: trim_f64(width),
123            height: trim_f64(height),
124        }
125    }
126
127    pub fn zero() -> Self {
128        ScreenDims {
129            width: 0.0,
130            height: 0.0,
131        }
132    }
133
134    pub fn square(square: f64) -> Self {
135        Self::new(square, square)
136    }
137
138    pub fn pad(&self, edge_insets: EdgeInsets) -> Self {
139        Self {
140            width: self.width + edge_insets.left + edge_insets.right,
141            height: self.height + edge_insets.top + edge_insets.bottom,
142        }
143    }
144
145    pub fn top_left_for_corner(&self, corner: ScreenPt, canvas: &Canvas) -> ScreenPt {
146        // TODO Ideally also avoid covered canvas areas
147        if corner.x + self.width < canvas.window_width {
148            // corner.x is the left corner
149            if corner.y + self.height < canvas.window_height {
150                // corner.y is the top corner
151                corner
152            } else {
153                // corner.y is the bottom corner
154                ScreenPt::new(corner.x, corner.y - self.height)
155            }
156        } else {
157            // corner.x is the right corner
158            if corner.y + self.height < canvas.window_height {
159                // corner.y is the top corner
160                ScreenPt::new(corner.x - self.width, corner.y)
161            } else {
162                // corner.y is the bottom corner
163                ScreenPt::new(corner.x - self.width, corner.y - self.height)
164            }
165        }
166    }
167}
168
169impl From<winit::dpi::LogicalSize<f64>> for ScreenDims {
170    fn from(size: winit::dpi::LogicalSize<f64>) -> ScreenDims {
171        ScreenDims {
172            width: size.width,
173            height: size.height,
174        }
175    }
176}
177
178impl From<ScreenDims> for winit::dpi::LogicalSize<f64> {
179    fn from(dims: ScreenDims) -> winit::dpi::LogicalSize<f64> {
180        winit::dpi::LogicalSize::new(dims.width, dims.height)
181    }
182}
183
184impl From<f64> for ScreenDims {
185    fn from(square: f64) -> ScreenDims {
186        ScreenDims::square(square)
187    }
188}
189
190impl From<i64> for ScreenDims {
191    fn from(square: i64) -> ScreenDims {
192        ScreenDims::square(square as f64)
193    }
194}
195
196/// (Width, Height) -> ScreenDims
197impl From<(f64, f64)> for ScreenDims {
198    fn from(width_and_height: (f64, f64)) -> ScreenDims {
199        ScreenDims::new(width_and_height.0, width_and_height.1)
200    }
201}
202
203impl From<geom::Bounds> for ScreenDims {
204    fn from(bounds: geom::Bounds) -> Self {
205        ScreenDims::new(bounds.width(), bounds.height())
206    }
207}
208
209impl From<ScreenDims> for taffy::geometry::Size<taffy::style::Dimension> {
210    fn from(dims: ScreenDims) -> Self {
211        Self {
212            width: taffy::style::Dimension::Points(dims.width as f32),
213            height: taffy::style::Dimension::Points(dims.height as f32),
214        }
215    }
216}