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