//! A [`Selectable`] widget that wraps some elements (preferrably all your apps elements) and makes //! all its children instances of [`Text`] and [`Rich`] became globally selectable instead of //! independently selectable. This widget also handles the copy shortcut ('ctrl + c') by checking //! for all selections contained inside it and writing to the [`Standard`] clipboard when there is //! some selection. //! //! WARNING: You should only have one [`Selectable`] on your app, preferrably as the root of your //! app to make sure that any [`Text`] or [`Rich`] is inside it. Having multiple selectables or a //! selectable along side some [`Text`] or [`Rich`] is undefined behavior and even though the //! selection might look ok for you the copied text will probably not be what you expect. //! //! [`Text`]: crate::Text //! [`Rich`]: crate::text::Rich //! [`Standard`]: crate::core::clipboard::Kind::Standard use crate::core::{ self, Element, Event, Length, Rectangle, Shell, Size, Vector, Widget, clipboard::{self, Clipboard}, keyboard, layout::{Layout, Limits, Node}, mouse, overlay, renderer::Style, widget::{Tree, operation::Operation, tree}, }; /// A [`Selectable`] widget that wraps some elements (preferrably all your apps elements) and makes /// all its children instances of [`Text`] and [`Rich`] became globally selectable instead of /// independently selectable. This widget also handles the copy shortcut ('ctrl + c') by checking /// for all selections contained inside it and writing to the [`Standard`] clipboard when there is /// some selection. /// /// WARNING: You should only have one [`Selectable`] on your app, preferrably as the root of your /// app to make sure that any [`Text`] or [`Rich`] is inside it. Having multiple selectables or a /// selectable along side some [`Text`] or [`Rich`] is undefined behavior and even though the /// selection might look ok for you the copied text will probably not be what you expect. /// /// [`Text`]: crate::Text /// [`Rich`]: crate::text::Rich /// [`Standard`]: crate::core::clipboard::Kind::Standard pub struct Selectable<'a, Message, Theme, Renderer> { content: Element<'a, Message, Theme, Renderer>, } impl<'a, Message, Theme, Renderer> Selectable<'a, Message, Theme, Renderer> { /// Creates a new [`Selectable`] with the given content. pub fn new( content: impl Into>, ) -> Self { Self { content: content.into(), } } } /// The internal state of a [`Selectable`] widget. #[derive(Debug, Default, Clone)] struct State { keyboard_modifiers: keyboard::Modifiers, } impl<'a, Message, Theme, Renderer> Widget for Selectable<'a, Message, Theme, Renderer> where Renderer: core::Renderer, { fn tag(&self) -> tree::Tag { tree::Tag::of::() } fn state(&self) -> tree::State { tree::State::new(State::default()) } fn children(&self) -> Vec { vec![Tree::new(&self.content)] } fn diff(&self, tree: &mut Tree) { tree.diff_children(std::slice::from_ref(&self.content)); } fn size(&self) -> Size { self.content.as_widget().size() } fn layout( &mut self, tree: &mut Tree, renderer: &Renderer, limits: &Limits, ) -> Node { let layout = self.content.as_widget_mut().layout( &mut tree.children[0], renderer, limits, ); self.content.as_widget_mut().operate( &mut tree.children[0], Layout::new(&layout), renderer, &mut crate::operation::global_selection(), ); layout } fn operate( &mut self, tree: &mut Tree, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation, ) { operation.traverse(&mut |operation| { self.content.as_widget_mut().operate( &mut tree.children[0], layout, renderer, operation, ); }); } fn update( &mut self, tree: &mut Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { self.content.as_widget_mut().update( &mut tree.children[0], event, layout, cursor, renderer, clipboard, shell, viewport, ); if shell.is_event_captured() { return; } let state = tree.state.downcast_mut::(); if let Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) = event && let keyboard::Key::Character("c") = key.as_ref() && state.keyboard_modifiers.command() { let mut selected = crate::operation::selected(); selected.traverse(&mut |operation| { self.content.as_widget_mut().operate( &mut tree.children[0], layout, renderer, &mut core::widget::operation::black_box(operation), ); }); if let core::widget::operation::Outcome::Some(selection) = selected.finish() { clipboard.write(clipboard::Kind::Standard, selection); shell.capture_event(); } } else if let Event::Keyboard(keyboard::Event::ModifiersChanged( modifiers, )) = event { state.keyboard_modifiers = *modifiers; } } fn mouse_interaction( &self, tree: &Tree, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { self.content.as_widget().mouse_interaction( &tree.children[0], layout, cursor, viewport, renderer, ) } fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Theme, style: &Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { self.content.as_widget().draw( &tree.children[0], renderer, theme, style, layout, cursor, viewport, ); } fn overlay<'b>( &'b mut self, state: &'b mut Tree, layout: Layout<'b>, renderer: &Renderer, viewport: &Rectangle, translation: Vector, ) -> Option> { self.content.as_widget_mut().overlay( &mut state.children[0], layout, renderer, viewport, translation, ) } } impl<'a, Message, Theme, Renderer> From> for Element<'a, Message, Theme, Renderer> where Message: 'a, Theme: 'a, Renderer: core::Renderer + 'a, { fn from( selectable: Selectable<'a, Message, Theme, Renderer>, ) -> Element<'a, Message, Theme, Renderer> { Element::new(selectable) } }