diff --git a/druid/src/text/input_component.rs b/druid/src/text/input_component.rs index a31992ae3a..4ba960872d 100644 --- a/druid/src/text/input_component.rs +++ b/druid/src/text/input_component.rs @@ -486,6 +486,37 @@ impl EditSession { } impl EditSession { + /// Insert text *not* from the IME, replacing the current selection. + /// + /// The caller is responsible for notifying the platform of the change in + /// text state, by calling [`EventCtx::invalidate_text_input`]. + #[must_use] + pub fn insert_text(&mut self, data: &mut T, new_text: &str) -> ImeUpdate { + let new_cursor_pos = self.selection.min() + new_text.len(); + data.edit(self.selection.range(), new_text); + self.selection = Selection::caret(new_cursor_pos); + self.scroll_to_selection_end(true); + ImeUpdate::Reset + } + + /// Sets the clipboard to the contents of the current selection. + /// + /// Returns `true` if the clipboard was set, and `false` if not (indicating) + /// that the selection was empty.) + pub fn set_clipboard(&self) -> bool { + if let Some(text) = self + .layout + .text() + .and_then(|txt| txt.slice(self.selection.range())) + { + if !text.is_empty() { + crate::Application::global().clipboard().put_string(text); + return true; + } + } + false + } + fn scroll_to_selection_end(&mut self, after_edit: bool) { self.external_scroll_to = Some(after_edit); } @@ -519,10 +550,10 @@ impl EditSession { let to_delete = crate::text::movement(movement, self.selection, &self.layout, true); self.selection = to_delete; - self.insert_text(buffer, "") + self.ime_insert_text(buffer, "") } } - ImeAction::Delete(_) => self.insert_text(buffer, ""), + ImeAction::Delete(_) => self.ime_insert_text(buffer, ""), ImeAction::DecomposingBackspace => { log::warn!("Decomposing Backspace is not implemented"); self.backspace(buffer); @@ -539,12 +570,12 @@ impl EditSession { if self.send_notification_on_return && !ignore_hotkey { self.external_action = Some(action); } else if self.accepts_newlines { - self.insert_text(buffer, &newline_type.to_string()); + self.ime_insert_text(buffer, &newline_type.to_string()); } } ImeAction::InsertTab { ignore_hotkey } => { if ignore_hotkey || self.accepts_tabs { - self.insert_text(buffer, "\t"); + self.ime_insert_text(buffer, "\t"); } else if !ignore_hotkey { self.external_action = Some(action); } @@ -554,8 +585,8 @@ impl EditSession { self.external_action = Some(action); } } - ImeAction::InsertSingleQuoteIgnoringSmartQuotes => self.insert_text(buffer, "'"), - ImeAction::InsertDoubleQuoteIgnoringSmartQuotes => self.insert_text(buffer, "\""), + ImeAction::InsertSingleQuoteIgnoringSmartQuotes => self.ime_insert_text(buffer, "'"), + ImeAction::InsertDoubleQuoteIgnoringSmartQuotes => self.ime_insert_text(buffer, "\""), ImeAction::Cancel if self.send_notification_on_cancel => { self.external_action = Some(action) } @@ -564,7 +595,9 @@ impl EditSession { } /// Replace the current selection with `text`, and advance the cursor. - fn insert_text(&mut self, buffer: &mut T, text: &str) { + /// + /// This should only be called from the IME. + fn ime_insert_text(&mut self, buffer: &mut T, text: &str) { let new_cursor_pos = self.selection.min() + text.len(); buffer.edit(self.selection.range(), text); self.external_selection_change = Some(Selection::caret(new_cursor_pos)); @@ -649,7 +682,6 @@ impl EditSession { self.selection = new_sel; self.update_pending_invalidation(ImeUpdate::SelectionChanged); } - //self.selection = self.selection.constrained(new_data); self.layout.rebuild_if_needed(ctx.text(), env); } } diff --git a/druid/src/text/layout.rs b/druid/src/text/layout.rs index f0011c2dc8..bd4c238966 100644 --- a/druid/src/text/layout.rs +++ b/druid/src/text/layout.rs @@ -174,7 +174,7 @@ impl TextLayout { /// Set the text to display. pub fn set_text(&mut self, text: T) { - if self.text.is_none() || self.text.as_ref().unwrap().as_str() != text.as_str() { + if self.text.is_none() || !self.text.as_ref().unwrap().same(&text) { self.text = Some(text); self.layout = None; } diff --git a/druid/src/widget/textbox.rs b/druid/src/widget/textbox.rs index 4aa6a4f5b6..316b176a86 100644 --- a/druid/src/widget/textbox.rs +++ b/druid/src/widget/textbox.rs @@ -412,6 +412,34 @@ impl Widget for TextBox { Event::ImeStateChange => { self.reset_cursor_blink(ctx.request_timer(CURSOR_BLINK_DURATION)); } + Event::Command(ref cmd) + if self.text().can_read() && ctx.is_focused() && cmd.is(crate::commands::COPY) => + { + self.text().borrow().set_clipboard(); + ctx.set_handled(); + } + Event::Command(cmd) + if self.text().can_write() && ctx.is_focused() && cmd.is(crate::commands::CUT) => + { + if self.text().borrow().set_clipboard() { + let inval = self.text_mut().borrow_mut().insert_text(data, ""); + ctx.invalidate_text_input(Some(inval)); + } + ctx.set_handled(); + } + Event::Paste(ref item) if self.text().can_write() => { + if let Some(string) = item.get_string() { + let text = if self.multiline { + &string + } else { + string.lines().next().unwrap_or("") + }; + if !text.is_empty() { + let inval = self.text_mut().borrow_mut().insert_text(data, text); + ctx.invalidate_text_input(Some(inval)); + } + } + } _ => (), } self.inner.event(ctx, event, data, env)