mirror of
https://github.com/penpot/penpot.git
synced 2026-08-01 19:06:18 +00:00
Merge pull request #8526 from penpot/azazeln28-feat-double-click-word-boundary-selection
🎉 Add word boundary selection
This commit is contained in:
commit
bdfa176b2f
@ -235,28 +235,35 @@
|
|||||||
(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-pointer-down (.-x off-pt) (.-y off-pt)))))
|
(wasm.api/text-editor-pointer-down off-pt))))
|
||||||
|
|
||||||
on-pointer-move
|
on-pointer-move
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(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-pointer-move (.-x off-pt) (.-y off-pt)))))
|
(wasm.api/text-editor-pointer-move off-pt))))
|
||||||
|
|
||||||
on-pointer-up
|
on-pointer-up
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(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-pointer-up (.-x off-pt) (.-y off-pt)))))
|
(wasm.api/text-editor-pointer-up off-pt))))
|
||||||
|
|
||||||
on-click
|
on-click
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(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 (.-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
|
on-focus
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@ -303,6 +310,7 @@
|
|||||||
|
|
||||||
[:foreignObject {:x x :y y :width width :height height}
|
[:foreignObject {:x x :y y :width width :height height}
|
||||||
[:div {:on-click on-click
|
[:div {:on-click on-click
|
||||||
|
:on-double-click on-double-click
|
||||||
:on-pointer-down on-pointer-down
|
:on-pointer-down on-pointer-down
|
||||||
:on-pointer-move on-pointer-move
|
:on-pointer-move on-pointer-move
|
||||||
:on-pointer-up on-pointer-up
|
:on-pointer-up on-pointer-up
|
||||||
|
|||||||
@ -93,6 +93,7 @@
|
|||||||
(def text-editor-pointer-up text-editor/text-editor-pointer-up)
|
(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-is-active? text-editor/text-editor-is-active?)
|
||||||
(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-sync-content text-editor/text-editor-sync-content)
|
(def text-editor-sync-content text-editor/text-editor-sync-content)
|
||||||
|
|
||||||
(def dpr
|
(def dpr
|
||||||
|
|||||||
@ -25,28 +25,28 @@
|
|||||||
|
|
||||||
(defn text-editor-set-cursor-from-offset
|
(defn text-editor-set-cursor-from-offset
|
||||||
"Sets caret position from shape relative coordinates"
|
"Sets caret position from shape relative coordinates"
|
||||||
[x y]
|
[{:keys [x y]}]
|
||||||
(when wasm/context-initialized?
|
(when wasm/context-initialized?
|
||||||
(h/call wasm/internal-module "_text_editor_set_cursor_from_offset" x y)))
|
(h/call wasm/internal-module "_text_editor_set_cursor_from_offset" x y)))
|
||||||
|
|
||||||
(defn text-editor-set-cursor-from-point
|
(defn text-editor-set-cursor-from-point
|
||||||
"Sets caret position from screen (canvas) coordinates"
|
"Sets caret position from screen (canvas) coordinates"
|
||||||
[x y]
|
[{:keys [x y]}]
|
||||||
(when wasm/context-initialized?
|
(when wasm/context-initialized?
|
||||||
(h/call wasm/internal-module "_text_editor_set_cursor_from_point" x y)))
|
(h/call wasm/internal-module "_text_editor_set_cursor_from_point" x y)))
|
||||||
|
|
||||||
(defn text-editor-pointer-down
|
(defn text-editor-pointer-down
|
||||||
[x y]
|
[{:keys [x y]}]
|
||||||
(when wasm/context-initialized?
|
(when wasm/context-initialized?
|
||||||
(h/call wasm/internal-module "_text_editor_pointer_down" x y)))
|
(h/call wasm/internal-module "_text_editor_pointer_down" x y)))
|
||||||
|
|
||||||
(defn text-editor-pointer-move
|
(defn text-editor-pointer-move
|
||||||
[x y]
|
[{:keys [x y]}]
|
||||||
(when wasm/context-initialized?
|
(when wasm/context-initialized?
|
||||||
(h/call wasm/internal-module "_text_editor_pointer_move" x y)))
|
(h/call wasm/internal-module "_text_editor_pointer_move" x y)))
|
||||||
|
|
||||||
(defn text-editor-pointer-up
|
(defn text-editor-pointer-up
|
||||||
[x y]
|
[{:keys [x y]}]
|
||||||
(when wasm/context-initialized?
|
(when wasm/context-initialized?
|
||||||
(h/call wasm/internal-module "_text_editor_pointer_up" x y)))
|
(h/call wasm/internal-module "_text_editor_pointer_up" x y)))
|
||||||
|
|
||||||
@ -100,6 +100,11 @@
|
|||||||
(when wasm/context-initialized?
|
(when wasm/context-initialized?
|
||||||
(h/call wasm/internal-module "_text_editor_select_all")))
|
(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
|
(defn text-editor-stop
|
||||||
[]
|
[]
|
||||||
(when wasm/context-initialized?
|
(when wasm/context-initialized?
|
||||||
|
|||||||
@ -199,6 +199,80 @@ impl TextEditorState {
|
|||||||
true
|
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) {
|
pub fn set_caret_from_position(&mut self, position: &TextPositionWithAffinity) {
|
||||||
self.selection.set_caret(*position);
|
self.selection.set_caret(*position);
|
||||||
self.push_event(TextEditorEvent::SelectionChanged);
|
self.push_event(TextEditorEvent::SelectionChanged);
|
||||||
|
|||||||
@ -122,6 +122,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]
|
#[no_mangle]
|
||||||
pub extern "C" fn text_editor_poll_event() -> u8 {
|
pub extern "C" fn text_editor_poll_event() -> u8 {
|
||||||
with_state_mut!(state, { state.text_editor_state.poll_event() as u8 })
|
with_state_mut!(state, { state.text_editor_state.poll_event() as u8 })
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user