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_node,
197                target_tree,
198                data: Some(accesskit::ActionData::SetTextSelection(selection)),
199            }) => {
200                if _widget_id.accesskit_id() == *target_node
201                    && *target_tree == accesskit::TreeId::ROOT
202                {
203                    let primary =
204                        ccursor_from_accesskit_text_position(_widget_id, galley, &selection.focus);
205                    let secondary =
206                        ccursor_from_accesskit_text_position(_widget_id, galley, &selection.anchor);
207                    if let (Some(primary), Some(secondary)) = (primary, secondary) {
208                        *self = Self {
209                            primary,
210                            secondary,
211                            h_pos: None,
212                        };
213                        return true;
214                    }
215                }
216                false
217            }
218
219            _ => false,
220        }
221    }
222}
223
224// ----------------------------------------------------------------------------
225
226#[cfg(feature = "accesskit")]
227fn ccursor_from_accesskit_text_position(
228    id: Id,
229    galley: &Galley,
230    position: &accesskit::TextPosition,
231) -> Option<CCursor> {
232    let mut total_length = 0usize;
233    for (i, row) in galley.rows.iter().enumerate() {
234        let row_id = id.with(i);
235        if row_id.accesskit_id() == position.node {
236            return Some(CCursor {
237                index: total_length + position.character_index,
238                prefer_next_row: !(position.character_index == row.glyphs.len()
239                    && !row.ends_with_newline
240                    && (i + 1) < galley.rows.len()),
241            });
242        }
243        total_length += row.glyphs.len() + (row.ends_with_newline as usize);
244    }
245    None
246}
247
248// ----------------------------------------------------------------------------
249
250/// Move a text cursor based on keyboard
251fn move_single_cursor(
252    os: OperatingSystem,
253    cursor: &mut CCursor,
254    h_pos: &mut Option<f32>,
255    galley: &Galley,
256    key: Key,
257    modifiers: &Modifiers,
258) {
259    let (new_cursor, new_h_pos) =
260        if os == OperatingSystem::Mac && modifiers.ctrl && !modifiers.shift {
261            match key {
262                Key::A => (galley.cursor_begin_of_row(cursor), None),
263                Key::E => (galley.cursor_end_of_row(cursor), None),
264                Key::P => galley.cursor_up_one_row(cursor, *h_pos),
265                Key::N => galley.cursor_down_one_row(cursor, *h_pos),
266                Key::B => (galley.cursor_left_one_character(cursor), None),
267                Key::F => (galley.cursor_right_one_character(cursor), None),
268                _ => return,
269            }
270        } else {
271            match key {
272                Key::ArrowLeft => {
273                    if modifiers.alt || modifiers.ctrl {
274                        // alt on mac, ctrl on windows
275                        (ccursor_previous_word(galley, *cursor), None)
276                    } else if modifiers.mac_cmd {
277                        (galley.cursor_begin_of_row(cursor), None)
278                    } else {
279                        (galley.cursor_left_one_character(cursor), None)
280                    }
281                }
282                Key::ArrowRight => {
283                    if modifiers.alt || modifiers.ctrl {
284                        // alt on mac, ctrl on windows
285                        (ccursor_next_word(galley, *cursor), None)
286                    } else if modifiers.mac_cmd {
287                        (galley.cursor_end_of_row(cursor), None)
288                    } else {
289                        (galley.cursor_right_one_character(cursor), None)
290                    }
291                }
292                Key::ArrowUp => {
293                    if modifiers.command {
294                        // mac and windows behavior
295                        (galley.begin(), None)
296                    } else {
297                        galley.cursor_up_one_row(cursor, *h_pos)
298                    }
299                }
300                Key::ArrowDown => {
301                    if modifiers.command {
302                        // mac and windows behavior
303                        (galley.end(), None)
304                    } else {
305                        galley.cursor_down_one_row(cursor, *h_pos)
306                    }
307                }
308
309                Key::Home => {
310                    if modifiers.ctrl {
311                        // windows behavior
312                        (galley.begin(), None)
313                    } else {
314                        (galley.cursor_begin_of_row(cursor), None)
315                    }
316                }
317                Key::End => {
318                    if modifiers.ctrl {
319                        // windows behavior
320                        (galley.end(), None)
321                    } else {
322                        (galley.cursor_end_of_row(cursor), None)
323                    }
324                }
325
326                _ => unreachable!(),
327            }
328        };
329
330    *cursor = new_cursor;
331    *h_pos = new_h_pos;
332}