Skip to main content

freya_edit/
event.rs

1use std::ops::Mul;
2
3use freya_core::{
4    elements::paragraph::{
5        ParagraphCursorExt,
6        ParagraphHolderInner,
7    },
8    prelude::*,
9};
10use keyboard_types::NamedKey;
11use torin::prelude::CursorPoint;
12
13use crate::{
14    EditableConfig,
15    EditorLine,
16    TextSelection,
17    text_editor::{
18        TextEditor,
19        TextEvent,
20    },
21};
22
23#[derive(Debug)]
24pub enum EditableEvent<'a> {
25    Release,
26    Move {
27        location: CursorPoint,
28        editor_line: EditorLine,
29        holder: &'a ParagraphHolder,
30    },
31    Down {
32        location: CursorPoint,
33        editor_line: EditorLine,
34        holder: &'a ParagraphHolder,
35    },
36    KeyDown {
37        key: &'a Key,
38        modifiers: Modifiers,
39        editor_line: Option<EditorLine>,
40        holder: Option<&'a ParagraphHolder>,
41    },
42    KeyUp {
43        key: &'a Key,
44    },
45}
46
47impl EditableEvent<'_> {
48    pub fn process<T: TextEditor>(
49        self,
50        mut editor: Writable<T>,
51        mut dragging: Writable<TextDragging>,
52        config: &'_ EditableConfig,
53    ) {
54        match self {
55            EditableEvent::Down {
56                location,
57                editor_line,
58                holder,
59            } => {
60                let location = holder.visible_location(location);
61                let holder = holder.0.borrow();
62                let ParagraphHolderInner {
63                    paragraph,
64                    scale_factor,
65                    ..
66                } = holder.as_ref().unwrap();
67
68                let mut text_editor = editor.write();
69
70                if dragging.peek().shift || dragging.peek().clicked {
71                    text_editor.selection_mut().set_as_range();
72                } else {
73                    text_editor.clear_selection();
74                }
75
76                let press_type = EventsCombos::<()>::pressed(location);
77                let press_type = if press_type.is_double() && config.select_all_on_double_click {
78                    PressEventType::Quadruple
79                } else {
80                    press_type
81                };
82                let char_position =
83                    paragraph.cursor_index_at_point(location.mul(*scale_factor).to_tuple());
84                let press_selection = text_editor.measure_selection(char_position, editor_line);
85                let new_selection = match press_type {
86                    PressEventType::Single => press_selection,
87                    PressEventType::Double => TextSelection::new_range(
88                        text_editor.find_word_boundaries(press_selection.pos()),
89                    ),
90                    PressEventType::Triple => TextSelection::new_range(
91                        text_editor.find_line_boundaries(press_selection.pos()),
92                    ),
93                    PressEventType::Quadruple => {
94                        TextSelection::new_range((0, text_editor.len_utf16_cu()))
95                    }
96                };
97
98                dragging
99                    .write()
100                    .start_selection(press_type, new_selection.clone());
101                if *text_editor.selection() != new_selection {
102                    *text_editor.selection_mut() = new_selection;
103                }
104            }
105            EditableEvent::Move {
106                location,
107                editor_line,
108                holder,
109            } => {
110                let location = holder.visible_location(location);
111                if dragging.peek().clicked {
112                    EventsCombos::<()>::moved(location);
113
114                    let paragraph = holder.0.borrow();
115                    let ParagraphHolderInner {
116                        paragraph,
117                        scale_factor,
118                        ..
119                    } = paragraph.as_ref().unwrap();
120
121                    let dist_position = location.mul(*scale_factor);
122
123                    // Calculate the end of the highlighting
124                    let to = paragraph.cursor_index_at_point(dist_position.to_tuple());
125
126                    if editor.peek().get_selection().is_none() {
127                        editor.write().selection_mut().set_as_range();
128                    }
129
130                    let current_selection = editor.peek().selection().clone();
131
132                    let new_selection =
133                        dragging
134                            .peek()
135                            .measure_selection(&*editor.peek(), to, editor_line);
136
137                    // Update the cursor if it has changed
138                    if current_selection != new_selection {
139                        let mut text_editor = editor.write();
140                        *text_editor.selection_mut() = new_selection;
141                    }
142                }
143            }
144            EditableEvent::Release => {
145                dragging.write().clicked = false;
146                EventsCombos::<()>::released();
147            }
148            EditableEvent::KeyDown {
149                key,
150                modifiers,
151                editor_line,
152                holder,
153            } => {
154                match key {
155                    // Handle dragging
156                    Key::Named(NamedKey::Shift) => {
157                        dragging.write().shift = true;
158                    }
159                    // Handle editing
160                    _ => {
161                        editor.write_if(|mut editor| {
162                            let event = editor.process_key(
163                                key,
164                                &modifiers,
165                                editor_line,
166                                holder,
167                                config.allow_tabs,
168                                config.allow_changes,
169                                config.allow_read_clipboard,
170                                config.allow_write_clipboard,
171                            );
172                            if event.contains(TextEvent::TEXT_CHANGED) {
173                                *dragging.write() = TextDragging::default();
174                            }
175                            !event.is_empty()
176                        });
177                    }
178                }
179            }
180            EditableEvent::KeyUp { key, .. } => {
181                if *key == Key::Named(NamedKey::Shift) {
182                    dragging.write().shift = false;
183                }
184            }
185        };
186    }
187}
188
189#[derive(Debug, PartialEq, Clone, Default)]
190pub struct TextDragging {
191    pub shift: bool,
192    pub clicked: bool,
193    multi_click_selection: Option<(PressEventType, TextSelection)>,
194}
195
196impl TextDragging {
197    /// Remember the selection granularity and anchor of a pointer press.
198    pub fn start_selection(&mut self, press_type: PressEventType, selection: TextSelection) {
199        self.clicked = true;
200        self.multi_click_selection = if press_type.is_single() {
201            None
202        } else {
203            Some((press_type, selection))
204        };
205    }
206
207    /// Extend a drag by the original press granularity.
208    pub fn measure_selection<T: TextEditor>(
209        &self,
210        editor: &T,
211        to: usize,
212        editor_line: EditorLine,
213    ) -> TextSelection {
214        let selection = editor.measure_selection(to, editor_line);
215        let Some((press_type, anchor)) = &self.multi_click_selection else {
216            return selection;
217        };
218        let position = selection.pos();
219        if press_type.is_quadruple() || (position >= anchor.start() && position <= anchor.end()) {
220            return anchor.clone();
221        }
222        let (start, end) = match press_type {
223            PressEventType::Double => editor.find_word_boundaries(position),
224            PressEventType::Triple => editor.find_line_boundaries(position),
225            _ => return selection,
226        };
227        if position < anchor.start() {
228            TextSelection::new_range((anchor.end(), start))
229        } else {
230            TextSelection::new_range((anchor.start(), end))
231        }
232    }
233}