1use epaint::text::{
4 cursor::{CCursor, Cursor},
5 Galley,
6};
7
8use crate::{epaint, NumExt, Rect, Response, Ui};
9
10use super::{CCursorRange, CursorRange};
11
12#[derive(Clone, Copy, Debug, Default)]
16#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
17#[cfg_attr(feature = "serde", serde(default))]
18pub struct TextCursorState {
19 cursor_range: Option<CursorRange>,
20
21 ccursor_range: Option<CCursorRange>,
24}
25
26impl From<CursorRange> for TextCursorState {
27 fn from(cursor_range: CursorRange) -> Self {
28 Self {
29 cursor_range: Some(cursor_range),
30 ccursor_range: Some(CCursorRange {
31 primary: cursor_range.primary.ccursor,
32 secondary: cursor_range.secondary.ccursor,
33 }),
34 }
35 }
36}
37
38impl From<CCursorRange> for TextCursorState {
39 fn from(ccursor_range: CCursorRange) -> Self {
40 Self {
41 cursor_range: None,
42 ccursor_range: Some(ccursor_range),
43 }
44 }
45}
46
47impl TextCursorState {
48 pub fn is_empty(&self) -> bool {
49 self.cursor_range.is_none() && self.ccursor_range.is_none()
50 }
51
52 pub fn char_range(&self) -> Option<CCursorRange> {
54 self.ccursor_range.or_else(|| {
55 self.cursor_range
56 .map(|cursor_range| cursor_range.as_ccursor_range())
57 })
58 }
59
60 pub fn range(&self, galley: &Galley) -> Option<CursorRange> {
61 self.cursor_range
62 .map(|cursor_range| {
63 CursorRange {
71 primary: galley.from_pcursor(cursor_range.primary.pcursor),
72 secondary: galley.from_pcursor(cursor_range.secondary.pcursor),
73 }
74 })
75 .or_else(|| {
76 self.ccursor_range.map(|ccursor_range| CursorRange {
77 primary: galley.from_ccursor(ccursor_range.primary),
78 secondary: galley.from_ccursor(ccursor_range.secondary),
79 })
80 })
81 }
82
83 pub fn set_char_range(&mut self, ccursor_range: Option<CCursorRange>) {
85 self.cursor_range = None;
86 self.ccursor_range = ccursor_range;
87 }
88
89 pub fn set_range(&mut self, cursor_range: Option<CursorRange>) {
90 self.cursor_range = cursor_range;
91 self.ccursor_range = None;
92 }
93}
94
95impl TextCursorState {
96 pub fn pointer_interaction(
100 &mut self,
101 ui: &Ui,
102 response: &Response,
103 cursor_at_pointer: Cursor,
104 galley: &Galley,
105 is_being_dragged: bool,
106 ) -> bool {
107 let text = galley.text();
108
109 if response.double_clicked() {
110 let ccursor_range = select_word_at(text, cursor_at_pointer.ccursor);
112 self.set_range(Some(CursorRange {
113 primary: galley.from_ccursor(ccursor_range.primary),
114 secondary: galley.from_ccursor(ccursor_range.secondary),
115 }));
116 true
117 } else if response.triple_clicked() {
118 let ccursor_range = select_line_at(text, cursor_at_pointer.ccursor);
120 self.set_range(Some(CursorRange {
121 primary: galley.from_ccursor(ccursor_range.primary),
122 secondary: galley.from_ccursor(ccursor_range.secondary),
123 }));
124 true
125 } else if response.sense.senses_drag() {
126 if response.hovered() && ui.input(|i| i.pointer.any_pressed()) {
127 if ui.input(|i| i.modifiers.shift) {
129 if let Some(mut cursor_range) = self.range(galley) {
130 cursor_range.primary = cursor_at_pointer;
131 self.set_range(Some(cursor_range));
132 } else {
133 self.set_range(Some(CursorRange::one(cursor_at_pointer)));
134 }
135 } else {
136 self.set_range(Some(CursorRange::one(cursor_at_pointer)));
137 }
138 true
139 } else if is_being_dragged {
140 if let Some(mut cursor_range) = self.range(galley) {
142 cursor_range.primary = cursor_at_pointer;
143 self.set_range(Some(cursor_range));
144 }
145 true
146 } else {
147 false
148 }
149 } else {
150 false
151 }
152 }
153}
154
155fn select_word_at(text: &str, ccursor: CCursor) -> CCursorRange {
156 if ccursor.index == 0 {
157 CCursorRange::two(ccursor, ccursor_next_word(text, ccursor))
158 } else {
159 let it = text.chars();
160 let mut it = it.skip(ccursor.index - 1);
161 if let Some(char_before_cursor) = it.next() {
162 if let Some(char_after_cursor) = it.next() {
163 if is_word_char(char_before_cursor) && is_word_char(char_after_cursor) {
164 let min = ccursor_previous_word(text, ccursor + 1);
165 let max = ccursor_next_word(text, min);
166 CCursorRange::two(min, max)
167 } else if is_word_char(char_before_cursor) {
168 let min = ccursor_previous_word(text, ccursor);
169 let max = ccursor_next_word(text, min);
170 CCursorRange::two(min, max)
171 } else if is_word_char(char_after_cursor) {
172 let max = ccursor_next_word(text, ccursor);
173 CCursorRange::two(ccursor, max)
174 } else {
175 let min = ccursor_previous_word(text, ccursor);
176 let max = ccursor_next_word(text, ccursor);
177 CCursorRange::two(min, max)
178 }
179 } else {
180 let min = ccursor_previous_word(text, ccursor);
181 CCursorRange::two(min, ccursor)
182 }
183 } else {
184 let max = ccursor_next_word(text, ccursor);
185 CCursorRange::two(ccursor, max)
186 }
187 }
188}
189
190fn select_line_at(text: &str, ccursor: CCursor) -> CCursorRange {
191 if ccursor.index == 0 {
192 CCursorRange::two(ccursor, ccursor_next_line(text, ccursor))
193 } else {
194 let it = text.chars();
195 let mut it = it.skip(ccursor.index - 1);
196 if let Some(char_before_cursor) = it.next() {
197 if let Some(char_after_cursor) = it.next() {
198 if (!is_linebreak(char_before_cursor)) && (!is_linebreak(char_after_cursor)) {
199 let min = ccursor_previous_line(text, ccursor + 1);
200 let max = ccursor_next_line(text, min);
201 CCursorRange::two(min, max)
202 } else if !is_linebreak(char_before_cursor) {
203 let min = ccursor_previous_line(text, ccursor);
204 let max = ccursor_next_line(text, min);
205 CCursorRange::two(min, max)
206 } else if !is_linebreak(char_after_cursor) {
207 let max = ccursor_next_line(text, ccursor);
208 CCursorRange::two(ccursor, max)
209 } else {
210 let min = ccursor_previous_line(text, ccursor);
211 let max = ccursor_next_line(text, ccursor);
212 CCursorRange::two(min, max)
213 }
214 } else {
215 let min = ccursor_previous_line(text, ccursor);
216 CCursorRange::two(min, ccursor)
217 }
218 } else {
219 let max = ccursor_next_line(text, ccursor);
220 CCursorRange::two(ccursor, max)
221 }
222 }
223}
224
225pub fn ccursor_next_word(text: &str, ccursor: CCursor) -> CCursor {
226 CCursor {
227 index: next_word_boundary_char_index(text.chars(), ccursor.index),
228 prefer_next_row: false,
229 }
230}
231
232fn ccursor_next_line(text: &str, ccursor: CCursor) -> CCursor {
233 CCursor {
234 index: next_line_boundary_char_index(text.chars(), ccursor.index),
235 prefer_next_row: false,
236 }
237}
238
239pub fn ccursor_previous_word(text: &str, ccursor: CCursor) -> CCursor {
240 let num_chars = text.chars().count();
241 CCursor {
242 index: num_chars
243 - next_word_boundary_char_index(text.chars().rev(), num_chars - ccursor.index),
244 prefer_next_row: true,
245 }
246}
247
248fn ccursor_previous_line(text: &str, ccursor: CCursor) -> CCursor {
249 let num_chars = text.chars().count();
250 CCursor {
251 index: num_chars
252 - next_line_boundary_char_index(text.chars().rev(), num_chars - ccursor.index),
253 prefer_next_row: true,
254 }
255}
256
257fn next_word_boundary_char_index(it: impl Iterator<Item = char>, mut index: usize) -> usize {
258 let mut it = it.skip(index);
259 if let Some(_first) = it.next() {
260 index += 1;
261
262 if let Some(second) = it.next() {
263 index += 1;
264 for next in it {
265 if is_word_char(next) != is_word_char(second) {
266 break;
267 }
268 index += 1;
269 }
270 }
271 }
272 index
273}
274
275fn next_line_boundary_char_index(it: impl Iterator<Item = char>, mut index: usize) -> usize {
276 let mut it = it.skip(index);
277 if let Some(_first) = it.next() {
278 index += 1;
279
280 if let Some(second) = it.next() {
281 index += 1;
282 for next in it {
283 if is_linebreak(next) != is_linebreak(second) {
284 break;
285 }
286 index += 1;
287 }
288 }
289 }
290 index
291}
292
293pub fn is_word_char(c: char) -> bool {
294 c.is_ascii_alphanumeric() || c == '_'
295}
296
297fn is_linebreak(c: char) -> bool {
298 c == '\r' || c == '\n'
299}
300
301pub fn find_line_start(text: &str, current_index: CCursor) -> CCursor {
303 let chars_count = text.chars().count();
309
310 let position = text
311 .chars()
312 .rev()
313 .skip(chars_count - current_index.index)
314 .position(|x| x == '\n');
315
316 match position {
317 Some(pos) => CCursor::new(current_index.index - pos),
318 None => CCursor::new(0),
319 }
320}
321
322pub fn byte_index_from_char_index(s: &str, char_index: usize) -> usize {
323 for (ci, (bi, _)) in s.char_indices().enumerate() {
324 if ci == char_index {
325 return bi;
326 }
327 }
328 s.len()
329}
330
331pub fn slice_char_range(s: &str, char_range: std::ops::Range<usize>) -> &str {
332 assert!(char_range.start <= char_range.end);
333 let start_byte = byte_index_from_char_index(s, char_range.start);
334 let end_byte = byte_index_from_char_index(s, char_range.end);
335 &s[start_byte..end_byte]
336}
337
338pub fn cursor_rect(galley: &Galley, cursor: &Cursor, row_height: f32) -> Rect {
340 let mut cursor_pos = galley.pos_from_cursor(cursor);
341
342 cursor_pos.max.y = cursor_pos.max.y.at_least(cursor_pos.min.y + row_height);
344
345 cursor_pos = cursor_pos.expand(1.5); cursor_pos
348}