Skip to main content

atmos/os_lib/
dom_range.rs

1//! W3C DOM Range / Selection 幾何・境界点純粋計算モジュール
2//!
3//! W3C DOM Level 2 Range および DOM Standard §5.2 に準拠した
4//! 境界点比較、折りたたみ、包含判定、Selection 状態管理の純粋ロジックを提供する。
5
6use core::cmp::Ordering;
7
8/// 比較方法(`Range.prototype.compareBoundaryPoints` 引数定数)
9pub const START_TO_START: u16 = 0;
10pub const START_TO_END: u16 = 1;
11pub const END_TO_END: u16 = 2;
12pub const END_TO_START: u16 = 3;
13
14/// 境界点(Boundary Point)。
15/// ノードのドキュメント順位置(`node_order`)と、ノード内オフセット(`offset`)。
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct BoundaryPoint {
18    /// ドキュメント順での一意な順序値(またはノードアリーナインデックス)
19    pub node_order: usize,
20    /// 子ノードインデックスまたはテキストオフセット
21    pub offset: usize,
22}
23
24impl BoundaryPoint {
25    pub const fn new(node_order: usize, offset: usize) -> Self {
26        Self { node_order, offset }
27    }
28
29    /// 2つの境界点の順序を比較する(純粋関数)。
30    /// 先にある点が `Less`、一致が `Equal`、後にある点が `Greater`。
31    pub fn compare(&self, other: &Self) -> Ordering {
32        match self.node_order.cmp(&other.node_order) {
33            Ordering::Equal => self.offset.cmp(&other.offset),
34            ord => ord,
35        }
36    }
37}
38
39/// Range の状態データ(純粋構造体)
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct RangeData {
42    pub start: BoundaryPoint,
43    pub end: BoundaryPoint,
44}
45
46impl RangeData {
47    pub const fn new(start: BoundaryPoint, end: BoundaryPoint) -> Self {
48        Self { start, end }
49    }
50
51    /// 始点と終点が一致している(折りたたまれている)か
52    pub fn is_collapsed(&self) -> bool {
53        self.start == self.end
54    }
55
56    /// Range を折りたたむ。
57    /// `to_start == true` なら終点を始点に揃える。
58    /// `to_start == false` なら始点を終点に揃える。
59    pub fn collapse(&mut self, to_start: bool) {
60        if to_start {
61            self.end = self.start;
62        } else {
63            self.start = self.end;
64        }
65    }
66
67    /// 始点を設定する。
68    /// W3C 仕様: 新しい始点が現在の終点より後にある場合、終点も新しい始点に揃えて折りたたむ。
69    pub fn set_start(&mut self, point: BoundaryPoint) {
70        self.start = point;
71        if self.start.compare(&self.end) == Ordering::Greater {
72            self.end = self.start;
73        }
74    }
75
76    /// 終点を設定する。
77    /// W3C 仕様: 新しい終点が現在の始点より前にある場合、始点も新しい終点に揃えて折りたたむ。
78    pub fn set_end(&mut self, point: BoundaryPoint) {
79        self.end = point;
80        if self.end.compare(&self.start) == Ordering::Less {
81            self.start = self.end;
82        }
83    }
84
85    /// `compareBoundaryPoints` の計算。
86    /// `how` に応じて比較対象を選び、this が前なら -1、等しければ 0、後なら 1 を返す。
87    pub fn compare_boundary_points(&self, how: u16, source: &Self) -> Result<i16, &'static str> {
88        let (this_pt, source_pt) = match how {
89            START_TO_START => (self.start, source.start),
90            START_TO_END => (self.end, source.start),
91            END_TO_END => (self.end, source.end),
92            END_TO_START => (self.start, source.end),
93            _ => return Err("Invalid compare how type"),
94        };
95        Ok(match this_pt.compare(&source_pt) {
96            Ordering::Less => -1,
97            Ordering::Equal => 0,
98            Ordering::Greater => 1,
99        })
100    }
101
102    /// 点 `point` が Range に含まれているか判定(`isPointInRange`)。
103    /// `start <= point && point <= end`
104    pub fn is_point_in_range(&self, point: BoundaryPoint) -> bool {
105        self.start.compare(&point) != Ordering::Greater
106            && point.compare(&self.end) != Ordering::Greater
107    }
108
109    /// 別の範囲 `(other_start, other_end)` と交差しているか判定(`intersectsNode` 等)。
110    pub fn intersects(&self, other_start: BoundaryPoint, other_end: BoundaryPoint) -> bool {
111        // other_end < self.start || other_start > self.end でなければ交差
112        other_end.compare(&self.start) != Ordering::Less
113            && other_start.compare(&self.end) != Ordering::Greater
114    }
115}
116
117/// Selection の種別
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum SelectionType {
120    None,
121    Caret,
122    Range,
123}
124
125impl SelectionType {
126    pub fn as_str(&self) -> &'static str {
127        match self {
128            SelectionType::None => "None",
129            SelectionType::Caret => "Caret",
130            SelectionType::Range => "Range",
131        }
132    }
133}
134
135/// Selection の純粋状態管理
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
137pub struct SelectionState {
138    pub range: Option<RangeData>,
139}
140
141impl SelectionState {
142    pub fn new() -> Self {
143        Self { range: None }
144    }
145
146    pub fn range_count(&self) -> usize {
147        if self.range.is_some() { 1 } else { 0 }
148    }
149
150    pub fn is_collapsed(&self) -> bool {
151        self.range.map(|r| r.is_collapsed()).unwrap_or(true)
152    }
153
154    pub fn selection_type(&self) -> SelectionType {
155        match self.range {
156            None => SelectionType::None,
157            Some(r) if r.is_collapsed() => SelectionType::Caret,
158            Some(_) => SelectionType::Range,
159        }
160    }
161
162    pub fn anchor_point(&self) -> Option<BoundaryPoint> {
163        self.range.map(|r| r.start)
164    }
165
166    pub fn focus_point(&self) -> Option<BoundaryPoint> {
167        self.range.map(|r| r.end)
168    }
169
170    pub fn add_range(&mut self, range: RangeData) {
171        self.range = Some(range);
172    }
173
174    pub fn remove_range(&mut self) {
175        self.range = None;
176    }
177
178    pub fn remove_all_ranges(&mut self) {
179        self.range = None;
180    }
181
182    pub fn collapse(&mut self, point: BoundaryPoint) {
183        self.range = Some(RangeData::new(point, point));
184    }
185
186    pub fn collapse_to_start(&mut self) {
187        if let Some(ref mut r) = self.range {
188            r.collapse(true);
189        }
190    }
191
192    pub fn collapse_to_end(&mut self) {
193        if let Some(ref mut r) = self.range {
194            r.collapse(false);
195        }
196    }
197}