chromiumoxide/
layout.rs

1//! Code based on [rust-headless-chrome](https://github.com/atroche/rust-headless-chrome/blob/master/src/browser/tab/element/box_model.rs)
2
3use chromiumoxide_cdp::cdp::browser_protocol::dom::Quad;
4use chromiumoxide_cdp::cdp::browser_protocol::input::{
5    DispatchMouseEventParams, DispatchMouseEventType, MouseButton,
6};
7use chromiumoxide_cdp::cdp::browser_protocol::page::Viewport;
8
9#[derive(Debug, Copy, Clone, PartialEq)]
10pub struct Point {
11    pub x: f64,
12    pub y: f64,
13}
14
15impl Point {
16    /// Create a new Point instance
17    pub fn new(x: f64, y: f64) -> Self {
18        Self { x, y }
19    }
20
21    fn area(&self, other: &Self) -> f64 {
22        (self.x * other.y - other.x * self.y) / 2.
23    }
24}
25
26impl std::ops::Add<Point> for Point {
27    type Output = Self;
28
29    fn add(self, other: Self) -> Self {
30        Self {
31            x: self.x + other.x,
32            y: self.y + other.y,
33        }
34    }
35}
36
37impl std::ops::Sub<Point> for Point {
38    type Output = Self;
39
40    fn sub(self, other: Self) -> Self {
41        Self {
42            x: self.x - other.x,
43            y: self.y - other.y,
44        }
45    }
46}
47
48impl std::ops::Div<f64> for Point {
49    type Output = Self;
50
51    fn div(self, other: f64) -> Self {
52        Self {
53            x: self.x / other,
54            y: self.y / other,
55        }
56    }
57}
58
59#[derive(Default, Debug, Copy, Clone, PartialEq)]
60pub struct Delta {
61    /// X delta in CSS pixels for mouse wheel event (default: 0).
62    pub delta_x: f64,
63    /// Y delta in CSS pixels for mouse wheel event (default: 0).
64    pub delta_y: f64,
65}
66
67impl Delta {
68    /// Create a new Delta instance
69    pub fn new(delta_x: f64, delta_y: f64) -> Self {
70        Self { delta_x, delta_y }
71    }
72
73    pub fn area(&self, other: &Self) -> f64 {
74        (self.delta_x * other.delta_y - other.delta_x * self.delta_y) / 2.
75    }
76}
77
78/// Converts a point into Left-Down-Single-Mouseclick
79impl From<Point> for DispatchMouseEventParams {
80    fn from(el: Point) -> DispatchMouseEventParams {
81        let mut params =
82            DispatchMouseEventParams::new(DispatchMouseEventType::MousePressed, el.x, el.y);
83        params.button = Some(MouseButton::Left);
84        params.click_count = Some(1);
85        params
86    }
87}
88
89/// Represents the scroll behavior mode.
90#[derive(Default, Debug, Clone, Copy)]
91pub enum ScrollBehavior {
92    #[default]
93    Auto,
94    Instant,
95    Smooth,
96}
97
98#[derive(Debug, Copy, Clone)]
99pub struct ElementQuad {
100    pub top_left: Point,
101    pub top_right: Point,
102    pub bottom_right: Point,
103    pub bottom_left: Point,
104}
105
106impl ElementQuad {
107    pub fn from_quad(quad: &Quad) -> Self {
108        assert_eq!(quad.inner().len(), 8);
109        let raw_quad = quad.inner();
110        Self {
111            top_left: Point {
112                x: raw_quad[0],
113                y: raw_quad[1],
114            },
115            top_right: Point {
116                x: raw_quad[2],
117                y: raw_quad[3],
118            },
119            bottom_right: Point {
120                x: raw_quad[4],
121                y: raw_quad[5],
122            },
123            bottom_left: Point {
124                x: raw_quad[6],
125                y: raw_quad[7],
126            },
127        }
128    }
129
130    pub fn quad_center(&self) -> Point {
131        Point {
132            x: (self.top_left.x + self.top_right.x + self.bottom_right.x + self.bottom_left.x) / 4.,
133            y: (self.top_left.y + self.top_right.y + self.bottom_right.y + self.bottom_left.y) / 4.,
134        }
135    }
136    /// Compute sum of all directed areas of adjacent triangles
137    /// https://en.wikipedia.org/wiki/Polygon#Simple_polygons
138    pub fn quad_area(&self) -> f64 {
139        let area = self.top_left.area(&self.top_right)
140            + self.top_right.area(&self.bottom_right)
141            + self.bottom_right.area(&self.bottom_left)
142            + self.bottom_left.area(&self.top_left);
143        area.abs()
144    }
145
146    pub fn height(&self) -> f64 {
147        self.bottom_left.y - self.top_left.y
148    }
149
150    pub fn width(&self) -> f64 {
151        self.top_right.x - self.top_left.x
152    }
153
154    /// The width divided by the height
155    pub fn aspect_ratio(&self) -> f64 {
156        self.width() / self.height()
157    }
158
159    /// The most left (smallest) x-coordinate
160    pub fn most_left(&self) -> f64 {
161        self.top_right
162            .x
163            .min(self.top_left.x)
164            .min(self.bottom_right.x)
165            .min(self.bottom_left.x)
166    }
167
168    /// The most right (largest) x-coordinate
169    pub fn most_right(&self) -> f64 {
170        self.top_right
171            .x
172            .max(self.top_left.x)
173            .max(self.bottom_right.x)
174            .max(self.bottom_left.x)
175    }
176
177    /// The most top (smallest) y-coordinate
178    pub fn most_top(&self) -> f64 {
179        self.top_right
180            .y
181            .min(self.top_left.y)
182            .min(self.bottom_right.y)
183            .min(self.bottom_left.y)
184    }
185
186    /// The most bottom (largest) y-coordinate
187    pub fn most_bottom(&self) -> f64 {
188        self.top_right
189            .y
190            .max(self.top_left.y)
191            .max(self.bottom_right.y)
192            .max(self.bottom_left.y)
193    }
194
195    /// If the most bottom point of `self` is above the most top point of
196    /// `other`
197    pub fn strictly_above(&self, other: &Self) -> bool {
198        self.most_bottom() < other.most_top()
199    }
200
201    /// If the most bottom point of `self` is above or on the same line as the
202    /// most top point of `other`
203    pub fn above(&self, other: &Self) -> bool {
204        self.most_bottom() <= other.most_top()
205    }
206
207    /// If the most top point of `self` is below the most bottom point of
208    /// `other`
209    pub fn strictly_below(&self, other: &Self) -> bool {
210        self.most_top() > other.most_bottom()
211    }
212
213    /// If the most top point of `self` is below or on the same line as the
214    /// most bottom point of `other`
215    pub fn below(&self, other: &Self) -> bool {
216        self.most_top() >= other.most_bottom()
217    }
218
219    /// If the most right point of `self` is left of the most left point of
220    /// `other`
221    pub fn strictly_left_of(&self, other: &Self) -> bool {
222        self.most_right() < other.most_left()
223    }
224
225    /// If the most right point of `self` is left or on the same line as the
226    /// most left point of `other`
227    pub fn left_of(&self, other: &Self) -> bool {
228        self.most_right() <= other.most_left()
229    }
230
231    /// If the most left point of `self` is right of the most right point of
232    /// `other`
233    pub fn strictly_right_of(&self, other: &Self) -> bool {
234        self.most_left() > other.most_right()
235    }
236
237    /// If the most left point of `self` is right or on the same line as the
238    /// most right point of `other`
239    pub fn right_of(&self, other: &Self) -> bool {
240        self.most_left() >= other.most_right()
241    }
242
243    /// If `self` is within the left/right boundaries defined by `other`.
244    pub fn within_horizontal_bounds_of(&self, other: &Self) -> bool {
245        self.most_left() >= other.most_left() && self.most_right() <= other.most_right()
246    }
247
248    /// If `self` is within the top/bottom boundaries defined by `other`.
249    pub fn within_vertical_bounds_of(&self, other: &Self) -> bool {
250        self.most_top() >= other.most_top() && self.most_bottom() <= other.most_bottom()
251    }
252
253    /// If `self` is within the boundaries defined by `other`.
254    pub fn within_bounds_of(&self, other: &Self) -> bool {
255        self.within_horizontal_bounds_of(other) && self.within_vertical_bounds_of(other)
256    }
257}
258
259#[derive(Debug, Clone)]
260pub struct BoxModel {
261    pub content: ElementQuad,
262    pub padding: ElementQuad,
263    pub border: ElementQuad,
264    pub margin: ElementQuad,
265    pub width: u32,
266    pub height: u32,
267}
268
269impl BoxModel {
270    /// Create a `Viewport` equal to the content-box, using a scale of 1.0
271    pub fn content_viewport(&self) -> Viewport {
272        Viewport {
273            x: self.content.top_left.x,
274            y: self.content.top_left.y,
275            width: self.content.width(),
276            height: self.content.height(),
277            scale: 1.0,
278        }
279    }
280
281    /// Create a `Viewport` equal to the padding-box, using a scale of 1.0
282    pub fn padding_viewport(&self) -> Viewport {
283        Viewport {
284            x: self.padding.top_left.x,
285            y: self.padding.top_left.y,
286            width: self.padding.width(),
287            height: self.padding.height(),
288            scale: 1.0,
289        }
290    }
291
292    /// Create a `Viewport` equal to the border-box, using a scale of 1.0
293    pub fn border_viewport(&self) -> Viewport {
294        Viewport {
295            x: self.border.top_left.x,
296            y: self.border.top_left.y,
297            width: self.border.width(),
298            height: self.border.height(),
299            scale: 1.0,
300        }
301    }
302
303    /// Create a `Viewport` equal to the margin-box, using a scale of 1.0
304    pub fn margin_viewport(&self) -> Viewport {
305        Viewport {
306            x: self.margin.top_left.x,
307            y: self.margin.top_left.y,
308            width: self.margin.width(),
309            height: self.margin.height(),
310            scale: 1.0,
311        }
312    }
313}
314
315#[derive(Debug, Clone)]
316pub struct BoundingBox {
317    /// the x coordinate of the element in pixels.
318    pub x: f64,
319    /// the y coordinate of the element in pixels.
320    pub y: f64,
321    /// the width of the element in pixels.
322    pub width: f64,
323    /// the height of the element in pixels.
324    pub height: f64,
325}