1use epaint::text::{Galley, cursor::CCursor};
4use unicode_segmentation::UnicodeSegmentation as _;
5
6use crate::{NumExt as _, Rect, Response, Ui, epaint};
7
8use super::CCursorRange;
9
10#[derive(Clone, Copy, Debug, Default)]
14#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
15#[cfg_attr(feature = "serde", serde(default))]
16pub struct TextCursorState {
17 ccursor_range: Option<CCursorRange>,
18}
19
20impl From<CCursorRange> for TextCursorState {
21 fn from(ccursor_range: CCursorRange) -> Self {
22 Self {
23 ccursor_range: Some(ccursor_range),
24 }
25 }
26}
27
28impl TextCursorState {
29 pub fn is_empty(&self) -> bool {
30 self.ccursor_range.is_none()
31 }
32
33 pub fn char_range(&self) -> Option<CCursorRange> {
35 self.ccursor_range
36 }
37
38 pub fn range(&self, galley: &Galley) -> Option<CCursorRange> {
41 self.ccursor_range.map(|mut range| {
42 range.primary = galley.clamp_cursor(&range.primary);
43 range.secondary = galley.clamp_cursor(&range.secondary);
44 range
45 })
46 }
47
48 pub fn set_char_range(&mut self, ccursor_range: Option<CCursorRange>) {
50 self.ccursor_range = ccursor_range;
51 }
52}
53
54impl TextCursorState {
55 pub fn pointer_interaction(
59 &mut self,
60 ui: &Ui,
61 response: &Response,
62 cursor_at_pointer: CCursor,
63 galley: &Galley,
64 is_being_dragged: bool,
65 ) -> bool {
66 let text = galley.text();
67
68 if response.double_clicked() {
69 let ccursor_range = select_word_at(text, cursor_at_pointer);
71 self.set_char_range(Some(ccursor_range));
72 true
73 } else if response.triple_clicked() {
74 let ccursor_range = select_line_at(text, cursor_at_pointer);
76 self.set_char_range(Some(ccursor_range));
77 true
78 } else if response.sense.senses_drag() {
79 if response.hovered() && ui.input(|i| i.pointer.any_pressed()) {
80 if ui.input(|i| i.modifiers.shift) {
82 if let Some(mut cursor_range) = self.range(galley) {
83 cursor_range.primary = cursor_at_pointer;
84 self.set_char_range(Some(cursor_range));
85 } else {
86 self.set_char_range(Some(CCursorRange::one(cursor_at_pointer)));
87 }
88 } else {
89 self.set_char_range(Some(CCursorRange::one(cursor_at_pointer)));
90 }
91 true
92 } else if is_being_dragged {
93 if let Some(mut cursor_range) = self.range(galley) {
95 cursor_range.primary = cursor_at_pointer;
96 self.set_char_range(Some(cursor_range));
97 }
98 true
99 } else {
100 false
101 }
102 } else {
103 false
104 }
105 }
106}
107
108fn select_word_at(text: &str, ccursor: CCursor) -> CCursorRange {
109 if ccursor.index == 0 {
110 CCursorRange::two(ccursor, ccursor_next_word(text, ccursor))
111 } else {
112 let it = text.chars();
113 let mut it = it.skip(ccursor.index - 1);
114 if let Some(char_before_cursor) = it.next() {
115 if let Some(char_after_cursor) = it.next() {
116 if is_word_char(char_before_cursor) && is_word_char(char_after_cursor) {
117 let min = ccursor_previous_word(text, ccursor + 1);
118 let max = ccursor_next_word(text, min);
119 CCursorRange::two(min, max)
120 } else if is_word_char(char_before_cursor) {
121 let min = ccursor_previous_word(text, ccursor);
122 let max = ccursor_next_word(text, min);
123 CCursorRange::two(min, max)
124 } else if is_word_char(char_after_cursor) {
125 let max = ccursor_next_word(text, ccursor);
126 CCursorRange::two(ccursor, max)
127 } else {
128 let min = ccursor_previous_word(text, ccursor);
129 let max = ccursor_next_word(text, ccursor);
130 CCursorRange::two(min, max)
131 }
132 } else {
133 let min = ccursor_previous_word(text, ccursor);
134 CCursorRange::two(min, ccursor)
135 }
136 } else {
137 let max = ccursor_next_word(text, ccursor);
138 CCursorRange::two(ccursor, max)
139 }
140 }
141}
142
143fn select_line_at(text: &str, ccursor: CCursor) -> CCursorRange {
144 if ccursor.index == 0 {
145 CCursorRange::two(ccursor, ccursor_next_line(text, ccursor))
146 } else {
147 let it = text.chars();
148 let mut it = it.skip(ccursor.index - 1);
149 if let Some(char_before_cursor) = it.next() {
150 if let Some(char_after_cursor) = it.next() {
151 if (!is_linebreak(char_before_cursor)) && (!is_linebreak(char_after_cursor)) {
152 let min = ccursor_previous_line(text, ccursor + 1);
153 let max = ccursor_next_line(text, min);
154 CCursorRange::two(min, max)
155 } else if !is_linebreak(char_before_cursor) {
156 let min = ccursor_previous_line(text, ccursor);
157 let max = ccursor_next_line(text, min);
158 CCursorRange::two(min, max)
159 } else if !is_linebreak(char_after_cursor) {
160 let max = ccursor_next_line(text, ccursor);
161 CCursorRange::two(ccursor, max)
162 } else {
163 let min = ccursor_previous_line(text, ccursor);
164 let max = ccursor_next_line(text, ccursor);
165 CCursorRange::two(min, max)
166 }
167 } else {
168 let min = ccursor_previous_line(text, ccursor);
169 CCursorRange::two(min, ccursor)
170 }
171 } else {
172 let max = ccursor_next_line(text, ccursor);
173 CCursorRange::two(ccursor, max)
174 }
175 }
176}
177
178pub fn ccursor_next_word(text: &str, ccursor: CCursor) -> CCursor {
179 CCursor {
180 index: next_word_boundary_char_index(text, ccursor.index),
181 prefer_next_row: false,
182 }
183}
184
185fn ccursor_next_line(text: &str, ccursor: CCursor) -> CCursor {
186 CCursor {
187 index: next_line_boundary_char_index(text.chars(), ccursor.index),
188 prefer_next_row: false,
189 }
190}
191
192pub fn ccursor_previous_word(text: &str, ccursor: CCursor) -> CCursor {
193 let num_chars = text.chars().count();
194 let reversed: String = text.graphemes(true).rev().collect();
195 CCursor {
196 index: num_chars
197 - next_word_boundary_char_index(&reversed, num_chars - ccursor.index).min(num_chars),
198 prefer_next_row: true,
199 }
200}
201
202fn ccursor_previous_line(text: &str, ccursor: CCursor) -> CCursor {
203 let num_chars = text.chars().count();
204 CCursor {
205 index: num_chars
206 - next_line_boundary_char_index(text.chars().rev(), num_chars - ccursor.index),
207 prefer_next_row: true,
208 }
209}
210
211fn next_word_boundary_char_index(text: &str, cursor_ci: usize) -> usize {
212 for (word_byte_index, word) in text.split_word_bound_indices() {
213 let word_ci = char_index_from_byte_index(text, word_byte_index);
214
215 for (dot_ci_offset, chr) in word.chars().enumerate() {
218 let dot_ci = word_ci + dot_ci_offset;
219 if chr == '.' && cursor_ci < dot_ci {
220 return dot_ci;
221 }
222 }
223
224 if cursor_ci < word_ci && !all_word_chars(word) {
229 return word_ci;
230 }
231 }
232
233 char_index_from_byte_index(text, text.len())
234}
235
236fn all_word_chars(text: &str) -> bool {
237 text.chars().all(is_word_char)
238}
239
240fn next_line_boundary_char_index(it: impl Iterator<Item = char>, mut index: usize) -> usize {
241 let mut it = it.skip(index);
242 if let Some(_first) = it.next() {
243 index += 1;
244
245 if let Some(second) = it.next() {
246 index += 1;
247 for next in it {
248 if is_linebreak(next) != is_linebreak(second) {
249 break;
250 }
251 index += 1;
252 }
253 }
254 }
255 index
256}
257
258pub fn is_word_char(c: char) -> bool {
259 c.is_alphanumeric() || c == '_'
260}
261
262fn is_linebreak(c: char) -> bool {
263 c == '\r' || c == '\n'
264}
265
266pub fn find_line_start(text: &str, current_index: CCursor) -> CCursor {
268 let chars_count = text.chars().count();
274
275 let position = text
276 .chars()
277 .rev()
278 .skip(chars_count - current_index.index)
279 .position(|x| x == '\n');
280
281 match position {
282 Some(pos) => CCursor::new(current_index.index - pos),
283 None => CCursor::new(0),
284 }
285}
286
287pub fn byte_index_from_char_index(s: &str, char_index: usize) -> usize {
288 for (ci, (bi, _)) in s.char_indices().enumerate() {
289 if ci == char_index {
290 return bi;
291 }
292 }
293 s.len()
294}
295
296pub fn char_index_from_byte_index(input: &str, byte_index: usize) -> usize {
297 for (ci, (bi, _)) in input.char_indices().enumerate() {
298 if bi == byte_index {
299 return ci;
300 }
301 }
302
303 input.char_indices().last().map_or(0, |(i, _)| i + 1)
304}
305
306pub fn slice_char_range(s: &str, char_range: std::ops::Range<usize>) -> &str {
307 assert!(
308 char_range.start <= char_range.end,
309 "Invalid range, start must be less than end, but start = {}, end = {}",
310 char_range.start,
311 char_range.end
312 );
313 let start_byte = byte_index_from_char_index(s, char_range.start);
314 let end_byte = byte_index_from_char_index(s, char_range.end);
315 &s[start_byte..end_byte]
316}
317
318pub fn cursor_rect(galley: &Galley, cursor: &CCursor, row_height: f32) -> Rect {
320 let mut cursor_pos = galley.pos_from_cursor(*cursor);
321
322 cursor_pos.max.y = cursor_pos.max.y.at_least(cursor_pos.min.y + row_height);
324
325 cursor_pos = cursor_pos.expand(1.5); cursor_pos
328}
329
330#[cfg(test)]
331mod test {
332 use crate::text_selection::text_cursor_state::next_word_boundary_char_index;
333
334 #[test]
335 fn test_next_word_boundary_char_index() {
336 let text = "abc d3f g_h i-j";
338 assert_eq!(next_word_boundary_char_index(text, 1), 3);
339 assert_eq!(next_word_boundary_char_index(text, 3), 7);
340 assert_eq!(next_word_boundary_char_index(text, 9), 11);
341 assert_eq!(next_word_boundary_char_index(text, 12), 13);
342 assert_eq!(next_word_boundary_char_index(text, 13), 15);
343 assert_eq!(next_word_boundary_char_index(text, 15), 15);
344
345 assert_eq!(next_word_boundary_char_index("", 0), 0);
346 assert_eq!(next_word_boundary_char_index("", 1), 0);
347
348 let text = "abc.def.ghi";
350 assert_eq!(next_word_boundary_char_index(text, 1), 3);
351 assert_eq!(next_word_boundary_char_index(text, 3), 7);
352 assert_eq!(next_word_boundary_char_index(text, 7), 11);
353
354 let text = "❤️👍 skvělá knihovna 👍❤️";
360 assert_eq!(next_word_boundary_char_index(text, 0), 2);
361 assert_eq!(next_word_boundary_char_index(text, 2), 3); assert_eq!(next_word_boundary_char_index(text, 6), 10);
363 assert_eq!(next_word_boundary_char_index(text, 9), 10);
364 assert_eq!(next_word_boundary_char_index(text, 12), 19);
365 assert_eq!(next_word_boundary_char_index(text, 15), 19);
366 assert_eq!(next_word_boundary_char_index(text, 19), 20);
367 assert_eq!(next_word_boundary_char_index(text, 20), 21);
368 }
369}