From 299eb54d6f14d79c659cd1c0ad8a8d3d1d258e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 17 Sep 2025 22:56:58 +0200 Subject: [PATCH] Improve naming in `iced_selector` crate --- core/src/widget/id.rs | 8 +- examples/todos/src/main.rs | 2 +- examples/visible_bounds/src/main.rs | 22 +- runtime/src/widget/selector.rs | 6 +- selector/src/find.rs | 21 +- selector/src/lib.rs | 43 ++-- selector/src/target.rs | 334 +++++++++++++++------------- test/src/emulator.rs | 6 +- test/src/instruction.rs | 2 +- test/src/lib.rs | 2 +- test/src/simulator.rs | 2 +- tester/src/recorder.rs | 6 +- 12 files changed, 235 insertions(+), 219 deletions(-) diff --git a/core/src/widget/id.rs b/core/src/widget/id.rs index 2e7331c0..1d67086e 100644 --- a/core/src/widget/id.rs +++ b/core/src/widget/id.rs @@ -8,9 +8,9 @@ static NEXT_ID: AtomicUsize = AtomicUsize::new(0); pub struct Id(Internal); impl Id { - /// Creates a custom [`Id`]. - pub fn new(id: impl Into>) -> Self { - Self(Internal::Custom(id.into())) + /// Creates a new [`Id`] from a static `str`. + pub const fn new(id: &'static str) -> Self { + Self(Internal::Custom(borrow::Cow::Borrowed(id))) } /// Creates a unique [`Id`]. @@ -31,7 +31,7 @@ impl From<&'static str> for Id { impl From for Id { fn from(value: String) -> Self { - Self::new(value) + Self(Internal::Custom(borrow::Cow::Owned(value))) } } diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index 1aaaf614..6f4e0808 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -307,7 +307,7 @@ pub enum TaskMessage { impl Task { fn text_input_id(i: usize) -> widget::Id { - widget::Id::new(format!("task-{i}")) + widget::Id::from(format!("task-{i}")) } fn new(description: String) -> Self { diff --git a/examples/visible_bounds/src/main.rs b/examples/visible_bounds/src/main.rs index 7a20ba86..4025dcf4 100644 --- a/examples/visible_bounds/src/main.rs +++ b/examples/visible_bounds/src/main.rs @@ -1,7 +1,7 @@ use iced::event::{self, Event}; use iced::mouse; use iced::widget::{ - column, container, row, scrollable, selector, space_x, space_y, text, + self, column, container, row, scrollable, selector, space_x, space_y, text, }; use iced::window; use iced::{ @@ -28,8 +28,8 @@ enum Message { MouseMoved(Point), WindowResized, Scrolled, - OuterFound(Option), - InnerFound(Option), + OuterFound(Option), + InnerFound(Option), } impl Example { @@ -41,18 +41,18 @@ impl Example { Task::none() } Message::Scrolled | Message::WindowResized => Task::batch(vec![ - selector::find_by_id(OUTER_CONTAINER).map(Message::OuterFound), - selector::find_by_id(INNER_CONTAINER).map(Message::InnerFound), + selector::visible_bounds(OUTER_CONTAINER) + .map(Message::OuterFound), + selector::visible_bounds(INNER_CONTAINER) + .map(Message::InnerFound), ]), Message::OuterFound(outer) => { - self.outer_bounds = - outer.as_ref().and_then(selector::Bounded::visible_bounds); + self.outer_bounds = outer; Task::none() } Message::InnerFound(inner) => { - self.inner_bounds = - inner.as_ref().and_then(selector::Bounded::visible_bounds); + self.inner_bounds = inner; Task::none() } @@ -157,5 +157,5 @@ impl Example { } } -const OUTER_CONTAINER: &str = "outer"; -const INNER_CONTAINER: &str = "inner"; +const OUTER_CONTAINER: widget::Id = widget::Id::new("outer"); +const INNER_CONTAINER: widget::Id = widget::Id::new("inner"); diff --git a/runtime/src/widget/selector.rs b/runtime/src/widget/selector.rs index fa7ff749..ee142246 100644 --- a/runtime/src/widget/selector.rs +++ b/runtime/src/widget/selector.rs @@ -1,7 +1,5 @@ //! Find and query widgets in your applications. -pub use iced_selector::Selector; - -pub use iced_selector::target::{Bounded, Match, Target, Text}; +pub use iced_selector::{Bounded, Candidate, Selector, Target, Text}; use crate::core::Rectangle; @@ -10,7 +8,7 @@ use crate::core::widget; use crate::task; /// Finds a widget by the given [`widget::Id`]. -pub fn find_by_id(id: impl Into) -> Task> { +pub fn find_by_id(id: impl Into) -> Task> { task::widget(id.into().find()) } diff --git a/selector/src/find.rs b/selector/src/find.rs index 15016933..eb3f0fa0 100644 --- a/selector/src/find.rs +++ b/selector/src/find.rs @@ -1,9 +1,10 @@ +use crate::Selector; use crate::core::widget::operation::{ Focusable, Outcome, Scrollable, TextInput, }; use crate::core::widget::{Id, Operation}; use crate::core::{Rectangle, Vector}; -use crate::{Selector, Target}; +use crate::target::Candidate; use std::any::Any; @@ -38,7 +39,7 @@ where { type Output = Option; - fn feed(&mut self, target: Target<'_>) { + fn feed(&mut self, target: Candidate<'_>) { if let Some(output) = self.selector.select(target) { self.output = Some(output); } @@ -81,7 +82,7 @@ where { type Output = Vec; - fn feed(&mut self, target: Target<'_>) { + fn feed(&mut self, target: Candidate<'_>) { if let Some(output) = self.selector.select(target) { self.outputs.push(output); } @@ -99,7 +100,7 @@ where pub trait Strategy { type Output; - fn feed(&mut self, target: Target<'_>); + fn feed(&mut self, target: Candidate<'_>); fn is_done(&self) -> bool; @@ -152,7 +153,7 @@ where return; } - self.strategy.feed(Target::Container { + self.strategy.feed(Candidate::Container { id, bounds, visible_bounds: self @@ -171,7 +172,7 @@ where return; } - self.strategy.feed(Target::Focusable { + self.strategy.feed(Candidate::Focusable { id, bounds, visible_bounds: self @@ -196,7 +197,7 @@ where let visible_bounds = self.viewport.intersection(&(bounds + self.translation)); - self.strategy.feed(Target::Scrollable { + self.strategy.feed(Candidate::Scrollable { id, bounds, visible_bounds, @@ -219,7 +220,7 @@ where return; } - self.strategy.feed(Target::TextInput { + self.strategy.feed(Candidate::TextInput { id, bounds, visible_bounds: self @@ -234,7 +235,7 @@ where return; } - self.strategy.feed(Target::Text { + self.strategy.feed(Candidate::Text { id, bounds, visible_bounds: self @@ -254,7 +255,7 @@ where return; } - self.strategy.feed(Target::Custom { + self.strategy.feed(Candidate::Custom { id, bounds, visible_bounds: self diff --git a/selector/src/lib.rs b/selector/src/lib.rs index 050d6a49..4e56172b 100644 --- a/selector/src/lib.rs +++ b/selector/src/lib.rs @@ -1,12 +1,11 @@ #![allow(missing_docs)] use iced_core as core; -pub mod target; - mod find; +mod target; pub use find::{Find, FindAll}; -pub use target::Target; +pub use target::{Bounded, Candidate, Target, Text}; use crate::core::Point; use crate::core::widget; @@ -14,7 +13,7 @@ use crate::core::widget; pub trait Selector { type Output; - fn select(&mut self, target: Target<'_>) -> Option; + fn select(&mut self, candidate: Candidate<'_>) -> Option; fn description(&self) -> String; @@ -36,9 +35,9 @@ pub trait Selector { impl Selector for &str { type Output = target::Text; - fn select(&mut self, target: Target<'_>) -> Option { - match target { - Target::TextInput { + fn select(&mut self, candidate: Candidate<'_>) -> Option { + match candidate { + Candidate::TextInput { id, bounds, visible_bounds, @@ -48,7 +47,7 @@ impl Selector for &str { bounds, visible_bounds, }), - Target::Text { + Candidate::Text { id, bounds, visible_bounds, @@ -70,8 +69,8 @@ impl Selector for &str { impl Selector for String { type Output = target::Text; - fn select(&mut self, target: Target<'_>) -> Option { - self.as_str().select(target) + fn select(&mut self, candidate: Candidate<'_>) -> Option { + self.as_str().select(candidate) } fn description(&self) -> String { @@ -80,14 +79,14 @@ impl Selector for String { } impl Selector for widget::Id { - type Output = target::Match; + type Output = Target; - fn select(&mut self, target: Target<'_>) -> Option { - if target.id() != Some(self) { + fn select(&mut self, candidate: Candidate<'_>) -> Option { + if candidate.id() != Some(self) { return None; } - Some(target::Match::from_target(target)) + Some(Target::from(candidate)) } fn description(&self) -> String { @@ -96,13 +95,13 @@ impl Selector for widget::Id { } impl Selector for Point { - type Output = target::Match; + type Output = Target; - fn select(&mut self, target: Target<'_>) -> Option { - target + fn select(&mut self, candidate: Candidate<'_>) -> Option { + candidate .visible_bounds() .is_some_and(|visible_bounds| visible_bounds.contains(*self)) - .then(|| target::Match::from_target(target)) + .then(|| Target::from(candidate)) } fn description(&self) -> String { @@ -112,12 +111,12 @@ impl Selector for Point { impl Selector for F where - F: FnMut(Target<'_>) -> Option, + F: FnMut(Candidate<'_>) -> Option, { type Output = T; - fn select(&mut self, target: Target<'_>) -> Option { - (self)(target) + fn select(&mut self, candidate: Candidate<'_>) -> Option { + (self)(candidate) } fn description(&self) -> String { @@ -126,6 +125,6 @@ where } /// Creates a new [`Selector`] that matches widgets with the given [`widget::Id`]. -pub fn id(id: impl Into) -> impl Selector { +pub fn id(id: impl Into) -> impl Selector { id.into() } diff --git a/selector/src/target.rs b/selector/src/target.rs index 21c0c38e..cd0c4bba 100644 --- a/selector/src/target.rs +++ b/selector/src/target.rs @@ -4,8 +4,152 @@ use crate::core::{Rectangle, Vector}; use std::any::Any; +#[derive(Debug, Clone, PartialEq)] +pub enum Target { + Container { + id: Option, + bounds: Rectangle, + visible_bounds: Option, + }, + Focusable { + id: Option, + bounds: Rectangle, + visible_bounds: Option, + }, + Scrollable { + id: Option, + bounds: Rectangle, + visible_bounds: Option, + content_bounds: Rectangle, + translation: Vector, + }, + TextInput { + id: Option, + bounds: Rectangle, + visible_bounds: Option, + content: String, + }, + Text { + id: Option, + bounds: Rectangle, + visible_bounds: Option, + content: String, + }, + Custom { + id: Option, + bounds: Rectangle, + visible_bounds: Option, + }, +} + +impl Target { + pub fn bounds(&self) -> Rectangle { + match self { + Target::Container { bounds, .. } + | Target::Focusable { bounds, .. } + | Target::Scrollable { bounds, .. } + | Target::TextInput { bounds, .. } + | Target::Text { bounds, .. } + | Target::Custom { bounds, .. } => *bounds, + } + } + + pub fn visible_bounds(&self) -> Option { + match self { + Target::Container { visible_bounds, .. } + | Target::Focusable { visible_bounds, .. } + | Target::Scrollable { visible_bounds, .. } + | Target::TextInput { visible_bounds, .. } + | Target::Text { visible_bounds, .. } + | Target::Custom { visible_bounds, .. } => *visible_bounds, + } + } +} + +impl From> for Target { + fn from(candidate: Candidate<'_>) -> Self { + match candidate { + Candidate::Container { + id, + bounds, + visible_bounds, + } => Self::Container { + id: id.cloned(), + bounds, + visible_bounds, + }, + Candidate::Focusable { + id, + bounds, + visible_bounds, + .. + } => Self::Focusable { + id: id.cloned(), + bounds, + visible_bounds, + }, + Candidate::Scrollable { + id, + bounds, + visible_bounds, + content_bounds, + translation, + .. + } => Self::Scrollable { + id: id.cloned(), + bounds, + visible_bounds, + content_bounds, + translation, + }, + Candidate::TextInput { + id, + bounds, + visible_bounds, + state, + } => Self::TextInput { + id: id.cloned(), + bounds, + visible_bounds, + content: state.text().to_owned(), + }, + Candidate::Text { + id, + bounds, + visible_bounds, + content, + } => Self::Text { + id: id.cloned(), + bounds, + visible_bounds, + content: content.to_owned(), + }, + Candidate::Custom { + id, + bounds, + visible_bounds, + .. + } => Self::Custom { + id: id.cloned(), + bounds, + visible_bounds, + }, + } + } +} + +impl Bounded for Target { + fn bounds(&self) -> Rectangle { + self.bounds() + } + + fn visible_bounds(&self) -> Option { + self.visible_bounds() + } +} + #[derive(Clone)] -pub enum Target<'a> { +pub enum Candidate<'a> { Container { id: Option<&'a Id>, bounds: Rectangle, @@ -45,171 +189,37 @@ pub enum Target<'a> { }, } -impl<'a> Target<'a> { +impl<'a> Candidate<'a> { pub fn id(&self) -> Option<&'a Id> { match self { - Target::Container { id, .. } - | Target::Focusable { id, .. } - | Target::Scrollable { id, .. } - | Target::TextInput { id, .. } - | Target::Text { id, .. } - | Target::Custom { id, .. } => *id, + Candidate::Container { id, .. } + | Candidate::Focusable { id, .. } + | Candidate::Scrollable { id, .. } + | Candidate::TextInput { id, .. } + | Candidate::Text { id, .. } + | Candidate::Custom { id, .. } => *id, } } pub fn bounds(&self) -> Rectangle { match self { - Target::Container { bounds, .. } - | Target::Focusable { bounds, .. } - | Target::Scrollable { bounds, .. } - | Target::TextInput { bounds, .. } - | Target::Text { bounds, .. } - | Target::Custom { bounds, .. } => *bounds, + Candidate::Container { bounds, .. } + | Candidate::Focusable { bounds, .. } + | Candidate::Scrollable { bounds, .. } + | Candidate::TextInput { bounds, .. } + | Candidate::Text { bounds, .. } + | Candidate::Custom { bounds, .. } => *bounds, } } pub fn visible_bounds(&self) -> Option { match self { - Target::Container { visible_bounds, .. } - | Target::Focusable { visible_bounds, .. } - | Target::Scrollable { visible_bounds, .. } - | Target::TextInput { visible_bounds, .. } - | Target::Text { visible_bounds, .. } - | Target::Custom { visible_bounds, .. } => *visible_bounds, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum Match { - Container { - id: Option, - bounds: Rectangle, - visible_bounds: Option, - }, - Focusable { - id: Option, - bounds: Rectangle, - visible_bounds: Option, - }, - Scrollable { - id: Option, - bounds: Rectangle, - visible_bounds: Option, - content_bounds: Rectangle, - translation: Vector, - }, - TextInput { - id: Option, - bounds: Rectangle, - visible_bounds: Option, - content: String, - }, - Text { - id: Option, - bounds: Rectangle, - visible_bounds: Option, - content: String, - }, - Custom { - id: Option, - bounds: Rectangle, - visible_bounds: Option, - }, -} - -impl Match { - pub fn from_target(target: Target<'_>) -> Self { - match target { - Target::Container { - id, - bounds, - visible_bounds, - } => Self::Container { - id: id.cloned(), - bounds, - visible_bounds, - }, - Target::Focusable { - id, - bounds, - visible_bounds, - .. - } => Self::Focusable { - id: id.cloned(), - bounds, - visible_bounds, - }, - Target::Scrollable { - id, - bounds, - visible_bounds, - content_bounds, - translation, - .. - } => Self::Scrollable { - id: id.cloned(), - bounds, - visible_bounds, - content_bounds, - translation, - }, - Target::TextInput { - id, - bounds, - visible_bounds, - state, - } => Self::TextInput { - id: id.cloned(), - bounds, - visible_bounds, - content: state.text().to_owned(), - }, - Target::Text { - id, - bounds, - visible_bounds, - content, - } => Self::Text { - id: id.cloned(), - bounds, - visible_bounds, - content: content.to_owned(), - }, - Target::Custom { - id, - bounds, - visible_bounds, - .. - } => Self::Custom { - id: id.cloned(), - bounds, - visible_bounds, - }, - } - } -} - -impl Bounded for Match { - fn bounds(&self) -> Rectangle { - match self { - Match::Container { bounds, .. } - | Match::Focusable { bounds, .. } - | Match::Scrollable { bounds, .. } - | Match::TextInput { bounds, .. } - | Match::Text { bounds, .. } - | Match::Custom { bounds, .. } => *bounds, - } - } - - fn visible_bounds(&self) -> Option { - match self { - Match::Container { visible_bounds, .. } - | Match::Focusable { visible_bounds, .. } - | Match::Scrollable { visible_bounds, .. } - | Match::TextInput { visible_bounds, .. } - | Match::Text { visible_bounds, .. } - | Match::Custom { visible_bounds, .. } => *visible_bounds, + Candidate::Container { visible_bounds, .. } + | Candidate::Focusable { visible_bounds, .. } + | Candidate::Scrollable { visible_bounds, .. } + | Candidate::TextInput { visible_bounds, .. } + | Candidate::Text { visible_bounds, .. } + | Candidate::Custom { visible_bounds, .. } => *visible_bounds, } } } @@ -234,17 +244,27 @@ pub enum Text { }, } -impl Bounded for Text { - fn bounds(&self) -> Rectangle { +impl Text { + pub fn bounds(&self) -> Rectangle { match self { Text::Raw { bounds, .. } | Text::Input { bounds, .. } => *bounds, } } - fn visible_bounds(&self) -> Option { + pub fn visible_bounds(&self) -> Option { match self { Text::Raw { visible_bounds, .. } | Text::Input { visible_bounds, .. } => *visible_bounds, } } } + +impl Bounded for Text { + fn bounds(&self) -> Rectangle { + self.bounds() + } + + fn visible_bounds(&self) -> Option { + self.visible_bounds() + } +} diff --git a/test/src/emulator.rs b/test/src/emulator.rs index 096949e2..5f46caf2 100644 --- a/test/src/emulator.rs +++ b/test/src/emulator.rs @@ -17,7 +17,6 @@ use crate::runtime::task; use crate::runtime::user_interface; use crate::runtime::window; use crate::runtime::{Task, UserInterface}; -use crate::selector; use crate::{Instruction, Selector}; use std::fmt; @@ -25,8 +24,8 @@ use std::fmt; /// A headless runtime that can run iced applications and execute /// [instructions](crate::Instruction). /// -/// An [`Emulator`] runs its program as close as possible to the real thing. -/// It will run subscriptions and tasks in the [`Executor`](Program::Executor) of +/// An [`Emulator`] runs its program as faithfully as possible to the real thing. +/// It will run subscriptions and tasks with the [`Executor`](Program::Executor) of /// the [`Program`]. /// /// If you want to run a simulation without side effects, use a [`Simulator`](crate::Simulator) @@ -289,7 +288,6 @@ impl Emulator

{ let Some(events) = interaction.events(|target| match target { instruction::Target::Point(position) => Some(*position), instruction::Target::Text(text) => { - use selector::target::Bounded; use widget::Operation; let mut operation = Selector::find(text.as_str()); diff --git a/test/src/instruction.rs b/test/src/instruction.rs index ca6ed200..c9feeb40 100644 --- a/test/src/instruction.rs +++ b/test/src/instruction.rs @@ -354,7 +354,7 @@ impl fmt::Display for Target { pub enum Keyboard { /// A key was pressed. Press(Key), - /// A key was release. + /// A key was released. Release(Key), /// A key was "typed" (press and released). Type(Key), diff --git a/test/src/lib.rs b/test/src/lib.rs index 5879fb04..ba5c8c9c 100644 --- a/test/src/lib.rs +++ b/test/src/lib.rs @@ -112,7 +112,7 @@ use std::path::Path; /// an [`Emulator`] of the given [`Program`](program::Program). /// /// Remember that an [`Emulator`] executes the real thing! Side effects _will_ -/// be performed. It is up to you to ensure your tests have reproducible environments +/// take place. It is up to you to ensure your tests have reproducible environments /// by leveraging [`Preset`][program::Preset]. pub fn run( program: impl program::Program + 'static, diff --git a/test/src/simulator.rs b/test/src/simulator.rs index b578e107..b0a9a5d1 100644 --- a/test/src/simulator.rs +++ b/test/src/simulator.rs @@ -12,7 +12,7 @@ use crate::core::{Element, Event, Font, Point, Settings, Size, SmolStr}; use crate::renderer; use crate::runtime::UserInterface; use crate::runtime::user_interface; -use crate::selector::target::Bounded; +use crate::selector::Bounded; use crate::{Error, Selector}; use std::borrow::Cow; diff --git a/tester/src/recorder.rs b/tester/src/recorder.rs index b44b40f1..03a161e4 100644 --- a/tester/src/recorder.rs +++ b/tester/src/recorder.rs @@ -11,7 +11,7 @@ use crate::core::{ }; use crate::test::Selector; use crate::test::instruction::{Interaction, Mouse, Target}; -use crate::test::selector::target; +use crate::test::selector; pub fn recorder<'a, Message, Renderer>( content: impl Into>, @@ -457,12 +457,12 @@ fn find_text( let (content, visible_bounds) = targets.into_iter().rev().find_map(|target| { - if let target::Match::Text { + if let selector::Target::Text { content, visible_bounds, .. } - | target::Match::TextInput { + | selector::Target::TextInput { content, visible_bounds, ..