diff --git a/crates/hooks/src/rope_editor.rs b/crates/hooks/src/rope_editor.rs index da6f628e5..1215c5af0 100644 --- a/crates/hooks/src/rope_editor.rs +++ b/crates/hooks/src/rope_editor.rs @@ -233,12 +233,11 @@ impl TextEditor for RopeEditor { fn measure_new_cursor(&self, to: usize, editor_id: usize) -> TextCursor { if self.mode == EditableMode::SingleLineMultipleEditors { - TextCursor::new(editor_id, to) + let row_char = self.line_to_char(editor_id); + let pos = row_char + to; + TextCursor::new(pos) } else { - let row = self.char_to_line(to); - let row_idx = self.line_to_char(row); - let col = to - row_idx; - TextCursor::new(row, col) + TextCursor::new(to) } } diff --git a/crates/hooks/src/text_editor.rs b/crates/hooks/src/text_editor.rs index b4c0d0e3d..93da07ac5 100644 --- a/crates/hooks/src/text_editor.rs +++ b/crates/hooks/src/text_editor.rs @@ -5,45 +5,27 @@ use freya_elements::events::keyboard::{Code, Key, Modifiers}; /// Holds the position of a cursor in a text #[derive(Clone, Default, PartialEq, Debug)] -pub struct TextCursor { - col: usize, - row: usize, -} +pub struct TextCursor(usize); impl TextCursor { - /// Construct a new [TextCursor] given a row and a column - pub fn new(row: usize, col: usize) -> Self { - Self { col, row } - } - - /// Move the cursor to a new row and column - pub fn move_to(&mut self, row: usize, col: usize) { - self.col = col; - self.row = row; - } - - /// Get the current column - pub fn col(&self) -> usize { - self.col - } - - /// Get the current row - pub fn row(&self) -> usize { - self.row + /// Construct a new [TextCursor] + pub fn new(pos: usize) -> Self { + Self(pos) } - /// Set a new column - pub fn set_col(&mut self, col: usize) { - self.col = col; + /// Get the position + pub fn pos(&self) -> usize { + self.0 } - /// Set a new row - pub fn set_row(&mut self, row: usize) { - self.row = row; + /// Set the position + pub fn set(&mut self, pos: usize) { + self.0 = pos; } - pub fn as_tuple(&self) -> (usize, usize) { - (self.row, self.col) + /// Write the position + pub fn write(&mut self) -> &mut usize { + &mut self.0 } } @@ -131,12 +113,21 @@ pub trait TextEditor { /// Get the cursor row fn cursor_row(&self) -> usize { - self.cursor().row() + let pos = self.cursor_pos(); + self.char_to_line(pos) } /// Get the cursor column fn cursor_col(&self) -> usize { - self.cursor().col() + let pos = self.cursor_pos(); + let line = self.char_to_line(pos); + let line_char = self.line_to_char(line); + pos - line_char + } + + /// Get the cursor row and col + fn cursor_row_and_col(&self) -> (usize, usize) { + (self.cursor_row(), self.cursor_col()) } /// Get the visible cursor position @@ -145,47 +136,91 @@ pub trait TextEditor { } /// Move the cursor 1 line down - fn cursor_down(&mut self) { - let new_row = self.cursor_row() + 1; - self.cursor_mut().set_row(new_row); + fn cursor_down(&mut self) -> bool { + let old_row = self.cursor_row(); + let old_col = self.cursor_col(); + + match old_row.cmp(&(self.len_lines() - 1)) { + Ordering::Less => { + // One line below + let new_row = old_row + 1; + let new_row_char = self.line_to_char(new_row); + self.cursor_mut().set(new_row_char + old_col); + + true + } + Ordering::Equal => { + let end = self.len_chars(); + // Reached max + self.cursor_mut().set(end); + + true + } + Ordering::Greater => { + // Can't go further + + false + } + } } /// Move the cursor 1 line up - fn cursor_up(&mut self) { - let new_row = self.cursor_row() - 1; - self.cursor_mut().set_row(new_row); + fn cursor_up(&mut self) -> bool { + let pos = self.cursor_pos(); + let old_row = self.cursor_row(); + let old_col = self.cursor_col(); + + if pos > 0 { + // Reached max + if old_row == 0 { + self.cursor_mut().set(0); + } else { + let new_row = old_row - 1; + let new_row_char = self.line_to_char(new_row); + self.cursor_mut().set(new_row_char + old_col); + } + + true + } else { + false + } } /// Move the cursor 1 char to the right - fn cursor_right(&mut self) { - let new_col = self.cursor_col() + 1; - self.cursor_mut().set_col(new_col); + fn cursor_right(&mut self) -> bool { + if self.cursor_pos() < self.len_chars() { + *self.cursor_mut().write() += 1; + + true + } else { + false + } } /// Move the cursor 1 char to the left - fn cursor_left(&mut self) { - let new_col = self.cursor_col() - 1; - self.cursor_mut().set_col(new_col); + fn cursor_left(&mut self) -> bool { + if self.cursor_pos() > 0 { + *self.cursor_mut().write() -= 1; + + true + } else { + false + } } /// Get the cursor position fn cursor_pos(&self) -> usize { - let line_begining = self.line_to_char(self.cursor_row()); - line_begining + self.cursor_col() + self.cursor().pos() } /// Get the cursor position fn visible_cursor_pos(&self) -> usize { - let line_begining = self.char_to_utf16_cu(self.line_to_char(self.cursor_row())); - line_begining + self.char_to_utf16_cu(self.cursor_col()) + self.char_to_utf16_cu(self.cursor_pos()) } /// Set the cursor position fn set_cursor_pos(&mut self, pos: usize) { - let row = self.char_to_line(pos); - let row_idx = self.line_to_char(row); - let col = pos - row_idx; - self.cursor_mut().move_to(row, col) + self.cursor_mut().set(pos); } // Check if has any selection at all @@ -241,33 +276,8 @@ pub trait TextEditor { self.expand_selection_to_cursor(); } - let last_line = self.len_lines() - 1; - - // Go one line down - match self.cursor_row().cmp(&last_line) { - Ordering::Equal => { - // Move the cursor to the end of the line - let current_line = self.line(self.cursor_row()).unwrap(); - let last_char = current_line.len_chars(); - self.cursor_mut().set_col(last_char); - event.insert(TextEvent::CURSOR_CHANGED); - } - Ordering::Less => { - let next_line = self.line(self.cursor_row() + 1).unwrap(); - - // Try to use the current cursor column, otherwise use the new line length - let cursor_col = if self.cursor_col() <= next_line.len_chars() { - self.cursor_col() - } else { - next_line.len_chars().max(1) - 1 - }; - - self.cursor_mut().set_col(cursor_col); - self.cursor_down(); - - event.insert(TextEvent::CURSOR_CHANGED); - } - _ => {} + if self.cursor_down() { + event.insert(TextEvent::CURSOR_CHANGED); } if modifiers.contains(Modifiers::SHIFT) { @@ -280,27 +290,8 @@ pub trait TextEditor { self.expand_selection_to_cursor(); } - // Go one character to the left - if self.cursor_col() > 0 { - self.cursor_left(); - + if self.cursor_left() { event.insert(TextEvent::CURSOR_CHANGED); - } else if self.cursor_row() > 0 { - // Go one line up if there is no more characters on the left - let prev_line = self.line(self.cursor_row() - 1); - if let Some(prev_line) = prev_line { - // Use the prev line length as new cursor column, otherwise just set it to 0 - let cursor_col = if prev_line.len_chars() > 0 { - prev_line.len_chars() - 1 - } else { - 0 - }; - - self.cursor_up(); - self.cursor_mut().set_col(cursor_col); - - event.insert(TextEvent::CURSOR_CHANGED); - } } if modifiers.contains(Modifiers::SHIFT) { @@ -313,20 +304,7 @@ pub trait TextEditor { self.expand_selection_to_cursor(); } - let current_line = self.line(self.cursor_row()).unwrap(); - - // Go one line down if there isn't more characters on the right - if self.cursor_row() < self.len_lines() - 1 - && self.cursor_col() == current_line.len_chars().max(1) - 1 - { - self.cursor_down(); - self.cursor_mut().set_col(0); - - event.insert(TextEvent::CURSOR_CHANGED); - } else if self.cursor_col() < current_line.len_chars() { - // Go one character to the right if possible - self.cursor_right(); - + if self.cursor_right() { event.insert(TextEvent::CURSOR_CHANGED); } @@ -340,24 +318,7 @@ pub trait TextEditor { self.expand_selection_to_cursor(); } - // Go one line up if there is any - if self.cursor_row() > 0 { - let prev_line = self.line(self.cursor_row() - 1).unwrap(); - - // Try to use the current cursor column, otherwise use the prev line length - let cursor_col = if self.cursor_col() <= prev_line.len_chars() { - self.cursor_col() - } else { - prev_line.len_chars().max(1) - 1 - }; - - self.cursor_up(); - self.cursor_mut().set_col(cursor_col); - - event.insert(TextEvent::CURSOR_CHANGED); - } else if self.cursor_col() > 0 { - // Move the cursor to the begining of the line - self.cursor_mut().set_col(0); + if self.cursor_up() { event.insert(TextEvent::CURSOR_CHANGED); } @@ -396,10 +357,9 @@ pub trait TextEditor { } Key::Enter => { // Breaks the line - let char_idx = self.line_to_char(self.cursor_row()) + self.cursor_col(); - self.insert_char('\n', char_idx); - self.cursor_down(); - self.cursor_mut().set_col(0); + let cursor_pos = self.cursor_pos(); + self.insert_char('\n', cursor_pos); + self.cursor_right(); event.insert(TextEvent::TEXT_CHANGED); } @@ -414,8 +374,8 @@ pub trait TextEditor { Code::Delete => {} Code::Space => { // Simply adds an space - let char_idx = self.line_to_char(self.cursor_row()) + self.cursor_col(); - self.insert_char(' ', char_idx); + let cursor_pos = self.cursor_pos(); + self.insert_char(' ', cursor_pos); self.cursor_right(); event.insert(TextEvent::TEXT_CHANGED); @@ -484,16 +444,16 @@ pub trait TextEditor { _ => { if let Ok(ch) = character.parse::() { // Inserts a character - let char_idx = self.line_to_char(self.cursor_row()) + self.cursor_col(); - self.insert_char(ch, char_idx); + let cursor_pos = self.cursor_pos(); + self.insert_char(ch, cursor_pos); self.cursor_right(); event.insert(TextEvent::TEXT_CHANGED); } else { // Inserts a text - let char_idx = self.line_to_char(self.cursor_row()) + self.cursor_col(); - self.insert(character, char_idx); - self.set_cursor_pos(char_idx + character.chars().count()); + let cursor_pos = self.cursor_pos(); + self.insert(character, cursor_pos); + self.set_cursor_pos(cursor_pos + character.chars().count()); event.insert(TextEvent::TEXT_CHANGED); } diff --git a/crates/hooks/src/use_editable.rs b/crates/hooks/src/use_editable.rs index 15e99239d..8a305392d 100644 --- a/crates/hooks/src/use_editable.rs +++ b/crates/hooks/src/use_editable.rs @@ -237,9 +237,9 @@ impl EditableConfig { } } - /// Specify a custom initial cursor positions. - pub fn with_cursor(mut self, (row, col): (usize, usize)) -> Self { - self.cursor = TextCursor::new(row, col); + /// Specify a custom initial cursor position. + pub fn with_cursor(mut self, pos: usize) -> Self { + self.cursor = TextCursor::new(pos); self } } @@ -272,43 +272,12 @@ pub fn use_editable(initializer: impl Fn() -> EditableConfig, mode: EditableMode // Update the cursor position calculated by the layout CursorLayoutResponse::CursorPosition { position, id } => { let mut text_editor = editor.write(); - - let new_cursor_row = match mode { - EditableMode::MultipleLinesSingleEditor => { - text_editor.char_to_line(text_editor.utf16_cu_to_char(position)) - } - EditableMode::SingleLineMultipleEditors => id, - }; - - let new_cursor_col = match mode { - EditableMode::MultipleLinesSingleEditor => text_editor - .utf16_cu_to_char( - position - - text_editor.char_to_utf16_cu( - text_editor.line_to_char(new_cursor_row), - ), - ), - EditableMode::SingleLineMultipleEditors => { - text_editor.utf16_cu_to_char(position) - } - }; - - let new_current_line = text_editor.line(new_cursor_row).unwrap(); - - // Use the line length as new column if the clicked column surpases the length - let new_cursor = if new_cursor_col >= new_current_line.utf16_len_chars() { - ( - text_editor.utf16_cu_to_char(new_current_line.utf16_len_chars()), - new_cursor_row, - ) - } else { - (new_cursor_col, new_cursor_row) - }; + let new_cursor = text_editor + .measure_new_cursor(text_editor.utf16_cu_to_char(position), id); // Only update and clear the selection if the cursor has changed - if text_editor.cursor().as_tuple() != new_cursor { - text_editor.cursor_mut().set_col(new_cursor.0); - text_editor.cursor_mut().set_row(new_cursor.1); + if *text_editor.cursor() != new_cursor { + *text_editor.cursor_mut() = new_cursor; if let TextDragging::FromCursorToPoint { cursor: from, .. } = dragging() { let to = text_editor.cursor_pos(); diff --git a/crates/hooks/tests/use_editable.rs b/crates/hooks/tests/use_editable.rs index c90377260..751484984 100644 --- a/crates/hooks/tests/use_editable.rs +++ b/crates/hooks/tests/use_editable.rs @@ -11,7 +11,6 @@ pub async fn multiple_lines_single_editor() { ); let cursor_attr = editable.cursor_attr(); let editor = editable.editor().read(); - let cursor = editor.cursor(); let cursor_pos = editor.visible_cursor_pos(); let onmousedown = move |e: MouseEvent| { @@ -45,7 +44,7 @@ pub async fn multiple_lines_single_editor() { label { color: "black", height: "50%", - "{cursor.row()}:{cursor.col()}" + "{editor.cursor_row()}:{editor.cursor_col()}" } } ) @@ -316,7 +315,6 @@ pub async fn highlight_multiple_lines_single_editor() { EditableMode::MultipleLinesSingleEditor, ); let editor = editable.editor().read(); - let cursor = editor.cursor(); let cursor_pos = editor.visible_cursor_pos(); let cursor_reference = editable.cursor_attr(); let highlights = editable.highlights_attr(0); @@ -358,7 +356,7 @@ pub async fn highlight_multiple_lines_single_editor() { label { color: "black", height: "50%", - "{cursor.row()}:{cursor.col()}" + "{editor.cursor_row()}:{editor.cursor_col()}" } } ) @@ -544,7 +542,6 @@ pub async fn special_text_editing() { ); let cursor_attr = editable.cursor_attr(); let editor = editable.editor().read(); - let cursor = editor.cursor(); let cursor_pos = editor.visible_cursor_pos(); let onmousedown = move |e: MouseEvent| { @@ -578,7 +575,7 @@ pub async fn special_text_editing() { label { color: "black", height: "50%", - "{cursor.row()}:{cursor.col()}" + "{editor.cursor_row()}:{editor.cursor_col()}" } } ) @@ -738,7 +735,6 @@ pub async fn backspace_remove() { ); let cursor_attr = editable.cursor_attr(); let editor = editable.editor().read(); - let cursor = editor.cursor(); let cursor_pos = editor.visible_cursor_pos(); let onmousedown = move |e: MouseEvent| { @@ -772,7 +768,7 @@ pub async fn backspace_remove() { label { color: "black", height: "50%", - "{cursor.row()}:{cursor.col()}" + "{editor.cursor_row()}:{editor.cursor_col()}" } } ) @@ -867,7 +863,6 @@ pub async fn highlight_shift_click_multiple_lines_single_editor() { EditableMode::MultipleLinesSingleEditor, ); let editor = editable.editor().read(); - let cursor = editor.cursor(); let cursor_pos = editor.visible_cursor_pos(); let cursor_reference = editable.cursor_attr(); let highlights = editable.highlights_attr(0); @@ -914,7 +909,7 @@ pub async fn highlight_shift_click_multiple_lines_single_editor() { label { color: "black", height: "50%", - "{cursor.row()}:{cursor.col()}" + "{editor.cursor_row()}:{editor.cursor_col()}" } } ) @@ -1134,7 +1129,6 @@ pub async fn highlight_all_text() { EditableMode::MultipleLinesSingleEditor, ); let editor = editable.editor().read(); - let cursor = editor.cursor(); let cursor_pos = editor.visible_cursor_pos(); let cursor_reference = editable.cursor_attr(); let highlights = editable.highlights_attr(0); @@ -1181,7 +1175,7 @@ pub async fn highlight_all_text() { label { color: "black", height: "50%", - "{cursor.row()}:{cursor.col()}" + "{editor.cursor_row()}:{editor.cursor_col()}" } } ) diff --git a/examples/floating_editors.rs b/examples/floating_editors.rs index 4597fa6af..99f586a0d 100644 --- a/examples/floating_editors.rs +++ b/examples/floating_editors.rs @@ -155,7 +155,7 @@ fn Editor() -> Element { ); let cursor_attr = editable.cursor_attr(); let editor = editable.editor().read(); - let cursor = editor.cursor().clone(); + let (cursor_row, cursor_col) = editor.cursor_row_and_col(); let mut font_size_percentage = use_signal(|| 15.0); let mut line_height_percentage = use_signal(|| 0.0); @@ -280,11 +280,11 @@ fn Editor() -> Element { scroll_with_arrows: false, { editor.lines().map(move |l| { - let is_line_selected = cursor.row() == line_index; + let is_line_selected = cursor_row == line_index; // Only show the cursor in the active line let character_index = if is_line_selected { - cursor.col().to_string() + cursor_col.to_string() } else { "none".to_string() }; diff --git a/examples/simple_editor.rs b/examples/simple_editor.rs index 47969f5b7..20c4b6163 100644 --- a/examples/simple_editor.rs +++ b/examples/simple_editor.rs @@ -25,7 +25,6 @@ fn app() -> Element { let cursor_reference = editable.cursor_attr(); let highlights = editable.highlights_attr(0); let editor = editable.editor().read(); - let cursor = editor.cursor(); let cursor_char = editor.visible_cursor_pos(); let onmousedown = move |e: MouseEvent| { @@ -79,7 +78,7 @@ fn app() -> Element { label { color: "black", height: "30", - "{cursor.col()}:{cursor.row()}" + "{editor.cursor_row()}:{editor.cursor_col()}" } } )