mirror of
https://github.com/penpot/penpot.git
synced 2026-09-08 13:09:22 +00:00
🐛 Fix paragraph selection (#11493)
This commit is contained in:
parent
fb22c1547c
commit
eb8d3a2fef
@ -159,6 +159,10 @@
|
|||||||
(or (.-isComposing native)
|
(or (.-isComposing native)
|
||||||
(= 229 (.-keyCode event)))))
|
(= 229 (.-keyCode event)))))
|
||||||
|
|
||||||
|
(defn- triple-click?
|
||||||
|
[^js native-event]
|
||||||
|
(>= (.-detail native-event) 3))
|
||||||
|
|
||||||
(defn- input-surface-class
|
(defn- input-surface-class
|
||||||
"Class list for the contenteditable capture surface.
|
"Class list for the contenteditable capture surface.
|
||||||
|
|
||||||
@ -492,7 +496,9 @@
|
|||||||
(fn [^js event]
|
(fn [^js event]
|
||||||
(let [native-event (dom/event->native-event event)
|
(let [native-event (dom/event->native-event event)
|
||||||
off-pt (dom/get-offset-position native-event)]
|
off-pt (dom/get-offset-position native-event)]
|
||||||
(wasm.api/text-editor-set-cursor-from-offset off-pt)
|
(if (triple-click? native-event)
|
||||||
|
(wasm.api/text-editor-select-paragraph off-pt)
|
||||||
|
(wasm.api/text-editor-set-cursor-from-offset off-pt))
|
||||||
(wasm.api/render-text-editor-overlay!))))
|
(wasm.api/render-text-editor-overlay!))))
|
||||||
|
|
||||||
on-double-click
|
on-double-click
|
||||||
|
|||||||
@ -311,6 +311,7 @@
|
|||||||
(def text-editor-get-active-shape-id text-editor/text-editor-get-active-shape-id)
|
(def text-editor-get-active-shape-id text-editor/text-editor-get-active-shape-id)
|
||||||
(def text-editor-select-all text-editor/text-editor-select-all)
|
(def text-editor-select-all text-editor/text-editor-select-all)
|
||||||
(def text-editor-select-word-boundary text-editor/text-editor-select-word-boundary)
|
(def text-editor-select-word-boundary text-editor/text-editor-select-word-boundary)
|
||||||
|
(def text-editor-select-paragraph text-editor/text-editor-select-paragraph)
|
||||||
(def text-editor-sync-content text-editor/text-editor-sync-content)
|
(def text-editor-sync-content text-editor/text-editor-sync-content)
|
||||||
|
|
||||||
(def dpr
|
(def dpr
|
||||||
|
|||||||
@ -450,6 +450,11 @@
|
|||||||
(when (wasm/ready?)
|
(when (wasm/ready?)
|
||||||
(h/call wasm/internal-module "_text_editor_select_word_boundary" x y)))
|
(h/call wasm/internal-module "_text_editor_select_word_boundary" x y)))
|
||||||
|
|
||||||
|
(defn text-editor-select-paragraph
|
||||||
|
[{:keys [x y]}]
|
||||||
|
(when (wasm/ready?)
|
||||||
|
(h/call wasm/internal-module "_text_editor_select_paragraph" x y)))
|
||||||
|
|
||||||
(defn text-editor-blur
|
(defn text-editor-blur
|
||||||
[]
|
[]
|
||||||
(when (wasm/ready?)
|
(when (wasm/ready?)
|
||||||
|
|||||||
@ -192,7 +192,7 @@ impl TextPositionWithAffinity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_without_affinity(paragraph: usize, offset: usize) -> Self {
|
pub fn new_downstream_affinity(paragraph: usize, offset: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
position_with_affinity: PositionWithAffinity {
|
position_with_affinity: PositionWithAffinity {
|
||||||
position: offset as i32,
|
position: offset as i32,
|
||||||
@ -203,6 +203,17 @@ impl TextPositionWithAffinity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_upstream_affinity(paragraph: usize, offset: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
position_with_affinity: PositionWithAffinity {
|
||||||
|
position: offset as i32,
|
||||||
|
affinity: Affinity::Upstream,
|
||||||
|
},
|
||||||
|
paragraph,
|
||||||
|
offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.position_with_affinity.position = 0;
|
self.position_with_affinity.position = 0;
|
||||||
self.position_with_affinity.affinity = Affinity::Downstream;
|
self.position_with_affinity.affinity = Affinity::Downstream;
|
||||||
|
|||||||
@ -9,10 +9,7 @@ use crate::shapes::{
|
|||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
use crate::wasm::text::helpers::{self as text_helpers, find_text_span_at_offset};
|
use crate::wasm::text::helpers::{self as text_helpers, find_text_span_at_offset};
|
||||||
use crate::wasm::text_editor::CursorDirection;
|
use crate::wasm::text_editor::CursorDirection;
|
||||||
use skia_safe::{
|
use skia_safe::Color;
|
||||||
textlayout::{Affinity, PositionWithAffinity},
|
|
||||||
Color,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct TextSelection {
|
pub struct TextSelection {
|
||||||
@ -329,7 +326,7 @@ impl TextComposition {
|
|||||||
|
|
||||||
let focus = selection.focus;
|
let focus = selection.focus;
|
||||||
let previous_len = self.previous.chars().count();
|
let previous_len = self.previous.chars().count();
|
||||||
let anchor = TextPositionWithAffinity::new_without_affinity(
|
let anchor = TextPositionWithAffinity::new_downstream_affinity(
|
||||||
focus.paragraph,
|
focus.paragraph,
|
||||||
focus.offset + previous_len,
|
focus.offset + previous_len,
|
||||||
);
|
);
|
||||||
@ -437,9 +434,22 @@ impl TextEditorState {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_all(&mut self, text_content: &TextContent) -> bool {
|
fn select_range(
|
||||||
|
&mut self,
|
||||||
|
text_content: &TextContent,
|
||||||
|
start: &TextPositionWithAffinity,
|
||||||
|
end: &TextPositionWithAffinity,
|
||||||
|
) {
|
||||||
self.is_pointer_selection_active = false;
|
self.is_pointer_selection_active = false;
|
||||||
self.set_caret_from_position(&TextPositionWithAffinity::empty());
|
self.is_click_event_skipped = false;
|
||||||
|
self.set_caret_from_position(start);
|
||||||
|
self.extend_selection_from_position(end);
|
||||||
|
self.update_styles(text_content);
|
||||||
|
self.reset_blink();
|
||||||
|
self.push_event(TextEditorEvent::SelectionChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_all(&mut self, text_content: &TextContent) -> bool {
|
||||||
let num_paragraphs = text_content.paragraphs().len().saturating_sub(1);
|
let num_paragraphs = text_content.paragraphs().len().saturating_sub(1);
|
||||||
let Some(last_paragraph) = text_content.paragraphs().last() else {
|
let Some(last_paragraph) = text_content.paragraphs().last() else {
|
||||||
return false;
|
return false;
|
||||||
@ -449,17 +459,11 @@ impl TextEditorState {
|
|||||||
};
|
};
|
||||||
// Offsets are counted in characters, not bytes.
|
// Offsets are counted in characters, not bytes.
|
||||||
let offset = text_helpers::paragraph_char_count(last_paragraph);
|
let offset = text_helpers::paragraph_char_count(last_paragraph);
|
||||||
self.extend_selection_from_position(&TextPositionWithAffinity::new(
|
self.select_range(
|
||||||
PositionWithAffinity {
|
text_content,
|
||||||
position: offset as i32,
|
&TextPositionWithAffinity::empty(),
|
||||||
affinity: Affinity::Upstream,
|
&TextPositionWithAffinity::new_upstream_affinity(num_paragraphs, offset),
|
||||||
},
|
);
|
||||||
num_paragraphs,
|
|
||||||
offset,
|
|
||||||
));
|
|
||||||
self.update_styles(text_content);
|
|
||||||
self.reset_blink();
|
|
||||||
self.push_event(TextEditorEvent::SelectionChanged);
|
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -469,8 +473,6 @@ impl TextEditorState {
|
|||||||
text_content: &TextContent,
|
text_content: &TextContent,
|
||||||
position: &TextPositionWithAffinity,
|
position: &TextPositionWithAffinity,
|
||||||
) {
|
) {
|
||||||
self.is_pointer_selection_active = false;
|
|
||||||
|
|
||||||
let paragraphs = text_content.paragraphs();
|
let paragraphs = text_content.paragraphs();
|
||||||
if paragraphs.is_empty() || position.paragraph >= paragraphs.len() {
|
if paragraphs.is_empty() || position.paragraph >= paragraphs.len() {
|
||||||
return;
|
return;
|
||||||
@ -485,7 +487,7 @@ impl TextEditorState {
|
|||||||
|
|
||||||
let chars: Vec<char> = paragraph_text.chars().collect();
|
let chars: Vec<char> = paragraph_text.chars().collect();
|
||||||
if chars.is_empty() {
|
if chars.is_empty() {
|
||||||
self.set_caret_from_position(&TextPositionWithAffinity::new_without_affinity(
|
self.set_caret_from_position(&TextPositionWithAffinity::new_downstream_affinity(
|
||||||
position.paragraph,
|
position.paragraph,
|
||||||
0,
|
0,
|
||||||
));
|
));
|
||||||
@ -507,7 +509,7 @@ impl TextEditorState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !text_helpers::is_word_char(chars[offset]) {
|
if !text_helpers::is_word_char(chars[offset]) {
|
||||||
self.set_caret_from_position(&TextPositionWithAffinity::new_without_affinity(
|
self.set_caret_from_position(&TextPositionWithAffinity::new_downstream_affinity(
|
||||||
position.paragraph,
|
position.paragraph,
|
||||||
position.offset.min(chars.len()),
|
position.offset.min(chars.len()),
|
||||||
));
|
));
|
||||||
@ -527,17 +529,31 @@ impl TextEditorState {
|
|||||||
end += 1;
|
end += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.set_caret_from_position(&TextPositionWithAffinity::new_without_affinity(
|
self.select_range(
|
||||||
position.paragraph,
|
text_content,
|
||||||
start,
|
&TextPositionWithAffinity::new_downstream_affinity(position.paragraph, start),
|
||||||
));
|
&TextPositionWithAffinity::new_downstream_affinity(position.paragraph, end),
|
||||||
self.extend_selection_from_position(&TextPositionWithAffinity::new_without_affinity(
|
);
|
||||||
position.paragraph,
|
}
|
||||||
end,
|
|
||||||
));
|
pub fn select_paragraph(
|
||||||
self.update_styles(text_content);
|
&mut self,
|
||||||
self.reset_blink();
|
text_content: &TextContent,
|
||||||
self.push_event(TextEditorEvent::SelectionChanged);
|
position: &TextPositionWithAffinity,
|
||||||
|
) {
|
||||||
|
let paragraphs = text_content.paragraphs();
|
||||||
|
if paragraphs.is_empty() || position.paragraph >= paragraphs.len() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offsets are counted in characters, not bytes.
|
||||||
|
let offset = text_helpers::paragraph_char_count(¶graphs[position.paragraph]);
|
||||||
|
|
||||||
|
self.select_range(
|
||||||
|
text_content,
|
||||||
|
&TextPositionWithAffinity::new_downstream_affinity(position.paragraph, 0),
|
||||||
|
&TextPositionWithAffinity::new_upstream_affinity(position.paragraph, offset),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_caret_from_position(&mut self, position: &TextPositionWithAffinity) {
|
pub fn set_caret_from_position(&mut self, position: &TextPositionWithAffinity) {
|
||||||
@ -838,7 +854,7 @@ impl TextEditorState {
|
|||||||
let cursor = self.selection.focus;
|
let cursor = self.selection.focus;
|
||||||
if text_helpers::split_paragraph_at_cursor(text_content, &cursor) {
|
if text_helpers::split_paragraph_at_cursor(text_content, &cursor) {
|
||||||
let new_cursor =
|
let new_cursor =
|
||||||
TextPositionWithAffinity::new_without_affinity(cursor.paragraph + 1, 0);
|
TextPositionWithAffinity::new_downstream_affinity(cursor.paragraph + 1, 0);
|
||||||
self.selection.set_caret(new_cursor);
|
self.selection.set_caret(new_cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,14 +29,14 @@ pub fn clamp_cursor(
|
|||||||
paragraphs: &[Paragraph],
|
paragraphs: &[Paragraph],
|
||||||
) -> TextPositionWithAffinity {
|
) -> TextPositionWithAffinity {
|
||||||
if paragraphs.is_empty() {
|
if paragraphs.is_empty() {
|
||||||
return TextPositionWithAffinity::new_without_affinity(0, 0);
|
return TextPositionWithAffinity::new_downstream_affinity(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let para_idx = position.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 = position.offset.min(para_len);
|
let char_offset = position.offset.min(para_len);
|
||||||
|
|
||||||
TextPositionWithAffinity::new_without_affinity(para_idx, char_offset)
|
TextPositionWithAffinity::new_downstream_affinity(para_idx, char_offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move cursor left by one character.
|
/// Move cursor left by one character.
|
||||||
@ -47,7 +47,7 @@ pub fn move_cursor_backward(
|
|||||||
) -> TextPositionWithAffinity {
|
) -> TextPositionWithAffinity {
|
||||||
if !word_boundary {
|
if !word_boundary {
|
||||||
if cursor.offset > 0 {
|
if cursor.offset > 0 {
|
||||||
return TextPositionWithAffinity::new_without_affinity(
|
return TextPositionWithAffinity::new_downstream_affinity(
|
||||||
cursor.paragraph,
|
cursor.paragraph,
|
||||||
cursor.offset - 1,
|
cursor.offset - 1,
|
||||||
);
|
);
|
||||||
@ -55,7 +55,7 @@ pub fn move_cursor_backward(
|
|||||||
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]);
|
||||||
return TextPositionWithAffinity::new_without_affinity(prev_para, char_count);
|
return TextPositionWithAffinity::new_downstream_affinity(prev_para, char_count);
|
||||||
}
|
}
|
||||||
return *cursor;
|
return *cursor;
|
||||||
}
|
}
|
||||||
@ -111,7 +111,7 @@ pub fn move_cursor_backward(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextPositionWithAffinity::new_without_affinity(para_idx, offset)
|
TextPositionWithAffinity::new_downstream_affinity(para_idx, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move cursor right by one character.
|
/// Move cursor right by one character.
|
||||||
@ -124,13 +124,13 @@ pub fn move_cursor_forward(
|
|||||||
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.offset < char_count {
|
if cursor.offset < char_count {
|
||||||
return TextPositionWithAffinity::new_without_affinity(
|
return TextPositionWithAffinity::new_downstream_affinity(
|
||||||
cursor.paragraph,
|
cursor.paragraph,
|
||||||
cursor.offset + 1,
|
cursor.offset + 1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if cursor.paragraph < paragraphs.len() - 1 {
|
if cursor.paragraph < paragraphs.len() - 1 {
|
||||||
return TextPositionWithAffinity::new_without_affinity(cursor.paragraph + 1, 0);
|
return TextPositionWithAffinity::new_downstream_affinity(cursor.paragraph + 1, 0);
|
||||||
}
|
}
|
||||||
return *cursor;
|
return *cursor;
|
||||||
}
|
}
|
||||||
@ -185,7 +185,7 @@ pub fn move_cursor_forward(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextPositionWithAffinity::new_without_affinity(para_idx, offset)
|
TextPositionWithAffinity::new_downstream_affinity(para_idx, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move cursor up by one line.
|
/// Move cursor up by one line.
|
||||||
@ -203,9 +203,9 @@ pub fn move_cursor_up(
|
|||||||
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.offset.min(char_count);
|
let new_offset = cursor.offset.min(char_count);
|
||||||
TextPositionWithAffinity::new_without_affinity(prev_para, new_offset)
|
TextPositionWithAffinity::new_downstream_affinity(prev_para, new_offset)
|
||||||
} else {
|
} else {
|
||||||
TextPositionWithAffinity::new_without_affinity(cursor.paragraph, 0)
|
TextPositionWithAffinity::new_downstream_affinity(cursor.paragraph, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,10 +224,10 @@ pub fn move_cursor_down(
|
|||||||
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.offset.min(char_count);
|
let new_offset = cursor.offset.min(char_count);
|
||||||
TextPositionWithAffinity::new_without_affinity(next_para, new_offset)
|
TextPositionWithAffinity::new_downstream_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]);
|
||||||
TextPositionWithAffinity::new_without_affinity(cursor.paragraph, char_count)
|
TextPositionWithAffinity::new_downstream_affinity(cursor.paragraph, char_count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +237,7 @@ pub fn move_cursor_line_start(
|
|||||||
_paragraphs: &[Paragraph],
|
_paragraphs: &[Paragraph],
|
||||||
) -> TextPositionWithAffinity {
|
) -> TextPositionWithAffinity {
|
||||||
// TODO: Implement proper line-start using line metrics
|
// TODO: Implement proper line-start using line metrics
|
||||||
TextPositionWithAffinity::new_without_affinity(cursor.paragraph, 0)
|
TextPositionWithAffinity::new_downstream_affinity(cursor.paragraph, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move cursor to end of current line.
|
/// Move cursor to end of current line.
|
||||||
@ -247,7 +247,7 @@ pub fn move_cursor_line_end(
|
|||||||
) -> TextPositionWithAffinity {
|
) -> 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]);
|
||||||
TextPositionWithAffinity::new_without_affinity(cursor.paragraph, char_count)
|
TextPositionWithAffinity::new_downstream_affinity(cursor.paragraph, char_count)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_word_char(c: char) -> bool {
|
pub fn is_word_char(c: char) -> bool {
|
||||||
@ -299,7 +299,7 @@ pub fn replace_text_with_newlines(
|
|||||||
|
|
||||||
if let Some(new_offset) = replace_text_at_cursor(text_content, ¤t_cursor, lines[0]) {
|
if let Some(new_offset) = replace_text_at_cursor(text_content, ¤t_cursor, lines[0]) {
|
||||||
current_cursor =
|
current_cursor =
|
||||||
TextPositionWithAffinity::new_without_affinity(current_cursor.paragraph, new_offset);
|
TextPositionWithAffinity::new_downstream_affinity(current_cursor.paragraph, new_offset);
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -309,9 +309,9 @@ pub fn replace_text_with_newlines(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
current_cursor =
|
current_cursor =
|
||||||
TextPositionWithAffinity::new_without_affinity(current_cursor.paragraph + 1, 0);
|
TextPositionWithAffinity::new_downstream_affinity(current_cursor.paragraph + 1, 0);
|
||||||
if let Some(new_offset) = replace_text_at_cursor(text_content, ¤t_cursor, line) {
|
if let Some(new_offset) = replace_text_at_cursor(text_content, ¤t_cursor, line) {
|
||||||
current_cursor = TextPositionWithAffinity::new_without_affinity(
|
current_cursor = TextPositionWithAffinity::new_downstream_affinity(
|
||||||
current_cursor.paragraph,
|
current_cursor.paragraph,
|
||||||
new_offset,
|
new_offset,
|
||||||
);
|
);
|
||||||
@ -338,7 +338,7 @@ pub fn insert_text_with_newlines(
|
|||||||
|
|
||||||
if let Some(new_offset) = insert_text_at_cursor(text_content, ¤t_cursor, lines[0]) {
|
if let Some(new_offset) = insert_text_at_cursor(text_content, ¤t_cursor, lines[0]) {
|
||||||
current_cursor =
|
current_cursor =
|
||||||
TextPositionWithAffinity::new_without_affinity(current_cursor.paragraph, new_offset);
|
TextPositionWithAffinity::new_downstream_affinity(current_cursor.paragraph, new_offset);
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -348,9 +348,9 @@ pub fn insert_text_with_newlines(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
current_cursor =
|
current_cursor =
|
||||||
TextPositionWithAffinity::new_without_affinity(current_cursor.paragraph + 1, 0);
|
TextPositionWithAffinity::new_downstream_affinity(current_cursor.paragraph + 1, 0);
|
||||||
if let Some(new_offset) = insert_text_at_cursor(text_content, ¤t_cursor, line) {
|
if let Some(new_offset) = insert_text_at_cursor(text_content, ¤t_cursor, line) {
|
||||||
current_cursor = TextPositionWithAffinity::new_without_affinity(
|
current_cursor = TextPositionWithAffinity::new_downstream_affinity(
|
||||||
current_cursor.paragraph,
|
current_cursor.paragraph,
|
||||||
new_offset,
|
new_offset,
|
||||||
);
|
);
|
||||||
@ -584,7 +584,7 @@ pub fn delete_char_before(
|
|||||||
let para = &mut paragraphs[cursor.paragraph];
|
let para = &mut paragraphs[cursor.paragraph];
|
||||||
let delete_pos = cursor.offset - 1;
|
let delete_pos = cursor.offset - 1;
|
||||||
delete_range_in_paragraph(para, delete_pos, cursor.offset);
|
delete_range_in_paragraph(para, delete_pos, cursor.offset);
|
||||||
Some(TextPositionWithAffinity::new_without_affinity(
|
Some(TextPositionWithAffinity::new_downstream_affinity(
|
||||||
cursor.paragraph,
|
cursor.paragraph,
|
||||||
delete_pos,
|
delete_pos,
|
||||||
))
|
))
|
||||||
@ -603,7 +603,7 @@ pub fn delete_char_before(
|
|||||||
|
|
||||||
paragraphs.remove(cursor.paragraph);
|
paragraphs.remove(cursor.paragraph);
|
||||||
|
|
||||||
Some(TextPositionWithAffinity::new_without_affinity(
|
Some(TextPositionWithAffinity::new_downstream_affinity(
|
||||||
prev_para_idx,
|
prev_para_idx,
|
||||||
prev_para_len,
|
prev_para_len,
|
||||||
))
|
))
|
||||||
@ -675,13 +675,13 @@ pub fn delete_word_before(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let selection = TextSelection {
|
let selection = TextSelection {
|
||||||
anchor: TextPositionWithAffinity::new_without_affinity(start_paragraph, start_offset),
|
anchor: TextPositionWithAffinity::new_downstream_affinity(start_paragraph, start_offset),
|
||||||
focus: TextPositionWithAffinity::new_without_affinity(end_paragraph, end_offset),
|
focus: TextPositionWithAffinity::new_downstream_affinity(end_paragraph, end_offset),
|
||||||
};
|
};
|
||||||
|
|
||||||
delete_selection_range(text_content, &selection);
|
delete_selection_range(text_content, &selection);
|
||||||
|
|
||||||
Some(TextPositionWithAffinity::new_without_affinity(
|
Some(TextPositionWithAffinity::new_downstream_affinity(
|
||||||
start_paragraph,
|
start_paragraph,
|
||||||
start_offset,
|
start_offset,
|
||||||
))
|
))
|
||||||
@ -748,8 +748,8 @@ pub fn delete_word_after(text_content: &mut TextContent, cursor: &TextPositionWi
|
|||||||
}
|
}
|
||||||
|
|
||||||
let selection = TextSelection {
|
let selection = TextSelection {
|
||||||
anchor: TextPositionWithAffinity::new_without_affinity(start_paragraph, start_offset),
|
anchor: TextPositionWithAffinity::new_downstream_affinity(start_paragraph, start_offset),
|
||||||
focus: TextPositionWithAffinity::new_without_affinity(end_paragraph, end_offset),
|
focus: TextPositionWithAffinity::new_downstream_affinity(end_paragraph, end_offset),
|
||||||
};
|
};
|
||||||
|
|
||||||
delete_selection_range(text_content, &selection);
|
delete_selection_range(text_content, &selection);
|
||||||
@ -912,8 +912,8 @@ mod tests {
|
|||||||
|
|
||||||
fn selection(start: (usize, usize), end: (usize, usize)) -> TextSelection {
|
fn selection(start: (usize, usize), end: (usize, usize)) -> TextSelection {
|
||||||
TextSelection {
|
TextSelection {
|
||||||
anchor: TextPositionWithAffinity::new_without_affinity(start.0, start.1),
|
anchor: TextPositionWithAffinity::new_downstream_affinity(start.0, start.1),
|
||||||
focus: TextPositionWithAffinity::new_without_affinity(end.0, end.1),
|
focus: TextPositionWithAffinity::new_downstream_affinity(end.0, end.1),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,8 @@ use crate::math::{Matrix, Point};
|
|||||||
use crate::mem;
|
use crate::mem;
|
||||||
use crate::render::text_editor as text_editor_render;
|
use crate::render::text_editor as text_editor_render;
|
||||||
use crate::render::SurfaceId;
|
use crate::render::SurfaceId;
|
||||||
use crate::shapes::{TextAlign, TextPositionWithAffinity, Type, VerticalAlign};
|
use crate::shapes::{TextAlign, TextContent, TextPositionWithAffinity, Type, VerticalAlign};
|
||||||
use crate::state::{State, TextEditorEvent};
|
use crate::state::{State, TextEditorEvent, TextEditorState};
|
||||||
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::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
@ -133,8 +133,10 @@ pub extern "C" fn text_editor_select_all() -> bool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
fn with_active_text_at_point<F>(x: f32, y: f32, apply: F)
|
||||||
pub extern "C" fn text_editor_select_word_boundary(x: f32, y: f32) {
|
where
|
||||||
|
F: FnOnce(&mut TextEditorState, &TextContent, &TextPositionWithAffinity),
|
||||||
|
{
|
||||||
with_state!(state, {
|
with_state!(state, {
|
||||||
if !get_text_editor_state().has_focus {
|
if !get_text_editor_state().has_focus {
|
||||||
return;
|
return;
|
||||||
@ -154,11 +156,33 @@ pub extern "C" fn text_editor_select_word_boundary(x: f32, y: f32) {
|
|||||||
|
|
||||||
let point = Point::new(x, y);
|
let point = Point::new(x, y);
|
||||||
if let Some(position) = text_content.get_caret_position_from_shape_coords(&point) {
|
if let Some(position) = text_content.get_caret_position_from_shape_coords(&point) {
|
||||||
get_text_editor_state().select_word_boundary(text_content, &position);
|
apply(get_text_editor_state(), text_content, &position);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn text_editor_select_word_boundary(x: f32, y: f32) {
|
||||||
|
with_active_text_at_point(x, y, |editor, text_content, position| {
|
||||||
|
editor.select_word_boundary(text_content, position)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn text_editor_select_paragraph(x: f32, y: f32) {
|
||||||
|
// A drag that produced a range must survive the trailing click; a jitter
|
||||||
|
// that left the caret collapsed must not suppress the paragraph select.
|
||||||
|
let editor = get_text_editor_state();
|
||||||
|
if editor.is_click_event_skipped && editor.selection.is_selection() {
|
||||||
|
editor.is_click_event_skipped = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
with_active_text_at_point(x, y, |editor, text_content, position| {
|
||||||
|
editor.select_paragraph(text_content, position)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn text_editor_poll_event() -> u8 {
|
pub extern "C" fn text_editor_poll_event() -> u8 {
|
||||||
get_text_editor_state().poll_event() as u8
|
get_text_editor_state().poll_event() as u8
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user