mirror of
https://github.com/penpot/penpot.git
synced 2026-03-18 08:26:33 +00:00
🎉 Add word boundary selection
This commit is contained in:
@@ -220,28 +220,35 @@
|
||||
(fn [^js event]
|
||||
(let [native-event (dom/event->native-event event)
|
||||
off-pt (dom/get-offset-position native-event)]
|
||||
(wasm.api/text-editor-pointer-down (.-x off-pt) (.-y off-pt)))))
|
||||
(wasm.api/text-editor-pointer-down off-pt))))
|
||||
|
||||
on-pointer-move
|
||||
(mf/use-fn
|
||||
(fn [^js event]
|
||||
(let [native-event (dom/event->native-event event)
|
||||
off-pt (dom/get-offset-position native-event)]
|
||||
(wasm.api/text-editor-pointer-move (.-x off-pt) (.-y off-pt)))))
|
||||
(wasm.api/text-editor-pointer-move off-pt))))
|
||||
|
||||
on-pointer-up
|
||||
(mf/use-fn
|
||||
(fn [^js event]
|
||||
(let [native-event (dom/event->native-event event)
|
||||
off-pt (dom/get-offset-position native-event)]
|
||||
(wasm.api/text-editor-pointer-up (.-x off-pt) (.-y off-pt)))))
|
||||
(wasm.api/text-editor-pointer-up off-pt))))
|
||||
|
||||
on-click
|
||||
(mf/use-fn
|
||||
(fn [^js event]
|
||||
(let [native-event (dom/event->native-event event)
|
||||
off-pt (dom/get-offset-position native-event)]
|
||||
(wasm.api/text-editor-set-cursor-from-offset (.-x off-pt) (.-y off-pt)))))
|
||||
(wasm.api/text-editor-set-cursor-from-offset off-pt))))
|
||||
|
||||
on-double-click
|
||||
(mf/use-fn
|
||||
(fn [^js event]
|
||||
(let [native-event (dom/event->native-event event)
|
||||
off-pt (dom/get-offset-position native-event)]
|
||||
(wasm.api/text-editor-select-word-boundary off-pt))))
|
||||
|
||||
on-focus
|
||||
(mf/use-fn
|
||||
@@ -288,6 +295,7 @@
|
||||
|
||||
[:foreignObject {:x x :y y :width width :height height}
|
||||
[:div {:on-click on-click
|
||||
:on-double-click on-double-click
|
||||
:on-pointer-down on-pointer-down
|
||||
:on-pointer-move on-pointer-move
|
||||
:on-pointer-up on-pointer-up
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
(def text-editor-pointer-up text-editor/text-editor-pointer-up)
|
||||
(def text-editor-is-active? text-editor/text-editor-is-active?)
|
||||
(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-sync-content text-editor/text-editor-sync-content)
|
||||
|
||||
(def dpr
|
||||
|
||||
@@ -25,28 +25,28 @@
|
||||
|
||||
(defn text-editor-set-cursor-from-offset
|
||||
"Sets caret position from shape relative coordinates"
|
||||
[x y]
|
||||
[{:keys [x y]}]
|
||||
(when wasm/context-initialized?
|
||||
(h/call wasm/internal-module "_text_editor_set_cursor_from_offset" x y)))
|
||||
|
||||
(defn text-editor-set-cursor-from-point
|
||||
"Sets caret position from screen (canvas) coordinates"
|
||||
[x y]
|
||||
[{:keys [x y]}]
|
||||
(when wasm/context-initialized?
|
||||
(h/call wasm/internal-module "_text_editor_set_cursor_from_point" x y)))
|
||||
|
||||
(defn text-editor-pointer-down
|
||||
[x y]
|
||||
[{:keys [x y]}]
|
||||
(when wasm/context-initialized?
|
||||
(h/call wasm/internal-module "_text_editor_pointer_down" x y)))
|
||||
|
||||
(defn text-editor-pointer-move
|
||||
[x y]
|
||||
[{:keys [x y]}]
|
||||
(when wasm/context-initialized?
|
||||
(h/call wasm/internal-module "_text_editor_pointer_move" x y)))
|
||||
|
||||
(defn text-editor-pointer-up
|
||||
[x y]
|
||||
[{:keys [x y]}]
|
||||
(when wasm/context-initialized?
|
||||
(h/call wasm/internal-module "_text_editor_pointer_up" x y)))
|
||||
|
||||
@@ -100,6 +100,11 @@
|
||||
(when wasm/context-initialized?
|
||||
(h/call wasm/internal-module "_text_editor_select_all")))
|
||||
|
||||
(defn text-editor-select-word-boundary
|
||||
[{:keys [x y]}]
|
||||
(when wasm/context-initialized?
|
||||
(h/call wasm/internal-module "_text_editor_select_word_boundary" x y)))
|
||||
|
||||
(defn text-editor-stop
|
||||
[]
|
||||
(when wasm/context-initialized?
|
||||
|
||||
@@ -199,6 +199,80 @@ impl TextEditorState {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn select_word_boundary(
|
||||
&mut self,
|
||||
content: &TextContent,
|
||||
position: &TextPositionWithAffinity,
|
||||
) {
|
||||
fn is_word_char(c: char) -> bool {
|
||||
c.is_alphanumeric() || c == '_'
|
||||
}
|
||||
|
||||
self.is_pointer_selection_active = false;
|
||||
|
||||
let paragraphs = content.paragraphs();
|
||||
if paragraphs.is_empty() || position.paragraph >= paragraphs.len() {
|
||||
return;
|
||||
}
|
||||
|
||||
let paragraph = ¶graphs[position.paragraph];
|
||||
let paragraph_text: String = paragraph
|
||||
.children()
|
||||
.iter()
|
||||
.map(|span| span.text.as_str())
|
||||
.collect();
|
||||
|
||||
let chars: Vec<char> = paragraph_text.chars().collect();
|
||||
if chars.is_empty() {
|
||||
self.set_caret_from_position(&TextPositionWithAffinity::new_without_affinity(
|
||||
position.paragraph,
|
||||
0,
|
||||
));
|
||||
self.reset_blink();
|
||||
self.push_event(TextEditorEvent::SelectionChanged);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut offset = position.offset.min(chars.len());
|
||||
|
||||
if offset == chars.len() {
|
||||
offset = offset.saturating_sub(1);
|
||||
} else if !is_word_char(chars[offset]) && offset > 0 && is_word_char(chars[offset - 1]) {
|
||||
offset -= 1;
|
||||
}
|
||||
|
||||
if !is_word_char(chars[offset]) {
|
||||
self.set_caret_from_position(&TextPositionWithAffinity::new_without_affinity(
|
||||
position.paragraph,
|
||||
position.offset.min(chars.len()),
|
||||
));
|
||||
self.reset_blink();
|
||||
self.push_event(TextEditorEvent::SelectionChanged);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut start = offset;
|
||||
while start > 0 && is_word_char(chars[start - 1]) {
|
||||
start -= 1;
|
||||
}
|
||||
|
||||
let mut end = offset + 1;
|
||||
while end < chars.len() && is_word_char(chars[end]) {
|
||||
end += 1;
|
||||
}
|
||||
|
||||
self.set_caret_from_position(&TextPositionWithAffinity::new_without_affinity(
|
||||
position.paragraph,
|
||||
start,
|
||||
));
|
||||
self.extend_selection_from_position(&TextPositionWithAffinity::new_without_affinity(
|
||||
position.paragraph,
|
||||
end,
|
||||
));
|
||||
self.reset_blink();
|
||||
self.push_event(TextEditorEvent::SelectionChanged);
|
||||
}
|
||||
|
||||
pub fn set_caret_from_position(&mut self, position: &TextPositionWithAffinity) {
|
||||
self.selection.set_caret(*position);
|
||||
self.push_event(TextEditorEvent::SelectionChanged);
|
||||
|
||||
@@ -121,6 +121,34 @@ pub extern "C" fn text_editor_select_all() -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn text_editor_select_word_boundary(x: f32, y: f32) {
|
||||
with_state_mut!(state, {
|
||||
if !state.text_editor_state.is_active {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(shape_id) = state.text_editor_state.active_shape_id else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(shape) = state.shapes.get(&shape_id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Type::Text(text_content) = &shape.shape_type else {
|
||||
return;
|
||||
};
|
||||
|
||||
let point = Point::new(x, y);
|
||||
if let Some(position) = text_content.get_caret_position_from_shape_coords(&point) {
|
||||
state
|
||||
.text_editor_state
|
||||
.select_word_boundary(text_content, &position);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn text_editor_poll_event() -> u8 {
|
||||
with_state_mut!(state, { state.text_editor_state.poll_event() as u8 })
|
||||
|
||||
Reference in New Issue
Block a user