From 56a210bb4eca148c2ef118fd53118a4aa323aa5e Mon Sep 17 00:00:00 2001 From: Hojjat Date: Thu, 10 Sep 2026 15:30:05 -0600 Subject: [PATCH] feat(text_context_menu): always show items, but disable them --- iced | 2 +- src/widget/selectable_text.rs | 2 ++ src/widget/text_context_menu.rs | 49 +++++++++++++++++++-------------- src/widget/text_editor.rs | 2 ++ src/widget/text_input/input.rs | 15 ++++++++++ 5 files changed, 48 insertions(+), 22 deletions(-) diff --git a/iced b/iced index ffe1f1db..2e12eba5 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit ffe1f1dbe3cbfd313f9b5fe8e36a4af462cae5d7 +Subproject commit 2e12eba5cd9ddf9995e113fbc9fa0ebf8f0f0f97 diff --git a/src/widget/selectable_text.rs b/src/widget/selectable_text.rs index 18328c40..67645d38 100644 --- a/src/widget/selectable_text.rs +++ b/src/widget/selectable_text.rs @@ -355,6 +355,8 @@ impl<'a, Message: Clone + 'static> Widget for S selected_text, false, has_selection, + self.inner.has_text(&tree.children[0]), + self.inner.clipboard_has_text(&tree.children[0]), &menu_bar_state, &pending_action, renderer, diff --git a/src/widget/text_context_menu.rs b/src/widget/text_context_menu.rs index 2e908331..0bf1eca9 100644 --- a/src/widget/text_context_menu.rs +++ b/src/widget/text_context_menu.rs @@ -217,7 +217,12 @@ where let selected_text = widget.selected_text(tree); let is_editable = widget.is_editable(); - let mut menu_roots = build_menu_roots(is_editable, selected_text.is_some()); + let mut menu_roots = build_menu_roots( + is_editable, + selected_text.is_some(), + widget.has_text(tree), + widget.clipboard_has_text(tree), + ); menu_roots.iter_mut().for_each(menu::Tree::set_index); let bounds = Rectangle { @@ -283,28 +288,28 @@ pub(crate) enum TextCtxAction { SelectAll, } -fn build_menu_roots(is_editable: bool, has_selection: bool) -> Vec> { - let mut items = Vec::with_capacity(4); +fn build_menu_roots( + is_editable: bool, + has_selection: bool, + has_text: bool, + clipboard_has_text: bool, +) -> Vec> { + let item = |label: &'static str, action: TextCtxAction, enabled: bool| { + menu::Tree::from(crate::Element::from( + menu::menu_button(vec![widget::text(label).into()]) + .on_press_maybe(enabled.then_some(action)), + )) + }; - if is_editable && has_selection { - items.push(menu::Tree::from(crate::Element::from( - menu::menu_button(vec![widget::text("Cut").into()]).on_press(TextCtxAction::Cut), - ))); - } - if has_selection { - items.push(menu::Tree::from(crate::Element::from( - menu::menu_button(vec![widget::text("Copy").into()]).on_press(TextCtxAction::Copy), - ))); - } + let mut items = Vec::with_capacity(4); if is_editable { - items.push(menu::Tree::from(crate::Element::from( - menu::menu_button(vec![widget::text("Paste").into()]).on_press(TextCtxAction::Paste), - ))); + items.push(item("Cut", TextCtxAction::Cut, has_selection)); } - items.push(menu::Tree::from(crate::Element::from( - menu::menu_button(vec![widget::text("Select All").into()]) - .on_press(TextCtxAction::SelectAll), - ))); + items.push(item("Copy", TextCtxAction::Copy, has_selection)); + if is_editable { + items.push(item("Paste", TextCtxAction::Paste, clipboard_has_text)); + } + items.push(item("Select All", TextCtxAction::SelectAll, has_text)); vec![menu::Tree::with_children( RcElementWrapper::new(crate::Element::from(widget::Row::new())), @@ -486,6 +491,8 @@ pub(crate) fn create_text_context_popup( selected_text: Option, is_editable: bool, has_selection: bool, + has_text: bool, + clipboard_has_text: bool, menu_bar_state: &MenuBarState, pending_action: &PendingAction, renderer: &crate::Renderer, @@ -499,7 +506,7 @@ pub(crate) fn create_text_context_popup( return; } - let mut menu_roots = build_menu_roots(is_editable, has_selection); + let mut menu_roots = build_menu_roots(is_editable, has_selection, has_text, clipboard_has_text); menu_roots.iter_mut().for_each(menu::Tree::set_index); let id = menu_bar_state.inner.with_data_mut(|state| { diff --git a/src/widget/text_editor.rs b/src/widget/text_editor.rs index f25cb277..1d303faf 100644 --- a/src/widget/text_editor.rs +++ b/src/widget/text_editor.rs @@ -297,6 +297,8 @@ impl<'a, Message: Clone + 'static> Widget( state.focus(); } state.context_menu_position = Some(pos); + state.clipboard_has_text = iced_core::widget::text::clipboard_has_text(clipboard); shell.capture_event(); return; } @@ -3096,6 +3101,7 @@ pub struct State { keyboard_modifiers: keyboard::Modifiers, scroll_offset: f32, context_menu_position: Option, + clipboard_has_text: bool, pub(crate) menu_bar_state: crate::widget::menu::MenuBarState, pub(crate) pending_action: crate::widget::text_context_menu::PendingAction, } @@ -3191,6 +3197,7 @@ impl State { scroll_offset: 0.0, dirty: false, context_menu_position: None, + clipboard_has_text: false, menu_bar_state: crate::widget::menu::MenuBarState::default(), pending_action: crate::widget::text_context_menu::pending_action(), } @@ -3554,6 +3561,14 @@ impl iced_core::widget::text::HasSelectableText true } + fn has_text(&self, tree: &WidgetTree) -> bool { + !tree.state.downcast_ref::().tracked_value.is_empty() + } + + fn clipboard_has_text(&self, tree: &WidgetTree) -> bool { + tree.state.downcast_ref::().clipboard_has_text + } + fn is_focused(&self, tree: &WidgetTree) -> bool { tree.state.downcast_ref::().is_focused() }