Introduce selector flag and decouple iced_widget from iced_runtime

This commit is contained in:
Héctor Ramón Jiménez 2025-08-23 05:15:57 +02:00
parent 34a42b5ad4
commit 81d1eda7fe
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
22 changed files with 118 additions and 67 deletions

2
Cargo.lock generated
View file

@ -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",

View file

@ -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

View file

@ -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;

View file

@ -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)]

View file

@ -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;

View file

@ -7,4 +7,4 @@ publish = false
[dependencies]
iced.workspace = true
iced.features = ["debug"]
iced.features = ["debug", "selector"]

View file

@ -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<Rectangle>,
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
enum Message {
MouseMoved(Point),
WindowResized,
Scrolled,
OuterBoundsFetched(Option<Rectangle>),
InnerBoundsFetched(Option<Rectangle>),
OuterFound(Option<selector::Match>),
InnerFound(Option<selector::Match>),
}
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()
}

View file

@ -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

View file

@ -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<T> {
},
/// Run a widget operation.
Widget(Box<dyn widget::Operation>),
Widget(Box<dyn core::widget::Operation>),
/// Run a clipboard action.
Clipboard(clipboard::Action),
@ -67,8 +66,8 @@ pub enum Action<T> {
}
impl<T> Action<T> {
/// 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))
}

View file

@ -1,4 +0,0 @@
//! Overlays for user interfaces.
mod nested;
pub use nested::Nested;

View file

@ -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`].
///

5
runtime/src/widget.rs Normal file
View file

@ -0,0 +1,5 @@
//! Operate on widgets and query them at runtime.
pub mod operation;
#[cfg(feature = "selector")]
pub mod selector;

View file

@ -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,

View file

@ -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<widget::Id>) -> Task<Option<Match>> {
task::widget(id.into().find())
}
/// Finds a widget that contains the given text.
pub fn find_by_text(text: impl Into<String>) -> Task<Option<Text>> {
task::widget(Selector::find(text.into()))
}

View file

@ -66,6 +66,40 @@ impl Selector for &str {
}
}
impl Selector for String {
type Output = target::Text;
fn select(&mut self, target: Target<'_>) -> Option<Self::Output> {
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;

View file

@ -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;

View file

@ -31,7 +31,6 @@ crisp = []
[dependencies]
iced_renderer.workspace = true
iced_runtime.workspace = true
num-traits.workspace = true
log.workspace = true

View file

@ -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<Renderer>(
}
}
/// Produces a [`Task`] that queries the visible screen bounds of the
/// [`Container`] with the given [`widget::Id`].
pub fn visible_bounds(_id: impl Into<widget::Id>) -> Task<Option<Rectangle>> {
todo!()
}
/// The appearance of a container.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Style {

View file

@ -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<RefCell<Nested<'this, Message, Theme, Renderer>>>,
overlay: Option<RefCell<overlay::Nested<'this, Message, Theme, Renderer>>>,
}
struct Overlay<'a, Message, Theme, Renderer>(
@ -334,7 +333,7 @@ impl<Message, Theme, Renderer> Drop for Overlay<'_, Message, Theme, Renderer> {
impl<Message, Theme, Renderer> Overlay<'_, Message, Theme, Renderer> {
fn with_overlay_maybe<T>(
&self,
f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T,
f: impl FnOnce(&mut overlay::Nested<'_, Message, Theme, Renderer>) -> T,
) -> Option<T> {
self.0.as_ref().unwrap().with_overlay(|overlay| {
overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut()))
@ -343,7 +342,7 @@ impl<Message, Theme, Renderer> Overlay<'_, Message, Theme, Renderer> {
fn with_overlay_mut_maybe<T>(
&mut self,
f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T,
f: impl FnOnce(&mut overlay::Nested<'_, Message, Theme, Renderer>) -> T,
) -> Option<T> {
self.0.as_mut().unwrap().with_overlay_mut(|overlay| {
overlay.as_mut().map(|nested| (f)(nested.get_mut()))

View file

@ -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<RefCell<Nested<'this, Event, Theme, Renderer>>>,
overlay: Option<RefCell<overlay::Nested<'this, Event, Theme, Renderer>>>,
}
struct OverlayInstance<'a, 'b, Message, Theme, Renderer, Event, S> {
@ -531,7 +532,7 @@ impl<Message, Theme, Renderer, Event, S>
{
fn with_overlay_maybe<T>(
&self,
f: impl FnOnce(&mut Nested<'_, Event, Theme, Renderer>) -> T,
f: impl FnOnce(&mut overlay::Nested<'_, Event, Theme, Renderer>) -> T,
) -> Option<T> {
self.overlay
.as_ref()
@ -546,7 +547,7 @@ impl<Message, Theme, Renderer, Event, S>
fn with_overlay_mut_maybe<T>(
&mut self,
f: impl FnOnce(&mut Nested<'_, Event, Theme, Renderer>) -> T,
f: impl FnOnce(&mut overlay::Nested<'_, Event, Theme, Renderer>) -> T,
) -> Option<T> {
self.overlay
.as_mut()

View file

@ -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<RefCell<Nested<'this, Message, Theme, Renderer>>>,
Option<RefCell<overlay::Nested<'this, Message, Theme, Renderer>>>,
&'this mut bool,
),
}
@ -372,7 +373,7 @@ struct Overlay<'a, 'b, Message, Theme, Renderer> {
impl<Message, Theme, Renderer> Overlay<'_, '_, Message, Theme, Renderer> {
fn with_overlay_maybe<T>(
&self,
f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T,
f: impl FnOnce(&mut overlay::Nested<'_, Message, Theme, Renderer>) -> T,
) -> Option<T> {
self.with_overlay(|(overlay, _layout)| {
overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut()))
@ -381,7 +382,7 @@ impl<Message, Theme, Renderer> Overlay<'_, '_, Message, Theme, Renderer> {
fn with_overlay_mut_maybe<T>(
&mut self,
f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T,
f: impl FnOnce(&mut overlay::Nested<'_, Message, Theme, Renderer>) -> T,
) -> Option<T> {
self.with_overlay_mut(|(overlay, _layout)| {
overlay.as_mut().map(|nested| (f)(nested.get_mut()))

View file

@ -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;