diff --git a/render-wasm/src/render/text_editor.rs b/render-wasm/src/render/text_editor.rs index 8d4243f80b..2cf7eeab0c 100644 --- a/render-wasm/src/render/text_editor.rs +++ b/render-wasm/src/render/text_editor.rs @@ -111,7 +111,7 @@ fn calculate_cursor_rect( let mut y_offset = vertical_align_offset(shape, &layout_paragraphs); for (idx, laid_out_para) in layout_paragraphs.iter().enumerate() { if idx == cursor.paragraph { - let char_pos = cursor.char_offset; + let char_pos = cursor.offset; // For cursor, we get a zero-width range at the position // We need to handle edge cases: // - At start of paragraph: use position 0 @@ -209,13 +209,13 @@ fn calculate_selection_rects( .sum(); let range_start = if para_idx == start.paragraph { - start.char_offset + start.offset } else { 0 }; let range_end = if para_idx == end.paragraph { - end.char_offset + end.offset } else { para_char_count }; diff --git a/render-wasm/src/shapes/text.rs b/render-wasm/src/shapes/text.rs index 1cad369768..b18a7b4aab 100644 --- a/render-wasm/src/shapes/text.rs +++ b/render-wasm/src/shapes/text.rs @@ -11,6 +11,7 @@ use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle}; use skia_safe::{ self as skia, paint::{self, Paint}, + textlayout::Affinity, textlayout::ParagraphBuilder, textlayout::ParagraphStyle, textlayout::PositionWithAffinity, @@ -112,31 +113,51 @@ impl TextContentSize { } } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Clone, Copy, Default)] pub struct TextPositionWithAffinity { #[allow(dead_code)] pub position_with_affinity: PositionWithAffinity, - pub paragraph: i32, - #[allow(dead_code)] - pub span: i32, - #[allow(dead_code)] - pub span_relative_offset: i32, - pub offset: i32, + pub paragraph: usize, + pub offset: usize, +} + +impl PartialEq for TextPositionWithAffinity { + fn eq(&self, other: &Self) -> bool { + self.paragraph == other.paragraph && self.offset == other.offset + } } impl TextPositionWithAffinity { pub fn new( position_with_affinity: PositionWithAffinity, - paragraph: i32, - span: i32, - span_relative_offset: i32, - offset: i32, + paragraph: usize, + offset: usize, ) -> Self { Self { position_with_affinity, paragraph, - span, - span_relative_offset, + offset, + } + } + + pub fn empty() -> Self { + Self { + position_with_affinity: PositionWithAffinity { + position: 0, + affinity: Affinity::Downstream, + }, + paragraph: 0, + offset: 0, + } + } + + pub fn new_without_affinity(paragraph: usize, offset: usize) -> Self { + Self { + position_with_affinity: PositionWithAffinity { + position: offset as i32, + affinity: Affinity::Downstream, + }, + paragraph, offset, } } @@ -433,10 +454,11 @@ impl TextContent { let mut offset_y = 0.0; let layout_paragraphs = self.layout.paragraphs.iter().flatten(); - let mut paragraph_index: i32 = -1; - let mut span_index: i32 = -1; - for layout_paragraph in layout_paragraphs { - paragraph_index += 1; + // IMPORTANT! I'm keeping this because I think it should be better to have the span index + // cached the same way we keep the paragraph index. + #[allow(dead_code)] + let mut _span_index: usize = 0; + for (paragraph_index, layout_paragraph) in layout_paragraphs.enumerate() { let start_y = offset_y; let end_y = offset_y + layout_paragraph.height(); @@ -453,20 +475,22 @@ impl TextContent { if matches { let position_with_affinity = layout_paragraph.get_glyph_position_at_coordinate(*point); - if let Some(paragraph) = self.paragraphs().get(paragraph_index as usize) { + if let Some(paragraph) = self.paragraphs().get(paragraph_index) { // Computed position keeps the current position in terms // of number of characters of text. This is used to know // in which span we are. - let mut computed_position = 0; - let mut span_offset = 0; + let mut computed_position: usize = 0; + + // This could be useful in the future as part of the TextPositionWithAffinity. + #[allow(dead_code)] + let mut _span_offset: usize = 0; // If paragraph has no spans, default to span 0, offset 0 if paragraph.children().is_empty() { - span_index = 0; - span_offset = 0; + _span_index = 0; + _span_offset = 0; } else { for span in paragraph.children() { - span_index += 1; let length = span.text.chars().count(); let start_position = computed_position; let end_position = computed_position + length; @@ -475,27 +499,26 @@ impl TextContent { // Handle empty spans: if the span is empty and current position // matches the start, this is the right span if length == 0 && current_position == start_position { - span_offset = 0; + _span_offset = 0; break; } if start_position <= current_position && end_position >= current_position { - span_offset = - position_with_affinity.position - start_position as i32; + _span_offset = + position_with_affinity.position as usize - start_position; break; } computed_position += length; + _span_index += 1; } } return Some(TextPositionWithAffinity::new( position_with_affinity, paragraph_index, - span_index, - span_offset, - position_with_affinity.position, + position_with_affinity.position as usize, )); } } @@ -516,9 +539,7 @@ impl TextContent { return Some(TextPositionWithAffinity::new( default_position, 0, // paragraph 0 - 0, // span 0 0, // offset 0 - 0, )); } diff --git a/render-wasm/src/state/text_editor.rs b/render-wasm/src/state/text_editor.rs index 8c384db2b3..dc0f4152d4 100644 --- a/render-wasm/src/state/text_editor.rs +++ b/render-wasm/src/state/text_editor.rs @@ -7,34 +7,10 @@ use skia_safe::{ Color, }; -/// Cursor position within text content. -/// Uses character offsets for precise positioning. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] -pub struct TextCursor { - pub paragraph: usize, - pub char_offset: usize, -} - -impl TextCursor { - pub fn new(paragraph: usize, char_offset: usize) -> Self { - Self { - paragraph, - char_offset, - } - } - - pub fn zero() -> Self { - Self { - paragraph: 0, - char_offset: 0, - } - } -} - #[derive(Debug, Clone, Copy, Default)] pub struct TextSelection { - pub anchor: TextCursor, - pub focus: TextCursor, + pub anchor: TextPositionWithAffinity, + pub focus: TextPositionWithAffinity, } impl TextSelection { @@ -42,10 +18,10 @@ impl TextSelection { Self::default() } - pub fn from_cursor(cursor: TextCursor) -> Self { + pub fn from_position_with_affinity(position: TextPositionWithAffinity) -> Self { Self { - anchor: cursor, - focus: cursor, + anchor: position, + focus: position, } } @@ -57,12 +33,12 @@ impl TextSelection { !self.is_collapsed() } - pub fn set_caret(&mut self, cursor: TextCursor) { + pub fn set_caret(&mut self, cursor: TextPositionWithAffinity) { self.anchor = cursor; self.focus = cursor; } - pub fn extend_to(&mut self, cursor: TextCursor) { + pub fn extend_to(&mut self, cursor: TextPositionWithAffinity) { self.focus = cursor; } @@ -74,24 +50,24 @@ impl TextSelection { self.focus = self.anchor; } - pub fn start(&self) -> TextCursor { + pub fn start(&self) -> TextPositionWithAffinity { if self.anchor.paragraph < self.focus.paragraph { self.anchor } else if self.anchor.paragraph > self.focus.paragraph { self.focus - } else if self.anchor.char_offset <= self.focus.char_offset { + } else if self.anchor.offset <= self.focus.offset { self.anchor } else { self.focus } } - pub fn end(&self) -> TextCursor { + pub fn end(&self) -> TextPositionWithAffinity { if self.anchor.paragraph > self.focus.paragraph { self.anchor } else if self.anchor.paragraph < self.focus.paragraph { self.focus - } else if self.anchor.char_offset >= self.focus.char_offset { + } else if self.anchor.offset >= self.focus.offset { self.anchor } else { self.focus @@ -102,7 +78,7 @@ impl TextSelection { /// Events that the text editor can emit for frontend synchronization #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)] -pub enum EditorEvent { +pub enum TextEditorEvent { None = 0, ContentChanged = 1, SelectionChanged = 2, @@ -131,7 +107,7 @@ pub struct TextEditorState { pub active_shape_id: Option, pub cursor_visible: bool, pub last_blink_time: f64, - pending_events: Vec, + pending_events: Vec, } impl TextEditorState { @@ -189,56 +165,44 @@ impl TextEditorState { pub fn select_all(&mut self, content: &TextContent) -> bool { self.is_pointer_selection_active = false; - self.set_caret_from_position(TextPositionWithAffinity::new( - PositionWithAffinity { - position: 0, - affinity: Affinity::Downstream, - }, - 0, - 0, - 0, - 0, - )); - let num_paragraphs = (content.paragraphs().len() - 1) as i32; + self.set_caret_from_position(&TextPositionWithAffinity::empty()); + let num_paragraphs = content.paragraphs().len() - 1; let Some(last_paragraph) = content.paragraphs().last() else { return false; }; - let num_spans = (last_paragraph.children().len() - 1) as i32; - let Some(last_text_span) = last_paragraph.children().last() else { + #[allow(dead_code)] + let _num_spans = last_paragraph.children().len() - 1; + let Some(_last_text_span) = last_paragraph.children().last() else { return false; }; let mut offset = 0; for span in last_paragraph.children() { offset += span.text.len(); } - self.extend_selection_from_position(TextPositionWithAffinity::new( + self.extend_selection_from_position(&TextPositionWithAffinity::new( PositionWithAffinity { position: offset as i32, affinity: Affinity::Upstream, }, num_paragraphs, - num_spans, - last_text_span.text.len() as i32, - offset as i32, + offset, )); self.reset_blink(); - self.push_event(crate::state::EditorEvent::SelectionChanged); + self.push_event(crate::state::TextEditorEvent::SelectionChanged); true } - pub fn set_caret_from_position(&mut self, position: TextPositionWithAffinity) { - let cursor = TextCursor::new(position.paragraph as usize, position.offset as usize); - self.selection.set_caret(cursor); + pub fn set_caret_from_position(&mut self, position: &TextPositionWithAffinity) { + self.selection.set_caret(*position); self.reset_blink(); - self.push_event(EditorEvent::SelectionChanged); + self.push_event(TextEditorEvent::SelectionChanged); } - pub fn extend_selection_from_position(&mut self, position: TextPositionWithAffinity) { - let cursor = TextCursor::new(position.paragraph as usize, position.offset as usize); - self.selection.extend_to(cursor); + pub fn extend_selection_from_position(&mut self, position: &TextPositionWithAffinity) { + self.selection.extend_to(*position); self.reset_blink(); - self.push_event(EditorEvent::SelectionChanged); + self.push_event(TextEditorEvent::SelectionChanged); } pub fn update_blink(&mut self, timestamp_ms: f64) { @@ -264,41 +228,17 @@ impl TextEditorState { self.last_blink_time = 0.0; } - pub fn push_event(&mut self, event: EditorEvent) { + pub fn push_event(&mut self, event: TextEditorEvent) { if self.pending_events.last() != Some(&event) { self.pending_events.push(event); } } - pub fn poll_event(&mut self) -> EditorEvent { - self.pending_events.pop().unwrap_or(EditorEvent::None) + pub fn poll_event(&mut self) -> TextEditorEvent { + self.pending_events.pop().unwrap_or(TextEditorEvent::None) } pub fn has_pending_events(&self) -> bool { !self.pending_events.is_empty() } - - pub fn set_caret_position_from( - &mut self, - text_position_with_affinity: TextPositionWithAffinity, - ) { - self.set_caret_from_position(text_position_with_affinity); - } -} - -/// TODO: Remove legacy code -#[derive(Debug, PartialEq, Clone, Copy)] -pub struct TextNodePosition { - pub paragraph: i32, - pub span: i32, -} - -impl TextNodePosition { - pub fn new(paragraph: i32, span: i32) -> Self { - Self { paragraph, span } - } - - pub fn is_invalid(&self) -> bool { - self.paragraph < 0 || self.span < 0 - } } diff --git a/render-wasm/src/wasm/text_editor.rs b/render-wasm/src/wasm/text_editor.rs index 771ee82627..c7285345e5 100644 --- a/render-wasm/src/wasm/text_editor.rs +++ b/render-wasm/src/wasm/text_editor.rs @@ -1,7 +1,7 @@ use crate::math::{Matrix, Point, Rect}; use crate::mem; -use crate::shapes::{Paragraph, Shape, TextContent, Type, VerticalAlign}; -use crate::state::{TextCursor, TextSelection}; +use crate::shapes::{Paragraph, Shape, TextContent, TextPositionWithAffinity, Type, VerticalAlign}; +use crate::state::TextSelection; use crate::utils::uuid_from_u32_quartet; use crate::utils::uuid_to_u32_quartet; use crate::{with_state, with_state_mut, STATE}; @@ -132,7 +132,7 @@ pub extern "C" fn text_editor_pointer_down(x: f32, y: f32) { if let Some(position) = text_content.get_caret_position_from_screen_coords(&point, &view_matrix, &shape_matrix) { - state.text_editor_state.set_caret_from_position(position); + state.text_editor_state.set_caret_from_position(&position); } }); } @@ -168,7 +168,7 @@ pub extern "C" fn text_editor_pointer_move(x: f32, y: f32) { { state .text_editor_state - .extend_selection_from_position(position); + .extend_selection_from_position(&position); } }); } @@ -203,7 +203,7 @@ pub extern "C" fn text_editor_pointer_up(x: f32, y: f32) { { state .text_editor_state - .extend_selection_from_position(position); + .extend_selection_from_position(&position); } state.text_editor_state.stop_pointer_selection(); }); @@ -231,7 +231,7 @@ pub extern "C" fn text_editor_set_cursor_from_point(x: f32, y: f32) { if let Some(position) = text_content.get_caret_position_from_screen_coords(&point, &view_matrix, &shape_matrix) { - state.text_editor_state.set_caret_from_position(position); + state.text_editor_state.set_caret_from_position(&position); } }); } @@ -276,7 +276,8 @@ pub extern "C" fn text_editor_insert_text() { let cursor = state.text_editor_state.selection.focus; if let Some(new_offset) = insert_text_at_cursor(text_content, &cursor, &text) { - let new_cursor = TextCursor::new(cursor.paragraph, new_offset); + let new_cursor = + TextPositionWithAffinity::new_without_affinity(cursor.paragraph, new_offset); state.text_editor_state.selection.set_caret(new_cursor); } @@ -286,10 +287,10 @@ pub extern "C" fn text_editor_insert_text() { state.text_editor_state.reset_blink(); state .text_editor_state - .push_event(crate::state::EditorEvent::ContentChanged); + .push_event(crate::state::TextEditorEvent::ContentChanged); state .text_editor_state - .push_event(crate::state::EditorEvent::NeedsLayout); + .push_event(crate::state::TextEditorEvent::NeedsLayout); state.render_state.mark_touched(shape_id); }); @@ -336,10 +337,10 @@ pub extern "C" fn text_editor_delete_backward() { state.text_editor_state.reset_blink(); state .text_editor_state - .push_event(crate::state::EditorEvent::ContentChanged); + .push_event(crate::state::TextEditorEvent::ContentChanged); state .text_editor_state - .push_event(crate::state::EditorEvent::NeedsLayout); + .push_event(crate::state::TextEditorEvent::NeedsLayout); state.render_state.mark_touched(shape_id); }); @@ -384,10 +385,10 @@ pub extern "C" fn text_editor_delete_forward() { state.text_editor_state.reset_blink(); state .text_editor_state - .push_event(crate::state::EditorEvent::ContentChanged); + .push_event(crate::state::TextEditorEvent::ContentChanged); state .text_editor_state - .push_event(crate::state::EditorEvent::NeedsLayout); + .push_event(crate::state::TextEditorEvent::NeedsLayout); state.render_state.mark_touched(shape_id); }); @@ -423,7 +424,8 @@ pub extern "C" fn text_editor_insert_paragraph() { let cursor = state.text_editor_state.selection.focus; if split_paragraph_at_cursor(text_content, &cursor) { - let new_cursor = TextCursor::new(cursor.paragraph + 1, 0); + let new_cursor = + TextPositionWithAffinity::new_without_affinity(cursor.paragraph + 1, 0); state.text_editor_state.selection.set_caret(new_cursor); } @@ -433,10 +435,10 @@ pub extern "C" fn text_editor_insert_paragraph() { state.text_editor_state.reset_blink(); state .text_editor_state - .push_event(crate::state::EditorEvent::ContentChanged); + .push_event(crate::state::TextEditorEvent::ContentChanged); state .text_editor_state - .push_event(crate::state::EditorEvent::NeedsLayout); + .push_event(crate::state::TextEditorEvent::NeedsLayout); state.render_state.mark_touched(shape_id); }); @@ -494,7 +496,7 @@ pub extern "C" fn text_editor_move_cursor(direction: CursorDirection, extend_sel state.text_editor_state.reset_blink(); state .text_editor_state - .push_event(crate::state::EditorEvent::SelectionChanged); + .push_event(crate::state::TextEditorEvent::SelectionChanged); }); } @@ -711,12 +713,12 @@ pub extern "C" fn text_editor_export_selection() -> *mut u8 { .map(|span| span.text.chars().count()) .sum(); let range_start = if para_idx == start.paragraph { - start.char_offset + start.offset } else { 0 }; let range_end = if para_idx == end.paragraph { - end.char_offset + end.offset } else { para_char_count }; @@ -764,9 +766,9 @@ pub extern "C" fn text_editor_get_selection(buffer_ptr: *mut u32) -> u32 { let sel = &state.text_editor_state.selection; unsafe { *buffer_ptr = sel.anchor.paragraph as u32; - *buffer_ptr.add(1) = sel.anchor.char_offset as u32; + *buffer_ptr.add(1) = sel.anchor.offset as u32; *buffer_ptr.add(2) = sel.focus.paragraph as u32; - *buffer_ptr.add(3) = sel.focus.char_offset as u32; + *buffer_ptr.add(3) = sel.focus.offset as u32; } 1 }) @@ -776,7 +778,11 @@ pub extern "C" fn text_editor_get_selection(buffer_ptr: *mut u32) -> u32 { // HELPERS: Cursor & Selection // ============================================================================ -fn get_cursor_rect(text_content: &TextContent, cursor: &TextCursor, shape: &Shape) -> Option { +fn get_cursor_rect( + text_content: &TextContent, + cursor: &TextPositionWithAffinity, + shape: &Shape, +) -> Option { let paragraphs = text_content.paragraphs(); if cursor.paragraph >= paragraphs.len() { return None; @@ -794,7 +800,7 @@ fn get_cursor_rect(text_content: &TextContent, cursor: &TextCursor, shape: &Shap let mut y_offset = valign_offset; for (idx, laid_out_para) in layout_paragraphs.iter().enumerate() { if idx == cursor.paragraph { - let char_pos = cursor.char_offset; + let char_pos = cursor.offset; use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle}; let rects = laid_out_para.get_rects_for_range( @@ -869,13 +875,13 @@ fn get_selection_rects( .map(|span| span.text.chars().count()) .sum(); let range_start = if para_idx == start.paragraph { - start.char_offset + start.offset } else { 0 }; let range_end = if para_idx == end.paragraph { - end.char_offset + end.offset } else { para_char_count }; @@ -914,40 +920,49 @@ fn paragraph_char_count(para: &Paragraph) -> usize { } /// Clamp a cursor position to valid bounds within the text content. -fn clamp_cursor(cursor: TextCursor, paragraphs: &[Paragraph]) -> TextCursor { +fn clamp_cursor( + position: TextPositionWithAffinity, + paragraphs: &[Paragraph], +) -> TextPositionWithAffinity { if paragraphs.is_empty() { - return TextCursor::new(0, 0); + return TextPositionWithAffinity::new_without_affinity(0, 0); } - let para_idx = cursor.paragraph.min(paragraphs.len() - 1); + let para_idx = position.paragraph.min(paragraphs.len() - 1); let para_len = paragraph_char_count(¶graphs[para_idx]); - let char_offset = cursor.char_offset.min(para_len); + let char_offset = position.offset.min(para_len); - TextCursor::new(para_idx, char_offset) + TextPositionWithAffinity::new_without_affinity(para_idx, char_offset) } /// Move cursor left by one character. -fn move_cursor_backward(cursor: &TextCursor, paragraphs: &[Paragraph]) -> TextCursor { - if cursor.char_offset > 0 { - TextCursor::new(cursor.paragraph, cursor.char_offset - 1) +fn move_cursor_backward( + cursor: &TextPositionWithAffinity, + paragraphs: &[Paragraph], +) -> TextPositionWithAffinity { + if cursor.offset > 0 { + TextPositionWithAffinity::new_without_affinity(cursor.paragraph, cursor.offset - 1) } else if cursor.paragraph > 0 { let prev_para = cursor.paragraph - 1; let char_count = paragraph_char_count(¶graphs[prev_para]); - TextCursor::new(prev_para, char_count) + TextPositionWithAffinity::new_without_affinity(prev_para, char_count) } else { *cursor } } /// Move cursor right by one character. -fn move_cursor_forward(cursor: &TextCursor, paragraphs: &[Paragraph]) -> TextCursor { +fn move_cursor_forward( + cursor: &TextPositionWithAffinity, + paragraphs: &[Paragraph], +) -> TextPositionWithAffinity { let para = ¶graphs[cursor.paragraph]; let char_count = paragraph_char_count(para); - if cursor.char_offset < char_count { - TextCursor::new(cursor.paragraph, cursor.char_offset + 1) + if cursor.offset < char_count { + TextPositionWithAffinity::new_without_affinity(cursor.paragraph, cursor.offset + 1) } else if cursor.paragraph < paragraphs.len() - 1 { - TextCursor::new(cursor.paragraph + 1, 0) + TextPositionWithAffinity::new_without_affinity(cursor.paragraph + 1, 0) } else { *cursor } @@ -955,52 +970,58 @@ fn move_cursor_forward(cursor: &TextCursor, paragraphs: &[Paragraph]) -> TextCur /// Move cursor up by one line. fn move_cursor_up( - cursor: &TextCursor, + cursor: &TextPositionWithAffinity, paragraphs: &[Paragraph], _text_content: &TextContent, _shape: &Shape, -) -> TextCursor { +) -> TextPositionWithAffinity { // TODO: Implement proper line-based navigation using line metrics if cursor.paragraph > 0 { let prev_para = cursor.paragraph - 1; let char_count = paragraph_char_count(¶graphs[prev_para]); - let new_offset = cursor.char_offset.min(char_count); - TextCursor::new(prev_para, new_offset) + let new_offset = cursor.offset.min(char_count); + TextPositionWithAffinity::new_without_affinity(prev_para, new_offset) } else { - TextCursor::new(cursor.paragraph, 0) + TextPositionWithAffinity::new_without_affinity(cursor.paragraph, 0) } } /// Move cursor down by one line. fn move_cursor_down( - cursor: &TextCursor, + cursor: &TextPositionWithAffinity, paragraphs: &[Paragraph], _text_content: &TextContent, _shape: &Shape, -) -> TextCursor { +) -> TextPositionWithAffinity { // TODO: Implement proper line-based navigation using line metrics if cursor.paragraph < paragraphs.len() - 1 { let next_para = cursor.paragraph + 1; let char_count = paragraph_char_count(¶graphs[next_para]); - let new_offset = cursor.char_offset.min(char_count); - TextCursor::new(next_para, new_offset) + let new_offset = cursor.offset.min(char_count); + TextPositionWithAffinity::new_without_affinity(next_para, new_offset) } else { let char_count = paragraph_char_count(¶graphs[cursor.paragraph]); - TextCursor::new(cursor.paragraph, char_count) + TextPositionWithAffinity::new_without_affinity(cursor.paragraph, char_count) } } /// Move cursor to start of current line. -fn move_cursor_line_start(cursor: &TextCursor, _paragraphs: &[Paragraph]) -> TextCursor { +fn move_cursor_line_start( + cursor: &TextPositionWithAffinity, + _paragraphs: &[Paragraph], +) -> TextPositionWithAffinity { // TODO: Implement proper line-start using line metrics - TextCursor::new(cursor.paragraph, 0) + TextPositionWithAffinity::new_without_affinity(cursor.paragraph, 0) } /// Move cursor to end of current line. -fn move_cursor_line_end(cursor: &TextCursor, paragraphs: &[Paragraph]) -> TextCursor { +fn move_cursor_line_end( + cursor: &TextPositionWithAffinity, + paragraphs: &[Paragraph], +) -> TextPositionWithAffinity { // TODO: Implement proper line-end using line metrics let char_count = paragraph_char_count(¶graphs[cursor.paragraph]); - TextCursor::new(cursor.paragraph, char_count) + TextPositionWithAffinity::new_without_affinity(cursor.paragraph, char_count) } // ============================================================================ @@ -1028,7 +1049,7 @@ fn find_span_at_offset(para: &Paragraph, char_offset: usize) -> Option<(usize, u /// Insert text at a cursor position. Returns the new character offset after insertion. fn insert_text_at_cursor( text_content: &mut TextContent, - cursor: &TextCursor, + cursor: &TextPositionWithAffinity, text: &str, ) -> Option { let paragraphs = text_content.paragraphs_mut(); @@ -1048,7 +1069,7 @@ fn insert_text_at_cursor( return Some(text.chars().count()); } - let (span_idx, offset_in_span) = find_span_at_offset(para, cursor.char_offset)?; + let (span_idx, offset_in_span) = find_span_at_offset(para, cursor.offset)?; let children = para.children_mut(); let span = &mut children[span_idx]; @@ -1063,7 +1084,7 @@ fn insert_text_at_cursor( new_text.insert_str(byte_offset, text); span.set_text(new_text); - Some(cursor.char_offset + text.chars().count()) + Some(cursor.offset + text.chars().count()) } /// Delete a range of text specified by a selection. @@ -1077,20 +1098,16 @@ fn delete_selection_range(text_content: &mut TextContent, selection: &TextSelect } if start.paragraph == end.paragraph { - delete_range_in_paragraph( - &mut paragraphs[start.paragraph], - start.char_offset, - end.char_offset, - ); + delete_range_in_paragraph(&mut paragraphs[start.paragraph], start.offset, end.offset); } else { let start_para_len = paragraph_char_count(¶graphs[start.paragraph]); delete_range_in_paragraph( &mut paragraphs[start.paragraph], - start.char_offset, + start.offset, start_para_len, ); - delete_range_in_paragraph(&mut paragraphs[end.paragraph], 0, end.char_offset); + delete_range_in_paragraph(&mut paragraphs[end.paragraph], 0, end.offset); if end.paragraph < paragraphs.len() { let end_para_children: Vec<_> = @@ -1189,13 +1206,19 @@ fn delete_range_in_paragraph(para: &mut Paragraph, start_offset: usize, end_offs } /// Delete the character before the cursor. Returns the new cursor position. -fn delete_char_before(text_content: &mut TextContent, cursor: &TextCursor) -> Option { - if cursor.char_offset > 0 { +fn delete_char_before( + text_content: &mut TextContent, + cursor: &TextPositionWithAffinity, +) -> Option { + if cursor.offset > 0 { let paragraphs = text_content.paragraphs_mut(); let para = &mut paragraphs[cursor.paragraph]; - let delete_pos = cursor.char_offset - 1; - delete_range_in_paragraph(para, delete_pos, cursor.char_offset); - Some(TextCursor::new(cursor.paragraph, delete_pos)) + let delete_pos = cursor.offset - 1; + delete_range_in_paragraph(para, delete_pos, cursor.offset); + Some(TextPositionWithAffinity::new_without_affinity( + cursor.paragraph, + delete_pos, + )) } else if cursor.paragraph > 0 { let prev_para_idx = cursor.paragraph - 1; let paragraphs = text_content.paragraphs_mut(); @@ -1211,14 +1234,17 @@ fn delete_char_before(text_content: &mut TextContent, cursor: &TextCursor) -> Op paragraphs.remove(cursor.paragraph); - Some(TextCursor::new(prev_para_idx, prev_para_len)) + Some(TextPositionWithAffinity::new_without_affinity( + prev_para_idx, + prev_para_len, + )) } else { None } } /// Delete the character after the cursor. -fn delete_char_after(text_content: &mut TextContent, cursor: &TextCursor) { +fn delete_char_after(text_content: &mut TextContent, cursor: &TextPositionWithAffinity) { let paragraphs = text_content.paragraphs_mut(); if cursor.paragraph >= paragraphs.len() { return; @@ -1226,9 +1252,9 @@ fn delete_char_after(text_content: &mut TextContent, cursor: &TextCursor) { let para_len = paragraph_char_count(¶graphs[cursor.paragraph]); - if cursor.char_offset < para_len { + if cursor.offset < para_len { let para = &mut paragraphs[cursor.paragraph]; - delete_range_in_paragraph(para, cursor.char_offset, cursor.char_offset + 1); + delete_range_in_paragraph(para, cursor.offset, cursor.offset + 1); } else if cursor.paragraph < paragraphs.len() - 1 { let next_para_idx = cursor.paragraph + 1; let next_children: Vec<_> = paragraphs[next_para_idx].children_mut().drain(..).collect(); @@ -1241,7 +1267,10 @@ fn delete_char_after(text_content: &mut TextContent, cursor: &TextCursor) { } /// Split a paragraph at the cursor position. Returns true if split was successful. -fn split_paragraph_at_cursor(text_content: &mut TextContent, cursor: &TextCursor) -> bool { +fn split_paragraph_at_cursor( + text_content: &mut TextContent, + cursor: &TextPositionWithAffinity, +) -> bool { let paragraphs = text_content.paragraphs_mut(); if cursor.paragraph >= paragraphs.len() { return false; @@ -1249,7 +1278,7 @@ fn split_paragraph_at_cursor(text_content: &mut TextContent, cursor: &TextCursor let para = ¶graphs[cursor.paragraph]; - let Some((span_idx, offset_in_span)) = find_span_at_offset(para, cursor.char_offset) else { + let Some((span_idx, offset_in_span)) = find_span_at_offset(para, cursor.offset) else { return false; };