Introduce new iced_selector subcrate and refactor Operation
This commit is contained in:
parent
8ca25d627f
commit
63142d34fc
29 changed files with 839 additions and 521 deletions
|
|
@ -1,4 +1,3 @@
|
|||
use crate::Instruction;
|
||||
use crate::core;
|
||||
use crate::core::mouse;
|
||||
use crate::core::renderer;
|
||||
|
|
@ -17,6 +16,7 @@ use crate::runtime::user_interface;
|
|||
use crate::runtime::window;
|
||||
use crate::runtime::{Action, Task, UserInterface};
|
||||
use crate::selector;
|
||||
use crate::{Instruction, Selector};
|
||||
|
||||
use std::fmt;
|
||||
|
||||
|
|
@ -216,10 +216,10 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
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::text(text.to_owned()).operation();
|
||||
let mut operation = Selector::find(text.as_str());
|
||||
|
||||
user_interface.operate(
|
||||
&self.renderer,
|
||||
|
|
@ -227,11 +227,8 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
);
|
||||
|
||||
match operation.finish() {
|
||||
widget::operation::Outcome::Some(matches) => {
|
||||
matches
|
||||
.first()
|
||||
.copied()
|
||||
.map(|target| target.bounds.center())
|
||||
widget::operation::Outcome::Some(text) => {
|
||||
Some(text?.visible_bounds()?.center())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
|
@ -273,7 +270,7 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
instruction::Expectation::Text(text) => {
|
||||
use widget::Operation;
|
||||
|
||||
let mut operation = selector::text(text).operation();
|
||||
let mut operation = Selector::find(text.as_str());
|
||||
|
||||
user_interface.operate(
|
||||
&self.renderer,
|
||||
|
|
@ -281,9 +278,7 @@ impl<P: Program + 'static> Emulator<P> {
|
|||
);
|
||||
|
||||
match operation.finish() {
|
||||
widget::operation::Outcome::Some(matches)
|
||||
if matches.len() == 1 =>
|
||||
{
|
||||
widget::operation::Outcome::Some(Some(_text)) => {
|
||||
self.runtime.send(Event::Ready);
|
||||
}
|
||||
_ => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use crate::Selector;
|
||||
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -7,8 +5,12 @@ use std::sync::Arc;
|
|||
#[derive(Debug, Clone, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// No matching widget was found for the [`Selector`].
|
||||
#[error("no matching widget was found for the selector: {0:?}")]
|
||||
NotFound(Selector),
|
||||
#[error("no matching widget was found for the selector: {selector}")]
|
||||
NotFound { selector: String },
|
||||
#[error("the matching target is not visible: {target:?}")]
|
||||
NotVisible {
|
||||
target: Arc<dyn std::fmt::Debug + Send + Sync>,
|
||||
},
|
||||
/// An IO operation failed.
|
||||
#[error("an IO operation failed: {0}")]
|
||||
IOFailed(Arc<io::Error>),
|
||||
|
|
|
|||
|
|
@ -89,10 +89,11 @@ use iced_renderer as renderer;
|
|||
use iced_runtime as runtime;
|
||||
use iced_runtime::core;
|
||||
|
||||
pub use iced_selector as selector;
|
||||
|
||||
pub mod emulator;
|
||||
pub mod ice;
|
||||
pub mod instruction;
|
||||
pub mod selector;
|
||||
pub mod simulator;
|
||||
|
||||
mod error;
|
||||
|
|
|
|||
|
|
@ -1,208 +0,0 @@
|
|||
//! Select widgets of a user interface.
|
||||
use crate::core::text;
|
||||
use crate::core::widget;
|
||||
use crate::core::{Rectangle, Vector};
|
||||
|
||||
/// A selector describes a strategy to find a certain widget in a user interface.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Selector {
|
||||
/// Find the widget with the given [`widget::Id`].
|
||||
Id(widget::Id),
|
||||
/// Find the widget containing the given [`text::Fragment`].
|
||||
Text(text::Fragment<'static>),
|
||||
}
|
||||
|
||||
impl Selector {
|
||||
pub fn operation<'a>(&self) -> impl widget::Operation<Vec<Target>> + 'a {
|
||||
match self {
|
||||
Selector::Id(id) => {
|
||||
struct FindById {
|
||||
id: widget::Id,
|
||||
target: Option<Target>,
|
||||
}
|
||||
|
||||
impl widget::Operation<Vec<Target>> for FindById {
|
||||
fn container(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(
|
||||
&mut dyn widget::Operation<Vec<Target>>,
|
||||
),
|
||||
) {
|
||||
if self.target.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if Some(&self.id) == id {
|
||||
self.target = Some(Target { bounds });
|
||||
return;
|
||||
}
|
||||
|
||||
operate_on_children(self);
|
||||
}
|
||||
|
||||
fn scrollable(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
_content_bounds: Rectangle,
|
||||
_translation: Vector,
|
||||
_state: &mut dyn widget::operation::Scrollable,
|
||||
) {
|
||||
if self.target.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if Some(&self.id) == id {
|
||||
self.target = Some(Target { bounds });
|
||||
}
|
||||
}
|
||||
|
||||
fn text_input(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
_state: &mut dyn widget::operation::TextInput,
|
||||
) {
|
||||
if self.target.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if Some(&self.id) == id {
|
||||
self.target = Some(Target { bounds });
|
||||
}
|
||||
}
|
||||
|
||||
fn text(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
_text: &str,
|
||||
) {
|
||||
if self.target.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if Some(&self.id) == id {
|
||||
self.target = Some(Target { bounds });
|
||||
}
|
||||
}
|
||||
|
||||
fn custom(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
_state: &mut dyn std::any::Any,
|
||||
) {
|
||||
if self.target.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
if Some(&self.id) == id {
|
||||
self.target = Some(Target { bounds });
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(
|
||||
&self,
|
||||
) -> widget::operation::Outcome<Vec<Target>>
|
||||
{
|
||||
if let Some(target) = self.target {
|
||||
widget::operation::Outcome::Some(vec![target])
|
||||
} else {
|
||||
widget::operation::Outcome::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(FindById {
|
||||
id: id.clone(),
|
||||
target: None,
|
||||
}) as Box<dyn widget::Operation<_>>
|
||||
}
|
||||
Selector::Text(text) => {
|
||||
struct FindByText {
|
||||
text: text::Fragment<'static>,
|
||||
target: Vec<Target>,
|
||||
}
|
||||
|
||||
impl widget::Operation<Vec<Target>> for FindByText {
|
||||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&widget::Id>,
|
||||
_bounds: Rectangle,
|
||||
operate_on_children: &mut dyn FnMut(
|
||||
&mut dyn widget::Operation<Vec<Target>>,
|
||||
),
|
||||
) {
|
||||
operate_on_children(self);
|
||||
}
|
||||
|
||||
fn text_input(
|
||||
&mut self,
|
||||
_id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
state: &mut dyn widget::operation::TextInput,
|
||||
) {
|
||||
if self.text == state.text() {
|
||||
self.target.push(Target { bounds });
|
||||
}
|
||||
}
|
||||
|
||||
fn text(
|
||||
&mut self,
|
||||
_id: Option<&widget::Id>,
|
||||
bounds: Rectangle,
|
||||
text: &str,
|
||||
) {
|
||||
if self.text == text {
|
||||
self.target.push(Target { bounds });
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(
|
||||
&self,
|
||||
) -> widget::operation::Outcome<Vec<Target>>
|
||||
{
|
||||
widget::operation::Outcome::Some(self.target.clone())
|
||||
}
|
||||
}
|
||||
|
||||
Box::new(FindByText {
|
||||
text: text.clone(),
|
||||
target: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<widget::Id> for Selector {
|
||||
fn from(id: widget::Id) -> Self {
|
||||
Self::Id(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Selector {
|
||||
fn from(text: &'static str) -> Self {
|
||||
Self::Text(text.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`Selector`] that finds the widget with the given [`widget::Id`].
|
||||
pub fn id(id: impl Into<widget::Id>) -> Selector {
|
||||
Selector::Id(id.into())
|
||||
}
|
||||
|
||||
/// Creates a [`Selector`] that finds the widget containing the given text fragment.
|
||||
pub fn text(fragment: impl text::IntoFragment<'static>) -> Selector {
|
||||
Selector::Text(fragment.into_fragment())
|
||||
}
|
||||
|
||||
/// A specific area, normally containing a widget.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Target {
|
||||
/// The bounds of the area.
|
||||
pub bounds: Rectangle,
|
||||
}
|
||||
|
|
@ -12,13 +12,14 @@ 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;
|
||||
use crate::selector::target::Bounded;
|
||||
use crate::{Error, Selector};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A user interface that can be interacted with and inspected programmatically.
|
||||
#[allow(missing_debug_implementations)]
|
||||
|
|
@ -100,14 +101,15 @@ where
|
|||
}
|
||||
|
||||
/// Finds the [`Target`] of the given widget [`Selector`] in the [`Simulator`].
|
||||
pub fn find(
|
||||
&mut self,
|
||||
selector: impl Into<Selector>,
|
||||
) -> Result<selector::Target, Error> {
|
||||
pub fn find<S>(&mut self, selector: S) -> Result<S::Output, Error>
|
||||
where
|
||||
S: Selector + Send,
|
||||
S::Output: Clone + Send,
|
||||
{
|
||||
use widget::Operation;
|
||||
|
||||
let selector = selector.into();
|
||||
let mut operation = selector.operation();
|
||||
let description = selector.description();
|
||||
let mut operation = selector.find();
|
||||
|
||||
self.raw.operate(
|
||||
&self.renderer,
|
||||
|
|
@ -115,10 +117,14 @@ where
|
|||
);
|
||||
|
||||
match operation.finish() {
|
||||
widget::operation::Outcome::Some(matches) => {
|
||||
matches.first().copied().ok_or(Error::NotFound(selector))
|
||||
widget::operation::Outcome::Some(output) => {
|
||||
output.ok_or(Error::NotFound {
|
||||
selector: description,
|
||||
})
|
||||
}
|
||||
_ => Err(Error::NotFound(selector)),
|
||||
_ => Err(Error::NotFound {
|
||||
selector: description,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,12 +140,20 @@ where
|
|||
/// This consists in:
|
||||
/// - Pointing the mouse cursor at the center of the [`Target`].
|
||||
/// - Simulating a [`click`].
|
||||
pub fn click(
|
||||
&mut self,
|
||||
selector: impl Into<Selector>,
|
||||
) -> Result<selector::Target, Error> {
|
||||
pub fn click<S>(&mut self, selector: S) -> Result<S::Output, Error>
|
||||
where
|
||||
S: Selector + Send,
|
||||
S::Output: Bounded + Clone + Send + Sync + 'static,
|
||||
{
|
||||
let target = self.find(selector)?;
|
||||
self.point_at(target.bounds.center());
|
||||
|
||||
let Some(visible_bounds) = target.visible_bounds() else {
|
||||
return Err(Error::NotVisible {
|
||||
target: Arc::new(target),
|
||||
});
|
||||
};
|
||||
|
||||
self.point_at(visible_bounds.center());
|
||||
|
||||
let _ = self.simulate(click());
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue