From 52f84e51e90db1c324310565f2aff8b7e6987cba Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector0193@gmail.com>
Date: Thu, 28 Jul 2022 03:53:47 +0200
Subject: Implement `Widget::operate` for `TextInput`

---
 native/src/element.rs           | 37 +++++++++++++++++++++++++++++++++++++
 native/src/user_interface.rs    |  8 +++++---
 native/src/widget.rs            |  1 +
 native/src/widget/action.rs     |  6 +++---
 native/src/widget/button.rs     | 16 ++++++++++++++++
 native/src/widget/column.rs     | 19 ++++++++++++++++++-
 native/src/widget/container.rs  | 17 ++++++++++++++++-
 native/src/widget/operation.rs  | 18 ++++++++----------
 native/src/widget/row.rs        | 19 ++++++++++++++++++-
 native/src/widget/scrollable.rs | 16 ++++++++++++++++
 native/src/widget/text_input.rs | 39 +++++++++++++++++++++++++++++++++++++--
 11 files changed, 175 insertions(+), 21 deletions(-)

(limited to 'native')

diff --git a/native/src/element.rs b/native/src/element.rs
index cc74035e..01b71aa4 100644
--- a/native/src/element.rs
+++ b/native/src/element.rs
@@ -3,6 +3,7 @@ use crate::layout;
 use crate::mouse;
 use crate::overlay;
 use crate::renderer;
+use crate::widget;
 use crate::widget::tree::{self, Tree};
 use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell, Widget};
 
@@ -248,6 +249,42 @@ where
         self.widget.layout(renderer, limits)
     }
 
+    fn operate(
+        &self,
+        tree: &mut Tree,
+        layout: Layout<'_>,
+        operation: &mut dyn widget::Operation<B>,
+    ) {
+        struct MapOperation<'a, B> {
+            operation: &'a mut dyn widget::Operation<B>,
+        }
+
+        impl<'a, T, B> widget::Operation<T> for MapOperation<'a, B> {
+            fn container(
+                &mut self,
+                id: Option<&widget::Id>,
+                operate_on_children: &mut dyn FnMut(
+                    &mut dyn widget::Operation<T>,
+                ),
+            ) {
+                self.operation.container(id, &mut |operation| {
+                    operate_on_children(&mut MapOperation { operation });
+                });
+            }
+
+            fn focusable(
+                &mut self,
+                state: &mut dyn widget::state::Focusable,
+                id: Option<&widget::Id>,
+            ) {
+                self.operation.focusable(state, id);
+            }
+        }
+
+        self.widget
+            .operate(tree, layout, &mut MapOperation { operation });
+    }
+
     fn on_event(
         &mut self,
         tree: &mut Tree,
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index 25557240..42669f95 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -485,9 +485,11 @@ where
         renderer: &Renderer,
         operation: &mut dyn widget::Operation<Message>,
     ) {
-        self.root
-            .as_widget()
-            .operate(Layout::new(&self.base), operation);
+        self.root.as_widget().operate(
+            &mut self.state,
+            Layout::new(&self.base),
+            operation,
+        );
 
         if let Some(layout) = self.overlay.as_ref() {
             if let Some(overlay) = self.root.as_widget().overlay(
diff --git a/native/src/widget.rs b/native/src/widget.rs
index 79f6ae3a..56ba28c8 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -171,6 +171,7 @@ where
     /// Applies an [`Operation`] to the [`Widget`].
     fn operate(
         &self,
+        _state: &mut Tree,
         _layout: Layout<'_>,
         _operation: &mut dyn Operation<Message>,
     ) {
diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs
index 23ea4269..69723358 100644
--- a/native/src/widget/action.rs
+++ b/native/src/widget/action.rs
@@ -42,7 +42,7 @@ where
     fn container(
         &mut self,
         id: Option<&Id>,
-        operate_on_children: &dyn Fn(&mut dyn Operation<B>),
+        operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
     ) {
         struct MapRef<'a, A, B> {
             operation: &'a mut dyn Operation<A>,
@@ -53,11 +53,11 @@ where
             fn container(
                 &mut self,
                 id: Option<&Id>,
-                operate_on_children: &dyn Fn(&mut dyn Operation<B>),
+                operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
             ) {
                 let Self { operation, f } = self;
 
-                operation.container(id, &|operation| {
+                operation.container(id, &mut |operation| {
                     operate_on_children(&mut MapRef { operation, f });
                 });
             }
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index 6eac6c1b..6c0b8f6e 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -8,6 +8,7 @@ use crate::overlay;
 use crate::renderer;
 use crate::touch;
 use crate::widget::tree::{self, Tree};
+use crate::widget::Operation;
 use crate::{
     Background, Clipboard, Color, Element, Layout, Length, Padding, Point,
     Rectangle, Shell, Vector, Widget,
@@ -164,6 +165,21 @@ where
         )
     }
 
+    fn operate(
+        &self,
+        tree: &mut Tree,
+        layout: Layout<'_>,
+        operation: &mut dyn Operation<Message>,
+    ) {
+        operation.container(None, &mut |operation| {
+            self.content.as_widget().operate(
+                &mut tree.children[0],
+                layout.children().next().unwrap(),
+                operation,
+            );
+        });
+    }
+
     fn on_event(
         &mut self,
         tree: &mut Tree,
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index 834f9858..a8b0f183 100644
--- a/native/src/widget/column.rs
+++ b/native/src/widget/column.rs
@@ -4,7 +4,7 @@ use crate::layout;
 use crate::mouse;
 use crate::overlay;
 use crate::renderer;
-use crate::widget::Tree;
+use crate::widget::{Operation, Tree};
 use crate::{
     Alignment, Clipboard, Element, Layout, Length, Padding, Point, Rectangle,
     Shell, Widget,
@@ -143,6 +143,23 @@ where
         )
     }
 
+    fn operate(
+        &self,
+        tree: &mut Tree,
+        layout: Layout<'_>,
+        operation: &mut dyn Operation<Message>,
+    ) {
+        operation.container(None, &mut |operation| {
+            self.children
+                .iter()
+                .zip(&mut tree.children)
+                .zip(layout.children())
+                .for_each(|((child, state), layout)| {
+                    child.as_widget().operate(state, layout, operation);
+                })
+        });
+    }
+
     fn on_event(
         &mut self,
         tree: &mut Tree,
diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs
index b0fa0315..2afad3f2 100644
--- a/native/src/widget/container.rs
+++ b/native/src/widget/container.rs
@@ -5,7 +5,7 @@ use crate::layout;
 use crate::mouse;
 use crate::overlay;
 use crate::renderer;
-use crate::widget::Tree;
+use crate::widget::{Operation, Tree};
 use crate::{
     Background, Clipboard, Color, Element, Layout, Length, Padding, Point,
     Rectangle, Shell, Widget,
@@ -165,6 +165,21 @@ where
         )
     }
 
+    fn operate(
+        &self,
+        tree: &mut Tree,
+        layout: Layout<'_>,
+        operation: &mut dyn Operation<Message>,
+    ) {
+        operation.container(None, &mut |operation| {
+            self.content.as_widget().operate(
+                &mut tree.children[0],
+                layout.children().next().unwrap(),
+                operation,
+            );
+        });
+    }
+
     fn on_event(
         &mut self,
         tree: &mut Tree,
diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs
index b6c108e0..2cfba005 100644
--- a/native/src/widget/operation.rs
+++ b/native/src/widget/operation.rs
@@ -5,7 +5,7 @@ pub trait Operation<T> {
     fn container(
         &mut self,
         id: Option<&Id>,
-        operate_on_children: &dyn Fn(&mut dyn Operation<T>),
+        operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
     );
 
     fn focusable(
@@ -37,14 +37,12 @@ pub fn focus<T>(target: Id) -> impl Operation<T> {
             state: &mut dyn state::Focusable,
             id: Option<&Id>,
         ) {
-            if state.is_focused() {
-                match id {
-                    Some(id) if id == &self.target => {
-                        state.focus();
-                    }
-                    _ => {
-                        state.unfocus();
-                    }
+            match id {
+                Some(id) if id == &self.target => {
+                    state.focus();
+                }
+                _ => {
+                    state.unfocus();
                 }
             }
         }
@@ -52,7 +50,7 @@ pub fn focus<T>(target: Id) -> impl Operation<T> {
         fn container(
             &mut self,
             _id: Option<&Id>,
-            operate_on_children: &dyn Fn(&mut dyn Operation<T>),
+            operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
         ) {
             operate_on_children(self)
         }
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index c342c277..eda7c2d3 100644
--- a/native/src/widget/row.rs
+++ b/native/src/widget/row.rs
@@ -4,7 +4,7 @@ use crate::layout::{self, Layout};
 use crate::mouse;
 use crate::overlay;
 use crate::renderer;
-use crate::widget::Tree;
+use crate::widget::{Operation, Tree};
 use crate::{
     Alignment, Clipboard, Element, Length, Padding, Point, Rectangle, Shell,
     Widget,
@@ -130,6 +130,23 @@ where
         )
     }
 
+    fn operate(
+        &self,
+        tree: &mut Tree,
+        layout: Layout<'_>,
+        operation: &mut dyn Operation<Message>,
+    ) {
+        operation.container(None, &mut |operation| {
+            self.children
+                .iter()
+                .zip(&mut tree.children)
+                .zip(layout.children())
+                .for_each(|((child, state), layout)| {
+                    child.as_widget().operate(state, layout, operation);
+                })
+        });
+    }
+
     fn on_event(
         &mut self,
         tree: &mut Tree,
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index b40c3743..91c13eb5 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -6,6 +6,7 @@ use crate::overlay;
 use crate::renderer;
 use crate::touch;
 use crate::widget::tree::{self, Tree};
+use crate::widget::Operation;
 use crate::{
     Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle,
     Shell, Size, Vector, Widget,
@@ -150,6 +151,21 @@ where
         )
     }
 
+    fn operate(
+        &self,
+        tree: &mut Tree,
+        layout: Layout<'_>,
+        operation: &mut dyn Operation<Message>,
+    ) {
+        operation.container(None, &mut |operation| {
+            self.content.as_widget().operate(
+                &mut tree.children[0],
+                layout.children().next().unwrap(),
+                operation,
+            );
+        });
+    }
+
     fn on_event(
         &mut self,
         tree: &mut Tree,
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index 1dbb8d6b..1ca5ccf2 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -19,11 +19,13 @@ use crate::mouse::{self, click};
 use crate::renderer;
 use crate::text::{self, Text};
 use crate::touch;
+use crate::widget;
+use crate::widget::operation::{self, Operation};
 use crate::widget::state;
 use crate::widget::tree::{self, Tree};
 use crate::{
-    Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle,
-    Shell, Size, Vector, Widget,
+    Clipboard, Color, Command, Element, Layout, Length, Padding, Point,
+    Rectangle, Shell, Size, Vector, Widget,
 };
 
 pub use iced_style::text_input::{Appearance, StyleSheet};
@@ -54,6 +56,7 @@ where
     Renderer: text::Renderer,
     Renderer::Theme: StyleSheet,
 {
+    id: Option<Id>,
     placeholder: String,
     value: Value,
     is_secure: bool,
@@ -84,6 +87,7 @@ where
         F: 'a + Fn(String) -> Message,
     {
         TextInput {
+            id: None,
             placeholder: String::from(placeholder),
             value: Value::new(value),
             is_secure: false,
@@ -98,6 +102,12 @@ where
         }
     }
 
+    /// Sets the [`Id`] of the [`TextInput`].
+    pub fn id(mut self, id: Id) -> Self {
+        self.id = Some(id);
+        self
+    }
+
     /// Converts the [`TextInput`] into a secure password input.
     pub fn password(mut self) -> Self {
         self.is_secure = true;
@@ -215,6 +225,17 @@ where
         layout(renderer, limits, self.width, self.padding, self.size)
     }
 
+    fn operate(
+        &self,
+        tree: &mut Tree,
+        _layout: Layout<'_>,
+        operation: &mut dyn Operation<Message>,
+    ) {
+        let state = tree.state.downcast_mut::<State>();
+
+        operation.focusable(state, self.id.as_ref().map(|id| &id.0));
+    }
+
     fn on_event(
         &mut self,
         tree: &mut Tree,
@@ -294,6 +315,19 @@ where
     }
 }
 
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct Id(widget::Id);
+
+impl Id {
+    pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
+        Self(widget::Id::new(id))
+    }
+}
+
+pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
+    Command::widget(operation::focus(id.0))
+}
+
 /// Computes the layout of a [`TextInput`].
 pub fn layout<Renderer>(
     renderer: &Renderer,
@@ -915,6 +949,7 @@ impl State {
     /// Focuses the [`TextInput`].
     pub fn focus(&mut self) {
         self.is_focused = true;
+        self.move_cursor_to_end();
     }
 
     /// Unfocuses the [`TextInput`].
-- 
cgit