From 80688689aa4b15bc23824df899974a9094a77b07 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 28 Jul 2022 02:46:51 +0200 Subject: Draft widget operations --- native/src/command.rs | 10 ++++++ native/src/command/action.rs | 7 ++++ native/src/lib.rs | 4 +-- native/src/overlay.rs | 9 +++++ native/src/overlay/element.rs | 10 ++++++ native/src/user_interface.rs | 21 +++++++++++ native/src/widget.rs | 17 +++++++++ native/src/widget/action.rs | 78 +++++++++++++++++++++++++++++++++++++++++ native/src/widget/helpers.rs | 4 +-- native/src/widget/id.rs | 38 ++++++++++++++++++++ native/src/widget/operation.rs | 62 ++++++++++++++++++++++++++++++++ native/src/widget/state.rs | 5 +++ native/src/widget/text_input.rs | 15 ++++++++ 13 files changed, 276 insertions(+), 4 deletions(-) create mode 100644 native/src/widget/action.rs create mode 100644 native/src/widget/id.rs create mode 100644 native/src/widget/operation.rs create mode 100644 native/src/widget/state.rs (limited to 'native') diff --git a/native/src/command.rs b/native/src/command.rs index 89d0f045..b0b12805 100644 --- a/native/src/command.rs +++ b/native/src/command.rs @@ -3,6 +3,8 @@ mod action; pub use action::Action; +use crate::widget; + use iced_futures::MaybeSend; use std::fmt; @@ -24,6 +26,13 @@ impl Command { Self(iced_futures::Command::single(action)) } + /// Creates a [`Command`] that performs a [`widget::Operation`]. + pub fn widget(operation: impl widget::Operation + 'static) -> Self { + Self(iced_futures::Command::single(Action::Widget( + widget::Action::new(operation), + ))) + } + /// Creates a [`Command`] that performs the action of the given future. pub fn perform( future: impl Future + 'static + MaybeSend, @@ -51,6 +60,7 @@ impl Command { ) -> Command where T: 'static, + A: 'static, { let Command(command) = self; diff --git a/native/src/command/action.rs b/native/src/command/action.rs index 1bb03cef..3fb02899 100644 --- a/native/src/command/action.rs +++ b/native/src/command/action.rs @@ -1,5 +1,6 @@ use crate::clipboard; use crate::system; +use crate::widget; use crate::window; use iced_futures::MaybeSend; @@ -23,6 +24,9 @@ pub enum Action { /// Run a system action. System(system::Action), + + /// Run a widget action. + Widget(widget::Action), } impl Action { @@ -34,6 +38,7 @@ impl Action { f: impl Fn(T) -> A + 'static + MaybeSend + Sync, ) -> Action where + A: 'static, T: 'static, { use iced_futures::futures::FutureExt; @@ -43,6 +48,7 @@ impl Action { Self::Clipboard(action) => Action::Clipboard(action.map(f)), Self::Window(window) => Action::Window(window), Self::System(system) => Action::System(system.map(f)), + Self::Widget(widget) => Action::Widget(widget.map(f)), } } } @@ -56,6 +62,7 @@ impl fmt::Debug for Action { } Self::Window(action) => write!(f, "Action::Window({:?})", action), Self::System(action) => write!(f, "Action::System({:?})", action), + Self::Widget(_action) => write!(f, "Action::Widget"), } } } diff --git a/native/src/lib.rs b/native/src/lib.rs index 13173901..73a6c624 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -32,8 +32,8 @@ html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] #![deny( - missing_debug_implementations, - missing_docs, +// missing_debug_implementations, +// missing_docs, unused_results, clippy::extra_unused_lifetimes, clippy::from_over_into, diff --git a/native/src/overlay.rs b/native/src/overlay.rs index c2a98693..905d3389 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -10,6 +10,7 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; +use crate::widget; use crate::widget::tree::{self, Tree}; use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size}; @@ -63,6 +64,14 @@ where /// Reconciliates the [`Widget`] with the provided [`Tree`]. fn diff(&self, _tree: &mut Tree) {} + /// Applies an [`Operation`] to the [`Widget`]. + fn operate( + &self, + _layout: Layout<'_>, + _operation: &mut dyn widget::Operation, + ) { + } + /// Processes a runtime [`Event`]. /// /// It receives: diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index de2e1f37..b919c221 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -4,6 +4,7 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; +use crate::widget; use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; /// A generic [`Overlay`]. @@ -102,6 +103,15 @@ where self.overlay .draw(renderer, theme, style, layout, cursor_position) } + + /// Applies an [`Operation`] to the [`Element`]. + pub fn operate( + &self, + layout: Layout<'_>, + operation: &mut dyn widget::Operation, + ) { + self.overlay.operate(layout, operation); + } } struct Map<'a, A, B, Renderer> { diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 9f3a8e21..25557240 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -479,6 +479,27 @@ where .unwrap_or(base_interaction) } + /// Applies a [`widget::Operation`] to the [`UserInterface`]. + pub fn operate( + &mut self, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + self.root + .as_widget() + .operate(Layout::new(&self.base), operation); + + if let Some(layout) = self.overlay.as_ref() { + if let Some(overlay) = self.root.as_widget().overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + ) { + overlay.operate(Layout::new(layout), operation); + } + } + } + /// Relayouts and returns a new [`UserInterface`] using the provided /// bounds. pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self { diff --git a/native/src/widget.rs b/native/src/widget.rs index 9a4f373a..79f6ae3a 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -17,6 +17,7 @@ pub mod column; pub mod container; pub mod helpers; pub mod image; +pub mod operation; pub mod pane_grid; pub mod pick_list; pub mod progress_bar; @@ -26,6 +27,7 @@ pub mod rule; pub mod scrollable; pub mod slider; pub mod space; +pub mod state; pub mod svg; pub mod text; pub mod text_input; @@ -33,6 +35,9 @@ pub mod toggler; pub mod tooltip; pub mod tree; +mod action; +mod id; + #[doc(no_inline)] pub use button::Button; #[doc(no_inline)] @@ -76,6 +81,10 @@ pub use tooltip::Tooltip; #[doc(no_inline)] pub use tree::Tree; +pub use action::Action; +pub use id::Id; +pub use operation::Operation; + use crate::event::{self, Event}; use crate::layout; use crate::mouse; @@ -159,6 +168,14 @@ where /// Reconciliates the [`Widget`] with the provided [`Tree`]. fn diff(&self, _tree: &mut Tree) {} + /// Applies an [`Operation`] to the [`Widget`]. + fn operate( + &self, + _layout: Layout<'_>, + _operation: &mut dyn Operation, + ) { + } + /// Processes a runtime [`Event`]. /// /// By default, it does nothing. diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs new file mode 100644 index 00000000..23ea4269 --- /dev/null +++ b/native/src/widget/action.rs @@ -0,0 +1,78 @@ +use crate::widget::state; +use crate::widget::{Id, Operation}; + +use iced_futures::MaybeSend; + +pub struct Action(Box>); + +impl Action { + pub fn new(operation: impl Operation + 'static) -> Self { + Self(Box::new(operation)) + } + + pub fn map( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> Action + where + T: 'static, + A: 'static, + { + Action(Box::new(Map { + operation: self.0, + f: Box::new(f), + })) + } + + pub fn into_operation(self) -> Box> { + self.0 + } +} + +struct Map { + operation: Box>, + f: Box B>, +} + +impl Operation for Map +where + A: 'static, + B: 'static, +{ + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &dyn Fn(&mut dyn Operation), + ) { + struct MapRef<'a, A, B> { + operation: &'a mut dyn Operation, + f: &'a dyn Fn(A) -> B, + } + + impl<'a, A, B> Operation for MapRef<'a, A, B> { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &dyn Fn(&mut dyn Operation), + ) { + let Self { operation, f } = self; + + operation.container(id, &|operation| { + operate_on_children(&mut MapRef { operation, f }); + }); + } + } + + let Self { operation, f } = self; + + MapRef { + operation: operation.as_mut(), + f, + } + .container(id, operate_on_children); + } + + fn focusable(&mut self, state: &mut dyn state::Focusable, id: Option<&Id>) { + self.operation.focusable(state, id); + } +} diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index 518ac23b..a62448e9 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -49,8 +49,8 @@ where /// [`Column`]: widget::Column pub fn column( children: Vec>, -) -> widget::Row<'_, Message, Renderer> { - widget::Row::with_children(children) +) -> widget::Column<'_, Message, Renderer> { + widget::Column::with_children(children) } /// Creates a new [`Row`] with the given children. diff --git a/native/src/widget/id.rs b/native/src/widget/id.rs new file mode 100644 index 00000000..4c0ab999 --- /dev/null +++ b/native/src/widget/id.rs @@ -0,0 +1,38 @@ +use std::borrow; +use std::sync::atomic::{self, AtomicUsize}; + +static NEXT_ID: AtomicUsize = AtomicUsize::new(0); + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Id(Internal); + +impl Id { + pub fn new(id: impl Into>) -> Self { + Self(Internal::Custom(id.into())) + } + + pub fn unique() -> Self { + let id = NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed); + + Self(Internal::Unique(id)) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Internal { + Unique(usize), + Custom(borrow::Cow<'static, str>), +} + +#[cfg(test)] +mod tests { + use super::Id; + + #[test] + fn unique_generates_different_ids() { + let a = Id::unique(); + let b = Id::unique(); + + assert_ne!(a, b); + } +} diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs new file mode 100644 index 00000000..b6c108e0 --- /dev/null +++ b/native/src/widget/operation.rs @@ -0,0 +1,62 @@ +use crate::widget::state; +use crate::widget::Id; + +pub trait Operation { + fn container( + &mut self, + id: Option<&Id>, + operate_on_children: &dyn Fn(&mut dyn Operation), + ); + + fn focusable( + &mut self, + _state: &mut dyn state::Focusable, + _id: Option<&Id>, + ) { + } + + fn finish(&self) -> Outcome { + Outcome::None + } +} + +pub enum Outcome { + None, + Some(T), + Chain(Box>), +} + +pub fn focus(target: Id) -> impl Operation { + struct Focus { + target: Id, + } + + impl Operation for Focus { + fn focusable( + &mut self, + state: &mut dyn state::Focusable, + id: Option<&Id>, + ) { + if state.is_focused() { + match id { + Some(id) if id == &self.target => { + state.focus(); + } + _ => { + state.unfocus(); + } + } + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &dyn Fn(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + Focus { target } +} diff --git a/native/src/widget/state.rs b/native/src/widget/state.rs new file mode 100644 index 00000000..d1984a71 --- /dev/null +++ b/native/src/widget/state.rs @@ -0,0 +1,5 @@ +pub trait Focusable { + fn is_focused(&self) -> bool; + fn focus(&mut self); + fn unfocus(&mut self); +} diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 4ef9e11b..1dbb8d6b 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -19,6 +19,7 @@ use crate::mouse::{self, click}; use crate::renderer; use crate::text::{self, Text}; use crate::touch; +use crate::widget::state; use crate::widget::tree::{self, Tree}; use crate::{ Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle, @@ -942,6 +943,20 @@ impl State { } } +impl state::Focusable for State { + fn is_focused(&self) -> bool { + State::is_focused(self) + } + + fn focus(&mut self) { + State::focus(self) + } + + fn unfocus(&mut self) { + State::unfocus(self) + } +} + mod platform { use crate::keyboard; -- cgit From 52f84e51e90db1c324310565f2aff8b7e6987cba Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez 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, + ) { + struct MapOperation<'a, B> { + operation: &'a mut dyn widget::Operation, + } + + impl<'a, T, B> widget::Operation for MapOperation<'a, B> { + fn container( + &mut self, + id: Option<&widget::Id>, + operate_on_children: &mut dyn FnMut( + &mut dyn widget::Operation, + ), + ) { + 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, ) { - 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, ) { 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), + operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { struct MapRef<'a, A, B> { operation: &'a mut dyn Operation, @@ -53,11 +53,11 @@ where fn container( &mut self, id: Option<&Id>, - operate_on_children: &dyn Fn(&mut dyn Operation), + operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { 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, + ) { + 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, + ) { + 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, + ) { + 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 { fn container( &mut self, id: Option<&Id>, - operate_on_children: &dyn Fn(&mut dyn Operation), + operate_on_children: &mut dyn FnMut(&mut dyn Operation), ); fn focusable( @@ -37,14 +37,12 @@ pub fn focus(target: Id) -> impl Operation { 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(target: Id) -> impl Operation { fn container( &mut self, _id: Option<&Id>, - operate_on_children: &dyn Fn(&mut dyn Operation), + operate_on_children: &mut dyn FnMut(&mut dyn Operation), ) { 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, + ) { + 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, + ) { + 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, 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, + ) { + let state = tree.state.downcast_mut::(); + + 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>) -> Self { + Self(widget::Id::new(id)) + } +} + +pub fn focus(id: Id) -> Command { + Command::widget(operation::focus(id.0)) +} + /// Computes the layout of a [`TextInput`]. pub fn layout( 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 From 744edbd6c17c3c9d872992c84e074f14c8e75a47 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 28 Jul 2022 03:54:02 +0200 Subject: Focus text inputs in `todos` example --- native/src/widget/text_input.rs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'native') diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 1ca5ccf2..a81cfaed 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -322,6 +322,10 @@ impl Id { pub fn new(id: impl Into>) -> Self { Self(widget::Id::new(id)) } + + pub fn unique() -> Self { + Self(widget::Id::unique()) + } } pub fn focus(id: Id) -> Command { -- cgit From 77c6864e7c772c5e228bc09fe40c2c0b8884386d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 2 Aug 2022 04:20:47 +0200 Subject: Implement `focus_next` operation ... as well as a `count_focusable` composable helper! --- native/src/widget/operation.rs | 93 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) (limited to 'native') diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index 2cfba005..5a0f0c18 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -58,3 +58,96 @@ pub fn focus(target: Id) -> impl Operation { Focus { target } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct FocusCount { + focused: Option, + total: usize, +} + +pub fn count_focusable(f: fn(FocusCount) -> O) -> impl Operation +where + O: Operation + 'static, +{ + struct CountFocusable { + count: FocusCount, + next: fn(FocusCount) -> O, + } + + impl Operation for CountFocusable + where + O: Operation + 'static, + { + fn focusable( + &mut self, + state: &mut dyn state::Focusable, + _id: Option<&Id>, + ) { + if state.is_focused() { + self.count.focused = Some(self.count.total); + } + + self.count.total += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + + fn finish(&self) -> Outcome { + Outcome::Chain(Box::new((self.next)(self.count))) + } + } + + CountFocusable { + count: FocusCount::default(), + next: f, + } +} + +pub fn focus_next() -> impl Operation { + struct FocusNext { + count: FocusCount, + current: usize, + } + + impl Operation for FocusNext { + fn focusable( + &mut self, + state: &mut dyn state::Focusable, + _id: Option<&Id>, + ) { + if self.count.total == 0 { + return; + } + + match self.count.focused { + None if self.current == 0 => state.focus(), + Some(focused) if focused == self.current => state.unfocus(), + Some(focused) if focused + 1 == self.current => state.focus(), + Some(focused) + if focused == self.count.total - 1 && self.current == 0 => + { + state.focus() + } + _ => {} + } + + self.current += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + count_focusable(|count| FocusNext { count, current: 0 }) +} -- cgit From 6eb3dd7e5edc8847875c288c41d1dec8b1dad06e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 4 Aug 2022 03:24:44 +0200 Subject: Implement `focus_previous` operation --- native/src/widget/operation.rs | 47 +++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 8 deletions(-) (limited to 'native') diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index 5a0f0c18..caf7ba1c 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -109,13 +109,13 @@ where } } -pub fn focus_next() -> impl Operation { - struct FocusNext { +pub fn focus_previous() -> impl Operation { + struct FocusPrevious { count: FocusCount, current: usize, } - impl Operation for FocusNext { + impl Operation for FocusPrevious { fn focusable( &mut self, state: &mut dyn state::Focusable, @@ -125,15 +125,46 @@ pub fn focus_next() -> impl Operation { return; } + match self.count.focused { + None if self.current == self.count.total - 1 => state.focus(), + Some(0) if self.current == 0 => state.unfocus(), + Some(0) => {} + Some(focused) if focused == self.current => state.unfocus(), + Some(focused) if focused - 1 == self.current => state.focus(), + _ => {} + } + + self.current += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + count_focusable(|count| FocusPrevious { count, current: 0 }) +} + +pub fn focus_next() -> impl Operation { + struct FocusNext { + count: FocusCount, + current: usize, + } + + impl Operation for FocusNext { + fn focusable( + &mut self, + state: &mut dyn state::Focusable, + _id: Option<&Id>, + ) { match self.count.focused { None if self.current == 0 => state.focus(), Some(focused) if focused == self.current => state.unfocus(), Some(focused) if focused + 1 == self.current => state.focus(), - Some(focused) - if focused == self.count.total - 1 && self.current == 0 => - { - state.focus() - } _ => {} } -- cgit From 13dd1ca0a83cc95eea52e2106da9dc1ee1f37958 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 4 Aug 2022 03:55:41 +0200 Subject: Implement `scrollable::snap_to` operation --- native/src/element.rs | 2 +- native/src/widget.rs | 1 - native/src/widget/action.rs | 10 +- native/src/widget/operation.rs | 173 ++---------------------------- native/src/widget/operation/focusable.rs | 149 +++++++++++++++++++++++++ native/src/widget/operation/scrollable.rs | 30 ++++++ native/src/widget/scrollable.rs | 42 +++++++- native/src/widget/state.rs | 6 +- native/src/widget/text_input.rs | 5 +- 9 files changed, 238 insertions(+), 180 deletions(-) create mode 100644 native/src/widget/operation/focusable.rs create mode 100644 native/src/widget/operation/scrollable.rs (limited to 'native') diff --git a/native/src/element.rs b/native/src/element.rs index 01b71aa4..8b994d73 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -274,7 +274,7 @@ where fn focusable( &mut self, - state: &mut dyn widget::state::Focusable, + state: &mut dyn widget::operation::Focusable, id: Option<&widget::Id>, ) { self.operation.focusable(state, id); diff --git a/native/src/widget.rs b/native/src/widget.rs index 56ba28c8..8890b8e7 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -27,7 +27,6 @@ pub mod rule; pub mod scrollable; pub mod slider; pub mod space; -pub mod state; pub mod svg; pub mod text; pub mod text_input; diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs index 69723358..21032dbb 100644 --- a/native/src/widget/action.rs +++ b/native/src/widget/action.rs @@ -1,5 +1,5 @@ -use crate::widget::state; -use crate::widget::{Id, Operation}; +use crate::widget::operation::{self, Operation}; +use crate::widget::Id; use iced_futures::MaybeSend; @@ -72,7 +72,11 @@ where .container(id, operate_on_children); } - fn focusable(&mut self, state: &mut dyn state::Focusable, id: Option<&Id>) { + fn focusable( + &mut self, + state: &mut dyn operation::Focusable, + id: Option<&Id>, + ) { self.operation.focusable(state, id); } } diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index caf7ba1c..4a075da9 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -1,4 +1,9 @@ -use crate::widget::state; +pub mod focusable; +pub mod scrollable; + +pub use focusable::Focusable; +pub use scrollable::Scrollable; + use crate::widget::Id; pub trait Operation { @@ -8,12 +13,9 @@ pub trait Operation { operate_on_children: &mut dyn FnMut(&mut dyn Operation), ); - fn focusable( - &mut self, - _state: &mut dyn state::Focusable, - _id: Option<&Id>, - ) { - } + fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {} + + fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {} fn finish(&self) -> Outcome { Outcome::None @@ -25,160 +27,3 @@ pub enum Outcome { Some(T), Chain(Box>), } - -pub fn focus(target: Id) -> impl Operation { - struct Focus { - target: Id, - } - - impl Operation for Focus { - fn focusable( - &mut self, - state: &mut dyn state::Focusable, - id: Option<&Id>, - ) { - match id { - Some(id) if id == &self.target => { - state.focus(); - } - _ => { - state.unfocus(); - } - } - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation), - ) { - operate_on_children(self) - } - } - - Focus { target } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct FocusCount { - focused: Option, - total: usize, -} - -pub fn count_focusable(f: fn(FocusCount) -> O) -> impl Operation -where - O: Operation + 'static, -{ - struct CountFocusable { - count: FocusCount, - next: fn(FocusCount) -> O, - } - - impl Operation for CountFocusable - where - O: Operation + 'static, - { - fn focusable( - &mut self, - state: &mut dyn state::Focusable, - _id: Option<&Id>, - ) { - if state.is_focused() { - self.count.focused = Some(self.count.total); - } - - self.count.total += 1; - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation), - ) { - operate_on_children(self) - } - - fn finish(&self) -> Outcome { - Outcome::Chain(Box::new((self.next)(self.count))) - } - } - - CountFocusable { - count: FocusCount::default(), - next: f, - } -} - -pub fn focus_previous() -> impl Operation { - struct FocusPrevious { - count: FocusCount, - current: usize, - } - - impl Operation for FocusPrevious { - fn focusable( - &mut self, - state: &mut dyn state::Focusable, - _id: Option<&Id>, - ) { - if self.count.total == 0 { - return; - } - - match self.count.focused { - None if self.current == self.count.total - 1 => state.focus(), - Some(0) if self.current == 0 => state.unfocus(), - Some(0) => {} - Some(focused) if focused == self.current => state.unfocus(), - Some(focused) if focused - 1 == self.current => state.focus(), - _ => {} - } - - self.current += 1; - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation), - ) { - operate_on_children(self) - } - } - - count_focusable(|count| FocusPrevious { count, current: 0 }) -} - -pub fn focus_next() -> impl Operation { - struct FocusNext { - count: FocusCount, - current: usize, - } - - impl Operation for FocusNext { - fn focusable( - &mut self, - state: &mut dyn state::Focusable, - _id: Option<&Id>, - ) { - match self.count.focused { - None if self.current == 0 => state.focus(), - Some(focused) if focused == self.current => state.unfocus(), - Some(focused) if focused + 1 == self.current => state.focus(), - _ => {} - } - - self.current += 1; - } - - fn container( - &mut self, - _id: Option<&Id>, - operate_on_children: &mut dyn FnMut(&mut dyn Operation), - ) { - operate_on_children(self) - } - } - - count_focusable(|count| FocusNext { count, current: 0 }) -} diff --git a/native/src/widget/operation/focusable.rs b/native/src/widget/operation/focusable.rs new file mode 100644 index 00000000..20a73291 --- /dev/null +++ b/native/src/widget/operation/focusable.rs @@ -0,0 +1,149 @@ +use crate::widget::operation::{Operation, Outcome}; +use crate::widget::Id; + +pub trait Focusable { + fn is_focused(&self) -> bool; + fn focus(&mut self); + fn unfocus(&mut self); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct Count { + focused: Option, + total: usize, +} + +pub fn focus(target: Id) -> impl Operation { + struct Focus { + target: Id, + } + + impl Operation for Focus { + fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) { + match id { + Some(id) if id == &self.target => { + state.focus(); + } + _ => { + state.unfocus(); + } + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + Focus { target } +} + +pub fn count(f: fn(Count) -> O) -> impl Operation +where + O: Operation + 'static, +{ + struct CountFocusable { + count: Count, + next: fn(Count) -> O, + } + + impl Operation for CountFocusable + where + O: Operation + 'static, + { + fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { + if state.is_focused() { + self.count.focused = Some(self.count.total); + } + + self.count.total += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + + fn finish(&self) -> Outcome { + Outcome::Chain(Box::new((self.next)(self.count))) + } + } + + CountFocusable { + count: Count::default(), + next: f, + } +} + +pub fn focus_previous() -> impl Operation { + struct FocusPrevious { + count: Count, + current: usize, + } + + impl Operation for FocusPrevious { + fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { + if self.count.total == 0 { + return; + } + + match self.count.focused { + None if self.current == self.count.total - 1 => state.focus(), + Some(0) if self.current == 0 => state.unfocus(), + Some(0) => {} + Some(focused) if focused == self.current => state.unfocus(), + Some(focused) if focused - 1 == self.current => state.focus(), + _ => {} + } + + self.current += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + count(|count| FocusPrevious { count, current: 0 }) +} + +pub fn focus_next() -> impl Operation { + struct FocusNext { + count: Count, + current: usize, + } + + impl Operation for FocusNext { + fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) { + match self.count.focused { + None if self.current == 0 => state.focus(), + Some(focused) if focused == self.current => state.unfocus(), + Some(focused) if focused + 1 == self.current => state.focus(), + _ => {} + } + + self.current += 1; + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + count(|count| FocusNext { count, current: 0 }) +} diff --git a/native/src/widget/operation/scrollable.rs b/native/src/widget/operation/scrollable.rs new file mode 100644 index 00000000..ed609d67 --- /dev/null +++ b/native/src/widget/operation/scrollable.rs @@ -0,0 +1,30 @@ +use crate::widget::{Id, Operation}; + +pub trait Scrollable { + fn snap_to(&mut self, percentage: f32); +} + +pub fn snap_to(target: Id, percentage: f32) -> impl Operation { + struct SnapTo { + target: Id, + percentage: f32, + } + + impl Operation for SnapTo { + fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) { + if Some(&self.target) == id { + state.snap_to(self.percentage); + } + } + + fn container( + &mut self, + _id: Option<&Id>, + operate_on_children: &mut dyn FnMut(&mut dyn Operation), + ) { + operate_on_children(self) + } + } + + SnapTo { target, percentage } +} diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 91c13eb5..b7a0b6ee 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -5,11 +5,12 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::touch; +use crate::widget; +use crate::widget::operation::{self, Operation}; use crate::widget::tree::{self, Tree}; -use crate::widget::Operation; use crate::{ - Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle, - Shell, Size, Vector, Widget, + Background, Clipboard, Color, Command, Element, Layout, Length, Point, + Rectangle, Shell, Size, Vector, Widget, }; use std::{f32, u32}; @@ -31,6 +32,7 @@ where Renderer: crate::Renderer, Renderer::Theme: StyleSheet, { + id: Option, height: Length, scrollbar_width: u16, scrollbar_margin: u16, @@ -48,6 +50,7 @@ where /// Creates a new [`Scrollable`]. pub fn new(content: impl Into>) -> Self { Scrollable { + id: None, height: Length::Shrink, scrollbar_width: 10, scrollbar_margin: 0, @@ -58,6 +61,12 @@ where } } + /// Sets the [`Id`] of the [`Scrollable`]. + pub fn id(mut self, id: Id) -> Self { + self.id = Some(id); + self + } + /// Sets the height of the [`Scrollable`]. pub fn height(mut self, height: Length) -> Self { self.height = height; @@ -157,6 +166,10 @@ where layout: Layout<'_>, operation: &mut dyn Operation, ) { + let state = tree.state.downcast_mut::(); + + operation.scrollable(state, self.id.as_ref().map(|id| &id.0)); + operation.container(None, &mut |operation| { self.content.as_widget().operate( &mut tree.children[0], @@ -303,6 +316,23 @@ where } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Id(widget::Id); + +impl Id { + pub fn new(id: impl Into>) -> Self { + Self(widget::Id::new(id)) + } + + pub fn unique() -> Self { + Self(widget::Id::unique()) + } +} + +pub fn snap_to(id: Id, percentage: f32) -> Command { + Command::widget(operation::scrollable::snap_to(id.0, percentage)) +} + /// Computes the layout of a [`Scrollable`]. pub fn layout( renderer: &Renderer, @@ -790,6 +820,12 @@ impl Default for State { } } +impl operation::Scrollable for State { + fn snap_to(&mut self, percentage: f32) { + State::snap_to(self, percentage); + } +} + /// The local state of a [`Scrollable`]. #[derive(Debug, Clone, Copy)] enum Offset { diff --git a/native/src/widget/state.rs b/native/src/widget/state.rs index d1984a71..8b137891 100644 --- a/native/src/widget/state.rs +++ b/native/src/widget/state.rs @@ -1,5 +1 @@ -pub trait Focusable { - fn is_focused(&self) -> bool; - fn focus(&mut self); - fn unfocus(&mut self); -} + diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index a81cfaed..e0216a5b 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -21,7 +21,6 @@ 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, Command, Element, Layout, Length, Padding, Point, @@ -329,7 +328,7 @@ impl Id { } pub fn focus(id: Id) -> Command { - Command::widget(operation::focus(id.0)) + Command::widget(operation::focusable::focus(id.0)) } /// Computes the layout of a [`TextInput`]. @@ -982,7 +981,7 @@ impl State { } } -impl state::Focusable for State { +impl operation::Focusable for State { fn is_focused(&self) -> bool { State::is_focused(self) } -- cgit From 66f7d43dc98df96c8b19cfd2aef6dcdd4187316c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 5 Aug 2022 05:15:41 +0200 Subject: Write missing documentation in `iced_native` --- native/src/lib.rs | 4 ++-- native/src/widget/action.rs | 6 ++++++ native/src/widget/id.rs | 5 +++++ native/src/widget/operation.rs | 31 +++++++++++++++++++++++++++++++ native/src/widget/operation/focusable.rs | 19 +++++++++++++++++++ native/src/widget/operation/scrollable.rs | 5 +++++ native/src/widget/scrollable.rs | 7 +++++++ native/src/widget/text_input.rs | 6 ++++++ 8 files changed, 81 insertions(+), 2 deletions(-) (limited to 'native') diff --git a/native/src/lib.rs b/native/src/lib.rs index 73a6c624..13173901 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -32,8 +32,8 @@ html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] #![deny( -// missing_debug_implementations, -// missing_docs, + missing_debug_implementations, + missing_docs, unused_results, clippy::extra_unused_lifetimes, clippy::from_over_into, diff --git a/native/src/widget/action.rs b/native/src/widget/action.rs index 21032dbb..766e902b 100644 --- a/native/src/widget/action.rs +++ b/native/src/widget/action.rs @@ -3,13 +3,17 @@ use crate::widget::Id; use iced_futures::MaybeSend; +/// An operation to be performed on the widget tree. +#[allow(missing_debug_implementations)] pub struct Action(Box>); impl Action { + /// Creates a new [`Action`] with the given [`Operation`]. pub fn new(operation: impl Operation + 'static) -> Self { Self(Box::new(operation)) } + /// Maps the output of an [`Action`] using the given function. pub fn map( self, f: impl Fn(T) -> A + 'static + MaybeSend + Sync, @@ -24,11 +28,13 @@ impl Action { })) } + /// Consumes the [`Action`] and returns the internal [`Operation`]. pub fn into_operation(self) -> Box> { self.0 } } +#[allow(missing_debug_implementations)] struct Map { operation: Box>, f: Box B>, diff --git a/native/src/widget/id.rs b/native/src/widget/id.rs index 4c0ab999..4b8fedf1 100644 --- a/native/src/widget/id.rs +++ b/native/src/widget/id.rs @@ -3,14 +3,19 @@ use std::sync::atomic::{self, AtomicUsize}; static NEXT_ID: AtomicUsize = AtomicUsize::new(0); +/// The identifier of a generic widget. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Id(Internal); impl Id { + /// Creates a custom [`Id`]. pub fn new(id: impl Into>) -> Self { Self(Internal::Custom(id.into())) } + /// Creates a unique [`Id`]. + /// + /// This function produces a different [`Id`] every time it is called. pub fn unique() -> Self { let id = NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed); diff --git a/native/src/widget/operation.rs b/native/src/widget/operation.rs index 4a075da9..ef636aa2 100644 --- a/native/src/widget/operation.rs +++ b/native/src/widget/operation.rs @@ -1,3 +1,4 @@ +//! Query or update internal widget state. pub mod focusable; pub mod scrollable; @@ -6,24 +7,54 @@ pub use scrollable::Scrollable; use crate::widget::Id; +use std::fmt; + +/// A piece of logic that can traverse the widget tree of an application in +/// order to query or update some widget state. pub trait Operation { + /// Operates on a widget that contains other widgets. + /// + /// The `operate_on_children` function can be called to return control to + /// the widget tree and keep traversing it. fn container( &mut self, id: Option<&Id>, operate_on_children: &mut dyn FnMut(&mut dyn Operation), ); + /// Operates on a widget that can be focused. fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {} + /// Operates on a widget that can be scrolled. fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {} + /// Finishes the [`Operation`] and returns its [`Outcome`]. fn finish(&self) -> Outcome { Outcome::None } } +/// The result of an [`Operation`]. pub enum Outcome { + /// The [`Operation`] produced no result. None, + + /// The [`Operation`] produced some result. Some(T), + + /// The [`Operation`] needs to be followed by another [`Operation`]. Chain(Box>), } + +impl fmt::Debug for Outcome +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::None => write!(f, "Outcome::None"), + Self::Some(output) => write!(f, "Outcome::Some({:?})", output), + Self::Chain(_) => write!(f, "Outcome::Chain(...)"), + } + } +} diff --git a/native/src/widget/operation/focusable.rs b/native/src/widget/operation/focusable.rs index 20a73291..d20bfef9 100644 --- a/native/src/widget/operation/focusable.rs +++ b/native/src/widget/operation/focusable.rs @@ -1,18 +1,30 @@ +//! Operate on widgets that can be focused. use crate::widget::operation::{Operation, Outcome}; use crate::widget::Id; +/// The internal state of a widget that can be focused. pub trait Focusable { + /// Returns whether the widget is focused or not. fn is_focused(&self) -> bool; + + /// Focuses the widget. fn focus(&mut self); + + /// Unfocuses the widget. fn unfocus(&mut self); } +/// A summary of the focusable widgets present on a widget tree. #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Count { + /// The index of the current focused widget, if any. focused: Option, + + /// The total amount of focusable widgets. total: usize, } +/// Produces an [`Operation`] that focuses the widget with the given [`Id`]. pub fn focus(target: Id) -> impl Operation { struct Focus { target: Id, @@ -42,6 +54,7 @@ pub fn focus(target: Id) -> impl Operation { Focus { target } } +/// Produces an [`Operation`] that generates a [`Count`]. pub fn count(f: fn(Count) -> O) -> impl Operation where O: Operation + 'static, @@ -82,6 +95,9 @@ where } } +/// Produces an [`Operation`] that searches for the current focuses widget, and +/// - if found, focuses the previous focusable widget. +/// - if not found, focuses the last focusable widget. pub fn focus_previous() -> impl Operation { struct FocusPrevious { count: Count, @@ -118,6 +134,9 @@ pub fn focus_previous() -> impl Operation { count(|count| FocusPrevious { count, current: 0 }) } +/// Produces an [`Operation`] that searches for the current focuses widget, and +/// - if found, focuses the next focusable widget. +/// - if not found, focuses the first focusable widget. pub fn focus_next() -> impl Operation { struct FocusNext { count: Count, diff --git a/native/src/widget/operation/scrollable.rs b/native/src/widget/operation/scrollable.rs index ed609d67..2210137d 100644 --- a/native/src/widget/operation/scrollable.rs +++ b/native/src/widget/operation/scrollable.rs @@ -1,9 +1,14 @@ +//! Operate on widgets that can be scrolled. use crate::widget::{Id, Operation}; +/// The internal state of a widget that can be scrolled. pub trait Scrollable { + /// Snaps the scroll of the widget to the given `percentage`. fn snap_to(&mut self, percentage: f32); } +/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to +/// the provided `percentage`. pub fn snap_to(target: Id, percentage: f32) -> impl Operation { struct SnapTo { target: Id, diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index b7a0b6ee..4ebb07a0 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -316,19 +316,26 @@ where } } +/// The identifier of a [`Scrollable`]. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Id(widget::Id); impl Id { + /// Creates a custom [`Id`]. pub fn new(id: impl Into>) -> Self { Self(widget::Id::new(id)) } + /// Creates a unique [`Id`]. + /// + /// This function produces a different [`Id`] every time it is called. pub fn unique() -> Self { Self(widget::Id::unique()) } } +/// Produces a [`Command`] that snaps the [`Scrollable`] with the given [`Id`] +/// to the provided `percentage`. pub fn snap_to(id: Id, percentage: f32) -> Command { Command::widget(operation::scrollable::snap_to(id.0, percentage)) } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index e0216a5b..8ddbc734 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -314,19 +314,25 @@ where } } +/// The identifier of a [`TextInput`]. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Id(widget::Id); impl Id { + /// Creates a custom [`Id`]. pub fn new(id: impl Into>) -> Self { Self(widget::Id::new(id)) } + /// Creates a unique [`Id`]. + /// + /// This function produces a different [`Id`] every time it is called. pub fn unique() -> Self { Self(widget::Id::unique()) } } +/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`]. pub fn focus(id: Id) -> Command { Command::widget(operation::focusable::focus(id.0)) } -- cgit From d5629c103cc6cb907b37937a591636beb003d24b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 5 Aug 2022 05:50:22 +0200 Subject: Remove `widget::state` leftover file --- native/src/widget/state.rs | 1 - 1 file changed, 1 deletion(-) delete mode 100644 native/src/widget/state.rs (limited to 'native') diff --git a/native/src/widget/state.rs b/native/src/widget/state.rs deleted file mode 100644 index 8b137891..00000000 --- a/native/src/widget/state.rs +++ /dev/null @@ -1 +0,0 @@ - -- cgit From ad5bd0970d7106a97d455a164a582ab1d0bff18b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 5 Aug 2022 06:01:54 +0200 Subject: Fix documentation in `operation::focusable` --- native/src/widget/operation/focusable.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'native') diff --git a/native/src/widget/operation/focusable.rs b/native/src/widget/operation/focusable.rs index d20bfef9..f17bf178 100644 --- a/native/src/widget/operation/focusable.rs +++ b/native/src/widget/operation/focusable.rs @@ -54,7 +54,8 @@ pub fn focus(target: Id) -> impl Operation { Focus { target } } -/// Produces an [`Operation`] that generates a [`Count`]. +/// Produces an [`Operation`] that generates a [`Count`] and chains it with the +/// provided function to build a new [`Operation`]. pub fn count(f: fn(Count) -> O) -> impl Operation where O: Operation + 'static, @@ -95,7 +96,7 @@ where } } -/// Produces an [`Operation`] that searches for the current focuses widget, and +/// Produces an [`Operation`] that searches for the current focused widget, and /// - if found, focuses the previous focusable widget. /// - if not found, focuses the last focusable widget. pub fn focus_previous() -> impl Operation { @@ -134,7 +135,7 @@ pub fn focus_previous() -> impl Operation { count(|count| FocusPrevious { count, current: 0 }) } -/// Produces an [`Operation`] that searches for the current focuses widget, and +/// Produces an [`Operation`] that searches for the current focused widget, and /// - if found, focuses the next focusable widget. /// - if not found, focuses the first focusable widget. pub fn focus_next() -> impl Operation { -- cgit