Introduce instruction::Target in test crate
This commit is contained in:
parent
f9755b0b7a
commit
bdcaadbe00
8 changed files with 150 additions and 82 deletions
|
|
@ -5,12 +5,20 @@ use crate::widget::operation::Operation;
|
||||||
|
|
||||||
/// The internal state of a widget that has text input.
|
/// The internal state of a widget that has text input.
|
||||||
pub trait TextInput {
|
pub trait TextInput {
|
||||||
|
/// Returns the current _visible_ text of the text input
|
||||||
|
///
|
||||||
|
/// Normally, this is either its value or its placeholder.
|
||||||
|
fn text(&self) -> &str;
|
||||||
|
|
||||||
/// Moves the cursor of the text input to the front of the input text.
|
/// Moves the cursor of the text input to the front of the input text.
|
||||||
fn move_cursor_to_front(&mut self);
|
fn move_cursor_to_front(&mut self);
|
||||||
|
|
||||||
/// Moves the cursor of the text input to the end of the input text.
|
/// Moves the cursor of the text input to the end of the input text.
|
||||||
fn move_cursor_to_end(&mut self);
|
fn move_cursor_to_end(&mut self);
|
||||||
|
|
||||||
/// Moves the cursor of the text input to an arbitrary location.
|
/// Moves the cursor of the text input to an arbitrary location.
|
||||||
fn move_cursor_to(&mut self, position: usize);
|
fn move_cursor_to(&mut self, position: usize);
|
||||||
|
|
||||||
/// Selects all the content of the text input.
|
/// Selects all the content of the text input.
|
||||||
fn select_all(&mut self);
|
fn select_all(&mut self);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ edition = "2024"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = ["tester"]
|
||||||
test = ["iced/test"]
|
test = ["iced/test"]
|
||||||
tester = ["test", "iced/tester"]
|
tester = ["test", "iced/tester"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,11 @@ viewport: 512x768
|
||||||
mode: impatient
|
mode: impatient
|
||||||
preset: Empty
|
preset: Empty
|
||||||
-----
|
-----
|
||||||
click left at (377.80, 236.50)
|
click left at "What needs to be done?"
|
||||||
type "Create the universe"
|
type "Create the universe"
|
||||||
type enter
|
type enter
|
||||||
type "Make an apple pie"
|
type "Make an apple pie"
|
||||||
type enter
|
type enter
|
||||||
click left at (135.40, 351.70)
|
click left at "Create the universe"
|
||||||
click left at (153.80, 398.10)
|
click left at "Make an apple pie"
|
||||||
move cursor to (511.40, 448.50)
|
expect "0 tasks left"
|
||||||
expect text "Create the universe"
|
|
||||||
expect text "Make an apple pie"
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ use crate::runtime::task;
|
||||||
use crate::runtime::user_interface;
|
use crate::runtime::user_interface;
|
||||||
use crate::runtime::window;
|
use crate::runtime::window;
|
||||||
use crate::runtime::{Action, Task, UserInterface};
|
use crate::runtime::{Action, Task, UserInterface};
|
||||||
|
use crate::selector;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
|
@ -212,7 +213,34 @@ impl<P: Program + 'static> Emulator<P> {
|
||||||
|
|
||||||
match instruction {
|
match instruction {
|
||||||
Instruction::Interact(interaction) => {
|
Instruction::Interact(interaction) => {
|
||||||
let events = interaction.events();
|
let Some(events) = interaction.events(|target| match target {
|
||||||
|
instruction::Target::Point(position) => Some(*position),
|
||||||
|
instruction::Target::Text(text) => {
|
||||||
|
use widget::Operation;
|
||||||
|
|
||||||
|
let mut operation =
|
||||||
|
selector::text(text.to_owned()).operation();
|
||||||
|
|
||||||
|
user_interface.operate(
|
||||||
|
&self.renderer,
|
||||||
|
&mut widget::operation::black_box(&mut operation),
|
||||||
|
);
|
||||||
|
|
||||||
|
match operation.finish() {
|
||||||
|
widget::operation::Outcome::Some(matches) => {
|
||||||
|
matches
|
||||||
|
.first()
|
||||||
|
.copied()
|
||||||
|
.map(|target| target.bounds.center())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) else {
|
||||||
|
self.runtime.send(Event::Failed);
|
||||||
|
self.cache = Some(user_interface.into_cache());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
for event in &events {
|
for event in &events {
|
||||||
if let core::Event::Mouse(mouse::Event::CursorMoved {
|
if let core::Event::Mouse(mouse::Event::CursorMoved {
|
||||||
|
|
@ -242,10 +270,10 @@ impl<P: Program + 'static> Emulator<P> {
|
||||||
self.resubscribe(program);
|
self.resubscribe(program);
|
||||||
}
|
}
|
||||||
Instruction::Expect(expectation) => match expectation {
|
Instruction::Expect(expectation) => match expectation {
|
||||||
instruction::Expectation::Presence(selector) => {
|
instruction::Expectation::Text(text) => {
|
||||||
use widget::Operation;
|
use widget::Operation;
|
||||||
|
|
||||||
let mut operation = selector.operation();
|
let mut operation = selector::text(text).operation();
|
||||||
|
|
||||||
user_interface.operate(
|
user_interface.operate(
|
||||||
&self.renderer,
|
&self.renderer,
|
||||||
|
|
@ -253,7 +281,9 @@ impl<P: Program + 'static> Emulator<P> {
|
||||||
);
|
);
|
||||||
|
|
||||||
match operation.finish() {
|
match operation.finish() {
|
||||||
widget::operation::Outcome::Some(Some(_)) => {
|
widget::operation::Outcome::Some(matches)
|
||||||
|
if matches.len() == 1 =>
|
||||||
|
{
|
||||||
self.runtime.send(Event::Ready);
|
self.runtime.send(Event::Ready);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
use crate::Selector;
|
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::mouse;
|
use crate::core::mouse;
|
||||||
use crate::core::{Event, Point};
|
use crate::core::{Event, Point};
|
||||||
use crate::selector;
|
|
||||||
use crate::simulator;
|
use crate::simulator;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
@ -38,7 +36,9 @@ impl Interaction {
|
||||||
pub fn from_event(event: Event) -> Option<Self> {
|
pub fn from_event(event: Event) -> Option<Self> {
|
||||||
Some(match event {
|
Some(match event {
|
||||||
Event::Mouse(mouse) => Self::Mouse(match mouse {
|
Event::Mouse(mouse) => Self::Mouse(match mouse {
|
||||||
mouse::Event::CursorMoved { position } => Mouse::Move(position),
|
mouse::Event::CursorMoved { position } => {
|
||||||
|
Mouse::Move(Target::Point(position))
|
||||||
|
}
|
||||||
mouse::Event::ButtonPressed(button) => {
|
mouse::Event::ButtonPressed(button) => {
|
||||||
Mouse::Press { button, at: None }
|
Mouse::Press { button, at: None }
|
||||||
}
|
}
|
||||||
|
|
@ -115,8 +115,8 @@ impl Interaction {
|
||||||
at: release_at,
|
at: release_at,
|
||||||
},
|
},
|
||||||
) if press == release
|
) if press == release
|
||||||
&& release_at.is_none_or(|release_at| {
|
&& release_at.as_ref().is_none_or(|release_at| {
|
||||||
Some(release_at) == press_at
|
Some(release_at) == press_at.as_ref()
|
||||||
}) =>
|
}) =>
|
||||||
{
|
{
|
||||||
(
|
(
|
||||||
|
|
@ -127,22 +127,6 @@ impl Interaction {
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
(
|
|
||||||
current @ Mouse::Release {
|
|
||||||
button: button_a,
|
|
||||||
at: at_a,
|
|
||||||
}
|
|
||||||
| current @ Mouse::Click {
|
|
||||||
button: button_a,
|
|
||||||
at: at_a,
|
|
||||||
},
|
|
||||||
Mouse::Release {
|
|
||||||
button: button_b,
|
|
||||||
at: at_b,
|
|
||||||
},
|
|
||||||
) if button_a == button_b && at_a == at_b => {
|
|
||||||
(Self::Mouse(current), None)
|
|
||||||
}
|
|
||||||
(current, next) => {
|
(current, next) => {
|
||||||
(Self::Mouse(current), Some(Self::Mouse(next)))
|
(Self::Mouse(current), Some(Self::Mouse(next)))
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +157,10 @@ impl Interaction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn events(&self) -> Vec<Event> {
|
pub fn events(
|
||||||
|
&self,
|
||||||
|
find_target: impl FnOnce(&Target) -> Option<Point>,
|
||||||
|
) -> Option<Vec<Event>> {
|
||||||
let mouse_move_ =
|
let mouse_move_ =
|
||||||
|to| Event::Mouse(mouse::Event::CursorMoved { position: to });
|
|to| Event::Mouse(mouse::Event::CursorMoved { position: to });
|
||||||
|
|
||||||
|
|
@ -187,20 +174,22 @@ impl Interaction {
|
||||||
|
|
||||||
let key_release = |key| simulator::release_key(key);
|
let key_release = |key| simulator::release_key(key);
|
||||||
|
|
||||||
match self {
|
Some(match self {
|
||||||
Interaction::Mouse(mouse) => match mouse {
|
Interaction::Mouse(mouse) => match mouse {
|
||||||
Mouse::Move(to) => vec![mouse_move_(*to)],
|
Mouse::Move(to) => vec![mouse_move_(find_target(to)?)],
|
||||||
Mouse::Press {
|
Mouse::Press {
|
||||||
button,
|
button,
|
||||||
at: Some(at),
|
at: Some(at),
|
||||||
} => vec![mouse_move_(*at), mouse_press(*button)],
|
} => vec![mouse_move_(find_target(at)?), mouse_press(*button)],
|
||||||
Mouse::Press { button, at: None } => {
|
Mouse::Press { button, at: None } => {
|
||||||
vec![mouse_press(*button)]
|
vec![mouse_press(*button)]
|
||||||
}
|
}
|
||||||
Mouse::Release {
|
Mouse::Release {
|
||||||
button,
|
button,
|
||||||
at: Some(at),
|
at: Some(at),
|
||||||
} => vec![mouse_move_(*at), mouse_release(*button)],
|
} => {
|
||||||
|
vec![mouse_move_(find_target(at)?), mouse_release(*button)]
|
||||||
|
}
|
||||||
Mouse::Release { button, at: None } => {
|
Mouse::Release { button, at: None } => {
|
||||||
vec![mouse_release(*button)]
|
vec![mouse_release(*button)]
|
||||||
}
|
}
|
||||||
|
|
@ -209,7 +198,7 @@ impl Interaction {
|
||||||
at: Some(at),
|
at: Some(at),
|
||||||
} => {
|
} => {
|
||||||
vec![
|
vec![
|
||||||
mouse_move_(*at),
|
mouse_move_(find_target(at)?),
|
||||||
mouse_press(*button),
|
mouse_press(*button),
|
||||||
mouse_release(*button),
|
mouse_release(*button),
|
||||||
]
|
]
|
||||||
|
|
@ -226,7 +215,7 @@ impl Interaction {
|
||||||
simulator::typewrite(text).collect()
|
simulator::typewrite(text).collect()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,40 +230,55 @@ impl fmt::Display for Interaction {
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Mouse {
|
pub enum Mouse {
|
||||||
Move(Point),
|
Move(Target),
|
||||||
Press {
|
Press {
|
||||||
button: mouse::Button,
|
button: mouse::Button,
|
||||||
at: Option<Point>,
|
at: Option<Target>,
|
||||||
},
|
},
|
||||||
Release {
|
Release {
|
||||||
button: mouse::Button,
|
button: mouse::Button,
|
||||||
at: Option<Point>,
|
at: Option<Target>,
|
||||||
},
|
},
|
||||||
Click {
|
Click {
|
||||||
button: mouse::Button,
|
button: mouse::Button,
|
||||||
at: Option<Point>,
|
at: Option<Target>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Mouse {
|
impl fmt::Display for Mouse {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Mouse::Move(point) => {
|
Mouse::Move(target) => {
|
||||||
write!(f, "move cursor to ({:.2}, {:.2})", point.x, point.y)
|
write!(f, "move cursor to {}", target)
|
||||||
}
|
}
|
||||||
Mouse::Press { button, at } => {
|
Mouse::Press { button, at } => {
|
||||||
write!(f, "press {}", format::button_at(*button, *at))
|
write!(f, "press {}", format::button_at(*button, at.as_ref()))
|
||||||
}
|
}
|
||||||
Mouse::Release { button, at } => {
|
Mouse::Release { button, at } => {
|
||||||
write!(f, "release {}", format::button_at(*button, *at))
|
write!(f, "release {}", format::button_at(*button, at.as_ref()))
|
||||||
}
|
}
|
||||||
Mouse::Click { button, at } => {
|
Mouse::Click { button, at } => {
|
||||||
write!(f, "click {}", format::button_at(*button, *at))
|
write!(f, "click {}", format::button_at(*button, at.as_ref()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Target {
|
||||||
|
Point(Point),
|
||||||
|
Text(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Target {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Point(point) => f.write_str(&format::point(*point)),
|
||||||
|
Self::Text(text) => f.write_str(&format::string(text)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Keyboard {
|
pub enum Keyboard {
|
||||||
Press(Key),
|
Press(Key),
|
||||||
|
|
@ -324,9 +328,9 @@ impl From<Key> for keyboard::Key {
|
||||||
mod format {
|
mod format {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn button_at(button: mouse::Button, at: Option<Point>) -> String {
|
pub fn button_at(button: mouse::Button, at: Option<&Target>) -> String {
|
||||||
if let Some(at) = at {
|
if let Some(at) = at {
|
||||||
format!("{} at {}", self::button(button), point(at))
|
format!("{} at {}", self::button(button), at)
|
||||||
} else {
|
} else {
|
||||||
self::button(button).to_owned()
|
self::button(button).to_owned()
|
||||||
}
|
}
|
||||||
|
|
@ -355,21 +359,22 @@ mod format {
|
||||||
Key::Backspace => "backspace",
|
Key::Backspace => "backspace",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn string(text: &str) -> String {
|
||||||
|
format!("\"{}\"", text.escape_default())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Expectation {
|
pub enum Expectation {
|
||||||
Presence(Selector),
|
Text(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Expectation {
|
impl fmt::Display for Expectation {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Expectation::Presence(Selector::Id(_id)) => {
|
Expectation::Text(text) => {
|
||||||
write!(f, "expect id") // TODO
|
write!(f, "expect {}", format::string(text))
|
||||||
}
|
|
||||||
Expectation::Presence(Selector::Text(text)) => {
|
|
||||||
write!(f, "expect text \"{text}\"")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -383,7 +388,7 @@ mod parser {
|
||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::character::complete::{char, multispace0, satisfy};
|
use nom::character::complete::{char, multispace0, satisfy};
|
||||||
use nom::combinator::{map, opt, recognize};
|
use nom::combinator::{cut, map, opt, recognize};
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
use nom::number::float;
|
use nom::number::float;
|
||||||
use nom::sequence::{delimited, preceded, separated_pair};
|
use nom::sequence::{delimited, preceded, separated_pair};
|
||||||
|
|
@ -418,7 +423,7 @@ mod parser {
|
||||||
|
|
||||||
fn mouse(input: &str) -> IResult<&str, Mouse> {
|
fn mouse(input: &str) -> IResult<&str, Mouse> {
|
||||||
let mouse_move =
|
let mouse_move =
|
||||||
preceded(tag("move cursor to "), point).map(Mouse::Move);
|
preceded(tag("move cursor to "), target).map(Mouse::Move);
|
||||||
|
|
||||||
alt((mouse_move, mouse_click)).parse(input)
|
alt((mouse_move, mouse_click)).parse(input)
|
||||||
}
|
}
|
||||||
|
|
@ -433,13 +438,21 @@ mod parser {
|
||||||
|
|
||||||
fn mouse_button_at(
|
fn mouse_button_at(
|
||||||
input: &str,
|
input: &str,
|
||||||
) -> IResult<&str, (mouse::Button, Option<Point>)> {
|
) -> IResult<&str, (mouse::Button, Option<Target>)> {
|
||||||
let (input, button) = mouse_button(input)?;
|
let (input, button) = mouse_button(input)?;
|
||||||
let (input, at) = opt(preceded(tag(" at "), point)).parse(input)?;
|
let (input, at) = opt(target).parse(input)?;
|
||||||
|
|
||||||
Ok((input, (button, at)))
|
Ok((input, (button, at)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn target(input: &str) -> IResult<&str, Target> {
|
||||||
|
preceded(
|
||||||
|
tag(" at "),
|
||||||
|
cut(alt((string.map(Target::Text), point.map(Target::Point)))),
|
||||||
|
)
|
||||||
|
.parse(input)
|
||||||
|
}
|
||||||
|
|
||||||
fn mouse_button(input: &str) -> IResult<&str, mouse::Button> {
|
fn mouse_button(input: &str) -> IResult<&str, mouse::Button> {
|
||||||
alt((
|
alt((
|
||||||
tag("left").map(|_| mouse::Button::Left),
|
tag("left").map(|_| mouse::Button::Left),
|
||||||
|
|
@ -457,8 +470,8 @@ mod parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expectation(input: &str) -> IResult<&str, Expectation> {
|
fn expectation(input: &str) -> IResult<&str, Expectation> {
|
||||||
map(preceded(tag("expect text "), string), |text| {
|
map(preceded(tag("expect "), string), |text| {
|
||||||
Expectation::Presence(selector::text(text))
|
Expectation::Text(text)
|
||||||
})
|
})
|
||||||
.parse(input)
|
.parse(input)
|
||||||
}
|
}
|
||||||
|
|
@ -474,6 +487,7 @@ mod parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn string(input: &str) -> IResult<&str, String> {
|
fn string(input: &str) -> IResult<&str, String> {
|
||||||
|
// TODO: Proper string literal parsing
|
||||||
delimited(
|
delimited(
|
||||||
char('"'),
|
char('"'),
|
||||||
map(recognize(many0(satisfy(|c| c != '"'))), str::to_owned),
|
map(recognize(many0(satisfy(|c| c != '"'))), str::to_owned),
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub enum Selector {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Selector {
|
impl Selector {
|
||||||
pub fn operation<'a>(&self) -> impl widget::Operation<Option<Target>> + 'a {
|
pub fn operation<'a>(&self) -> impl widget::Operation<Vec<Target>> + 'a {
|
||||||
match self {
|
match self {
|
||||||
Selector::Id(id) => {
|
Selector::Id(id) => {
|
||||||
struct FindById {
|
struct FindById {
|
||||||
|
|
@ -21,13 +21,13 @@ impl Selector {
|
||||||
target: Option<Target>,
|
target: Option<Target>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl widget::Operation<Option<Target>> for FindById {
|
impl widget::Operation<Vec<Target>> for FindById {
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: Option<&widget::Id>,
|
id: Option<&widget::Id>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(
|
operate_on_children: &mut dyn FnMut(
|
||||||
&mut dyn widget::Operation<Option<Target>>,
|
&mut dyn widget::Operation<Vec<Target>>,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
if self.target.is_some() {
|
if self.target.is_some() {
|
||||||
|
|
@ -106,9 +106,13 @@ impl Selector {
|
||||||
|
|
||||||
fn finish(
|
fn finish(
|
||||||
&self,
|
&self,
|
||||||
) -> widget::operation::Outcome<Option<Target>>
|
) -> widget::operation::Outcome<Vec<Target>>
|
||||||
{
|
{
|
||||||
widget::operation::Outcome::Some(self.target)
|
if let Some(target) = self.target {
|
||||||
|
widget::operation::Outcome::Some(vec![target])
|
||||||
|
} else {
|
||||||
|
widget::operation::Outcome::None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,51 +124,54 @@ impl Selector {
|
||||||
Selector::Text(text) => {
|
Selector::Text(text) => {
|
||||||
struct FindByText {
|
struct FindByText {
|
||||||
text: text::Fragment<'static>,
|
text: text::Fragment<'static>,
|
||||||
target: Option<Target>,
|
target: Vec<Target>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl widget::Operation<Option<Target>> for FindByText {
|
impl widget::Operation<Vec<Target>> for FindByText {
|
||||||
fn container(
|
fn container(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&widget::Id>,
|
_id: Option<&widget::Id>,
|
||||||
_bounds: Rectangle,
|
_bounds: Rectangle,
|
||||||
operate_on_children: &mut dyn FnMut(
|
operate_on_children: &mut dyn FnMut(
|
||||||
&mut dyn widget::Operation<Option<Target>>,
|
&mut dyn widget::Operation<Vec<Target>>,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
if self.target.is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
operate_on_children(self);
|
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(
|
fn text(
|
||||||
&mut self,
|
&mut self,
|
||||||
_id: Option<&widget::Id>,
|
_id: Option<&widget::Id>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
text: &str,
|
text: &str,
|
||||||
) {
|
) {
|
||||||
if self.target.is_some() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.text == text {
|
if self.text == text {
|
||||||
self.target = Some(Target { bounds });
|
self.target.push(Target { bounds });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(
|
fn finish(
|
||||||
&self,
|
&self,
|
||||||
) -> widget::operation::Outcome<Option<Target>>
|
) -> widget::operation::Outcome<Vec<Target>>
|
||||||
{
|
{
|
||||||
widget::operation::Outcome::Some(self.target)
|
widget::operation::Outcome::Some(self.target.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Box::new(FindByText {
|
Box::new(FindByText {
|
||||||
text: text.clone(),
|
text: text.clone(),
|
||||||
target: None,
|
target: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,9 @@ where
|
||||||
);
|
);
|
||||||
|
|
||||||
match operation.finish() {
|
match operation.finish() {
|
||||||
widget::operation::Outcome::Some(Some(target)) => Ok(target),
|
widget::operation::Outcome::Some(matches) => {
|
||||||
|
matches.first().copied().ok_or(Error::NotFound(selector))
|
||||||
|
}
|
||||||
_ => Err(Error::NotFound(selector)),
|
_ => Err(Error::NotFound(selector)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1631,6 +1631,14 @@ impl<P: text::Paragraph> operation::Focusable for State<P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: text::Paragraph> operation::TextInput for State<P> {
|
impl<P: text::Paragraph> operation::TextInput for State<P> {
|
||||||
|
fn text(&self) -> &str {
|
||||||
|
if self.value.content().is_empty() {
|
||||||
|
self.placeholder.content()
|
||||||
|
} else {
|
||||||
|
self.value.content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn move_cursor_to_front(&mut self) {
|
fn move_cursor_to_front(&mut self) {
|
||||||
State::move_cursor_to_front(self);
|
State::move_cursor_to_front(self);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue