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
270
selector/src/find.rs
Normal file
270
selector/src/find.rs
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
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 std::any::Any;
|
||||
|
||||
pub type Find<S> = Finder<One<S>>;
|
||||
pub type FindAll<S> = Finder<All<S>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct One<S>
|
||||
where
|
||||
S: Selector,
|
||||
{
|
||||
selector: S,
|
||||
output: Option<S::Output>,
|
||||
}
|
||||
|
||||
impl<S> One<S>
|
||||
where
|
||||
S: Selector,
|
||||
{
|
||||
pub fn new(selector: S) -> Self {
|
||||
Self {
|
||||
selector,
|
||||
output: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Strategy for One<S>
|
||||
where
|
||||
S: Selector,
|
||||
S::Output: Clone,
|
||||
{
|
||||
type Output = Option<S::Output>;
|
||||
|
||||
fn feed(&mut self, target: Target<'_>) {
|
||||
if let Some(output) = self.selector.select(target) {
|
||||
self.output = Some(output);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_done(&self) -> bool {
|
||||
self.output.is_some()
|
||||
}
|
||||
|
||||
fn finish(&self) -> Self::Output {
|
||||
self.output.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct All<S>
|
||||
where
|
||||
S: Selector,
|
||||
{
|
||||
selector: S,
|
||||
outputs: Vec<S::Output>,
|
||||
}
|
||||
|
||||
impl<S> All<S>
|
||||
where
|
||||
S: Selector,
|
||||
{
|
||||
pub fn new(selector: S) -> Self {
|
||||
Self {
|
||||
selector,
|
||||
outputs: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Strategy for All<S>
|
||||
where
|
||||
S: Selector,
|
||||
S::Output: Clone,
|
||||
{
|
||||
type Output = Vec<S::Output>;
|
||||
|
||||
fn feed(&mut self, target: Target<'_>) {
|
||||
if let Some(output) = self.selector.select(target) {
|
||||
self.outputs.push(output);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_done(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn finish(&self) -> Self::Output {
|
||||
self.outputs.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Strategy {
|
||||
type Output;
|
||||
|
||||
fn feed(&mut self, target: Target<'_>);
|
||||
|
||||
fn is_done(&self) -> bool;
|
||||
|
||||
fn finish(&self) -> Self::Output;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Finder<S> {
|
||||
strategy: S,
|
||||
stack: Vec<(Rectangle, Vector)>,
|
||||
viewport: Rectangle,
|
||||
translation: Vector,
|
||||
}
|
||||
|
||||
impl<S> Finder<S> {
|
||||
pub fn new(strategy: S) -> Self {
|
||||
Self {
|
||||
strategy,
|
||||
stack: vec![(Rectangle::INFINITE, Vector::ZERO)],
|
||||
viewport: Rectangle::INFINITE,
|
||||
translation: Vector::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Operation<S::Output> for Finder<S>
|
||||
where
|
||||
S: Strategy + Send,
|
||||
S::Output: Send,
|
||||
{
|
||||
fn traverse(
|
||||
&mut self,
|
||||
operate: &mut dyn FnMut(&mut dyn Operation<S::Output>),
|
||||
) {
|
||||
if self.strategy.is_done() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.stack.push((self.viewport, self.translation));
|
||||
operate(self);
|
||||
let _ = self.stack.pop();
|
||||
|
||||
let (viewport, translation) = self.stack.last().unwrap();
|
||||
self.viewport = *viewport;
|
||||
self.translation = *translation;
|
||||
}
|
||||
|
||||
fn container(&mut self, id: Option<&Id>, bounds: Rectangle) {
|
||||
if self.strategy.is_done() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.strategy.feed(Target::Container {
|
||||
id,
|
||||
bounds,
|
||||
visible_bounds: self
|
||||
.viewport
|
||||
.intersection(&(bounds + self.translation)),
|
||||
});
|
||||
}
|
||||
|
||||
fn focusable(
|
||||
&mut self,
|
||||
id: Option<&Id>,
|
||||
bounds: Rectangle,
|
||||
state: &mut dyn Focusable,
|
||||
) {
|
||||
if self.strategy.is_done() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.strategy.feed(Target::Focusable {
|
||||
id,
|
||||
bounds,
|
||||
visible_bounds: self
|
||||
.viewport
|
||||
.intersection(&(bounds + self.translation)),
|
||||
state,
|
||||
});
|
||||
}
|
||||
|
||||
fn scrollable(
|
||||
&mut self,
|
||||
id: Option<&Id>,
|
||||
bounds: Rectangle,
|
||||
content_bounds: Rectangle,
|
||||
translation: Vector,
|
||||
state: &mut dyn Scrollable,
|
||||
) {
|
||||
if self.strategy.is_done() {
|
||||
return;
|
||||
}
|
||||
|
||||
let visible_bounds =
|
||||
self.viewport.intersection(&(bounds + self.translation));
|
||||
|
||||
self.strategy.feed(Target::Scrollable {
|
||||
id,
|
||||
bounds,
|
||||
visible_bounds,
|
||||
content_bounds,
|
||||
translation,
|
||||
state,
|
||||
});
|
||||
|
||||
self.translation = self.translation - translation;
|
||||
self.viewport = visible_bounds.unwrap_or_default();
|
||||
}
|
||||
|
||||
fn text_input(
|
||||
&mut self,
|
||||
id: Option<&Id>,
|
||||
bounds: Rectangle,
|
||||
state: &mut dyn TextInput,
|
||||
) {
|
||||
if self.strategy.is_done() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.strategy.feed(Target::TextInput {
|
||||
id,
|
||||
bounds,
|
||||
visible_bounds: self
|
||||
.viewport
|
||||
.intersection(&(bounds + self.translation)),
|
||||
state,
|
||||
});
|
||||
}
|
||||
|
||||
fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) {
|
||||
if self.strategy.is_done() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.strategy.feed(Target::Text {
|
||||
id,
|
||||
bounds,
|
||||
visible_bounds: self
|
||||
.viewport
|
||||
.intersection(&(bounds + self.translation)),
|
||||
content: text,
|
||||
});
|
||||
}
|
||||
|
||||
fn custom(
|
||||
&mut self,
|
||||
id: Option<&Id>,
|
||||
bounds: Rectangle,
|
||||
state: &mut dyn Any,
|
||||
) {
|
||||
if self.strategy.is_done() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.strategy.feed(Target::Custom {
|
||||
id,
|
||||
bounds,
|
||||
visible_bounds: self
|
||||
.viewport
|
||||
.intersection(&(bounds + self.translation)),
|
||||
state,
|
||||
});
|
||||
}
|
||||
|
||||
fn finish(&self) -> Outcome<S::Output> {
|
||||
Outcome::Some(self.strategy.finish())
|
||||
}
|
||||
}
|
||||
113
selector/src/lib.rs
Normal file
113
selector/src/lib.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#![allow(missing_docs)]
|
||||
use iced_core as core;
|
||||
|
||||
pub mod target;
|
||||
|
||||
mod find;
|
||||
|
||||
pub use find::{Find, FindAll};
|
||||
pub use target::Target;
|
||||
|
||||
use crate::core::widget::Id;
|
||||
|
||||
pub trait Selector {
|
||||
type Output;
|
||||
|
||||
fn select(&mut self, target: Target<'_>) -> Option<Self::Output>;
|
||||
|
||||
fn description(&self) -> String;
|
||||
|
||||
fn find(self) -> Find<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Find::new(find::One::new(self))
|
||||
}
|
||||
|
||||
fn find_all(self) -> FindAll<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
FindAll::new(find::All::new(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Selector for &str {
|
||||
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;
|
||||
|
||||
fn select(&mut self, target: Target<'_>) -> Option<Self::Output> {
|
||||
if target.id() != Some(self) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(target::Match::from_target(target))
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
format!("id == \"{:?}\"", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T> Selector for F
|
||||
where
|
||||
F: FnMut(Target<'_>) -> Option<T>,
|
||||
{
|
||||
type Output = T;
|
||||
|
||||
fn select(&mut self, target: Target<'_>) -> Option<Self::Output> {
|
||||
(self)(target)
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
format!("custom selector: {}", std::any::type_name_of_val(self))
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn inspect(position: Point) -> impl Selector<Output = (Match, Rectangle)> {
|
||||
// visible(move |target: Target<'_>, visible_bounds: Rectangle| {
|
||||
// visible_bounds
|
||||
// .contains(position)
|
||||
// .then(|| Match::from_target(target))
|
||||
// })
|
||||
// }
|
||||
|
||||
// pub fn visible<T>(
|
||||
// f: impl Fn(Target<'_>, Rectangle) -> Option<T>,
|
||||
// ) -> impl Selector<Output = (T, Rectangle)> {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
238
selector/src/target.rs
Normal file
238
selector/src/target.rs
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
use crate::core::widget::Id;
|
||||
use crate::core::widget::operation::{Focusable, Scrollable, TextInput};
|
||||
use crate::core::{Rectangle, Vector};
|
||||
|
||||
use std::any::Any;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub enum Target<'a> {
|
||||
Container {
|
||||
id: Option<&'a Id>,
|
||||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
},
|
||||
Focusable {
|
||||
id: Option<&'a Id>,
|
||||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
state: &'a dyn Focusable,
|
||||
},
|
||||
Scrollable {
|
||||
id: Option<&'a Id>,
|
||||
bounds: Rectangle,
|
||||
content_bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
translation: Vector,
|
||||
state: &'a dyn Scrollable,
|
||||
},
|
||||
TextInput {
|
||||
id: Option<&'a Id>,
|
||||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
state: &'a dyn TextInput,
|
||||
},
|
||||
Text {
|
||||
id: Option<&'a Id>,
|
||||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
content: &'a str,
|
||||
},
|
||||
Custom {
|
||||
id: Option<&'a Id>,
|
||||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
state: &'a dyn Any,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> Target<'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,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Match {
|
||||
Container {
|
||||
id: Option<Id>,
|
||||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
},
|
||||
Focusable {
|
||||
id: Option<Id>,
|
||||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
},
|
||||
Scrollable {
|
||||
id: Option<Id>,
|
||||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
content_bounds: Rectangle,
|
||||
translation: Vector,
|
||||
},
|
||||
TextInput {
|
||||
id: Option<Id>,
|
||||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
},
|
||||
Text {
|
||||
id: Option<Id>,
|
||||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
content: String,
|
||||
},
|
||||
Custom {
|
||||
id: Option<Id>,
|
||||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
},
|
||||
}
|
||||
|
||||
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,
|
||||
..
|
||||
} => Self::TextInput {
|
||||
id: id.cloned(),
|
||||
bounds,
|
||||
visible_bounds,
|
||||
},
|
||||
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<Rectangle> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Bounded: std::fmt::Debug {
|
||||
fn bounds(&self) -> Rectangle;
|
||||
|
||||
fn visible_bounds(&self) -> Option<Rectangle>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Text {
|
||||
Raw {
|
||||
id: Option<Id>,
|
||||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
},
|
||||
Input {
|
||||
id: Option<Id>,
|
||||
bounds: Rectangle,
|
||||
visible_bounds: Option<Rectangle>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Bounded for Text {
|
||||
fn bounds(&self) -> Rectangle {
|
||||
match self {
|
||||
Text::Raw { bounds, .. } | Text::Input { bounds, .. } => *bounds,
|
||||
}
|
||||
}
|
||||
|
||||
fn visible_bounds(&self) -> Option<Rectangle> {
|
||||
match self {
|
||||
Text::Raw { visible_bounds, .. }
|
||||
| Text::Input { visible_bounds, .. } => *visible_bounds,
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue