From d0f221c48b498d19a7a054d1f8f3b4d6a66eb92e Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 10 Sep 2026 13:44:45 -0600 Subject: [PATCH] feat(text): expose has_text and clipboard_has_text for context menus --- core/src/widget/text.rs | 34 +++++++++++++++++++++++++++++++++- widget/src/text_editor.rs | 14 ++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs index 2598b74e..c57fadbb 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -224,6 +224,7 @@ pub struct State { focused: bool, keyboard_focused: bool, context_menu_position: Option, + clipboard_has_text: bool, } impl Default for State

{ @@ -234,6 +235,7 @@ impl Default for State

{ focused: false, keyboard_focused: false, context_menu_position: None, + clipboard_has_text: false, } } } @@ -529,6 +531,7 @@ where Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => { if let Some(pos) = cursor.position_over(bounds) { state.context_menu_position = Some(pos); + state.clipboard_has_text = clipboard_has_text(clipboard); state.focused = true; state.keyboard_focused = false; shell.capture_event(); @@ -1023,6 +1026,13 @@ pub fn danger(theme: &Theme) -> Style { use crate::widget::tree::Tree as WidgetTree; +/// Returns `true` if the clipboard currently holds non-empty text. +pub fn clipboard_has_text(clipboard: &dyn Clipboard) -> bool { + clipboard + .read(crate::clipboard::Kind::Standard) + .is_some_and(|s| !s.is_empty()) +} + /// Implement this on a **widget** to enable context menu support for /// text selection (Copy, Select All, and optionally Cut / Paste) in libcosmic pub trait HasSelectableText { @@ -1032,11 +1042,23 @@ pub trait HasSelectableText { /// Selects all text. fn select_all(&self, tree: &mut WidgetTree); - /// Returns `true` if the widget is editable (enables Cut / Paste). + /// Returns `true` if the widget is editable (shows Cut / Paste). fn is_editable(&self) -> bool { false } + /// Returns `true` if the widget has any text (enables Select All). + fn has_text(&self, _tree: &WidgetTree) -> bool { + true + } + + /// Returns whether the clipboard held text when the context menu was + /// requested (enables Paste). Widgets cache this on right-click because + /// overlays have no clipboard access. + fn clipboard_has_text(&self, _tree: &WidgetTree) -> bool { + true + } + /// Returns `true` if the widget is currently focused. fn is_focused(&self, tree: &WidgetTree) -> bool; @@ -1106,6 +1128,16 @@ impl HasSelectableText sel.end = count; } + fn has_text(&self, tree: &WidgetTree) -> bool { + let state = tree.state.downcast_ref::>(); + !state.paragraph.content().is_empty() + } + + fn clipboard_has_text(&self, tree: &WidgetTree) -> bool { + let state = tree.state.downcast_ref::>(); + state.clipboard_has_text + } + fn is_focused(&self, tree: &WidgetTree) -> bool { let state = tree.state.downcast_ref::>(); state.is_focused() diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 34a8825f..0681989b 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -545,6 +545,7 @@ pub struct State { highlighter_settings: Highlighter::Settings, highlighter_format_address: usize, context_menu_position: Option, + clipboard_has_text: bool, pending_edit: Option, } @@ -632,6 +633,7 @@ where highlighter_settings: self.highlighter_settings.clone(), highlighter_format_address: self.highlighter_format as usize, context_menu_position: None, + clipboard_has_text: false, pending_edit: None, }) } @@ -736,6 +738,8 @@ where state.focus = Some(Focus::now()); } state.context_menu_position = Some(pos); + state.clipboard_has_text = + widget::text::clipboard_has_text(clipboard); shell.capture_event(); return; } @@ -1649,6 +1653,16 @@ where self.on_edit.is_some() } + fn has_text(&self, _tree: &widget::Tree) -> bool { + !self.content.is_empty() + } + + fn clipboard_has_text(&self, tree: &widget::Tree) -> bool { + tree.state + .downcast_ref::>() + .clipboard_has_text + } + fn is_focused(&self, tree: &widget::Tree) -> bool { tree.state.downcast_ref::>().is_focused() }