egui/text_selection/
cursor_range.rs

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