aboutsummaryrefslogtreecommitdiff
path: root/src/selectable.rs
diff options
context:
space:
mode:
authoralex-ds13 <145657253+alex-ds13@users.noreply.github.com>2025-12-04 02:40:12 +0000
committerPolesznyák Márk <contact@pml68.dev>2025-12-30 18:13:59 +0100
commitde65c1dc7285b15c87862198b98b435345715d7d (patch)
treeb864fda857f039595990405c2143d80ebbe8029a /src/selectable.rs
parentfeat: implement selection across bounds with hacky copy solution (diff)
downloadiced_selection-de65c1dc7285b15c87862198b98b435345715d7d.tar.gz
feat(selectable): draft implementation of selectable widget
Diffstat (limited to 'src/selectable.rs')
-rw-r--r--src/selectable.rs243
1 files changed, 243 insertions, 0 deletions
diff --git a/src/selectable.rs b/src/selectable.rs
new file mode 100644
index 0000000..217f913
--- /dev/null
+++ b/src/selectable.rs
@@ -0,0 +1,243 @@
+//! 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.
+//!
+//!
+//! [`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.
+///
+///
+/// [`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<Element<'a, Message, Theme, Renderer>>,
+ ) -> 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<Message, Theme, Renderer>
+ for Selectable<'a, Message, Theme, Renderer>
+where
+ Renderer: core::Renderer,
+{
+ fn tag(&self) -> tree::Tag {
+ tree::Tag::of::<State>()
+ }
+
+ fn state(&self) -> tree::State {
+ tree::State::new(State::default())
+ }
+
+ fn children(&self) -> Vec<Tree> {
+ vec![Tree::new(&self.content)]
+ }
+
+ fn diff(&self, tree: &mut Tree) {
+ tree.diff_children(std::slice::from_ref(&self.content));
+ }
+
+ fn size(&self) -> Size<Length> {
+ 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::<State>();
+
+ 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<overlay::Element<'b, Message, Theme, Renderer>> {
+ self.content.as_widget_mut().overlay(
+ &mut state.children[0],
+ layout,
+ renderer,
+ viewport,
+ translation,
+ )
+ }
+}
+
+impl<'a, Message, Theme, Renderer>
+ From<Selectable<'a, Message, Theme, Renderer>>
+ 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)
+ }
+}