egui/text_selection/
cursor_range.rs

1use epaint::{Galley, text::cursor::CCursor};
2
3use crate::{Event, Id, Key, Modifiers, os::OperatingSystem};
4
5use super::text_cursor_state::{ccursor_next_word, ccursor_previous_word, slice_char_range};
6
7/// A selected text range (could be a range of length zero).
8///
9/// The selection is based on character count (NOT byte count!).
10#[derive(Clone, Copy, Debug, Default, PartialEq)]
11#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
12pub struct CCursorRange {
13    /// When selecting with a mouse, this is where the mouse was released.
14    /// When moving with e.g. shift+arrows, this is what moves.
15    /// Note that the two ends can come in any order, and also be equal (no selection).
16    pub primary: CCursor,
17
18    /// When selecting with a mouse, this is where the mouse was first pressed.
19    /// This part of the cursor does not move when shift is down.
20    pub secondary: CCursor,
21
22    /// Saved horizontal position of the cursor.
23    pub h_pos: Option<f32>,
24}
25
26impl CCursorRange {
27    /// The empty range.
28    #[inline]
29    pub fn one(ccursor: CCursor) -> Self {
30        Self {
31            primary: ccursor,
32            secondary: ccursor,
33            h_pos: None,
34        }
35    }
36
37    #[inline]
38    pub fn two(min: impl Into<CCursor>, max: impl Into<CCursor>) -> Self {
39        Self {
40            primary: max.into(),
41            secondary: min.into(),
42            h_pos: None,
43        }
44    }
45
46    /// Select all the text in a galley
47    pub fn select_all(galley: &Galley) -> Self {
48        Self::two(galley.begin(), galley.end())
49    }
50
51    /// The range of selected character indices.
52    pub fn as_sorted_char_range(&self) -> std::ops::Range<usize> {
53        let [start, end] = self.sorted_cursors();
54        std::ops::Range {
55            start: start.index,
56            end: end.index,
57        }
58    }
59
60    /// True if the selected range contains no characters.
61    #[inline]
62    pub fn is_empty(&self) -> bool {
63        self.primary == self.secondary
64    }
65
66    /// Is `self` a super-set of the other range?
67    pub fn contains(&self, other: Self) -> bool {
68        let [self_min, self_max] = self.sorted_cursors();
69        let [other_min, other_max] = other.sorted_cursors();
70        self_min.index <= other_min.index && other_max.index <= self_max.index
71    }
72
73    /// If there is a selection, None is returned.
74    /// If the two ends are the same, that is returned.
75    pub fn single(&self) -> Option<CCursor> {
76        if self.is_empty() {
77            Some(self.primary)
78        } else {
79            None
80        }
81    }
82
83    #[inline]
84    pub fn is_sorted(&self) -> bool {
85        let p = self.primary;
86        let s = self.secondary;
87        (p.index, p.prefer_next_row) <= (s.index, s.prefer_next_row)
88    }
89
90    /// returns the two ends ordered
91    #[inline]
92    pub fn sorted_cursors(&self) -> [CCursor; 2] {
93        if self.is_sorted() {
94            [self.primary, self.secondary]
95        } else {
96            [self.secondary, self.primary]
97        }
98    }
99
100    #[inline]
101    #[deprecated = "Use `self.sorted_cursors` instead."]
102    pub fn sorted(&self) -> [CCursor; 2] {
103        self.sorted_cursors()
104    }
105
106    pub fn slice_str<'s>(&self, text: &'s str) -> &'s str {
107        let [min, max] = self.sorted_cursors();
108        slice_char_range(text, min.index..max.index)
109    }
110
111    /// Check for key presses that are moving the cursor.
112    ///
113    /// Returns `true` if we did mutate `self`.
114    pub fn on_key_press(
115        &mut self,
116        os: OperatingSystem,
117        galley: &Galley,
118        modifiers: &Modifiers,
119        key: Key,
120    ) -> bool {
121        match key {
122            Key::A if modifiers.command => {
123                *self = Self::select_all(galley);
124                true
125            }
126
127            Key::ArrowLeft | Key::ArrowRight if modifiers.is_none() && !self.is_empty() => {
128                if key == Key::ArrowLeft {
129                    *self = Self::one(self.sorted_cursors()[0]);
130                } else {
131                    *self = Self::one(self.sorted_cursors()[1]);
132                }
133                true
134            }
135
136            Key::ArrowLeft
137            | Key::ArrowRight
138            | Key::ArrowUp
139            | Key::ArrowDown
140            | Key::Home
141            | Key::End => {
142                move_single_cursor(
143                    os,
144                    &mut self.primary,
145                    &mut self.h_pos,
146                    galley,
147                    key,
148                    modifiers,
149                );
150                if !modifiers.shift {
151                    self.secondary = self.primary;
152                }
153                true
154            }
155
156            Key::P | Key::N | Key::B | Key::F | Key::A | Key::E
157                if os == OperatingSystem::Mac && modifiers.ctrl && !modifiers.shift =>
158            {
159                move_single_cursor(
160                    os,
161                    &mut self.primary,
162                    &mut self.h_pos,
163                    galley,
164                    key,
165                    modifiers,
166                );
167                self.secondary = self.primary;
168                true
169            }
170
171            _ => false,
172        }
173    }
174
175    /// Check for events that modify the cursor range.
176    ///
177    /// Returns `true` if such an event was found and handled.
178    pub fn on_event(
179        &mut self,
180        os: OperatingSystem,
181        event: &Event,
182        galley: &Galley,
183        _widget_id: Id,
184    ) -> bool {
185        match event {
186            Event::Key {
187                modifiers,
188                key,
189                pressed: true,
190                ..
191            } => self.on_key_press(os, galley, modifiers, *key),
192
193            #[cfg(feature = "accesskit")]
194            Event::AccessKitActionRequest(accesskit::ActionRequest {
195                action: accesskit::Action::SetTextSelection,
196                target,
197                data: Some(accesskit::ActionData::SetTextSelection(selection)),
198            }) => {
199                if _widget_id.accesskit_id() == *target {
200                    let primary =
201                        ccursor_from_accesskit_text_position(_widget_id, galley, &selection.focus);
202                    let secondary =
203                        ccursor_from_accesskit_text_position(_widget_id, galley, &selection.anchor);
204                    if let (Some(primary), Some(secondary)) = (primary, secondary) {
205                        *self = Self {
206                            primary,
207                            secondary,
208                            h_pos: None,
209                        };
210                        return true;
211                    }
212                }
213                false
214            }
215
216            _ => false,
217        }
218    }
219}
220
221// ----------------------------------------------------------------------------
222
223#[cfg(feature = "accesskit")]
224fn ccursor_from_accesskit_text_position(
225    id: Id,
226    galley: &Galley,
227    position: &accesskit::TextPosition,
228) -> Option<CCursor> {
229    let mut total_length = 0usize;
230    for (i, row) in galley.rows.iter().enumerate() {
231        let row_id = id.with(i);
232        if row_id.accesskit_id() == position.node {
233            return Some(CCursor {
234                index: total_length + position.character_index,
235                prefer_next_row: !(position.character_index == row.glyphs.len()
236                    && !row.ends_with_newline
237                    && (i + 1) < galley.rows.len()),
238            });
239        }
240        total_length += row.glyphs.len() + (row.ends_with_newline as usize);
241    }
242    None
243}
244
245// ----------------------------------------------------------------------------
246
247/// Move a text cursor based on keyboard
248fn move_single_cursor(
249    os: OperatingSystem,
250    cursor: &mut CCursor,
251    h_pos: &mut Option<f32>,
252    galley: &Galley,
253    key: Key,
254    modifiers: &Modifiers,
255) {
256    let (new_cursor, new_h_pos) =
257        if os == OperatingSystem::Mac && modifiers.ctrl && !modifiers.shift {
258            match key {
259                Key::A => (galley.cursor_begin_of_row(cursor), None),
260                Key::E => (galley.cursor_end_of_row(cursor), None),
261                Key::P => galley.cursor_up_one_row(cursor, *h_pos),
262                Key::N => galley.cursor_down_one_row(cursor, *h_pos),
263                Key::B => (galley.cursor_left_one_character(cursor), None),
264                Key::F => (galley.cursor_right_one_character(cursor), None),
265                _ => return,
266            }
267        } else {
268            match key {
269                Key::ArrowLeft => {
270                    if modifiers.alt || modifiers.ctrl {
271                        // alt on mac, ctrl on windows
272                        (ccursor_previous_word(galley, *cursor), None)
273                    } else if modifiers.mac_cmd {
274                        (galley.cursor_begin_of_row(cursor), None)
275                    } else {
276                        (galley.cursor_left_one_character(cursor), None)
277                    }
278                }
279                Key::ArrowRight => {
280                    if modifiers.alt || modifiers.ctrl {
281                        // alt on mac, ctrl on windows
282                        (ccursor_next_word(galley, *cursor), None)
283                    } else if modifiers.mac_cmd {
284                        (galley.cursor_end_of_row(cursor), None)
285                    } else {
286                        (galley.cursor_right_one_character(cursor), None)
287                    }
288                }
289                Key::ArrowUp => {
290                    if modifiers.command {
291                        // mac and windows behavior
292                        (galley.begin(), None)
293                    } else {
294                        galley.cursor_up_one_row(cursor, *h_pos)
295                    }
296                }
297                Key::ArrowDown => {
298                    if modifiers.command {
299                        // mac and windows behavior
300                        (galley.end(), None)
301                    } else {
302                        galley.cursor_down_one_row(cursor, *h_pos)
303                    }
304                }
305
306                Key::Home => {
307                    if modifiers.ctrl {
308                        // windows behavior
309                        (galley.begin(), None)
310                    } else {
311                        (galley.cursor_begin_of_row(cursor), None)
312                    }
313                }
314                Key::End => {
315                    if modifiers.ctrl {
316                        // windows behavior
317                        (galley.end(), None)
318                    } else {
319                        (galley.cursor_end_of_row(cursor), None)
320                    }
321                }
322
323                _ => unreachable!(),
324            }
325        };
326
327    *cursor = new_cursor;
328    *h_pos = new_h_pos;
329}