atmos/os_lib/web_engine/
selection.rs1extern crate alloc;
5use alloc::string::String;
6use alloc::vec::Vec;
7use core::cmp::Ordering;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct TextPosition {
12 pub element_index: usize,
14 pub char_offset: usize,
16}
17
18impl TextPosition {
19 pub const fn new(element_index: usize, char_offset: usize) -> Self {
20 Self {
21 element_index,
22 char_offset,
23 }
24 }
25
26 pub fn compare(&self, other: &Self) -> Ordering {
27 match self.element_index.cmp(&other.element_index) {
28 Ordering::Equal => self.char_offset.cmp(&other.char_offset),
29 ord => ord,
30 }
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct SelectionRange {
37 pub anchor: TextPosition,
38 pub focus: TextPosition,
39}
40
41impl SelectionRange {
42 pub const fn new(anchor: TextPosition, focus: TextPosition) -> Self {
43 Self { anchor, focus }
44 }
45
46 pub fn is_empty(&self) -> bool {
48 self.anchor == self.focus
49 }
50
51 pub fn normalized(&self) -> (TextPosition, TextPosition) {
53 if self.anchor.compare(&self.focus) == Ordering::Greater {
54 (self.focus, self.anchor)
55 } else {
56 (self.anchor, self.focus)
57 }
58 }
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct HighlightRect {
64 pub element_index: usize,
65 pub x: i32,
66 pub y: i32,
67 pub width: i32,
68 pub height: i32,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct TextElementLayout {
74 pub element_index: usize,
75 pub text: String,
76 pub x: i32,
77 pub y: i32,
78 pub width: i32,
79 pub height: i32,
80}
81
82pub fn hit_test_text_char(text: &str, width: i32, local_x: i32) -> usize {
84 if local_x <= 0 {
85 return 0;
86 }
87 let total_chars = text.chars().count();
88 if total_chars == 0 {
89 return 0;
90 }
91 if width <= 0 {
92 return 0;
93 }
94 if local_x >= width {
95 return total_chars;
96 }
97
98 let half_w = width as i64 / 2;
101 let idx = ((local_x as i64 * total_chars as i64 + half_w) / width as i64) as usize;
102 idx.min(total_chars)
103}
104
105pub fn compute_selection_highlights(
107 elements: &[TextElementLayout],
108 range: &SelectionRange,
109) -> Vec<HighlightRect> {
110 let mut out = Vec::new();
111 if range.is_empty() {
112 return out;
113 }
114
115 let (start, end) = range.normalized();
116
117 for el in elements {
118 if el.element_index < start.element_index || el.element_index > end.element_index {
119 continue;
120 }
121
122 let total_chars = el.text.chars().count();
123 if total_chars == 0 || el.width <= 0 {
124 continue;
125 }
126
127 let char_start = if el.element_index == start.element_index {
128 start.char_offset.min(total_chars)
129 } else {
130 0
131 };
132
133 let char_end = if el.element_index == end.element_index {
134 end.char_offset.min(total_chars)
135 } else {
136 total_chars
137 };
138
139 if char_start < char_end {
140 let hx = el.x + (el.width as i64 * char_start as i64 / total_chars as i64) as i32;
141 let hw = (el.width as i64 * (char_end - char_start) as i64 / total_chars as i64) as i32;
142 out.push(HighlightRect {
143 element_index: el.element_index,
144 x: hx,
145 y: el.y,
146 width: hw.max(1),
147 height: el.height,
148 });
149 }
150 }
151
152 out
153}
154
155pub fn extract_selected_text(
157 elements: &[TextElementLayout],
158 range: &SelectionRange,
159) -> String {
160 if range.is_empty() {
161 return String::new();
162 }
163
164 let (start, end) = range.normalized();
165 let mut parts = Vec::new();
166
167 for el in elements {
168 if el.element_index < start.element_index || el.element_index > end.element_index {
169 continue;
170 }
171
172 let total_chars = el.text.chars().count();
173 if total_chars == 0 {
174 continue;
175 }
176
177 let char_start = if el.element_index == start.element_index {
178 start.char_offset.min(total_chars)
179 } else {
180 0
181 };
182
183 let char_end = if el.element_index == end.element_index {
184 end.char_offset.min(total_chars)
185 } else {
186 total_chars
187 };
188
189 if char_start < char_end {
190 let sub: String = el
191 .text
192 .chars()
193 .skip(char_start)
194 .take(char_end - char_start)
195 .collect();
196 parts.push(sub);
197 }
198 }
199
200 parts.join("\n")
201}