Try to find text targets in tester::recorder
This commit is contained in:
parent
81d1eda7fe
commit
1923d1db1e
5 changed files with 245 additions and 59 deletions
|
|
@ -546,6 +546,7 @@ impl<P: Program + 'static> Tester<P> {
|
||||||
column(self.instructions.iter().enumerate().map(
|
column(self.instructions.iter().enumerate().map(
|
||||||
|(i, instruction)| {
|
|(i, instruction)| {
|
||||||
monospace(instruction.to_string())
|
monospace(instruction.to_string())
|
||||||
|
.wrapping(text::Wrapping::None) // TODO: Ellipsize?
|
||||||
.size(10)
|
.size(10)
|
||||||
.style(move |theme: &Theme| text::Style {
|
.style(move |theme: &Theme| text::Style {
|
||||||
color: match &self.state {
|
color: match &self.state {
|
||||||
|
|
|
||||||
|
|
@ -3,26 +3,29 @@ use crate::core::mouse;
|
||||||
use crate::core::overlay;
|
use crate::core::overlay;
|
||||||
use crate::core::renderer;
|
use crate::core::renderer;
|
||||||
use crate::core::widget;
|
use crate::core::widget;
|
||||||
|
use crate::core::widget::operation;
|
||||||
use crate::core::widget::tree;
|
use crate::core::widget::tree;
|
||||||
use crate::core::{
|
use crate::core::{
|
||||||
self, Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell,
|
self, Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell,
|
||||||
Size, Vector, Widget,
|
Size, Theme, Vector, Widget,
|
||||||
};
|
};
|
||||||
use crate::test::instruction::Interaction;
|
use crate::test::Selector;
|
||||||
|
use crate::test::instruction::{Interaction, Mouse, Target};
|
||||||
|
use crate::test::selector::target;
|
||||||
|
|
||||||
pub fn recorder<'a, Message, Theme, Renderer>(
|
pub fn recorder<'a, Message, Renderer>(
|
||||||
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
) -> Recorder<'a, Message, Theme, Renderer> {
|
) -> Recorder<'a, Message, Renderer> {
|
||||||
Recorder::new(content)
|
Recorder::new(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Recorder<'a, Message, Theme, Renderer> {
|
pub struct Recorder<'a, Message, Renderer> {
|
||||||
content: Element<'a, Message, Theme, Renderer>,
|
content: Element<'a, Message, Theme, Renderer>,
|
||||||
on_record: Option<Box<dyn Fn(Interaction) -> Message + 'a>>,
|
on_record: Option<Box<dyn Fn(Interaction) -> Message + 'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> Recorder<'a, Message, Theme, Renderer> {
|
impl<'a, Message, Renderer> Recorder<'a, Message, Renderer> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
content: impl Into<Element<'a, Message, Theme, Renderer>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
|
@ -41,14 +44,46 @@ impl<'a, Message, Theme, Renderer> Recorder<'a, Message, Theme, Renderer> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
struct State {
|
||||||
for Recorder<'_, Message, Theme, Renderer>
|
last_hovered: Option<Rectangle>,
|
||||||
|
last_hovered_overlay: Option<Rectangle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Message, Renderer> Widget<Message, Theme, Renderer>
|
||||||
|
for Recorder<'_, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: core::Renderer,
|
Renderer: core::Renderer,
|
||||||
{
|
{
|
||||||
|
fn tag(&self) -> tree::Tag {
|
||||||
|
tree::Tag::of::<State>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> tree::State {
|
||||||
|
tree::State::new(State {
|
||||||
|
last_hovered: None,
|
||||||
|
last_hovered_overlay: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children(&self) -> Vec<widget::Tree> {
|
||||||
|
vec![widget::Tree::new(&self.content)]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diff(&self, tree: &mut tree::Tree) {
|
||||||
|
tree.diff_children(std::slice::from_ref(&self.content));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(&self) -> Size<Length> {
|
||||||
|
self.content.as_widget().size()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> Size<Length> {
|
||||||
|
self.content.as_widget().size_hint()
|
||||||
|
}
|
||||||
|
|
||||||
fn update(
|
fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &mut widget::Tree,
|
tree: &mut widget::Tree,
|
||||||
event: &Event,
|
event: &Event,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
|
|
@ -61,37 +96,43 @@ where
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.content.as_widget_mut().update(
|
|
||||||
state, event, layout, cursor, renderer, clipboard, shell, viewport,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(on_record) = &self.on_record {
|
if let Some(on_record) = &self.on_record {
|
||||||
record(event, cursor, shell, layout.bounds(), on_record);
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
|
record(
|
||||||
|
event,
|
||||||
|
cursor,
|
||||||
|
shell,
|
||||||
|
layout.bounds(),
|
||||||
|
&mut state.last_hovered,
|
||||||
|
on_record,
|
||||||
|
|position| {
|
||||||
|
use widget::Operation;
|
||||||
|
|
||||||
|
let mut selector = position.find_all();
|
||||||
|
|
||||||
|
self.content.as_widget_mut().operate(
|
||||||
|
&mut tree.children[0],
|
||||||
|
layout,
|
||||||
|
renderer,
|
||||||
|
&mut operation::black_box(&mut selector),
|
||||||
|
);
|
||||||
|
|
||||||
|
selector.finish()
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn tag(&self) -> tree::Tag {
|
self.content.as_widget_mut().update(
|
||||||
self.content.as_widget().tag()
|
&mut tree.children[0],
|
||||||
}
|
event,
|
||||||
|
layout,
|
||||||
fn state(&self) -> tree::State {
|
cursor,
|
||||||
self.content.as_widget().state()
|
renderer,
|
||||||
}
|
clipboard,
|
||||||
|
shell,
|
||||||
fn children(&self) -> Vec<widget::Tree> {
|
viewport,
|
||||||
self.content.as_widget().children()
|
);
|
||||||
}
|
|
||||||
|
|
||||||
fn diff(&self, tree: &mut tree::Tree) {
|
|
||||||
self.content.as_widget().diff(tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size(&self) -> Size<Length> {
|
|
||||||
self.content.as_widget().size()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size_hint(&self) -> Size<Length> {
|
|
||||||
self.content.as_widget().size_hint()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout(
|
fn layout(
|
||||||
|
|
@ -100,7 +141,9 @@ where
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
) -> layout::Node {
|
) -> layout::Node {
|
||||||
self.content.as_widget().layout(tree, renderer, limits)
|
self.content
|
||||||
|
.as_widget()
|
||||||
|
.layout(&mut tree.children[0], renderer, limits)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(
|
fn draw(
|
||||||
|
|
@ -113,39 +156,70 @@ where
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
self.content
|
self.content.as_widget().draw(
|
||||||
.as_widget()
|
&tree.children[0],
|
||||||
.draw(tree, renderer, theme, style, layout, cursor, viewport);
|
renderer,
|
||||||
|
theme,
|
||||||
|
style,
|
||||||
|
layout,
|
||||||
|
cursor,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
|
||||||
|
let state = tree.state.downcast_ref::<State>();
|
||||||
|
|
||||||
|
let Some(last_hovered) = &state.last_hovered else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let palette = theme.palette();
|
||||||
|
|
||||||
|
renderer.with_layer(*viewport, |renderer| {
|
||||||
|
renderer.fill_quad(
|
||||||
|
renderer::Quad {
|
||||||
|
bounds: *last_hovered,
|
||||||
|
..renderer::Quad::default()
|
||||||
|
},
|
||||||
|
palette.primary.scale_alpha(0.5),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
&self,
|
&self,
|
||||||
state: &widget::Tree,
|
tree: &widget::Tree,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
) -> mouse::Interaction {
|
) -> mouse::Interaction {
|
||||||
self.content
|
self.content.as_widget().mouse_interaction(
|
||||||
.as_widget()
|
&tree.children[0],
|
||||||
.mouse_interaction(state, layout, cursor, viewport, renderer)
|
layout,
|
||||||
|
cursor,
|
||||||
|
viewport,
|
||||||
|
renderer,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operate(
|
fn operate(
|
||||||
&self,
|
&self,
|
||||||
state: &mut widget::Tree,
|
tree: &mut widget::Tree,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
operation: &mut dyn widget::Operation,
|
operation: &mut dyn widget::Operation,
|
||||||
) {
|
) {
|
||||||
self.content
|
self.content.as_widget().operate(
|
||||||
.as_widget()
|
&mut tree.children[0],
|
||||||
.operate(state, layout, renderer, operation);
|
layout,
|
||||||
|
renderer,
|
||||||
|
operation,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overlay<'a>(
|
fn overlay<'a>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
state: &'a mut widget::Tree,
|
tree: &'a mut widget::Tree,
|
||||||
layout: Layout<'a>,
|
layout: Layout<'a>,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
|
|
@ -153,25 +227,34 @@ where
|
||||||
) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
|
) -> Option<overlay::Element<'a, Message, Theme, Renderer>> {
|
||||||
self.content
|
self.content
|
||||||
.as_widget_mut()
|
.as_widget_mut()
|
||||||
.overlay(state, layout, renderer, &layout.bounds(), translation)
|
.overlay(
|
||||||
|
&mut tree.children[0],
|
||||||
|
layout,
|
||||||
|
renderer,
|
||||||
|
&layout.bounds(),
|
||||||
|
translation,
|
||||||
|
)
|
||||||
.map(|raw| {
|
.map(|raw| {
|
||||||
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
overlay::Element::new(Box::new(Overlay {
|
overlay::Element::new(Box::new(Overlay {
|
||||||
raw,
|
raw,
|
||||||
bounds: layout.bounds(),
|
bounds: layout.bounds(),
|
||||||
|
last_hovered: &mut state.last_hovered_overlay,
|
||||||
on_record: self.on_record.as_deref(),
|
on_record: self.on_record.as_deref(),
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Message, Theme, Renderer> From<Recorder<'a, Message, Theme, Renderer>>
|
impl<'a, Message, Renderer> From<Recorder<'a, Message, Renderer>>
|
||||||
for Element<'a, Message, Theme, Renderer>
|
for Element<'a, Message, Theme, Renderer>
|
||||||
where
|
where
|
||||||
Message: 'a,
|
Message: 'a,
|
||||||
Theme: 'a,
|
Theme: 'a,
|
||||||
Renderer: core::Renderer + 'a,
|
Renderer: core::Renderer + 'a,
|
||||||
{
|
{
|
||||||
fn from(recorder: Recorder<'a, Message, Theme, Renderer>) -> Self {
|
fn from(recorder: Recorder<'a, Message, Renderer>) -> Self {
|
||||||
Element::new(recorder)
|
Element::new(recorder)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -179,6 +262,7 @@ where
|
||||||
struct Overlay<'a, Message, Theme, Renderer> {
|
struct Overlay<'a, Message, Theme, Renderer> {
|
||||||
raw: overlay::Element<'a, Message, Theme, Renderer>,
|
raw: overlay::Element<'a, Message, Theme, Renderer>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
|
last_hovered: &'a mut Option<Rectangle>,
|
||||||
on_record: Option<&'a dyn Fn(Interaction) -> Message>,
|
on_record: Option<&'a dyn Fn(Interaction) -> Message>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,13 +314,33 @@ where
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(on_event) = &self.on_record {
|
||||||
|
record(
|
||||||
|
event,
|
||||||
|
cursor,
|
||||||
|
shell,
|
||||||
|
self.bounds,
|
||||||
|
self.last_hovered,
|
||||||
|
on_event,
|
||||||
|
|position| {
|
||||||
|
use widget::Operation;
|
||||||
|
|
||||||
|
let mut selector = position.find_all();
|
||||||
|
|
||||||
|
self.raw.as_overlay_mut().operate(
|
||||||
|
layout,
|
||||||
|
renderer,
|
||||||
|
&mut operation::black_box(&mut selector),
|
||||||
|
);
|
||||||
|
|
||||||
|
selector.finish()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
self.raw
|
self.raw
|
||||||
.as_overlay_mut()
|
.as_overlay_mut()
|
||||||
.update(event, layout, cursor, renderer, clipboard, shell);
|
.update(event, layout, cursor, renderer, clipboard, shell);
|
||||||
|
|
||||||
if let Some(on_event) = &self.on_record {
|
|
||||||
record(event, cursor, shell, self.bounds, on_event);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mouse_interaction(
|
fn mouse_interaction(
|
||||||
|
|
@ -262,6 +366,7 @@ where
|
||||||
overlay::Element::new(Box::new(Overlay {
|
overlay::Element::new(Box::new(Overlay {
|
||||||
raw,
|
raw,
|
||||||
bounds: self.bounds,
|
bounds: self.bounds,
|
||||||
|
last_hovered: self.last_hovered,
|
||||||
on_record: self.on_record,
|
on_record: self.on_record,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
@ -277,7 +382,9 @@ fn record<Message>(
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
|
last_hovered: &mut Option<Rectangle>,
|
||||||
on_record: impl Fn(Interaction) -> Message,
|
on_record: impl Fn(Interaction) -> Message,
|
||||||
|
find: impl FnOnce(Point) -> operation::Outcome<Vec<target::Match>>,
|
||||||
) {
|
) {
|
||||||
if let Event::Mouse(_) = event
|
if let Event::Mouse(_) = event
|
||||||
&& !cursor.is_over(bounds)
|
&& !cursor.is_over(bounds)
|
||||||
|
|
@ -294,7 +401,43 @@ fn record<Message>(
|
||||||
Interaction::from_event(event)
|
Interaction::from_event(event)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(interaction) = interaction {
|
if let Some(mut interaction) = interaction {
|
||||||
|
if let Interaction::Mouse(
|
||||||
|
Mouse::Move(at)
|
||||||
|
| Mouse::Press { at: Some(at), .. }
|
||||||
|
| Mouse::Release { at: Some(at), .. }
|
||||||
|
| Mouse::Click { at: Some(at), .. },
|
||||||
|
) = &mut interaction
|
||||||
|
{
|
||||||
|
if let Target::Point(position) = *at
|
||||||
|
&& let operation::Outcome::Some(targets) =
|
||||||
|
find(position + (bounds.position() - Point::ORIGIN))
|
||||||
|
&& let Some((content, visible_bounds)) =
|
||||||
|
targets.into_iter().rev().find_map(|target| {
|
||||||
|
if let target::Match::Text {
|
||||||
|
content,
|
||||||
|
visible_bounds,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| target::Match::TextInput {
|
||||||
|
content,
|
||||||
|
visible_bounds,
|
||||||
|
..
|
||||||
|
} = target
|
||||||
|
{
|
||||||
|
Some((content, visible_bounds))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
*at = Target::Text(content);
|
||||||
|
*last_hovered = visible_bounds;
|
||||||
|
} else {
|
||||||
|
*last_hovered = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
shell.publish(on_record(interaction));
|
shell.publish(on_record(interaction));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ mod find;
|
||||||
pub use find::{Find, FindAll};
|
pub use find::{Find, FindAll};
|
||||||
pub use target::Target;
|
pub use target::Target;
|
||||||
|
|
||||||
|
use crate::core::Point;
|
||||||
use crate::core::widget::Id;
|
use crate::core::widget::Id;
|
||||||
|
|
||||||
pub trait Selector {
|
pub trait Selector {
|
||||||
|
|
@ -116,6 +117,21 @@ impl Selector for Id {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Selector for Point {
|
||||||
|
type Output = target::Match;
|
||||||
|
|
||||||
|
fn select(&mut self, target: Target<'_>) -> Option<Self::Output> {
|
||||||
|
target
|
||||||
|
.visible_bounds()
|
||||||
|
.is_some_and(|visible_bounds| visible_bounds.contains(*self))
|
||||||
|
.then(|| target::Match::from_target(target))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> String {
|
||||||
|
format!("bounds contains {:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<F, T> Selector for F
|
impl<F, T> Selector for F
|
||||||
where
|
where
|
||||||
F: FnMut(Target<'_>) -> Option<T>,
|
F: FnMut(Target<'_>) -> Option<T>,
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,17 @@ impl<'a> Target<'a> {
|
||||||
| Target::Custom { bounds, .. } => *bounds,
|
| Target::Custom { bounds, .. } => *bounds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn visible_bounds(&self) -> Option<Rectangle> {
|
||||||
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
|
@ -93,6 +104,7 @@ pub enum Match {
|
||||||
id: Option<Id>,
|
id: Option<Id>,
|
||||||
bounds: Rectangle,
|
bounds: Rectangle,
|
||||||
visible_bounds: Option<Rectangle>,
|
visible_bounds: Option<Rectangle>,
|
||||||
|
content: String,
|
||||||
},
|
},
|
||||||
Text {
|
Text {
|
||||||
id: Option<Id>,
|
id: Option<Id>,
|
||||||
|
|
@ -147,11 +159,12 @@ impl Match {
|
||||||
id,
|
id,
|
||||||
bounds,
|
bounds,
|
||||||
visible_bounds,
|
visible_bounds,
|
||||||
..
|
state,
|
||||||
} => Self::TextInput {
|
} => Self::TextInput {
|
||||||
id: id.cloned(),
|
id: id.cloned(),
|
||||||
bounds,
|
bounds,
|
||||||
visible_bounds,
|
visible_bounds,
|
||||||
|
content: state.text().to_owned(),
|
||||||
},
|
},
|
||||||
Target::Text {
|
Target::Text {
|
||||||
id,
|
id,
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,19 @@ impl Interaction {
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
(
|
||||||
|
Mouse::Click {
|
||||||
|
button,
|
||||||
|
at: Some(click_at),
|
||||||
|
},
|
||||||
|
Mouse::Move(move_at),
|
||||||
|
) if click_at == move_at => (
|
||||||
|
Self::Mouse(Mouse::Click {
|
||||||
|
button,
|
||||||
|
at: Some(click_at),
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
),
|
||||||
(current, next) => {
|
(current, next) => {
|
||||||
(Self::Mouse(current), Some(Self::Mouse(next)))
|
(Self::Mouse(current), Some(Self::Mouse(next)))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue