Skip to main content

engine_observables_api/
types.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Cross-cutting supporting types shared across the four query
6//! traits. Kept minimal — promote types here only when more than one
7//! trait module needs them.
8
9use malloc_size_of_derive::MallocSizeOf;
10use serde::{Deserialize, Serialize};
11
12/// Opaque per-engine node identity. Lanes choose how to mint these;
13/// consumers only compare for equality and use them as map keys.
14///
15/// The wire shape is `u64` so consumers can serialize hits + selection
16/// ranges across IPC without needing a generic NodeId parameter.
17#[derive(
18    Clone, Copy, Debug, Default, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize,
19)]
20pub struct SourceNodeId(pub u64);
21
22/// Half-open `[start, end)` byte-offset range into the lane's source
23/// text. Returned by `text_range_for_fragment` and consumed by
24/// `rects_for_selection`. Byte offsets, not chars or grapheme
25/// clusters, because that's what source/edit machinery needs.
26#[derive(
27    Clone, Copy, Debug, Default, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize,
28)]
29pub struct SourceRange {
30    pub start: usize,
31    pub end: usize,
32}
33
34impl SourceRange {
35    pub fn new(start: usize, end: usize) -> Self {
36        Self { start, end }
37    }
38
39    pub fn is_empty(&self) -> bool {
40        self.end <= self.start
41    }
42
43    pub fn len(&self) -> usize {
44        self.end.saturating_sub(self.start)
45    }
46}
47
48/// Viewport-space point (CSS pixels, post-transform). Lane impls
49/// translate from their device/layout coordinate space when needed.
50#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
51pub struct Point {
52    pub x: f32,
53    pub y: f32,
54}
55
56impl Point {
57    pub fn new(x: f32, y: f32) -> Self {
58        Self { x, y }
59    }
60}
61
62/// Viewport-space rectangle (CSS pixels). Origin-and-size shape; we
63/// avoid pulling in `euclid` here because consumers may want to use a
64/// different geometry crate.
65#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
66pub struct Rect {
67    pub origin: Point,
68    pub width: f32,
69    pub height: f32,
70}
71
72impl Rect {
73    pub fn new(origin: Point, width: f32, height: f32) -> Self {
74        Self {
75            origin,
76            width,
77            height,
78        }
79    }
80
81    pub fn contains(&self, point: Point) -> bool {
82        point.x >= self.origin.x
83            && point.x < self.origin.x + self.width
84            && point.y >= self.origin.y
85            && point.y < self.origin.y + self.height
86    }
87}
88
89/// BCP 47 language tag (e.g., "en", "en-US", "zh-Hant"). Owned String
90/// because language tags don't have a single shared registry crate we
91/// want to force; consumers can `Lang::from(s.to_string())` cheaply.
92#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
93pub struct Lang(pub String);
94
95impl Lang {
96    pub fn new(tag: impl Into<String>) -> Self {
97        Self(tag.into())
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104
105    #[test]
106    fn rect_contains_inclusive_top_left_exclusive_bottom_right() {
107        let r = Rect::new(Point::new(10.0, 10.0), 100.0, 50.0);
108        assert!(r.contains(Point::new(10.0, 10.0)));
109        assert!(r.contains(Point::new(50.0, 30.0)));
110        assert!(!r.contains(Point::new(110.0, 10.0))); // right edge exclusive
111        assert!(!r.contains(Point::new(50.0, 60.0))); // bottom edge exclusive
112        assert!(!r.contains(Point::new(9.9, 30.0)));
113    }
114
115    #[test]
116    fn source_range_len_and_empty() {
117        assert_eq!(SourceRange::new(5, 10).len(), 5);
118        assert!(SourceRange::new(5, 5).is_empty());
119        assert!(SourceRange::new(10, 5).is_empty());
120    }
121
122    #[test]
123    fn source_node_id_round_trips_through_serde() {
124        let id = SourceNodeId(0xCAFE_BABE);
125        let json = serde_json::to_string(&id).unwrap();
126        let back: SourceNodeId = serde_json::from_str(&json).unwrap();
127        assert_eq!(back, id);
128    }
129}