From 81d1eda7fecd1b2e25c6352f7acdf70054057a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 23 Aug 2025 05:15:57 +0200 Subject: [PATCH] Introduce `selector` flag and decouple `iced_widget` from `iced_runtime` --- Cargo.lock | 2 +- Cargo.toml | 2 ++ core/src/overlay.rs | 2 ++ {runtime => core}/src/overlay/nested.rs | 14 ++++---- devtools/src/lib.rs | 4 +-- examples/visible_bounds/Cargo.toml | 2 +- examples/visible_bounds/src/main.rs | 25 +++++++------- runtime/Cargo.toml | 7 +++- runtime/src/lib.rs | 9 +++-- runtime/src/overlay.rs | 4 --- runtime/src/user_interface.rs | 2 +- runtime/src/widget.rs | 5 +++ .../src => runtime/src/widget}/operation.rs | 6 ++-- runtime/src/widget/selector.rs | 18 ++++++++++ selector/src/lib.rs | 34 +++++++++++++++++++ src/lib.rs | 6 ++-- widget/Cargo.toml | 1 - widget/src/container.rs | 7 ---- widget/src/lazy.rs | 9 +++-- widget/src/lazy/component.rs | 11 +++--- widget/src/lazy/responsive.rs | 11 +++--- widget/src/lib.rs | 4 +-- 22 files changed, 118 insertions(+), 67 deletions(-) rename {runtime => core}/src/overlay/nested.rs (97%) delete mode 100644 runtime/src/overlay.rs create mode 100644 runtime/src/widget.rs rename {widget/src => runtime/src/widget}/operation.rs (97%) create mode 100644 runtime/src/widget/selector.rs diff --git a/Cargo.lock b/Cargo.lock index cda9648a..6fd97456 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2622,6 +2622,7 @@ dependencies = [ "iced_core", "iced_debug", "iced_futures", + "iced_selector", "raw-window-handle 0.6.2", "sipper", "thiserror 2.0.14", @@ -2690,7 +2691,6 @@ version = "0.14.0-dev" dependencies = [ "iced_highlighter", "iced_renderer", - "iced_runtime", "log", "num-traits", "ouroboros", diff --git a/Cargo.toml b/Cargo.toml index 432a9cd4..c8087550 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,8 @@ crisp = ["iced_core/crisp", "iced_widget/crisp"] webgl = ["iced_renderer/webgl"] # Enables syntax highligthing highlighter = ["iced_highlighter", "iced_widget/highlighter"] +# Enables the `widget::selector` module +selector = ["iced_runtime/selector"] # Enables the advanced module advanced = ["iced_core/advanced", "iced_widget/advanced"] # Embeds Fira Sans into the final application; useful for testing and Wasm builds diff --git a/core/src/overlay.rs b/core/src/overlay.rs index 5f7ebce9..76fe321a 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -1,9 +1,11 @@ //! Display interactive elements on top of other widgets. mod element; mod group; +mod nested; pub use element::Element; pub use group::Group; +pub use nested::Nested; use crate::layout; use crate::mouse; diff --git a/runtime/src/overlay/nested.rs b/core/src/overlay/nested.rs similarity index 97% rename from runtime/src/overlay/nested.rs rename to core/src/overlay/nested.rs index 83c58804..25d94c91 100644 --- a/runtime/src/overlay/nested.rs +++ b/core/src/overlay/nested.rs @@ -1,10 +1,10 @@ -use crate::core::event; -use crate::core::layout; -use crate::core::mouse; -use crate::core::overlay; -use crate::core::renderer; -use crate::core::widget; -use crate::core::{Clipboard, Event, Layout, Shell, Size}; +use crate::event; +use crate::layout; +use crate::mouse; +use crate::overlay; +use crate::renderer; +use crate::widget; +use crate::{Clipboard, Event, Layout, Shell, Size}; /// An overlay container that displays nested overlays #[allow(missing_debug_implementations)] diff --git a/devtools/src/lib.rs b/devtools/src/lib.rs index 209eb100..18208a7a 100644 --- a/devtools/src/lib.rs +++ b/devtools/src/lib.rs @@ -1,11 +1,11 @@ #![allow(missing_docs)] use iced_debug as debug; use iced_program as program; +use iced_program::runtime; +use iced_program::runtime::futures; #[cfg(feature = "tester")] use iced_test as test; use iced_widget::core; -use iced_widget::runtime; -use iced_widget::runtime::futures; mod comet; mod executor; diff --git a/examples/visible_bounds/Cargo.toml b/examples/visible_bounds/Cargo.toml index a11af963..7cdccb60 100644 --- a/examples/visible_bounds/Cargo.toml +++ b/examples/visible_bounds/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] iced.workspace = true -iced.features = ["debug"] +iced.features = ["debug", "selector"] diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs index 0029de55..3930b462 100644 --- a/examples/visible_bounds/src/main.rs +++ b/examples/visible_bounds/src/main.rs @@ -1,7 +1,8 @@ use iced::event::{self, Event}; use iced::mouse; use iced::widget::{ - column, container, horizontal_space, row, scrollable, text, vertical_space, + column, container, horizontal_space, row, scrollable, selector, text, + vertical_space, }; use iced::window; use iced::{ @@ -23,13 +24,13 @@ struct Example { inner_bounds: Option, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] enum Message { MouseMoved(Point), WindowResized, Scrolled, - OuterBoundsFetched(Option), - InnerBoundsFetched(Option), + OuterFound(Option), + InnerFound(Option), } impl Example { @@ -41,18 +42,18 @@ impl Example { Task::none() } Message::Scrolled | Message::WindowResized => Task::batch(vec![ - container::visible_bounds(OUTER_CONTAINER) - .map(Message::OuterBoundsFetched), - container::visible_bounds(INNER_CONTAINER) - .map(Message::InnerBoundsFetched), + selector::find_by_id(OUTER_CONTAINER).map(Message::OuterFound), + selector::find_by_id(INNER_CONTAINER).map(Message::InnerFound), ]), - Message::OuterBoundsFetched(outer_bounds) => { - self.outer_bounds = outer_bounds; + Message::OuterFound(outer) => { + self.outer_bounds = + outer.as_ref().and_then(selector::Bounded::visible_bounds); Task::none() } - Message::InnerBoundsFetched(inner_bounds) => { - self.inner_bounds = inner_bounds; + Message::InnerFound(inner) => { + self.inner_bounds = + inner.as_ref().and_then(selector::Bounded::visible_bounds); Task::none() } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 2dc60474..baddbfa4 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -10,6 +10,9 @@ homepage.workspace = true categories.workspace = true keywords.workspace = true +[features] +selector = ["dep:iced_selector"] + [lints] workspace = true @@ -17,7 +20,6 @@ workspace = true bytes.workspace = true iced_core.workspace = true iced_debug.workspace = true - iced_futures.workspace = true raw-window-handle.workspace = true @@ -25,3 +27,6 @@ thiserror.workspace = true sipper.workspace = true sipper.optional = true + +iced_selector.workspace = true +iced_selector.optional = true diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 457f723c..af43edce 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -12,10 +12,10 @@ pub mod clipboard; pub mod font; pub mod keyboard; -pub mod overlay; pub mod system; pub mod task; pub mod user_interface; +pub mod widget; pub mod window; pub use iced_core as core; @@ -25,7 +25,6 @@ pub use iced_futures as futures; pub use task::Task; pub use user_interface::UserInterface; -use crate::core::widget; use crate::futures::futures::channel::oneshot; use std::borrow::Cow; @@ -45,7 +44,7 @@ pub enum Action { }, /// Run a widget operation. - Widget(Box), + Widget(Box), /// Run a clipboard action. Clipboard(clipboard::Action), @@ -67,8 +66,8 @@ pub enum Action { } impl Action { - /// Creates a new [`Action::Widget`] with the given [`widget::Operation`]. - pub fn widget(operation: impl widget::Operation + 'static) -> Self { + /// Creates a new [`Action::Widget`] with the given [`widget::Operation`](core::widget::Operation). + pub fn widget(operation: impl core::widget::Operation + 'static) -> Self { Self::Widget(Box::new(operation)) } diff --git a/runtime/src/overlay.rs b/runtime/src/overlay.rs deleted file mode 100644 index 03390980..00000000 --- a/runtime/src/overlay.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! Overlays for user interfaces. -mod nested; - -pub use nested::Nested; diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 95e7574f..631c3883 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -2,13 +2,13 @@ use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; +use crate::core::overlay; use crate::core::renderer; use crate::core::widget; use crate::core::window; use crate::core::{ Clipboard, Element, InputMethod, Layout, Rectangle, Shell, Size, Vector, }; -use crate::overlay; /// A set of interactive graphical elements with a specific [`Layout`]. /// diff --git a/runtime/src/widget.rs b/runtime/src/widget.rs new file mode 100644 index 00000000..e5ba2b0e --- /dev/null +++ b/runtime/src/widget.rs @@ -0,0 +1,5 @@ +//! Operate on widgets and query them at runtime. +pub mod operation; + +#[cfg(feature = "selector")] +pub mod selector; diff --git a/widget/src/operation.rs b/runtime/src/widget/operation.rs similarity index 97% rename from widget/src/operation.rs rename to runtime/src/widget/operation.rs index db24e1f2..ab03bbe0 100644 --- a/widget/src/operation.rs +++ b/runtime/src/widget/operation.rs @@ -1,8 +1,8 @@ //! Change internal widget state. -use crate::Id; +use crate::core::widget::Id; use crate::core::widget::operation; -use crate::runtime::task; -use crate::runtime::{Action, Task}; +use crate::task; +use crate::{Action, Task}; pub use crate::core::widget::operation::scrollable::{ AbsoluteOffset, RelativeOffset, diff --git a/runtime/src/widget/selector.rs b/runtime/src/widget/selector.rs new file mode 100644 index 00000000..d6fc6f9a --- /dev/null +++ b/runtime/src/widget/selector.rs @@ -0,0 +1,18 @@ +//! Find and query widgets in your applications. +pub use iced_selector::Selector; + +pub use iced_selector::target::{Bounded, Match, Target, Text}; + +use crate::Task; +use crate::core::widget; +use crate::task; + +/// Finds a widget by the given [`widget::Id`]. +pub fn find_by_id(id: impl Into) -> Task> { + task::widget(id.into().find()) +} + +/// Finds a widget that contains the given text. +pub fn find_by_text(text: impl Into) -> Task> { + task::widget(Selector::find(text.into())) +} diff --git a/selector/src/lib.rs b/selector/src/lib.rs index e9a36076..c32d065f 100644 --- a/selector/src/lib.rs +++ b/selector/src/lib.rs @@ -66,6 +66,40 @@ impl Selector for &str { } } +impl Selector for String { + type Output = target::Text; + + fn select(&mut self, target: Target<'_>) -> Option { + match target { + Target::TextInput { + id, + bounds, + visible_bounds, + state, + } if state.text() == *self => Some(target::Text::Input { + id: id.cloned(), + bounds, + visible_bounds, + }), + Target::Text { + id, + bounds, + visible_bounds, + content, + } if content == *self => Some(target::Text::Raw { + id: id.cloned(), + bounds, + visible_bounds, + }), + _ => None, + } + } + + fn description(&self) -> String { + format!("text == \"{}\"", self.escape_default()) + } +} + impl Selector for Id { type Output = target::Match; diff --git a/src/lib.rs b/src/lib.rs index aa19c6ff..79867909 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -327,7 +327,7 @@ //! Tasks can also be used to interact with the iced runtime. Some modules //! expose functions that create tasks for different purposes—like [changing //! window settings](window#functions), [focusing a widget](widget::operation::focus_next), or -//! [querying its visible bounds](widget::container::visible_bounds). +//! [querying its visible bounds](widget::selector::find_by_id). //! //! Like futures and streams, tasks expose [a monadic interface](Task::then)—but they can also be //! [mapped](Task::map), [chained](Task::chain), [batched](Task::batch), [canceled](Task::abortable), @@ -620,15 +620,13 @@ pub mod touch { #[allow(hidden_glob_reexports)] pub mod widget { //! Use the built-in widgets or create your own. + pub use iced_runtime::widget::*; pub use iced_widget::*; // We hide the re-exported modules by `iced_widget` mod core {} mod graphics {} - mod native {} mod renderer {} - mod style {} - mod runtime {} } pub use application::Application; diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 024314ff..dc64b61d 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -31,7 +31,6 @@ crisp = [] [dependencies] iced_renderer.workspace = true -iced_runtime.workspace = true num-traits.workspace = true log.workspace = true diff --git a/widget/src/container.rs b/widget/src/container.rs index a9883668..16a827da 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -34,7 +34,6 @@ use crate::core::{ Padding, Pixels, Rectangle, Shadow, Shell, Size, Theme, Vector, Widget, color, }; -use crate::runtime::Task; /// A widget that aligns its contents inside of its boundaries. /// @@ -457,12 +456,6 @@ pub fn draw_background( } } -/// Produces a [`Task`] that queries the visible screen bounds of the -/// [`Container`] with the given [`widget::Id`]. -pub fn visible_bounds(_id: impl Into) -> Task> { - todo!() -} - /// The appearance of a container. #[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Style { diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index b538b460..88a648c6 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -20,7 +20,6 @@ use crate::core::widget::{self, Widget}; use crate::core::{ self, Clipboard, Event, Length, Rectangle, Shell, Size, Vector, }; -use crate::runtime::overlay::Nested; use ouroboros::self_referencing; use rustc_hash::FxHasher; @@ -286,7 +285,7 @@ where element .as_widget_mut() .overlay(tree, *layout, renderer, viewport, translation) - .map(|overlay| RefCell::new(Nested::new(overlay))) + .map(|overlay| RefCell::new(overlay::Nested::new(overlay))) }, } .build(); @@ -317,7 +316,7 @@ struct Inner<'a, Message: 'a, Theme: 'a, Renderer: 'a> { #[borrows(mut element, mut tree, layout)] #[not_covariant] - overlay: Option>>, + overlay: Option>>, } struct Overlay<'a, Message, Theme, Renderer>( @@ -334,7 +333,7 @@ impl Drop for Overlay<'_, Message, Theme, Renderer> { impl Overlay<'_, Message, Theme, Renderer> { fn with_overlay_maybe( &self, - f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T, + f: impl FnOnce(&mut overlay::Nested<'_, Message, Theme, Renderer>) -> T, ) -> Option { self.0.as_ref().unwrap().with_overlay(|overlay| { overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut())) @@ -343,7 +342,7 @@ impl Overlay<'_, Message, Theme, Renderer> { fn with_overlay_mut_maybe( &mut self, - f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T, + f: impl FnOnce(&mut overlay::Nested<'_, Message, Theme, Renderer>) -> T, ) -> Option { self.0.as_mut().unwrap().with_overlay_mut(|overlay| { overlay.as_mut().map(|nested| (f)(nested.get_mut())) diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index 8bd04d64..eedc1afa 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -9,7 +9,6 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::{ self, Clipboard, Element, Length, Rectangle, Shell, Size, Vector, Widget, }; -use crate::runtime::overlay::Nested; use ouroboros::self_referencing; use std::cell::RefCell; @@ -472,7 +471,9 @@ where viewport, translation, ) - .map(|overlay| RefCell::new(Nested::new(overlay))) + .map(|overlay| { + RefCell::new(overlay::Nested::new(overlay)) + }) }, ) }, @@ -519,7 +520,7 @@ struct Inner<'a, 'b, Message, Theme, Renderer, Event, S> { #[borrows(mut instance, mut tree)] #[not_covariant] - overlay: Option>>, + overlay: Option>>, } struct OverlayInstance<'a, 'b, Message, Theme, Renderer, Event, S> { @@ -531,7 +532,7 @@ impl { fn with_overlay_maybe( &self, - f: impl FnOnce(&mut Nested<'_, Event, Theme, Renderer>) -> T, + f: impl FnOnce(&mut overlay::Nested<'_, Event, Theme, Renderer>) -> T, ) -> Option { self.overlay .as_ref() @@ -546,7 +547,7 @@ impl fn with_overlay_mut_maybe( &mut self, - f: impl FnOnce(&mut Nested<'_, Event, Theme, Renderer>) -> T, + f: impl FnOnce(&mut overlay::Nested<'_, Event, Theme, Renderer>) -> T, ) -> Option { self.overlay .as_mut() diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index 4e90a178..6c227a7a 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -9,7 +9,6 @@ use crate::core::{ Vector, Widget, }; use crate::horizontal_space; -use crate::runtime::overlay::Nested; use ouroboros::self_referencing; use std::cell::{RefCell, RefMut}; @@ -327,7 +326,9 @@ where viewport, translation, ) - .map(|overlay| RefCell::new(Nested::new(overlay))), + .map(|overlay| { + RefCell::new(overlay::Nested::new(overlay)) + }), is_layout_invalid, ) }, @@ -364,7 +365,7 @@ struct Overlay<'a, 'b, Message, Theme, Renderer> { #[borrows(mut content, mut tree)] #[not_covariant] overlay: ( - Option>>, + Option>>, &'this mut bool, ), } @@ -372,7 +373,7 @@ struct Overlay<'a, 'b, Message, Theme, Renderer> { impl Overlay<'_, '_, Message, Theme, Renderer> { fn with_overlay_maybe( &self, - f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T, + f: impl FnOnce(&mut overlay::Nested<'_, Message, Theme, Renderer>) -> T, ) -> Option { self.with_overlay(|(overlay, _layout)| { overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut())) @@ -381,7 +382,7 @@ impl Overlay<'_, '_, Message, Theme, Renderer> { fn with_overlay_mut_maybe( &mut self, - f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T, + f: impl FnOnce(&mut overlay::Nested<'_, Message, Theme, Renderer>) -> T, ) -> Option { self.with_overlay_mut(|(overlay, _layout)| { overlay.as_mut().map(|nested| (f)(nested.get_mut())) diff --git a/widget/src/lib.rs b/widget/src/lib.rs index ec480889..4f4db734 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -4,9 +4,8 @@ )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub use iced_renderer as renderer; +pub use iced_renderer::core; pub use iced_renderer::graphics; -pub use iced_runtime as runtime; -pub use iced_runtime::core; pub use core::widget::Id; @@ -25,7 +24,6 @@ pub mod container; pub mod float; pub mod grid; pub mod keyed; -pub mod operation; pub mod overlay; pub mod pane_grid; pub mod pick_list;