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#[derive(Clone, Copy, Debug, Default, PartialEq)]
11#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
12pub struct CCursorRange {
13 pub primary: CCursor,
17
18 pub secondary: CCursor,
21
22 pub h_pos: Option<f32>,
24}
25
26impl CCursorRange {
27 #[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 pub fn select_all(galley: &Galley) -> Self {
48 Self::two(galley.begin(), galley.end())
49 }
50
51 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 #[inline]
62 pub fn is_empty(&self) -> bool {
63 self.primary == self.secondary
64 }
65
66 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 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 #[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 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 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#[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
245fn 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 (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 (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 (galley.begin(), None)
293 } else {
294 galley.cursor_up_one_row(cursor, *h_pos)
295 }
296 }
297 Key::ArrowDown => {
298 if modifiers.command {
299 (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 (galley.begin(), None)
310 } else {
311 (galley.cursor_begin_of_row(cursor), None)
312 }
313 }
314 Key::End => {
315 if modifiers.ctrl {
316 (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}