diff options
| author | alex-ds13 <145657253+alex-ds13@users.noreply.github.com> | 2025-11-26 16:11:18 +0000 |
|---|---|---|
| committer | Polesznyák Márk <contact@pml68.dev> | 2025-12-30 18:13:59 +0100 |
| commit | 688b6b58b2ccf7006ec53acb63e69fa9469c1e11 (patch) | |
| tree | e1a7c62e4e4f2028cd3b0ce0208c388ae29475ff | |
| parent | docs: update changelog (diff) | |
| download | iced_selection-688b6b58b2ccf7006ec53acb63e69fa9469c1e11.tar.gz | |
feat: implement selection across bounds with hacky copy solution
- This commit implements the selection across bounds, which is it allows
a selection to start out of bounds and allows a selection that started
on another widget to be dragged in bounds and select multiple
different widgets. However in order to get the copy working on
`ctrl/cmd + c` it resorts to a hacky solution where each widget clears
the clipboard when a drag movement comes into its bounds and when the
user presses `ctrl/cmd + c` it appends it's selected text to the
clipboard.
Diffstat (limited to '')
| -rw-r--r-- | src/text.rs | 88 | ||||
| -rw-r--r-- | src/text/rich.rs | 90 |
2 files changed, 170 insertions, 8 deletions
diff --git a/src/text.rs b/src/text.rs index 851e77b..acc7d2c 100644 --- a/src/text.rs +++ b/src/text.rs @@ -177,6 +177,8 @@ pub struct State { selection: Selection, dragging: Option<Dragging>, last_click: Option<mouse::Click>, + potential_click: Option<mouse::Click>, + has_cleared_clipboard: bool, keyboard_modifiers: keyboard::Modifiers, visual_lines_bounds: Vec<core::Rectangle>, } @@ -434,6 +436,14 @@ where shell.capture_event(); } else { + if let Some(position) = cursor.land().position() { + let potential_click = mouse::Click::new( + position, + mouse::Button::Left, + state.potential_click, + ); + state.potential_click = Some(potential_click); + } state.selection = Selection::default(); } } @@ -441,6 +451,7 @@ where | Event::Touch(touch::Event::FingerLifted { .. }) | Event::Touch(touch::Event::FingerLost { .. }) => { state.dragging = None; + state.potential_click = None; } Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { @@ -472,6 +483,65 @@ where ); } }; + } else if let Some(potential_click) = state.potential_click + && let Some(position) = cursor.position_over(bounds) + { + // We had a click outside of bounds that dragged inside bounds + let (line, index) = state + .grapheme_line_and_index( + potential_click.position(), + bounds, + ) + .unwrap_or((0, 0)); + let new_start = SelectionEnd { line, index }; + state.selection.select_range(new_start, new_start); + + let (line, index) = state + .grapheme_line_and_index(position, bounds) + .unwrap_or((0, 0)); + + match potential_click.kind() { + click::Kind::Single => { + let new_end = SelectionEnd { line, index }; + + state.selection.change_selection(new_end); + + state.dragging = Some(Dragging::Grapheme); + } + click::Kind::Double => { + let new_end = SelectionEnd { line, index }; + + state.selection.select_word( + new_start.line, + new_start.index, + &state.paragraph, + ); + state.selection.change_selection_by_word( + new_end, + &state.paragraph, + ); + + state.dragging = Some(Dragging::Word); + } + click::Kind::Triple => { + state + .selection + .select_line(new_start.line, &state.paragraph); + state.selection.change_selection_by_line( + line, + &state.paragraph, + ); + state.dragging = Some(Dragging::Line); + } + } + + state.last_click = Some(potential_click); + state.potential_click = None; + + // Clear the clipboard so that we can append to it if the user presses the copy + // shortcut to be able to include all selected texts. + clipboard.write(clipboard::Kind::Standard, String::new()); + state.has_cleared_clipboard = true; } } Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { @@ -480,10 +550,20 @@ where if state.keyboard_modifiers.command() && !state.selection.is_empty() => { - clipboard.write( - clipboard::Kind::Standard, - state.selection.text(&state.paragraph), - ); + let contents = + if state.has_cleared_clipboard { + state.has_cleared_clipboard = false; + clipboard + .read(clipboard::Kind::Standard) + .map(|c| { + if !c.is_empty() { c + "\n" } else { c } + }) + .unwrap_or_default() + + &state.selection.text(&state.paragraph) + } else { + state.selection.text(&state.paragraph) + }; + clipboard.write(clipboard::Kind::Standard, contents); shell.capture_event(); } diff --git a/src/text/rich.rs b/src/text/rich.rs index 865f64a..2285b4e 100644 --- a/src/text/rich.rs +++ b/src/text/rich.rs @@ -224,6 +224,8 @@ struct State<Link> { selection: Selection, dragging: Option<Dragging>, last_click: Option<mouse::Click>, + potential_click: Option<mouse::Click>, + has_cleared_clipboard: bool, keyboard_modifiers: keyboard::Modifiers, visual_lines_bounds: Vec<core::Rectangle>, } @@ -364,6 +366,8 @@ where selection: Selection::default(), dragging: None, last_click: None, + potential_click: None, + has_cleared_clipboard: false, keyboard_modifiers: keyboard::Modifiers::default(), visual_lines_bounds: Vec::new(), }) @@ -628,6 +632,14 @@ where shell.capture_event(); } else { + if let Some(position) = cursor.land().position() { + let potential_click = mouse::Click::new( + position, + mouse::Button::Left, + state.potential_click, + ); + state.potential_click = Some(potential_click); + } state.selection = Selection::default(); } } @@ -635,6 +647,7 @@ where | Event::Touch(touch::Event::FingerLifted { .. }) | Event::Touch(touch::Event::FingerLost { .. }) => { state.dragging = None; + state.potential_click = None; if !matches!( event, Event::Touch(touch::Event::FingerLost { .. }) @@ -689,6 +702,65 @@ where ); } }; + } else if let Some(potential_click) = state.potential_click + && let Some(position) = cursor.position_over(bounds) + { + // We had a click outside of bounds that dragged inside bounds + let (line, index) = state + .grapheme_line_and_index( + potential_click.position(), + bounds, + ) + .unwrap_or((0, 0)); + let new_start = SelectionEnd { line, index }; + state.selection.select_range(new_start, new_start); + + let (line, index) = state + .grapheme_line_and_index(position, bounds) + .unwrap_or((0, 0)); + + match potential_click.kind() { + mouse::click::Kind::Single => { + let new_end = SelectionEnd { line, index }; + + state.selection.change_selection(new_end); + + state.dragging = Some(Dragging::Grapheme); + } + mouse::click::Kind::Double => { + let new_end = SelectionEnd { line, index }; + + state.selection.select_word( + new_start.line, + new_start.index, + &state.paragraph, + ); + state.selection.change_selection_by_word( + new_end, + &state.paragraph, + ); + + state.dragging = Some(Dragging::Word); + } + mouse::click::Kind::Triple => { + state + .selection + .select_line(new_start.line, &state.paragraph); + state.selection.change_selection_by_line( + line, + &state.paragraph, + ); + state.dragging = Some(Dragging::Line); + } + } + + state.last_click = Some(potential_click); + state.potential_click = None; + + // Clear the clipboard so that we can append to it if the user presses the copy + // shortcut to be able to include all selected texts. + clipboard.write(clipboard::Kind::Standard, String::new()); + state.has_cleared_clipboard = true; } } Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { @@ -697,10 +769,20 @@ where if state.keyboard_modifiers.command() && !state.selection.is_empty() => { - clipboard.write( - clipboard::Kind::Standard, - state.selection.text(&state.paragraph), - ); + let contents = + if state.has_cleared_clipboard { + state.has_cleared_clipboard = false; + clipboard + .read(clipboard::Kind::Standard) + .map(|c| { + if !c.is_empty() { c + "\n" } else { c } + }) + .unwrap_or_default() + + &state.selection.text(&state.paragraph) + } else { + state.selection.text(&state.paragraph) + }; + clipboard.write(clipboard::Kind::Standard, contents); shell.capture_event(); } |
