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(is_editable, selected_text.is_some());
221 menu_roots.iter_mut().for_each(menu::Tree::set_index);
222
223 let bounds = Rectangle {
224 x: click_position.x,
225 y: click_position.y,
226 width: 240.0,
227 height: 240.0,
228 };
229
230 let item_count = menu_roots[0].children.len();
231 menu_bar_state.inner.with_data_mut(|state| {
232 let stale = state.menu_states.first().is_some_and(|ms| {
233 ms.menu_bounds.child_positions.len() != item_count
234 || (ms.menu_bounds.parent_bounds.x - bounds.x).abs() > 0.5
235 || (ms.menu_bounds.parent_bounds.y - bounds.y).abs() > 0.5
236 });
237 if !state.open || stale {
238 state.menu_states.clear();
239 state.active_root.clear();
240 state.open = true;
241 }
242 menu_roots_diff(&mut menu_roots, &mut state.tree);
243 });
244
245 let menu = Menu {
246 tree: menu_bar_state.clone(),
247 menu_roots: Cow::Owned(menu_roots),
248 bounds_expand: 16,
249 menu_overlays_parent: true,
250 close_condition: CloseCondition {
251 leave: false,
252 click_outside: true,
253 click_inside: true,
254 },
255 item_width: ItemWidth::Uniform(240),
256 item_height: ItemHeight::Dynamic(40),
257 bar_bounds: bounds,
258 main_offset: -(bounds.height as i32),
259 cross_offset: 0,
260 root_bounds_list: vec![bounds],
261 path_highlight: Some(PathHighlight::MenuActive),
262 style: Cow::Owned(theme::menu_bar::MenuBarStyle::Default),
263 position: Point::new(translation.x, translation.y),
264 is_overlay: true,
265 window_id: iced::window::Id::NONE,
266 depth: 0,
267 on_surface_action: None,
268 };
269
270 Some(overlay::Element::new(Box::new(TextMenuOverlay {
271 menu,
272 widget,
273 tree,
274 on_input,
275 })))
276}
277
278#[derive(Clone, Copy, PartialEq, Eq)]
279pub(crate) enum TextCtxAction {
280 Copy,
281 Cut,
282 Paste,
283 SelectAll,
284}
285
286fn build_menu_roots(is_editable: bool, has_selection: bool) -> Vec<menu::Tree<TextCtxAction>> {
287 let mut items = Vec::with_capacity(4);
288
289 if is_editable && has_selection {
290 items.push(menu::Tree::from(crate::Element::from(
291 menu::menu_button(vec![widget::text("Cut").into()]).on_press(TextCtxAction::Cut),
292 )));
293 }
294 if has_selection {
295 items.push(menu::Tree::from(crate::Element::from(
296 menu::menu_button(vec![widget::text("Copy").into()]).on_press(TextCtxAction::Copy),
297 )));
298 }
299 if is_editable {
300 items.push(menu::Tree::from(crate::Element::from(
301 menu::menu_button(vec![widget::text("Paste").into()]).on_press(TextCtxAction::Paste),
302 )));
303 }
304 items.push(menu::Tree::from(crate::Element::from(
305 menu::menu_button(vec![widget::text("Select All").into()])
306 .on_press(TextCtxAction::SelectAll),
307 )));
308
309 vec![menu::Tree::with_children(
310 RcElementWrapper::new(crate::Element::from(widget::Row::new())),
311 items,
312 )]
313}
314
315struct TextMenuOverlay<'a, W, Message: Clone + 'static> {
316 menu: Menu<'a, TextCtxAction>,
317 widget: &'a W,
318 tree: &'a mut Tree,
319 on_input: Option<&'a dyn Fn(String) -> Message>,
320}
321
322impl<W, Message> overlay::Overlay<Message, crate::Theme, crate::Renderer>
323 for TextMenuOverlay<'_, W, Message>
324where
325 W: HasSelectableText,
326 Message: Clone + 'static,
327{
328 fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> iced_core::layout::Node {
329 let needs_init = self
332 .menu
333 .tree
334 .inner
335 .with_data(|state| state.open && state.menu_states.is_empty());
336
337 if needs_init {
338 let overlay_offset = Point::ORIGIN - self.menu.position;
339 let bar_bounds = self.menu.bar_bounds;
340 let main_offset = self.menu.main_offset as f32;
341 let overlay_cursor = bar_bounds.center();
342
343 let mut init_messages: Vec<TextCtxAction> = Vec::new();
344 let mut init_shell = Shell::new(&mut init_messages);
345 menu::init_root_menu(
346 &mut self.menu,
347 renderer,
348 &mut init_shell,
349 overlay_cursor,
350 bounds,
351 overlay_offset,
352 bar_bounds,
353 main_offset,
354 );
355 }
356
357 self.menu.layout(
358 renderer,
359 Limits::NONE
360 .min_width(bounds.width)
361 .max_width(bounds.width)
362 .min_height(bounds.height)
363 .max_height(bounds.height),
364 )
365 }
366
367 fn draw(
368 &self,
369 renderer: &mut crate::Renderer,
370 theme: &crate::Theme,
371 style: &renderer::Style,
372 layout: Layout<'_>,
373 cursor: mouse::Cursor,
374 ) {
375 self.menu.draw(renderer, theme, style, layout, cursor);
376 }
377
378 fn update(
379 &mut self,
380 event: &event::Event,
381 layout: Layout<'_>,
382 cursor: mouse::Cursor,
383 renderer: &crate::Renderer,
384 clipboard: &mut dyn Clipboard,
385 shell: &mut Shell<'_, Message>,
386 ) {
387 if matches!(
393 event,
394 event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right))
395 | event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right))
396 ) {
397 let over_menu = cursor
398 .position()
399 .is_some_and(|p| layout.bounds().contains(p));
400 if !over_menu {
401 self.menu.tree.inner.with_data_mut(|state| {
402 state.menu_states.clear();
403 state.active_root.clear();
404 state.open = false;
405 });
406 self.widget.set_context_menu_position(self.tree, None);
407 shell.request_redraw();
408 }
409 return;
410 }
411
412 let mut local_messages = Vec::new();
413 let mut local_shell = Shell::new(&mut local_messages);
414
415 self.menu
416 .update(event, layout, cursor, renderer, clipboard, &mut local_shell);
417
418 if local_shell.is_event_captured() {
419 shell.capture_event();
420 }
421 shell.request_redraw_at(local_shell.redraw_request());
422 if local_shell.is_layout_invalid() {
423 shell.invalidate_layout();
424 }
425
426 for action in local_messages {
427 match action {
428 TextCtxAction::Copy => {
429 self.widget.copy_to_clipboard(self.tree, clipboard);
430 }
431 TextCtxAction::Cut => {
432 self.widget.copy_to_clipboard(self.tree, clipboard);
433 if let Some(contents) = self.widget.delete_selection(self.tree) {
434 if let Some(on_input) = self.on_input {
435 shell.publish((on_input)(contents));
436 }
437 }
438 }
439 TextCtxAction::Paste => {
440 let content: String = clipboard
441 .read(clipboard::Kind::Standard)
442 .unwrap_or_default();
443 if let Some(contents) = self.widget.paste_text(self.tree, &content) {
444 if let Some(on_input) = self.on_input {
445 shell.publish((on_input)(contents));
446 }
447 }
448 }
449 TextCtxAction::SelectAll => {
450 self.widget.select_all(self.tree);
451 }
452 }
453 self.widget.set_context_menu_position(self.tree, None);
454 shell.request_redraw();
456 }
457
458 let is_open = self.menu.tree.inner.with_data(|state| state.open);
459 if !is_open {
460 self.widget.set_context_menu_position(self.tree, None);
461 }
462 }
463
464 fn mouse_interaction(
465 &self,
466 layout: Layout<'_>,
467 cursor: mouse::Cursor,
468 _renderer: &crate::Renderer,
469 ) -> mouse::Interaction {
470 if cursor.is_over(layout.bounds()) {
471 mouse::Interaction::Idle
472 } else {
473 mouse::Interaction::None
474 }
475 }
476}
477
478#[cfg(wayland_platform)]
484pub(crate) fn create_text_context_popup(
485 click_position: Point,
486 selected_text: Option<String>,
487 is_editable: bool,
488 has_selection: bool,
489 menu_bar_state: &MenuBarState,
490 pending_action: &PendingAction,
491 renderer: &crate::Renderer,
492 viewport: &Rectangle,
493 cursor: mouse::Cursor,
494 window_id: window::Id,
495) {
496 use iced_runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
497
498 if window_id == iced_core::window::Id::NONE {
499 return;
500 }
501
502 let mut menu_roots = build_menu_roots(is_editable, has_selection);
503 menu_roots.iter_mut().for_each(menu::Tree::set_index);
504
505 let id = menu_bar_state.inner.with_data_mut(|state| {
506 state.menu_states.clear();
507 state.active_root.clear();
508 menu_roots_diff(&mut menu_roots, &mut state.tree);
509 if let Some(id) = state.popup_id.get(&window_id).copied() {
510 queue_destroy_popup(id);
511 state.view_cursor = cursor;
512 id
513 } else {
514 state.open = true;
515 state.view_cursor = cursor;
516 iced::window::Id::unique()
517 }
518 });
519
520 let bounds = Rectangle {
521 x: click_position.x,
522 y: click_position.y,
523 width: 240.0,
524 height: 240.0,
525 };
526
527 let mut popup_menu: Menu<'static, TextCtxAction> = Menu {
528 tree: menu_bar_state.clone(),
529 menu_roots: Cow::Owned(menu_roots),
530 bounds_expand: 16,
531 menu_overlays_parent: true,
532 close_condition: CloseCondition {
533 leave: false,
534 click_outside: true,
535 click_inside: true,
536 },
537 item_width: ItemWidth::Uniform(240),
538 item_height: ItemHeight::Dynamic(40),
539 bar_bounds: bounds,
540 main_offset: -(bounds.height as i32),
541 cross_offset: 0,
542 root_bounds_list: vec![bounds],
543 path_highlight: Some(PathHighlight::MenuActive),
544 style: Cow::Owned(theme::menu_bar::MenuBarStyle::Default),
545 position: Point::new(0., 0.),
546 is_overlay: false,
547 window_id: id,
548 depth: 0,
549 on_surface_action: None,
550 };
551
552 {
553 let mut init_messages: Vec<TextCtxAction> = Vec::new();
554 let mut init_shell = Shell::new(&mut init_messages);
555 menu::init_root_menu(
556 &mut popup_menu,
557 renderer,
558 &mut init_shell,
559 cursor.position().unwrap_or_default(),
560 viewport.size(),
561 Vector::new(0., 0.),
562 bounds,
563 -(bounds.height),
564 );
565 }
566
567 let anchor_rect = menu_bar_state.inner.with_data_mut(|state| {
568 state.popup_id.insert(window_id, id);
569 let pos = cursor.position().unwrap_or_default();
570 iced::Rectangle {
571 x: pos.x as i32,
572 y: pos.y as i32,
573 width: 1,
574 height: 1,
575 }
576 });
577
578 let menu_node = popup_menu.layout(renderer, iced::Limits::NONE.min_width(1.).min_height(1.));
579 let popup_size = menu_node.size();
580
581 let positioner = SctkPositioner {
582 size: Some((
583 popup_size.width.ceil() as u32 + 2,
584 popup_size.height.ceil() as u32 + 2,
585 )),
586 anchor_rect,
587 anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::None,
588 gravity: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight,
589 reactive: true,
590 ..Default::default()
591 };
592
593 let settings = SctkPopupSettings {
597 parent: window_id,
598 id,
599 positioner,
600 parent_size: None,
601 grab: true,
602 close_with_children: false,
603 input_zone: None,
604 };
605
606 let t = crate::theme::THEME.lock().unwrap();
607 let styling = t.appearance(&crate::theme::menu_bar::MenuBarStyle::Default, false);
608 drop(t);
609 let rad = styling.menu_border_radius;
610 PENDING_POPUP_REQUESTS.with(|q| {
611 q.borrow_mut().push(PopupRequest {
612 settings,
613 menu: popup_menu,
614 selected_text,
615 pending_action: pending_action.clone(),
616 live_settings: LiveSettings {
617 corners: Some(CornerRadius {
618 top_left: rad[0] as u32,
619 top_right: rad[1] as u32,
620 bottom_left: rad[2] as u32,
621 bottom_right: rad[3] as u32,
622 }),
623 ..Default::default()
624 },
625 });
626 });
627 wake_runtime();
628}
629
630#[cfg(wayland_platform)]
633pub(crate) fn dismiss_popup_on_event(
634 menu_bar_state: &MenuBarState,
635 event: &event::Event,
636 window_id: window::Id,
637) {
638 let is_dismiss = matches!(
639 event,
640 event::Event::Mouse(mouse::Event::ButtonPressed(
641 mouse::Button::Left | mouse::Button::Middle
642 )) | event::Event::Keyboard(iced_core::keyboard::Event::KeyPressed {
643 key: iced_core::keyboard::Key::Named(iced_core::keyboard::key::Named::Escape),
644 ..
645 }) | event::Event::Touch(iced_core::touch::Event::FingerPressed { .. })
646 );
647 if !is_dismiss {
648 return;
649 }
650
651 let popup_id = menu_bar_state
652 .inner
653 .with_data(|state| state.popup_id.get(&window_id).copied());
654 if let Some(popup_id) = popup_id {
655 menu_bar_state.inner.with_data_mut(|state| {
656 state.popup_id.retain(|_, v| *v != popup_id);
657 state.reset();
658 });
659 queue_destroy_popup(popup_id);
660 }
661}
662
663#[derive(Clone)]
666struct TextContextMenuPopup<Message: Clone + 'static> {
667 menu: Menu<'static, TextCtxAction>,
668 selected_text: Option<String>,
669 pending_action: PendingAction,
670 _phantom: std::marker::PhantomData<Message>,
671}
672
673impl<Message: Clone + 'static> iced_core::widget::Widget<Message, crate::Theme, crate::Renderer>
674 for TextContextMenuPopup<Message>
675{
676 fn size(&self) -> Size<iced_core::Length> {
677 use iced_core::widget::Widget;
678 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::size(&self.menu)
679 }
680
681 fn tag(&self) -> iced_core::widget::tree::Tag {
682 use iced_core::widget::Widget;
683 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::tag(&self.menu)
684 }
685
686 fn state(&self) -> iced_core::widget::tree::State {
687 use iced_core::widget::Widget;
688 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::state(&self.menu)
689 }
690
691 fn children(&self) -> Vec<iced_core::widget::Tree> {
692 use iced_core::widget::Widget;
693 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::children(&self.menu)
694 }
695
696 fn diff(&mut self, tree: &mut iced_core::widget::Tree) {
697 use iced_core::widget::Widget;
698 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::diff(&mut self.menu, tree);
699 }
700
701 fn layout(
702 &mut self,
703 tree: &mut iced_core::widget::Tree,
704 renderer: &crate::Renderer,
705 limits: &iced_core::layout::Limits,
706 ) -> iced_core::layout::Node {
707 use iced_core::widget::Widget;
708 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::layout(
709 &mut self.menu,
710 tree,
711 renderer,
712 limits,
713 )
714 }
715
716 fn draw(
717 &self,
718 tree: &iced_core::widget::Tree,
719 renderer: &mut crate::Renderer,
720 theme: &crate::Theme,
721 style: &renderer::Style,
722 layout: Layout<'_>,
723 cursor: mouse::Cursor,
724 viewport: &Rectangle,
725 ) {
726 use iced_core::widget::Widget;
727 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::draw(
728 &self.menu, tree, renderer, theme, style, layout, cursor, viewport,
729 );
730 }
731
732 fn update(
733 &mut self,
734 tree: &mut iced_core::widget::Tree,
735 event: &event::Event,
736 layout: Layout<'_>,
737 cursor: mouse::Cursor,
738 renderer: &crate::Renderer,
739 clipboard: &mut dyn Clipboard,
740 shell: &mut Shell<'_, Message>,
741 viewport: &Rectangle,
742 ) {
743 #[cfg(wayland_platform)]
744 {
745 use iced_core::event::wayland::PopupEvent;
746 let popup_event = match event {
747 event::Event::PlatformSpecific(iced_core::event::PlatformSpecific::Wayland(
748 iced_core::event::wayland::Event::Popup(e, _, _),
749 )) => Some(e),
750 _ => None,
751 };
752 if matches!(popup_event, Some(PopupEvent::Done | PopupEvent::Unfocused)) {
753 let popup_id = self.menu.window_id;
754 self.menu.tree.inner.with_data_mut(|state| {
755 state.popup_id.retain(|_, v| *v != popup_id);
756 state.reset();
757 });
758 if matches!(popup_event, Some(PopupEvent::Unfocused)) {
761 queue_destroy_popup(popup_id);
762 }
763 return;
764 }
765 }
766
767 #[cfg(wayland_platform)]
770 if matches!(
771 event,
772 event::Event::Keyboard(iced_core::keyboard::Event::KeyPressed {
773 key: iced_core::keyboard::Key::Named(iced_core::keyboard::key::Named::Escape),
774 ..
775 })
776 ) {
777 let popup_id = self.menu.window_id;
778 self.menu.tree.inner.with_data_mut(|state| {
779 state.popup_id.retain(|_, v| *v != popup_id);
780 state.reset();
781 });
782 queue_destroy_popup(popup_id);
783 shell.capture_event();
784 return;
785 }
786
787 let mut local_messages: Vec<TextCtxAction> = Vec::new();
788 let mut local_shell = Shell::new(&mut local_messages);
789
790 {
791 use iced_core::widget::Widget;
792 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::update(
793 &mut self.menu,
794 tree,
795 event,
796 layout,
797 cursor,
798 renderer,
799 clipboard,
800 &mut local_shell,
801 viewport,
802 );
803 }
804
805 if local_shell.is_event_captured() {
806 shell.capture_event();
807 }
808 shell.request_redraw_at(local_shell.redraw_request());
809 if local_shell.is_layout_invalid() {
810 shell.invalidate_layout();
811 }
812
813 for action in local_messages {
814 match action {
815 TextCtxAction::Copy => {
816 if let Some(ref text) = self.selected_text {
817 clipboard.write(clipboard::Kind::Standard, text.clone());
818 }
819 }
820 TextCtxAction::Cut => {
821 if let Some(ref text) = self.selected_text {
822 clipboard.write(clipboard::Kind::Standard, text.clone());
823 }
824 if let Ok(mut guard) = self.pending_action.lock() {
825 *guard = Some(TextCtxAction::Cut);
826 }
827 }
828 TextCtxAction::Paste => {
829 if let Ok(mut guard) = self.pending_action.lock() {
830 *guard = Some(TextCtxAction::Paste);
831 }
832 }
833 TextCtxAction::SelectAll => {
834 if let Ok(mut guard) = self.pending_action.lock() {
835 *guard = Some(TextCtxAction::SelectAll);
836 }
837 }
838 }
839 }
840
841 #[cfg(wayland_platform)]
846 {
847 let menu_closed = self.menu.tree.inner.with_data(|state| !state.open);
848 if menu_closed {
849 let popup_id = self.menu.window_id;
850 self.menu.tree.inner.with_data_mut(|state| {
851 state.popup_id.retain(|_, v| *v != popup_id);
852 state.reset();
853 });
854 queue_destroy_popup(popup_id);
855 }
856 }
857 }
858
859 fn mouse_interaction(
860 &self,
861 tree: &iced_core::widget::Tree,
862 layout: Layout<'_>,
863 cursor: mouse::Cursor,
864 viewport: &Rectangle,
865 renderer: &crate::Renderer,
866 ) -> mouse::Interaction {
867 use iced_core::widget::Widget;
868 Widget::<TextCtxAction, crate::Theme, crate::Renderer>::mouse_interaction(
869 &self.menu, tree, layout, cursor, viewport, renderer,
870 )
871 }
872}
873
874impl<Message: Clone + 'static> From<TextContextMenuPopup<Message>>
875 for crate::Element<'static, Message>
876{
877 fn from(popup: TextContextMenuPopup<Message>) -> Self {
878 Self::new(popup)
879 }
880}