mirror of
https://github.com/penpot/penpot.git
synced 2026-02-25 11:19:28 -05:00
Compare commits
1 Commits
develop
...
azazeln28-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a12b59d101 |
@@ -111,7 +111,7 @@ fn calculate_cursor_rect(
|
|||||||
let mut y_offset = vertical_align_offset(shape, &layout_paragraphs);
|
let mut y_offset = vertical_align_offset(shape, &layout_paragraphs);
|
||||||
for (idx, laid_out_para) in layout_paragraphs.iter().enumerate() {
|
for (idx, laid_out_para) in layout_paragraphs.iter().enumerate() {
|
||||||
if idx == cursor.paragraph {
|
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
|
// For cursor, we get a zero-width range at the position
|
||||||
// We need to handle edge cases:
|
// We need to handle edge cases:
|
||||||
// - At start of paragraph: use position 0
|
// - At start of paragraph: use position 0
|
||||||
@@ -209,13 +209,13 @@ fn calculate_selection_rects(
|
|||||||
.sum();
|
.sum();
|
||||||
|
|
||||||
let range_start = if para_idx == start.paragraph {
|
let range_start = if para_idx == start.paragraph {
|
||||||
start.char_offset
|
start.offset
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
let range_end = if para_idx == end.paragraph {
|
let range_end = if para_idx == end.paragraph {
|
||||||
end.char_offset
|
end.offset
|
||||||
} else {
|
} else {
|
||||||
para_char_count
|
para_char_count
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle};
|
|||||||
use skia_safe::{
|
use skia_safe::{
|
||||||
self as skia,
|
self as skia,
|
||||||
paint::{self, Paint},
|
paint::{self, Paint},
|
||||||
|
textlayout::Affinity,
|
||||||
textlayout::ParagraphBuilder,
|
textlayout::ParagraphBuilder,
|
||||||
textlayout::ParagraphStyle,
|
textlayout::ParagraphStyle,
|
||||||
textlayout::PositionWithAffinity,
|
textlayout::PositionWithAffinity,
|
||||||
@@ -112,31 +113,51 @@ impl TextContentSize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct TextPositionWithAffinity {
|
pub struct TextPositionWithAffinity {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub position_with_affinity: PositionWithAffinity,
|
pub position_with_affinity: PositionWithAffinity,
|
||||||
pub paragraph: i32,
|
pub paragraph: usize,
|
||||||
#[allow(dead_code)]
|
pub offset: usize,
|
||||||
pub span: i32,
|
}
|
||||||
#[allow(dead_code)]
|
|
||||||
pub span_relative_offset: i32,
|
impl PartialEq for TextPositionWithAffinity {
|
||||||
pub offset: i32,
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.paragraph == other.paragraph && self.offset == other.offset
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextPositionWithAffinity {
|
impl TextPositionWithAffinity {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
position_with_affinity: PositionWithAffinity,
|
position_with_affinity: PositionWithAffinity,
|
||||||
paragraph: i32,
|
paragraph: usize,
|
||||||
span: i32,
|
offset: usize,
|
||||||
span_relative_offset: i32,
|
|
||||||
offset: i32,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
position_with_affinity,
|
position_with_affinity,
|
||||||
paragraph,
|
paragraph,
|
||||||
span,
|
offset,
|
||||||
span_relative_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,
|
offset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,10 +454,11 @@ impl TextContent {
|
|||||||
let mut offset_y = 0.0;
|
let mut offset_y = 0.0;
|
||||||
let layout_paragraphs = self.layout.paragraphs.iter().flatten();
|
let layout_paragraphs = self.layout.paragraphs.iter().flatten();
|
||||||
|
|
||||||
let mut paragraph_index: i32 = -1;
|
// IMPORTANT! I'm keeping this because I think it should be better to have the span index
|
||||||
let mut span_index: i32 = -1;
|
// cached the same way we keep the paragraph index.
|
||||||
for layout_paragraph in layout_paragraphs {
|
#[allow(dead_code)]
|
||||||
paragraph_index += 1;
|
let mut _span_index: usize = 0;
|
||||||
|
for (paragraph_index, layout_paragraph) in layout_paragraphs.enumerate() {
|
||||||
let start_y = offset_y;
|
let start_y = offset_y;
|
||||||
let end_y = offset_y + layout_paragraph.height();
|
let end_y = offset_y + layout_paragraph.height();
|
||||||
|
|
||||||
@@ -453,20 +475,22 @@ impl TextContent {
|
|||||||
if matches {
|
if matches {
|
||||||
let position_with_affinity =
|
let position_with_affinity =
|
||||||
layout_paragraph.get_glyph_position_at_coordinate(*point);
|
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
|
// Computed position keeps the current position in terms
|
||||||
// of number of characters of text. This is used to know
|
// of number of characters of text. This is used to know
|
||||||
// in which span we are.
|
// in which span we are.
|
||||||
let mut computed_position = 0;
|
let mut computed_position: usize = 0;
|
||||||
let mut span_offset = 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 has no spans, default to span 0, offset 0
|
||||||
if paragraph.children().is_empty() {
|
if paragraph.children().is_empty() {
|
||||||
span_index = 0;
|
_span_index = 0;
|
||||||
span_offset = 0;
|
_span_offset = 0;
|
||||||
} else {
|
} else {
|
||||||
for span in paragraph.children() {
|
for span in paragraph.children() {
|
||||||
span_index += 1;
|
|
||||||
let length = span.text.chars().count();
|
let length = span.text.chars().count();
|
||||||
let start_position = computed_position;
|
let start_position = computed_position;
|
||||||
let end_position = computed_position + length;
|
let end_position = computed_position + length;
|
||||||
@@ -475,27 +499,26 @@ impl TextContent {
|
|||||||
// Handle empty spans: if the span is empty and current position
|
// Handle empty spans: if the span is empty and current position
|
||||||
// matches the start, this is the right span
|
// matches the start, this is the right span
|
||||||
if length == 0 && current_position == start_position {
|
if length == 0 && current_position == start_position {
|
||||||
span_offset = 0;
|
_span_offset = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if start_position <= current_position
|
if start_position <= current_position
|
||||||
&& end_position >= current_position
|
&& end_position >= current_position
|
||||||
{
|
{
|
||||||
span_offset =
|
_span_offset =
|
||||||
position_with_affinity.position - start_position as i32;
|
position_with_affinity.position as usize - start_position;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
computed_position += length;
|
computed_position += length;
|
||||||
|
_span_index += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Some(TextPositionWithAffinity::new(
|
return Some(TextPositionWithAffinity::new(
|
||||||
position_with_affinity,
|
position_with_affinity,
|
||||||
paragraph_index,
|
paragraph_index,
|
||||||
span_index,
|
position_with_affinity.position as usize,
|
||||||
span_offset,
|
|
||||||
position_with_affinity.position,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -516,9 +539,7 @@ impl TextContent {
|
|||||||
return Some(TextPositionWithAffinity::new(
|
return Some(TextPositionWithAffinity::new(
|
||||||
default_position,
|
default_position,
|
||||||
0, // paragraph 0
|
0, // paragraph 0
|
||||||
0, // span 0
|
|
||||||
0, // offset 0
|
0, // offset 0
|
||||||
0,
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,34 +7,10 @@ use skia_safe::{
|
|||||||
Color,
|
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)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct TextSelection {
|
pub struct TextSelection {
|
||||||
pub anchor: TextCursor,
|
pub anchor: TextPositionWithAffinity,
|
||||||
pub focus: TextCursor,
|
pub focus: TextPositionWithAffinity,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextSelection {
|
impl TextSelection {
|
||||||
@@ -42,10 +18,10 @@ impl TextSelection {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_cursor(cursor: TextCursor) -> Self {
|
pub fn from_position_with_affinity(position: TextPositionWithAffinity) -> Self {
|
||||||
Self {
|
Self {
|
||||||
anchor: cursor,
|
anchor: position,
|
||||||
focus: cursor,
|
focus: position,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,12 +33,12 @@ impl TextSelection {
|
|||||||
!self.is_collapsed()
|
!self.is_collapsed()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_caret(&mut self, cursor: TextCursor) {
|
pub fn set_caret(&mut self, cursor: TextPositionWithAffinity) {
|
||||||
self.anchor = cursor;
|
self.anchor = cursor;
|
||||||
self.focus = cursor;
|
self.focus = cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extend_to(&mut self, cursor: TextCursor) {
|
pub fn extend_to(&mut self, cursor: TextPositionWithAffinity) {
|
||||||
self.focus = cursor;
|
self.focus = cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,24 +50,24 @@ impl TextSelection {
|
|||||||
self.focus = self.anchor;
|
self.focus = self.anchor;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&self) -> TextCursor {
|
pub fn start(&self) -> TextPositionWithAffinity {
|
||||||
if self.anchor.paragraph < self.focus.paragraph {
|
if self.anchor.paragraph < self.focus.paragraph {
|
||||||
self.anchor
|
self.anchor
|
||||||
} else if self.anchor.paragraph > self.focus.paragraph {
|
} else if self.anchor.paragraph > self.focus.paragraph {
|
||||||
self.focus
|
self.focus
|
||||||
} else if self.anchor.char_offset <= self.focus.char_offset {
|
} else if self.anchor.offset <= self.focus.offset {
|
||||||
self.anchor
|
self.anchor
|
||||||
} else {
|
} else {
|
||||||
self.focus
|
self.focus
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn end(&self) -> TextCursor {
|
pub fn end(&self) -> TextPositionWithAffinity {
|
||||||
if self.anchor.paragraph > self.focus.paragraph {
|
if self.anchor.paragraph > self.focus.paragraph {
|
||||||
self.anchor
|
self.anchor
|
||||||
} else if self.anchor.paragraph < self.focus.paragraph {
|
} else if self.anchor.paragraph < self.focus.paragraph {
|
||||||
self.focus
|
self.focus
|
||||||
} else if self.anchor.char_offset >= self.focus.char_offset {
|
} else if self.anchor.offset >= self.focus.offset {
|
||||||
self.anchor
|
self.anchor
|
||||||
} else {
|
} else {
|
||||||
self.focus
|
self.focus
|
||||||
@@ -102,7 +78,7 @@ impl TextSelection {
|
|||||||
/// Events that the text editor can emit for frontend synchronization
|
/// Events that the text editor can emit for frontend synchronization
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum EditorEvent {
|
pub enum TextEditorEvent {
|
||||||
None = 0,
|
None = 0,
|
||||||
ContentChanged = 1,
|
ContentChanged = 1,
|
||||||
SelectionChanged = 2,
|
SelectionChanged = 2,
|
||||||
@@ -131,7 +107,7 @@ pub struct TextEditorState {
|
|||||||
pub active_shape_id: Option<Uuid>,
|
pub active_shape_id: Option<Uuid>,
|
||||||
pub cursor_visible: bool,
|
pub cursor_visible: bool,
|
||||||
pub last_blink_time: f64,
|
pub last_blink_time: f64,
|
||||||
pending_events: Vec<EditorEvent>,
|
pending_events: Vec<TextEditorEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextEditorState {
|
impl TextEditorState {
|
||||||
@@ -189,56 +165,44 @@ impl TextEditorState {
|
|||||||
|
|
||||||
pub fn select_all(&mut self, content: &TextContent) -> bool {
|
pub fn select_all(&mut self, content: &TextContent) -> bool {
|
||||||
self.is_pointer_selection_active = false;
|
self.is_pointer_selection_active = false;
|
||||||
self.set_caret_from_position(TextPositionWithAffinity::new(
|
self.set_caret_from_position(&TextPositionWithAffinity::empty());
|
||||||
PositionWithAffinity {
|
let num_paragraphs = content.paragraphs().len() - 1;
|
||||||
position: 0,
|
|
||||||
affinity: Affinity::Downstream,
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
));
|
|
||||||
let num_paragraphs = (content.paragraphs().len() - 1) as i32;
|
|
||||||
let Some(last_paragraph) = content.paragraphs().last() else {
|
let Some(last_paragraph) = content.paragraphs().last() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let num_spans = (last_paragraph.children().len() - 1) as i32;
|
#[allow(dead_code)]
|
||||||
let Some(last_text_span) = last_paragraph.children().last() else {
|
let _num_spans = last_paragraph.children().len() - 1;
|
||||||
|
let Some(_last_text_span) = last_paragraph.children().last() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
for span in last_paragraph.children() {
|
for span in last_paragraph.children() {
|
||||||
offset += span.text.len();
|
offset += span.text.len();
|
||||||
}
|
}
|
||||||
self.extend_selection_from_position(TextPositionWithAffinity::new(
|
self.extend_selection_from_position(&TextPositionWithAffinity::new(
|
||||||
PositionWithAffinity {
|
PositionWithAffinity {
|
||||||
position: offset as i32,
|
position: offset as i32,
|
||||||
affinity: Affinity::Upstream,
|
affinity: Affinity::Upstream,
|
||||||
},
|
},
|
||||||
num_paragraphs,
|
num_paragraphs,
|
||||||
num_spans,
|
offset,
|
||||||
last_text_span.text.len() as i32,
|
|
||||||
offset as i32,
|
|
||||||
));
|
));
|
||||||
self.reset_blink();
|
self.reset_blink();
|
||||||
self.push_event(crate::state::EditorEvent::SelectionChanged);
|
self.push_event(crate::state::TextEditorEvent::SelectionChanged);
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_caret_from_position(&mut self, position: TextPositionWithAffinity) {
|
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(*position);
|
||||||
self.selection.set_caret(cursor);
|
|
||||||
self.reset_blink();
|
self.reset_blink();
|
||||||
self.push_event(EditorEvent::SelectionChanged);
|
self.push_event(TextEditorEvent::SelectionChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extend_selection_from_position(&mut self, position: TextPositionWithAffinity) {
|
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(*position);
|
||||||
self.selection.extend_to(cursor);
|
|
||||||
self.reset_blink();
|
self.reset_blink();
|
||||||
self.push_event(EditorEvent::SelectionChanged);
|
self.push_event(TextEditorEvent::SelectionChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_blink(&mut self, timestamp_ms: f64) {
|
pub fn update_blink(&mut self, timestamp_ms: f64) {
|
||||||
@@ -264,41 +228,17 @@ impl TextEditorState {
|
|||||||
self.last_blink_time = 0.0;
|
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) {
|
if self.pending_events.last() != Some(&event) {
|
||||||
self.pending_events.push(event);
|
self.pending_events.push(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll_event(&mut self) -> EditorEvent {
|
pub fn poll_event(&mut self) -> TextEditorEvent {
|
||||||
self.pending_events.pop().unwrap_or(EditorEvent::None)
|
self.pending_events.pop().unwrap_or(TextEditorEvent::None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_pending_events(&self) -> bool {
|
pub fn has_pending_events(&self) -> bool {
|
||||||
!self.pending_events.is_empty()
|
!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::math::{Matrix, Point, Rect};
|
||||||
use crate::mem;
|
use crate::mem;
|
||||||
use crate::shapes::{Paragraph, Shape, TextContent, Type, VerticalAlign};
|
use crate::shapes::{Paragraph, Shape, TextContent, TextPositionWithAffinity, Type, VerticalAlign};
|
||||||
use crate::state::{TextCursor, TextSelection};
|
use crate::state::TextSelection;
|
||||||
use crate::utils::uuid_from_u32_quartet;
|
use crate::utils::uuid_from_u32_quartet;
|
||||||
use crate::utils::uuid_to_u32_quartet;
|
use crate::utils::uuid_to_u32_quartet;
|
||||||
use crate::{with_state, with_state_mut, STATE};
|
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) =
|
if let Some(position) =
|
||||||
text_content.get_caret_position_from_screen_coords(&point, &view_matrix, &shape_matrix)
|
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
|
state
|
||||||
.text_editor_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
|
state
|
||||||
.text_editor_state
|
.text_editor_state
|
||||||
.extend_selection_from_position(position);
|
.extend_selection_from_position(&position);
|
||||||
}
|
}
|
||||||
state.text_editor_state.stop_pointer_selection();
|
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) =
|
if let Some(position) =
|
||||||
text_content.get_caret_position_from_screen_coords(&point, &view_matrix, &shape_matrix)
|
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;
|
let cursor = state.text_editor_state.selection.focus;
|
||||||
|
|
||||||
if let Some(new_offset) = insert_text_at_cursor(text_content, &cursor, &text) {
|
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);
|
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.reset_blink();
|
||||||
state
|
state
|
||||||
.text_editor_state
|
.text_editor_state
|
||||||
.push_event(crate::state::EditorEvent::ContentChanged);
|
.push_event(crate::state::TextEditorEvent::ContentChanged);
|
||||||
state
|
state
|
||||||
.text_editor_state
|
.text_editor_state
|
||||||
.push_event(crate::state::EditorEvent::NeedsLayout);
|
.push_event(crate::state::TextEditorEvent::NeedsLayout);
|
||||||
|
|
||||||
state.render_state.mark_touched(shape_id);
|
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.reset_blink();
|
||||||
state
|
state
|
||||||
.text_editor_state
|
.text_editor_state
|
||||||
.push_event(crate::state::EditorEvent::ContentChanged);
|
.push_event(crate::state::TextEditorEvent::ContentChanged);
|
||||||
state
|
state
|
||||||
.text_editor_state
|
.text_editor_state
|
||||||
.push_event(crate::state::EditorEvent::NeedsLayout);
|
.push_event(crate::state::TextEditorEvent::NeedsLayout);
|
||||||
|
|
||||||
state.render_state.mark_touched(shape_id);
|
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.reset_blink();
|
||||||
state
|
state
|
||||||
.text_editor_state
|
.text_editor_state
|
||||||
.push_event(crate::state::EditorEvent::ContentChanged);
|
.push_event(crate::state::TextEditorEvent::ContentChanged);
|
||||||
state
|
state
|
||||||
.text_editor_state
|
.text_editor_state
|
||||||
.push_event(crate::state::EditorEvent::NeedsLayout);
|
.push_event(crate::state::TextEditorEvent::NeedsLayout);
|
||||||
|
|
||||||
state.render_state.mark_touched(shape_id);
|
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;
|
let cursor = state.text_editor_state.selection.focus;
|
||||||
|
|
||||||
if split_paragraph_at_cursor(text_content, &cursor) {
|
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);
|
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.reset_blink();
|
||||||
state
|
state
|
||||||
.text_editor_state
|
.text_editor_state
|
||||||
.push_event(crate::state::EditorEvent::ContentChanged);
|
.push_event(crate::state::TextEditorEvent::ContentChanged);
|
||||||
state
|
state
|
||||||
.text_editor_state
|
.text_editor_state
|
||||||
.push_event(crate::state::EditorEvent::NeedsLayout);
|
.push_event(crate::state::TextEditorEvent::NeedsLayout);
|
||||||
|
|
||||||
state.render_state.mark_touched(shape_id);
|
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.reset_blink();
|
||||||
state
|
state
|
||||||
.text_editor_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())
|
.map(|span| span.text.chars().count())
|
||||||
.sum();
|
.sum();
|
||||||
let range_start = if para_idx == start.paragraph {
|
let range_start = if para_idx == start.paragraph {
|
||||||
start.char_offset
|
start.offset
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
let range_end = if para_idx == end.paragraph {
|
let range_end = if para_idx == end.paragraph {
|
||||||
end.char_offset
|
end.offset
|
||||||
} else {
|
} else {
|
||||||
para_char_count
|
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;
|
let sel = &state.text_editor_state.selection;
|
||||||
unsafe {
|
unsafe {
|
||||||
*buffer_ptr = sel.anchor.paragraph as u32;
|
*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(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
|
1
|
||||||
})
|
})
|
||||||
@@ -776,7 +778,11 @@ pub extern "C" fn text_editor_get_selection(buffer_ptr: *mut u32) -> u32 {
|
|||||||
// HELPERS: Cursor & Selection
|
// 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();
|
let paragraphs = text_content.paragraphs();
|
||||||
if cursor.paragraph >= paragraphs.len() {
|
if cursor.paragraph >= paragraphs.len() {
|
||||||
return None;
|
return None;
|
||||||
@@ -794,7 +800,7 @@ fn get_cursor_rect(text_content: &TextContent, cursor: &TextCursor, shape: &Shap
|
|||||||
let mut y_offset = valign_offset;
|
let mut y_offset = valign_offset;
|
||||||
for (idx, laid_out_para) in layout_paragraphs.iter().enumerate() {
|
for (idx, laid_out_para) in layout_paragraphs.iter().enumerate() {
|
||||||
if idx == cursor.paragraph {
|
if idx == cursor.paragraph {
|
||||||
let char_pos = cursor.char_offset;
|
let char_pos = cursor.offset;
|
||||||
|
|
||||||
use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle};
|
use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle};
|
||||||
let rects = laid_out_para.get_rects_for_range(
|
let rects = laid_out_para.get_rects_for_range(
|
||||||
@@ -869,13 +875,13 @@ fn get_selection_rects(
|
|||||||
.map(|span| span.text.chars().count())
|
.map(|span| span.text.chars().count())
|
||||||
.sum();
|
.sum();
|
||||||
let range_start = if para_idx == start.paragraph {
|
let range_start = if para_idx == start.paragraph {
|
||||||
start.char_offset
|
start.offset
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
let range_end = if para_idx == end.paragraph {
|
let range_end = if para_idx == end.paragraph {
|
||||||
end.char_offset
|
end.offset
|
||||||
} else {
|
} else {
|
||||||
para_char_count
|
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.
|
/// 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() {
|
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 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.
|
/// Move cursor left by one character.
|
||||||
fn move_cursor_backward(cursor: &TextCursor, paragraphs: &[Paragraph]) -> TextCursor {
|
fn move_cursor_backward(
|
||||||
if cursor.char_offset > 0 {
|
cursor: &TextPositionWithAffinity,
|
||||||
TextCursor::new(cursor.paragraph, cursor.char_offset - 1)
|
paragraphs: &[Paragraph],
|
||||||
|
) -> TextPositionWithAffinity {
|
||||||
|
if cursor.offset > 0 {
|
||||||
|
TextPositionWithAffinity::new_without_affinity(cursor.paragraph, cursor.offset - 1)
|
||||||
} else if cursor.paragraph > 0 {
|
} else if cursor.paragraph > 0 {
|
||||||
let prev_para = cursor.paragraph - 1;
|
let prev_para = cursor.paragraph - 1;
|
||||||
let char_count = paragraph_char_count(¶graphs[prev_para]);
|
let char_count = paragraph_char_count(¶graphs[prev_para]);
|
||||||
TextCursor::new(prev_para, char_count)
|
TextPositionWithAffinity::new_without_affinity(prev_para, char_count)
|
||||||
} else {
|
} else {
|
||||||
*cursor
|
*cursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move cursor right by one character.
|
/// 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 para = ¶graphs[cursor.paragraph];
|
||||||
let char_count = paragraph_char_count(para);
|
let char_count = paragraph_char_count(para);
|
||||||
|
|
||||||
if cursor.char_offset < char_count {
|
if cursor.offset < char_count {
|
||||||
TextCursor::new(cursor.paragraph, cursor.char_offset + 1)
|
TextPositionWithAffinity::new_without_affinity(cursor.paragraph, cursor.offset + 1)
|
||||||
} else if cursor.paragraph < paragraphs.len() - 1 {
|
} else if cursor.paragraph < paragraphs.len() - 1 {
|
||||||
TextCursor::new(cursor.paragraph + 1, 0)
|
TextPositionWithAffinity::new_without_affinity(cursor.paragraph + 1, 0)
|
||||||
} else {
|
} else {
|
||||||
*cursor
|
*cursor
|
||||||
}
|
}
|
||||||
@@ -955,52 +970,58 @@ fn move_cursor_forward(cursor: &TextCursor, paragraphs: &[Paragraph]) -> TextCur
|
|||||||
|
|
||||||
/// Move cursor up by one line.
|
/// Move cursor up by one line.
|
||||||
fn move_cursor_up(
|
fn move_cursor_up(
|
||||||
cursor: &TextCursor,
|
cursor: &TextPositionWithAffinity,
|
||||||
paragraphs: &[Paragraph],
|
paragraphs: &[Paragraph],
|
||||||
_text_content: &TextContent,
|
_text_content: &TextContent,
|
||||||
_shape: &Shape,
|
_shape: &Shape,
|
||||||
) -> TextCursor {
|
) -> TextPositionWithAffinity {
|
||||||
// TODO: Implement proper line-based navigation using line metrics
|
// TODO: Implement proper line-based navigation using line metrics
|
||||||
if cursor.paragraph > 0 {
|
if cursor.paragraph > 0 {
|
||||||
let prev_para = cursor.paragraph - 1;
|
let prev_para = cursor.paragraph - 1;
|
||||||
let char_count = paragraph_char_count(¶graphs[prev_para]);
|
let char_count = paragraph_char_count(¶graphs[prev_para]);
|
||||||
let new_offset = cursor.char_offset.min(char_count);
|
let new_offset = cursor.offset.min(char_count);
|
||||||
TextCursor::new(prev_para, new_offset)
|
TextPositionWithAffinity::new_without_affinity(prev_para, new_offset)
|
||||||
} else {
|
} else {
|
||||||
TextCursor::new(cursor.paragraph, 0)
|
TextPositionWithAffinity::new_without_affinity(cursor.paragraph, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move cursor down by one line.
|
/// Move cursor down by one line.
|
||||||
fn move_cursor_down(
|
fn move_cursor_down(
|
||||||
cursor: &TextCursor,
|
cursor: &TextPositionWithAffinity,
|
||||||
paragraphs: &[Paragraph],
|
paragraphs: &[Paragraph],
|
||||||
_text_content: &TextContent,
|
_text_content: &TextContent,
|
||||||
_shape: &Shape,
|
_shape: &Shape,
|
||||||
) -> TextCursor {
|
) -> TextPositionWithAffinity {
|
||||||
// TODO: Implement proper line-based navigation using line metrics
|
// TODO: Implement proper line-based navigation using line metrics
|
||||||
if cursor.paragraph < paragraphs.len() - 1 {
|
if cursor.paragraph < paragraphs.len() - 1 {
|
||||||
let next_para = cursor.paragraph + 1;
|
let next_para = cursor.paragraph + 1;
|
||||||
let char_count = paragraph_char_count(¶graphs[next_para]);
|
let char_count = paragraph_char_count(¶graphs[next_para]);
|
||||||
let new_offset = cursor.char_offset.min(char_count);
|
let new_offset = cursor.offset.min(char_count);
|
||||||
TextCursor::new(next_para, new_offset)
|
TextPositionWithAffinity::new_without_affinity(next_para, new_offset)
|
||||||
} else {
|
} else {
|
||||||
let char_count = paragraph_char_count(¶graphs[cursor.paragraph]);
|
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.
|
/// 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
|
// 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.
|
/// 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
|
// TODO: Implement proper line-end using line metrics
|
||||||
let char_count = paragraph_char_count(¶graphs[cursor.paragraph]);
|
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.
|
/// Insert text at a cursor position. Returns the new character offset after insertion.
|
||||||
fn insert_text_at_cursor(
|
fn insert_text_at_cursor(
|
||||||
text_content: &mut TextContent,
|
text_content: &mut TextContent,
|
||||||
cursor: &TextCursor,
|
cursor: &TextPositionWithAffinity,
|
||||||
text: &str,
|
text: &str,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
let paragraphs = text_content.paragraphs_mut();
|
let paragraphs = text_content.paragraphs_mut();
|
||||||
@@ -1048,7 +1069,7 @@ fn insert_text_at_cursor(
|
|||||||
return Some(text.chars().count());
|
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 children = para.children_mut();
|
||||||
let span = &mut children[span_idx];
|
let span = &mut children[span_idx];
|
||||||
@@ -1063,7 +1084,7 @@ fn insert_text_at_cursor(
|
|||||||
new_text.insert_str(byte_offset, text);
|
new_text.insert_str(byte_offset, text);
|
||||||
span.set_text(new_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.
|
/// 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 {
|
if start.paragraph == end.paragraph {
|
||||||
delete_range_in_paragraph(
|
delete_range_in_paragraph(&mut paragraphs[start.paragraph], start.offset, end.offset);
|
||||||
&mut paragraphs[start.paragraph],
|
|
||||||
start.char_offset,
|
|
||||||
end.char_offset,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
let start_para_len = paragraph_char_count(¶graphs[start.paragraph]);
|
let start_para_len = paragraph_char_count(¶graphs[start.paragraph]);
|
||||||
delete_range_in_paragraph(
|
delete_range_in_paragraph(
|
||||||
&mut paragraphs[start.paragraph],
|
&mut paragraphs[start.paragraph],
|
||||||
start.char_offset,
|
start.offset,
|
||||||
start_para_len,
|
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() {
|
if end.paragraph < paragraphs.len() {
|
||||||
let end_para_children: Vec<_> =
|
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.
|
/// Delete the character before the cursor. Returns the new cursor position.
|
||||||
fn delete_char_before(text_content: &mut TextContent, cursor: &TextCursor) -> Option<TextCursor> {
|
fn delete_char_before(
|
||||||
if cursor.char_offset > 0 {
|
text_content: &mut TextContent,
|
||||||
|
cursor: &TextPositionWithAffinity,
|
||||||
|
) -> Option<TextPositionWithAffinity> {
|
||||||
|
if cursor.offset > 0 {
|
||||||
let paragraphs = text_content.paragraphs_mut();
|
let paragraphs = text_content.paragraphs_mut();
|
||||||
let para = &mut paragraphs[cursor.paragraph];
|
let para = &mut paragraphs[cursor.paragraph];
|
||||||
let delete_pos = cursor.char_offset - 1;
|
let delete_pos = cursor.offset - 1;
|
||||||
delete_range_in_paragraph(para, delete_pos, cursor.char_offset);
|
delete_range_in_paragraph(para, delete_pos, cursor.offset);
|
||||||
Some(TextCursor::new(cursor.paragraph, delete_pos))
|
Some(TextPositionWithAffinity::new_without_affinity(
|
||||||
|
cursor.paragraph,
|
||||||
|
delete_pos,
|
||||||
|
))
|
||||||
} else if cursor.paragraph > 0 {
|
} else if cursor.paragraph > 0 {
|
||||||
let prev_para_idx = cursor.paragraph - 1;
|
let prev_para_idx = cursor.paragraph - 1;
|
||||||
let paragraphs = text_content.paragraphs_mut();
|
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);
|
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 {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete the character after the cursor.
|
/// 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();
|
let paragraphs = text_content.paragraphs_mut();
|
||||||
if cursor.paragraph >= paragraphs.len() {
|
if cursor.paragraph >= paragraphs.len() {
|
||||||
return;
|
return;
|
||||||
@@ -1226,9 +1252,9 @@ fn delete_char_after(text_content: &mut TextContent, cursor: &TextCursor) {
|
|||||||
|
|
||||||
let para_len = paragraph_char_count(¶graphs[cursor.paragraph]);
|
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];
|
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 {
|
} else if cursor.paragraph < paragraphs.len() - 1 {
|
||||||
let next_para_idx = cursor.paragraph + 1;
|
let next_para_idx = cursor.paragraph + 1;
|
||||||
let next_children: Vec<_> = paragraphs[next_para_idx].children_mut().drain(..).collect();
|
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.
|
/// 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();
|
let paragraphs = text_content.paragraphs_mut();
|
||||||
if cursor.paragraph >= paragraphs.len() {
|
if cursor.paragraph >= paragraphs.len() {
|
||||||
return false;
|
return false;
|
||||||
@@ -1249,7 +1278,7 @@ fn split_paragraph_at_cursor(text_content: &mut TextContent, cursor: &TextCursor
|
|||||||
|
|
||||||
let para = ¶graphs[cursor.paragraph];
|
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;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user