mirror of
https://github.com/penpot/penpot.git
synced 2026-02-24 10:47:49 -05:00
Compare commits
1 Commits
alotor-exp
...
azazeln28-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2e689a645 |
@@ -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
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ use skia_safe::{
|
||||
textlayout::ParagraphBuilder,
|
||||
textlayout::ParagraphStyle,
|
||||
textlayout::PositionWithAffinity,
|
||||
textlayout::Affinity,
|
||||
Contains,
|
||||
};
|
||||
|
||||
@@ -112,34 +113,55 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -433,10 +455,12 @@ 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;
|
||||
let mut paragraph_index: usize = 0;
|
||||
// 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 layout_paragraph in layout_paragraphs {
|
||||
paragraph_index += 1;
|
||||
let start_y = offset_y;
|
||||
let end_y = offset_y + layout_paragraph.height();
|
||||
|
||||
@@ -457,16 +481,15 @@ impl TextContent {
|
||||
// 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;
|
||||
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_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;
|
||||
@@ -483,23 +506,23 @@ impl TextContent {
|
||||
&& end_position >= current_position
|
||||
{
|
||||
span_offset =
|
||||
position_with_affinity.position - start_position as i32;
|
||||
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,
|
||||
));
|
||||
}
|
||||
}
|
||||
offset_y += layout_paragraph.height();
|
||||
paragraph_index += 1;
|
||||
}
|
||||
|
||||
// Handle completely empty text shapes: if there are no paragraphs or all paragraphs
|
||||
@@ -516,9 +539,7 @@ impl TextContent {
|
||||
return Some(TextPositionWithAffinity::new(
|
||||
default_position,
|
||||
0, // paragraph 0
|
||||
0, // span 0
|
||||
0, // offset 0
|
||||
0,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Uuid>,
|
||||
pub cursor_visible: bool,
|
||||
pub last_blink_time: f64,
|
||||
pending_events: Vec<EditorEvent>,
|
||||
pending_events: Vec<TextEditorEvent>,
|
||||
}
|
||||
|
||||
impl TextEditorState {
|
||||
@@ -227,18 +203,16 @@ impl TextEditorState {
|
||||
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 +238,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
@@ -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,7 @@ 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 +286,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 +336,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 +384,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 +423,7 @@ 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 +433,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 +494,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 +711,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 +764,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 +776,7 @@ 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<Rect> {
|
||||
fn get_cursor_rect(text_content: &TextContent, cursor: &TextPositionWithAffinity, shape: &Shape) -> Option<Rect> {
|
||||
let paragraphs = text_content.paragraphs();
|
||||
if cursor.paragraph >= paragraphs.len() {
|
||||
return None;
|
||||
@@ -794,7 +794,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 +869,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 +914,40 @@ 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 +955,52 @@ 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 +1028,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<usize> {
|
||||
let paragraphs = text_content.paragraphs_mut();
|
||||
@@ -1048,7 +1048,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 +1063,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.
|
||||
@@ -1079,18 +1079,18 @@ 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,
|
||||
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 +1189,13 @@ 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<TextCursor> {
|
||||
if cursor.char_offset > 0 {
|
||||
fn delete_char_before(text_content: &mut TextContent, cursor: &TextPositionWithAffinity) -> Option<TextPositionWithAffinity> {
|
||||
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 +1211,14 @@ 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 +1226,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 +1241,7 @@ 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 +1249,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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user