egui/text_selection/
cursor_range.rs

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