freya_edit/
event.rs

1use std::ops::Mul;
2
3use freya_core::{
4    elements::paragraph::ParagraphHolderInner,
5    prelude::*,
6};
7use torin::prelude::CursorPoint;
8
9use crate::{
10    EditableConfig,
11    rope_editor::RopeEditor,
12    text_editor::{
13        TextEditor,
14        TextEvent,
15    },
16};
17
18pub enum EditableEvent<'a> {
19    Release,
20    Move {
21        location: CursorPoint,
22        editor_id: usize,
23        holder: &'a ParagraphHolder,
24    },
25    Down {
26        location: CursorPoint,
27        editor_id: usize,
28        holder: &'a ParagraphHolder,
29    },
30    KeyDown {
31        key: &'a Key,
32        modifiers: Modifiers,
33    },
34    KeyUp {
35        key: &'a Key,
36    },
37}
38
39impl EditableEvent<'_> {
40    pub fn process<'a, 'b>(
41        self,
42        mut editor: impl MutView<'b, RopeEditor>,
43        mut dragging: impl MutView<'b, TextDragging>,
44        config: &'_ EditableConfig,
45    ) {
46        match self {
47            EditableEvent::Down {
48                location,
49                editor_id,
50                holder,
51            } => {
52                let holder = holder.0.borrow();
53                let ParagraphHolderInner {
54                    paragraph,
55                    scale_factor,
56                } = holder.as_ref().unwrap();
57
58                dragging
59                    .write()
60                    .set_cursor_coords(location.mul(*scale_factor));
61
62                let mut text_editor = editor.write();
63                text_editor.clear_selection();
64
65                match EventsCombos::pressed(location) {
66                    PressEventType::Triple => {
67                        let char_position = paragraph.get_glyph_position_at_coordinate(
68                            location.mul(*scale_factor).to_i32().to_tuple(),
69                        );
70                        let new_cursor = text_editor
71                            .measure_new_cursor(char_position.position as usize, editor_id);
72
73                        // Get the line start char and its length
74                        let line = text_editor.rope().char_to_line(new_cursor.pos());
75                        let line_char = text_editor.rope().line_to_char(line);
76                        let line_len = text_editor.rope().line(line).len_utf16_cu();
77
78                        // Select the whole line
79                        text_editor.set_selection((line_char, line_char + line_len));
80                    }
81                    PressEventType::Double => {
82                        let char_position = paragraph.get_glyph_position_at_coordinate(
83                            location.mul(*scale_factor).to_i32().to_tuple(),
84                        );
85                        let new_cursor = text_editor
86                            .measure_new_cursor(char_position.position as usize, editor_id);
87
88                        // Find word boundaries
89                        let selection = text_editor.find_word_boundaries(new_cursor.pos());
90
91                        // Set cursor to end of word and select the word
92                        *text_editor.cursor_mut() = new_cursor.clone();
93                        text_editor.set_selection(selection);
94                    }
95                    PressEventType::Single => {
96                        let char_position = paragraph.get_glyph_position_at_coordinate(
97                            location.mul(*scale_factor).to_i32().to_tuple(),
98                        );
99                        let new_cursor = text_editor
100                            .measure_new_cursor(char_position.position as usize, editor_id);
101
102                        // Only update and clear the selection if the cursor has changed
103                        if *text_editor.cursor() != new_cursor {
104                            *text_editor.cursor_mut() = new_cursor;
105                            if let TextDragging::FromCursorToPoint { cursor: from, .. } =
106                                &*dragging.peek()
107                            {
108                                let to = text_editor.cursor_pos();
109                                text_editor.set_selection((*from, to));
110                            } else {
111                                text_editor.clear_selection();
112                            }
113                        }
114                    }
115                }
116            }
117            EditableEvent::Move {
118                location,
119                editor_id,
120                holder,
121            } => {
122                if let Some(origin) = dragging.peek().get_cursor_coords() {
123                    let paragraph = holder.0.borrow();
124                    let ParagraphHolderInner {
125                        paragraph,
126                        scale_factor,
127                    } = paragraph.as_ref().unwrap();
128
129                    let origin_position = origin;
130                    let dist_position = location.mul(*scale_factor);
131
132                    // Calculate the start of the highlighting
133                    let origin_char = paragraph
134                        .get_glyph_position_at_coordinate(origin_position.to_i32().to_tuple());
135                    // Calculate the end of the highlighting
136                    let dist_char = paragraph
137                        .get_glyph_position_at_coordinate(dist_position.to_i32().to_tuple());
138                    let from = origin_char.position as usize;
139                    let to = dist_char.position as usize;
140
141                    let current_cursor = editor.peek().cursor().clone();
142                    let current_selection = editor.peek().get_selection();
143
144                    let maybe_new_cursor = editor.peek().measure_new_cursor(to, editor_id);
145                    let maybe_new_selection =
146                        editor.peek().measure_new_selection(from, to, editor_id);
147
148                    // Update the text selection if it has changed
149                    if let Some(current_selection) = current_selection {
150                        if current_selection != maybe_new_selection {
151                            let mut text_editor = editor.write();
152                            text_editor.set_selection(maybe_new_selection);
153                        }
154                    } else {
155                        let mut text_editor = editor.write();
156                        text_editor.set_selection(maybe_new_selection);
157                    }
158
159                    // Update the cursor if it has changed
160                    if current_cursor != maybe_new_cursor {
161                        let mut text_editor = editor.write();
162                        *text_editor.cursor_mut() = maybe_new_cursor;
163                    }
164                }
165            }
166            EditableEvent::Release => {
167                let dragging = &mut *dragging.write();
168                match dragging {
169                    TextDragging::FromCursorToPoint { shift, clicked, .. } if *shift => {
170                        *clicked = false;
171                    }
172                    _ => {
173                        *dragging = TextDragging::None;
174                    }
175                }
176            }
177            EditableEvent::KeyDown { key, modifiers } => {
178                match key {
179                    // Handle dragging
180                    Key::Shift => {
181                        let dragging = &mut *dragging.write();
182                        match dragging {
183                            TextDragging::FromCursorToPoint {
184                                shift: shift_pressed,
185                                ..
186                            } => {
187                                *shift_pressed = true;
188                            }
189                            TextDragging::None => {
190                                *dragging = TextDragging::FromCursorToPoint {
191                                    shift: true,
192                                    clicked: false,
193                                    cursor: editor.peek().cursor_pos(),
194                                    dist: None,
195                                }
196                            }
197                            _ => {}
198                        }
199                    }
200                    // Handle editing
201                    _ => {
202                        editor.write_if(|mut ditor| {
203                            let event = ditor.process_key(
204                                key,
205                                &modifiers,
206                                config.allow_tabs,
207                                config.allow_changes,
208                                config.allow_clipboard,
209                            );
210                            if event.contains(TextEvent::TEXT_CHANGED) {
211                                *dragging.write() = TextDragging::None;
212                            }
213                            !event.is_empty()
214                        });
215                    }
216                }
217            }
218            EditableEvent::KeyUp { key } => {
219                if *key == Key::Shift {
220                    if let TextDragging::FromCursorToPoint { shift, .. } = &mut *dragging.write() {
221                        *shift = false;
222                    }
223                } else {
224                    *dragging.write() = TextDragging::None;
225                }
226            }
227        };
228    }
229}
230
231/// Indicates the type of text dragging being done.
232#[derive(Debug, PartialEq, Clone)]
233pub enum TextDragging {
234    None,
235    FromPointToPoint {
236        src: CursorPoint,
237    },
238    FromCursorToPoint {
239        shift: bool,
240        clicked: bool,
241        cursor: usize,
242        dist: Option<CursorPoint>,
243    },
244}
245
246impl TextDragging {
247    pub fn has_cursor_coords(&self) -> bool {
248        match self {
249            Self::None => false,
250            Self::FromPointToPoint { .. } => true,
251            Self::FromCursorToPoint { dist, .. } => dist.is_some(),
252        }
253    }
254
255    pub fn set_cursor_coords(&mut self, cursor: CursorPoint) {
256        match self {
257            Self::FromPointToPoint { src } => *src = cursor,
258            Self::FromCursorToPoint {
259                dist, shift: true, ..
260            } => *dist = Some(cursor),
261            _ => *self = Self::FromPointToPoint { src: cursor },
262        }
263    }
264
265    pub fn get_cursor_coords(&self) -> Option<CursorPoint> {
266        match self {
267            Self::None => None,
268            Self::FromPointToPoint { src } => Some(*src),
269            Self::FromCursorToPoint { dist, clicked, .. } => {
270                if *clicked {
271                    *dist
272                } else {
273                    None
274                }
275            }
276        }
277    }
278}