1pub use iced_core::widget::text::HasSelectableText;
18#[cfg(wayland_platform)]
19use iced_core::window;
20#[cfg(wayland_platform)]
21use iced_runtime::platform_specific::wayland::CornerRadius;
22
23#[cfg(wayland_platform)]
24use crate::surface::action::LiveSettings;
25use crate::widget::RcElementWrapper;
26#[cfg(wayland_platform)]
27use crate::widget::menu::StyleSheet;
28use crate::widget::menu::{
29 self, CloseCondition, ItemHeight, ItemWidth, Menu, MenuBarState, PathHighlight, menu_roots_diff,
30};
31use crate::{theme, widget};
32
33use iced_core::layout::Limits;
34use iced_core::widget::Tree;
35use iced_core::{
36 Clipboard, Layout, Point, Rectangle, Shell, Size, Vector, clipboard, mouse, overlay, renderer,
37};
38use iced_widget::core::event;
39use std::borrow::Cow;
40use std::sync::{Arc, Mutex};
41
42pub(crate) type PendingAction = Arc<Mutex<Option<TextCtxAction>>>;
45
46pub(crate) fn pending_action() -> PendingAction {
48 Arc::new(Mutex::new(None))
49}
50
51pub(crate) fn take_pending_action(pending: &PendingAction) -> Option<TextCtxAction> {
53 pending.lock().ok().and_then(|mut guard| guard.take())
54}
55
56use std::cell::Cell;
57
58thread_local! {
59 static CURRENT_WINDOW_ID: Cell<iced_core::window::Id> = const { Cell::new(iced_core::window::Id::NONE) };
60}
61
62#[cfg(wayland_platform)]
63use iced_runtime::platform_specific::wayland::popup::SctkPopupSettings;
64
65#[cfg(wayland_platform)]
69pub(crate) struct PopupRequest {
70 live_settings: LiveSettings,
71 settings: SctkPopupSettings,
72 menu: Menu<'static, TextCtxAction>,
73 selected_text: Option<String>,
74 pending_action: PendingAction,
75}
76
77#[cfg(wayland_platform)]
78thread_local! {
79 static PENDING_POPUP_REQUESTS: std::cell::RefCell<Vec<PopupRequest>> =
80 const { std::cell::RefCell::new(Vec::new()) };
81}
82
83pub(crate) fn set_current_window_id(id: iced_core::window::Id) {
84 CURRENT_WINDOW_ID.set(id);
85}
86
87pub(crate) fn current_window_id() -> iced_core::window::Id {
88 CURRENT_WINDOW_ID.get()
89}
90
91#[cfg(wayland_platform)]
93pub(crate) fn take_popup_requests() -> Vec<PopupRequest> {
94 PENDING_POPUP_REQUESTS.with(|q| std::mem::take(&mut *q.borrow_mut()))
95}
96
97#[cfg(wayland_platform)]
101#[allow(clippy::type_complexity)]
102pub(crate) fn into_popup_view<Message: Clone + 'static>(
103 req: PopupRequest,
104) -> (
105 LiveSettings,
106 SctkPopupSettings,
107 Box<dyn Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync>,
108) {
109 let PopupRequest {
110 settings,
111 menu,
112 selected_text,
113 pending_action,
114 live_settings,
115 } = req;
116
117 let view = Box::new(move || {
118 let popup_widget: TextContextMenuPopup<Message> = TextContextMenuPopup {
119 menu: menu.clone(),
120 selected_text: selected_text.clone(),
121 pending_action: pending_action.clone(),
122 _phantom: std::marker::PhantomData,
123 };
124 crate::Element::from(crate::widget::container(popup_widget).center(iced_core::Length::Fill))
125 .map(crate::action::app)
126 });
127
128 (live_settings, settings, view)
129}
130
131#[cfg(wayland_platform)]
132thread_local! {
133 static PENDING_POPUP_DESTROYS: std::cell::RefCell<Vec<iced_core::window::Id>> =
134 const { std::cell::RefCell::new(Vec::new()) };
135}
136
137#[cfg(wayland_platform)]
144fn queue_destroy_popup(id: iced_core::window::Id) {
145 PENDING_POPUP_DESTROYS.with(|q| q.borrow_mut().push(id));
146 wake_runtime();
147}
148
149#[cfg(wayland_platform)]
150static WAKE_TX: std::sync::OnceLock<iced_futures::futures::channel::mpsc::Sender<()>> =
151 std::sync::OnceLock::new();
152
153#[cfg(wayland_platform)]
155struct PopupWake;
156
157#[cfg(wayland_platform)]
159fn wake_runtime() {
160 if let Some(tx) = WAKE_TX.get() {
161 let _ = tx.clone().try_send(());
162 }
163}
164
165#[cfg(wayland_platform)]
170pub(crate) fn wake_subscription<Message: Send + 'static>()
171-> iced_futures::Subscription<crate::Action<Message>> {
172 use iced_futures::futures::{SinkExt, StreamExt};
173 iced_futures::Subscription::run_with(std::any::TypeId::of::<PopupWake>(), |_| {
174 iced::stream::channel(
175 16,
176 |mut output: iced_futures::futures::channel::mpsc::Sender<crate::Action<Message>>| async move {
177 let (tx, mut rx) = iced_futures::futures::channel::mpsc::channel(16);
178 let _ = WAKE_TX.set(tx);
179 while rx.next().await.is_some() {
180 let _ = output.send(crate::Action::None).await;
181 }
182 },
183 )
184 })
185}
186
187#[cfg(wayland_platform)]
193pub(crate) fn take_popup_destroys() -> Vec<iced_core::window::Id> {
194 PENDING_POPUP_DESTROYS.with(|q| std::mem::take(&mut *q.borrow_mut()))
195}
196
197pub(crate) fn context_menu_overlay<'a, W, Message>(
206 widget: &'a W,
207 tree: &'a mut Tree,
208 on_input: Option<&'a dyn Fn(String) -> Message>,
209 translation: Vector,
210 menu_bar_state: MenuBarState,
211) -> Option<overlay::Element<'a, Message, crate::Theme, crate::Renderer>>
212where
213 W: HasSelectableText + 'a,
214 Message: Clone + 'static,
215{
216 let click_position = widget.context_menu_position(tree)?;
217 let selected_text = widget.selected_text(tree);
218 let is_editable = widget.is_editable();
219
220 let mut menu_roots = build_menu_roots(
221 is_editable,
222 selected_text.is_some(),
223 widget.has_text(tree),
224 widget.clipboard_has_text(tree),
225 );
226 menu_roots.iter_mut().for_each(menu::Tree::set_index);
227
228 let bounds = Rectangle {
229 x: click_position.x,
230 y: click_position.y,
231 width: 240.0,
232 height: 240.0,
233 };
234
235 let item_count = menu_roots[0].children.len();
236 menu_bar_state.inner.with_data_mut(|state| {
237 let stale = state.menu_states.first().is_some_and(|ms| {
238 ms.menu_bounds.child_positions.len() != item_count
239 || (ms.menu_bounds.parent_bounds.x - bounds.x).abs() > 0.5
240 || (ms.menu_bounds.parent_bounds.y - bounds.y).abs() > 0.5
241 });
242 if !state.open || stale {
243 state.menu_states.clear();
244 state.active_root.clear();
245 state.open = true;
246 }
247 menu_roots_diff(&mut menu_roots, &mut state.tree);
248 });
249
250 let menu = Menu {
251 tree: menu_bar_state.clone(),
252 menu_roots: Cow::Owned(menu_roots),
253 bounds_expand: 16,
254 menu_overlays_parent: true,
255 close_condition: CloseCondition {
256 leave: false,
257 click_outside: true,
258 click_inside: true,
259 },
260 item_width: ItemWidth::Uniform(240),
261 item_height: ItemHeight::Dynamic(40),
262 bar_bounds: bounds,
263 main_offset: -(bounds.height as i32),
264 cross_offset: 0,
265 root_bounds_list: vec![bounds],
266 path_highlight: Some(PathHighlight::MenuActive),
267 style: Cow::Owned(theme::menu_bar::MenuBarStyle::Default),
268 position: Point::new(translation.x, translation.y),
269 is_overlay: true,
270 window_id: iced::window::Id::NONE,
271 depth: 0,
272 on_surface_action: None,
273 };
274
275 Some(overlay::Element::new(Box::new(TextMenuOverlay {
276 menu,
277 widget,
278 tree,
279 on_input,
280 })))
281}
282
283#[derive(Clone, Copy, PartialEq, Eq)]
284pub(crate) enum TextCtxAction {
285 Copy,
286 Cut,
287 Paste,
288 SelectAll,
289}
290
291fn build_menu_roots(
292 is_editable: bool,
293 has_selection: bool,
294 has_text: bool,
295 clipboard_has_text: bool,
296) -> Vec<menu::Tree<TextCtxAction>> {
297 let item = |label: &'static str, action: TextCtxAction, enabled: bool| {
298 menu::Tree::from(crate::Element::from(
299 menu::menu_button(vec![widget::text(label).into()])
300 .on_press_maybe(enabled.then_some(action)),
301 ))
302 };
303
304 let mut items = Vec::with_capacity(4);
305 if is_editable {
306 items.push(item("Cut", TextCtxAction::Cut, has_selection));
307 }
308 items.push(item("Copy", TextCtxAction::Copy, has_selection));
309 if is_editable {
310 items.push(item("Paste", TextCtxAction::Paste, clipboard_has_text));
311 }
312 items.push(item("Select All", TextCtxAction::SelectAll, has_text));
313
314 vec![menu::Tree::with_children(
315 RcElementWrapper::new(crate::Element::from(widget::Row::new())),
316 items,
317 )]
318}
319
320struct TextMenuOverlay<'a, W, Message: Clone + 'static> {
321 menu: Menu<'a, TextCtxAction>,
322 widget: &'a W,
323 tree: &'a mut Tree,
324 on_input: Option<&'a dyn Fn(String) -> Message>,
325}
326
327impl<W, Message> overlay::Overlay<Message, crate::Theme, crate::Renderer>
328 for TextMenuOverlay<'_, W, Message>
329where
330 W: HasSelectableText,
331 Message: Clone + 'static,
332{
333 fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> iced_core::layout::Node {
334 let needs_init = self
337 .menu
338 .tree
339 .inner
340 .with_data(|state| state.open && state.menu_states.is_empty());
341
342 if needs_init {
343 let overlay_offset = Point::ORIGIN - self.menu.position;
344 let bar_bounds = self.menu.bar_bounds;
345 let main_offset = self.menu.main_offset as f32;
346 let overlay_cursor = bar_bounds.center();
347
348 let mut init_messages: Vec<TextCtxAction> = Vec::new();
349 let mut init_shell = Shell::new(&mut init_messages);
350 menu::init_root_menu(
351 &mut self.menu,
352 renderer,
353 &mut init_shell,
354 overlay_cursor,
355 bounds,
356 overlay_offset,
357 bar_bounds,
358 main_offset,
359 );
360 }
361
362 self.menu.layout(
363 renderer,
364 Limits::NONE
365 .min_width(bounds.width)
366 .max_width(bounds.width)
367 .min_height(bounds.height)
368 .max_height(bounds.height),
369 )
370 }
371
372 fn draw(
373 &self,
374 renderer: &mut crate::Renderer,
375 theme: &crate::Theme,
376 style: &renderer::Style,
377 layout: Layout<'_>,
378 cursor: mouse::Cursor,
379 ) {
380 self.menu.draw(renderer, theme, style, layout, cursor);
381 }
382
383 fn update(
384 &mut self,
385 event: &event::Event,
386 layout: Layout<'_>,
387 cursor: mouse::Cursor,
388 renderer: &crate::Renderer,
389 clipboard: &mut dyn Clipboard,
390 shell: &mut Shell<'_, Message>,
391 ) {
392 if matches!(
398 event,
399 event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right))
400 | event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right))
401 ) {
402 let over_menu = cursor
403 .position()
404 .is_some_and(|p| layout.bounds().contains(p));
405 if !over_menu {
406 self.menu.tree.inner.with_data_mut(|state| {
407 state.menu_states.clear();
408 state.active_root.clear();
409 state.open = false;
410 });
411 self.widget.set_context_menu_position(self.tree, None);
412 shell.request_redraw();
413 }
414 return;
415 }
416
417 let mut local_messages = Vec::new();
418 let mut local_shell = Shell::new(&mut local_messages);
419
420 self.menu
421 .update(event, layout, cursor, renderer, clipboard, &mut local_shell);
422
423 if local_shell.is_event_captured() {
424 shell.capture_event();
425 }
426 shell.request_redraw_at(local_shell.redraw_request());
427 if local_shell.is_layout_invalid() {
428 shell.invalidate_layout();
429 }
430
431 for action in local_messages {
432 match action {
433 TextCtxAction::Copy => {
434 self.widget.copy_to_clipboard(self.tree, clipboard);
435 }
436 TextCtxAction::Cut => {
437 self.widget.copy_to_clipboard(self.tree, clipboard);
438 if let Some(contents) = self.widget.delete_selection(self.tree) {
439 if let Some(on_input) = self.on_input {
440 shell.publish((on_input)(contents));
441 }
442 }
443 }
444 TextCtxAction::Paste => {
445 let content: String = clipboard
446 .read(clipboard::Kind::Standard)
447 .unwrap_or_default();
448 if let Some(contents) = self.widget.paste_text(self.tree, &content) {
449 if let Some(on_input) = self.on_input {
450 shell.publish((on_input)(contents));
451 }
452 }
453 }
454 TextCtxAction::SelectAll => {
455 self.widget.select_all(self.tree);
456 }
457 }
458 self.widget.set_context_menu_position(self.tree, None);
459 shell.request_redraw();
461 }
462
463 let is_open = self.menu.tree.inner.with_data(|state| state.open);
464 if !is_open {
465 self.widget.set_context_menu_position(self.tree, None);
466 }
467 }
468
469 fn mouse_interaction(
470 &self,
471 layout: Layout<'_>,
472 cursor: mouse::Cursor,
473 _renderer: &crate::Renderer,
474 ) -> mouse::Interaction {
475 if cursor.is_over(layout.bounds()) {
476 mouse::Interaction::Idle
477 } else {
478 mouse::Interaction::None
479 }
480 }
481}
482
483#[cfg(wayland_platform)]
489pub(crate) fn create_text_context_popup(
490 click_position: Point,
491 selected_text: Option<String>,
492 is_editable: bool,
493 has_selection: bool,
494 has_text: bool,
495 clipboard_has_text: bool,
496 menu_bar_state: &MenuBarState,
497 pending_action: &PendingAction,
498 renderer: &crate::Renderer,
499 viewport: &Rectangle,
500 cursor: mouse::Cursor,
501 window_id: window::Id,
502) {
503 use iced_runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
504
505 if window_id == iced_core::window::Id::NONE {
506 return;
507 }
508
509 let mut menu_roots = build_menu_roots(is_editable, has_selection, has_text, clipboard_has_text);
510 menu_roots.iter_mut().for_each(menu::Tree::set_index);
511
512 let id = menu_bar_state.inner.with_data_mut(|state| {
513 state.menu_states.clear();
514 state.active_root.clear();
515 menu_roots_diff(&mut menu_roots, &mut state.tree);
516 if let Some(id) = state.popup_id.get(&window_id).copied() {
517 queue_destroy_popup(id);
518 state.view_cursor = cursor;
519 id
520 } else {
521 state.open = true;
522 state.view_cursor = cursor;
523 iced::window::Id::unique()
524 }
525 });
526
527 let bounds = Rectangle {
528 x: click_position.x,
529 y: click_position.y,
530 width: 240.0,
531 height: 240.0,
532 };
533
534 let mut popup_menu: Menu<'static, TextCtxAction> = Menu {
535 tree: menu_bar_state.clone(),
536 menu_roots: Cow::Owned(menu_roots),
537 bounds_expand: 16,
538 menu_overlays_parent: true,
539 close_condition: CloseCondition {
540 leave: false,
541 click_outside: true,
542 click_inside: true,
543 },
544 item_width: ItemWidth::Uniform(240),
545 item_height: ItemHeight::Dynamic(40),
546 bar_bounds: bounds,
547 main_offset: -(bounds.height as i32),
548 cross_offset: 0,
549 root_bounds_list: vec![bounds],
550 path_highlight: Some(PathHighlight::MenuActive),
551 style: Cow::Owned(theme::menu_bar::MenuBarStyle::Default),
552 position: Point::new(0., 0.),
553 is_overlay: false,
554 window_id: id,
555 depth: 0,
556 on_surface_action: None,
557 };
558
559 {
560 let mut init_messages: Vec<TextCtxAction> = Vec::new();
561 let mut init_shell = Shell::new(&mut init_messages);
562 menu::init_root_menu(
563 &mut popup_menu,
564 renderer,
565 &mut init_shell,
566 cursor.position().unwrap_or_default(),
567 viewport.size(),
568 Vector::new(0., 0.),
569 bounds,
570 -(bounds.height),
571 );
572 }
573
574 let anchor_rect = menu_bar_state.inner.with_data_mut(|state| {
575 state.popup_id.insert(window_id, id);
576 let pos = cursor.position().unwrap_or_default();
577 iced::Rectangle {
578 x: pos.x as i32,
579 y: pos.y as i32,
580 width: 1,
581 height: 1,
582 }
583 });
584
585 let menu_node = popup_menu.layout(renderer, iced::Limits::NONE.min_width(1.).min_height(1.));
586 let popup_size = menu_node.size();
587
588 let positioner = SctkPositioner {
589 size: Some((
590 popup_size.width.ceil() as u32 + 2,
591 popup_size.height.ceil() as u32 + 2,
592 )),
593 anchor_rect,
594 anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::None,
595 gravity: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight,
596 reactive: true,
597 ..Default::default()
598 };
599
600 let settings = SctkPopupSettings {
604 parent: window_id,
605 id,
606 positioner,
607 parent_size: None,
608 grab: true,
609 close_with_children: false,
610 input_zone: None,
611 };
612
613 let t = crate::theme::THEME.lock().unwrap();
614 let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false);
615 drop(t);
616 let rad = styling.menu_border_radius;
617 PENDING_POPUP_REQUESTS.with(|q| {
618 q.borrow_mut().push(PopupRequest {
619 settings,
620 menu: popup_menu,
621 selected_text,
622 pending_action: pending_action.clone(),
623 live_settings: LiveSettings {
624 corners: Some(CornerRadius {
625 top_left: rad[0] as u32,
626 top_right: rad[1] as u32,
627 bottom_left: rad[2] as u32,
628 bottom_right: rad[3] as u32,
629 }),
630 ..Default::default()
631 },
632 });
633 });
634 wake_runtime();
635}
636
637#[cfg(wayland_platform)]
640pub(crate) fn dismiss_popup_on_event(
641 menu_bar_state: &MenuBarState,
642 event: &event::Event,
643 window_id: window::Id,
644) {
645 let is_dismiss = matches!(
646 event,
647 event::Event::Mouse(mouse::Event::ButtonPressed(
648 mouse::Button::Left | mouse::Button::Middle
649 )) | event::Event::Keyboard(iced_core::keyboard::Event::KeyPressed {
650 key: iced_core::keyboard::Key::Named(iced_core::keyboard::key::Named::Escape),
651 ..
652 }) | event::Event::Touch(iced_core::touch::Event::FingerPressed { .. })
653 );
654 if !is_dismiss {
655 return;
656 }
657
658 let popup_id = menu_bar_state
659 .inner
660 .with_data(|state| state.popup_id.get(&window_id).copied());
661 if let Some(popup_id) = popup_id {
662 menu_bar_state.inner.with_data_mut(|state| {
663 state.popup_id.retain(|_, v| *v != popup_id);
664 state.reset();
665 });
666 queue_destroy_popup(popup_id);
667 }
668}
669
670#[derive(Clone)]
673struct TextContextMenuPopup<Message: Clone + 'static> {
674 menu: Menu<'static, TextCtxAction>,
675 selected_text: Option<String>,
676 pending_action: PendingAction,
677 _phantom: std::marker::PhantomData<Message>,
678}
679
680impl<Message: Clone + 'static> iced_core::widget::Widget<Message, crate::Theme, crate::Renderer>
681 for TextContextMenuPopup<Message>
682{
683 fn size(&self) -> Size<iced_core::Length> {
684 use iced_core::widget::Widget;
685 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::size(&self.menu)
686 }
687
688 fn tag(&self) -> iced_core::widget::tree::Tag {
689 use iced_core::widget::Widget;
690 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::tag(&self.menu)
691 }
692
693 fn state(&self) -> iced_core::widget::tree::State {
694 use iced_core::widget::Widget;
695 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::state(&self.menu)
696 }
697
698 fn children(&self) -> Vec<iced_core::widget::Tree> {
699 use iced_core::widget::Widget;
700 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::children(&self.menu)
701 }
702
703 fn diff(&mut self, tree: &mut iced_core::widget::Tree) {
704 use iced_core::widget::Widget;
705 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::diff(&mut self.menu, tree);
706 }
707
708 fn layout(
709 &mut self,
710 tree: &mut iced_core::widget::Tree,
711 renderer: &crate::Renderer,
712 limits: &iced_core::layout::Limits,
713 ) -> iced_core::layout::Node {
714 use iced_core::widget::Widget;
715 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::layout(
716 &mut self.menu,
717 tree,
718 renderer,
719 limits,
720 )
721 }
722
723 fn draw(
724 &self,
725 tree: &iced_core::widget::Tree,
726 renderer: &mut crate::Renderer,
727 theme: &crate::Theme,
728 style: &renderer::Style,
729 layout: Layout<'_>,
730 cursor: mouse::Cursor,
731 viewport: &Rectangle,
732 ) {
733 use iced_core::widget::Widget;
734 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::draw(
735 &self.menu, tree, renderer, theme, style, layout, cursor, viewport,
736 );
737 }
738
739 fn update(
740 &mut self,
741 tree: &mut iced_core::widget::Tree,
742 event: &event::Event,
743 layout: Layout<'_>,
744 cursor: mouse::Cursor,
745 renderer: &crate::Renderer,
746 clipboard: &mut dyn Clipboard,
747 shell: &mut Shell<'_, Message>,
748 viewport: &Rectangle,
749 ) {
750 #[cfg(wayland_platform)]
751 {
752 use iced_core::event::wayland::PopupEvent;
753 let popup_event = match event {
754 event::Event::PlatformSpecific(iced_core::event::PlatformSpecific::Wayland(
755 iced_core::event::wayland::Event::Popup(e, _, _),
756 )) => Some(e),
757 _ => None,
758 };
759 if matches!(popup_event, Some(PopupEvent::Done | PopupEvent::Unfocused)) {
760 let popup_id = self.menu.window_id;
761 self.menu.tree.inner.with_data_mut(|state| {
762 state.popup_id.retain(|_, v| *v != popup_id);
763 state.reset();
764 });
765 if matches!(popup_event, Some(PopupEvent::Unfocused)) {
768 queue_destroy_popup(popup_id);
769 }
770 return;
771 }
772 }
773
774 #[cfg(wayland_platform)]
777 if matches!(
778 event,
779 event::Event::Keyboard(iced_core::keyboard::Event::KeyPressed {
780 key: iced_core::keyboard::Key::Named(iced_core::keyboard::key::Named::Escape),
781 ..
782 })
783 ) {
784 let popup_id = self.menu.window_id;
785 self.menu.tree.inner.with_data_mut(|state| {
786 state.popup_id.retain(|_, v| *v != popup_id);
787 state.reset();
788 });
789 queue_destroy_popup(popup_id);
790 shell.capture_event();
791 return;
792 }
793
794 let mut local_messages: Vec<TextCtxAction> = Vec::new();
795 let mut local_shell = Shell::new(&mut local_messages);
796
797 {
798 use iced_core::widget::Widget;
799 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::update(
800 &mut self.menu,
801 tree,
802 event,
803 layout,
804 cursor,
805 renderer,
806 clipboard,
807 &mut local_shell,
808 viewport,
809 );
810 }
811
812 if local_shell.is_event_captured() {
813 shell.capture_event();
814 }
815 shell.request_redraw_at(local_shell.redraw_request());
816 if local_shell.is_layout_invalid() {
817 shell.invalidate_layout();
818 }
819
820 for action in local_messages {
821 match action {
822 TextCtxAction::Copy => {
823 if let Some(ref text) = self.selected_text {
824 clipboard.write(clipboard::Kind::Standard, text.clone());
825 }
826 }
827 TextCtxAction::Cut => {
828 if let Some(ref text) = self.selected_text {
829 clipboard.write(clipboard::Kind::Standard, text.clone());
830 }
831 if let Ok(mut guard) = self.pending_action.lock() {
832 *guard = Some(TextCtxAction::Cut);
833 }
834 }
835 TextCtxAction::Paste => {
836 if let Ok(mut guard) = self.pending_action.lock() {
837 *guard = Some(TextCtxAction::Paste);
838 }
839 }
840 TextCtxAction::SelectAll => {
841 if let Ok(mut guard) = self.pending_action.lock() {
842 *guard = Some(TextCtxAction::SelectAll);
843 }
844 }
845 }
846 }
847
848 #[cfg(wayland_platform)]
853 {
854 let menu_closed = self.menu.tree.inner.with_data(|state| !state.open);
855 if menu_closed {
856 let popup_id = self.menu.window_id;
857 self.menu.tree.inner.with_data_mut(|state| {
858 state.popup_id.retain(|_, v| *v != popup_id);
859 state.reset();
860 });
861 queue_destroy_popup(popup_id);
862 }
863 }
864 }
865
866 fn mouse_interaction(
867 &self,
868 tree: &iced_core::widget::Tree,
869 layout: Layout<'_>,
870 cursor: mouse::Cursor,
871 viewport: &Rectangle,
872 renderer: &crate::Renderer,
873 ) -> mouse::Interaction {
874 use iced_core::widget::Widget;
875 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::mouse_interaction(
876 &self.menu, tree, layout, cursor, viewport, renderer,
877 )
878 }
879}
880
881impl<Message: Clone + 'static> From<TextContextMenuPopup<Message>>
882 for crate::Element<'static, Message>
883{
884 fn from(popup: TextContextMenuPopup<Message>) -> Self {
885 Self::new(popup)
886 }
887}