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    /// 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, 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        assert_eq!(quad.inner().len(), 8);
111        let raw_quad = quad.inner();
112        Self {
113            top_left: Point {
114                x: raw_quad[0],
115                y: raw_quad[1],
116            },
117            top_right: Point {
118                x: raw_quad[2],
119                y: raw_quad[3],
120            },
121            bottom_right: Point {
122                x: raw_quad[4],
123                y: raw_quad[5],
124            },
125            bottom_left: Point {
126                x: raw_quad[6],
127                y: raw_quad[7],
128            },
129        }
130    }
131
132    pub fn quad_center(&self) -> Point {
133        Point {
134            x: (self.top_left.x + self.top_right.x + self.bottom_right.x + self.bottom_left.x) / 4.,
135            y: (self.top_left.y + self.top_right.y + self.bottom_right.y + self.bottom_left.y) / 4.,
136        }
137    }
138    /// Compute sum of all directed areas of adjacent triangles
139    /// https://en.wikipedia.org/wiki/Polygon#Simple_polygons
140    pub fn quad_area(&self) -> f64 {
141        let area = self.top_left.area(&self.top_right)
142            + self.top_right.area(&self.bottom_right)
143            + self.bottom_right.area(&self.bottom_left)
144            + self.bottom_left.area(&self.top_left);
145        area.abs()
146    }
147
148    /// Get the height of the shape based on the vertical distance
149    /// between the top-left and bottom-left points.
150    pub fn height(&self) -> f64 {
151        self.bottom_left.y - self.top_left.y
152    }
153
154    /// Get the width of the shape based on the horizontal distance
155    /// between the top-left and top-right points.
156    pub fn width(&self) -> f64 {
157        self.top_right.x - self.top_left.x
158    }
159
160    /// The width divided by the height
161    pub fn aspect_ratio(&self) -> f64 {
162        self.width() / self.height()
163    }
164
165    /// The most left (smallest) x-coordinate
166    pub fn most_left(&self) -> f64 {
167        self.top_right
168            .x
169            .min(self.top_left.x)
170            .min(self.bottom_right.x)
171            .min(self.bottom_left.x)
172    }
173
174    /// The most right (largest) x-coordinate
175    pub fn most_right(&self) -> f64 {
176        self.top_right
177            .x
178            .max(self.top_left.x)
179            .max(self.bottom_right.x)
180            .max(self.bottom_left.x)
181    }
182
183    /// The most top (smallest) y-coordinate
184    pub fn most_top(&self) -> f64 {
185        self.top_right
186            .y
187            .min(self.top_left.y)
188            .min(self.bottom_right.y)
189            .min(self.bottom_left.y)
190    }
191
192    /// The most bottom (largest) y-coordinate
193    pub fn most_bottom(&self) -> f64 {
194        self.top_right
195            .y
196            .max(self.top_left.y)
197            .max(self.bottom_right.y)
198            .max(self.bottom_left.y)
199    }
200
201    /// If the most bottom point of `self` is above the most top point of
202    /// `other`
203    pub fn strictly_above(&self, other: &Self) -> bool {
204        self.most_bottom() < other.most_top()
205    }
206
207    /// If the most bottom point of `self` is above or on the same line as the
208    /// most top point of `other`
209    pub fn above(&self, other: &Self) -> bool {
210        self.most_bottom() <= other.most_top()
211    }
212
213    /// If the most top point of `self` is below the most bottom point of
214    /// `other`
215    pub fn strictly_below(&self, other: &Self) -> bool {
216        self.most_top() > other.most_bottom()
217    }
218
219    /// If the most top point of `self` is below or on the same line as the
220    /// most bottom point of `other`
221    pub fn below(&self, other: &Self) -> bool {
222        self.most_top() >= other.most_bottom()
223    }
224
225    /// If the most right point of `self` is left of the most left point of
226    /// `other`
227    pub fn strictly_left_of(&self, other: &Self) -> bool {
228        self.most_right() < other.most_left()
229    }
230
231    /// If the most right point of `self` is left or on the same line as the
232    /// most left point of `other`
233    pub fn left_of(&self, other: &Self) -> bool {
234        self.most_right() <= other.most_left()
235    }
236
237    /// If the most left point of `self` is right of the most right point of
238    /// `other`
239    pub fn strictly_right_of(&self, other: &Self) -> bool {
240        self.most_left() > other.most_right()
241    }
242
243    /// If the most left point of `self` is right or on the same line as the
244    /// most right point of `other`
245    pub fn right_of(&self, other: &Self) -> bool {
246        self.most_left() >= other.most_right()
247    }
248
249    /// If `self` is within the left/right boundaries defined by `other`.
250    pub fn within_horizontal_bounds_of(&self, other: &Self) -> bool {
251        self.most_left() >= other.most_left() && self.most_right() <= other.most_right()
252    }
253
254    /// If `self` is within the top/bottom boundaries defined by `other`.
255    pub fn within_vertical_bounds_of(&self, other: &Self) -> bool {
256        self.most_top() >= other.most_top() && self.most_bottom() <= other.most_bottom()
257    }
258
259    /// If `self` is within the boundaries defined by `other`.
260    pub fn within_bounds_of(&self, other: &Self) -> bool {
261        self.within_horizontal_bounds_of(other) && self.within_vertical_bounds_of(other)
262    }
263}
264
265#[derive(Debug, Clone)]
266pub struct BoxModel {
267    /// Content area quad.
268    pub content: ElementQuad,
269    /// Padding area quad.
270    pub padding: ElementQuad,
271    /// Border area quad.
272    pub border: ElementQuad,
273    /// Margin area quad.
274    pub margin: ElementQuad,
275    /// Width of the element.
276    pub width: u32,
277    /// Height of the element.
278    pub height: u32,
279}
280
281impl BoxModel {
282    /// Create a `Viewport` equal to the content-box, using a scale of 1.0
283    pub fn content_viewport(&self) -> Viewport {
284        Viewport {
285            x: self.content.top_left.x,
286            y: self.content.top_left.y,
287            width: self.content.width(),
288            height: self.content.height(),
289            scale: 1.0,
290        }
291    }
292
293    /// Create a `Viewport` equal to the padding-box, using a scale of 1.0
294    pub fn padding_viewport(&self) -> Viewport {
295        Viewport {
296            x: self.padding.top_left.x,
297            y: self.padding.top_left.y,
298            width: self.padding.width(),
299            height: self.padding.height(),
300            scale: 1.0,
301        }
302    }
303
304    /// Create a `Viewport` equal to the border-box, using a scale of 1.0
305    pub fn border_viewport(&self) -> Viewport {
306        Viewport {
307            x: self.border.top_left.x,
308            y: self.border.top_left.y,
309            width: self.border.width(),
310            height: self.border.height(),
311            scale: 1.0,
312        }
313    }
314
315    /// Create a `Viewport` equal to the margin-box, using a scale of 1.0
316    pub fn margin_viewport(&self) -> Viewport {
317        Viewport {
318            x: self.margin.top_left.x,
319            y: self.margin.top_left.y,
320            width: self.margin.width(),
321            height: self.margin.height(),
322            scale: 1.0,
323        }
324    }
325}
326
327#[derive(Debug, Clone)]
328pub struct BoundingBox {
329    /// the x coordinate of the element in pixels.
330    pub x: f64,
331    /// the y coordinate of the element in pixels.
332    pub y: f64,
333    /// the width of the element in pixels.
334    pub width: f64,
335    /// the height of the element in pixels.
336    pub height: f64,
337}