Skip to main content

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