use std::sync::Arc;
use std::time::Duration;
use crate::element::{Element, Rect};
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Point {
pub x: i32,
pub y: i32,
}
impl Point {
pub const fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Anchor {
#[default]
Center,
TopLeft,
TopRight,
BottomLeft,
BottomRight,
Offset {
dx: i32,
dy: i32,
},
}
pub fn anchor_point(rect: &Rect, anchor: Anchor) -> Point {
let (x, y, w, h) = (rect.x, rect.y, rect.width as i32, rect.height as i32);
match anchor {
Anchor::Center => Point::new(x + w / 2, y + h / 2),
Anchor::TopLeft => Point::new(x, y),
Anchor::TopRight => Point::new(x + w, y),
Anchor::BottomLeft => Point::new(x, y + h),
Anchor::BottomRight => Point::new(x + w, y + h),
Anchor::Offset { dx, dy } => Point::new(x + dx, y + dy),
}
}
pub fn point_for(element: &Element, anchor: Anchor) -> Result<Point> {
let bounds = element.bounds.ok_or(Error::NoElementBounds)?;
Ok(anchor_point(&bounds, anchor))
}
pub trait IntoPoint {
fn into_point(self) -> Result<Point>;
}
impl IntoPoint for Point {
fn into_point(self) -> Result<Point> {
Ok(self)
}
}
impl IntoPoint for (i32, i32) {
fn into_point(self) -> Result<Point> {
Ok(Point::new(self.0, self.1))
}
}
impl IntoPoint for &Element {
fn into_point(self) -> Result<Point> {
point_for(self, Anchor::Center)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum MouseButton {
#[default]
Left,
Right,
Middle,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct ScrollDelta {
pub dx: i32,
pub dy: i32,
}
impl ScrollDelta {
pub const fn new(dx: i32, dy: i32) -> Self {
Self { dx, dy }
}
pub const fn vertical(dy: i32) -> Self {
Self { dx: 0, dy }
}
pub const fn horizontal(dx: i32) -> Self {
Self { dx, dy: 0 }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Key {
Char(char),
Shift,
Ctrl,
Alt,
Meta,
Enter,
Escape,
Backspace,
Tab,
Space,
Delete,
Insert,
ArrowUp,
ArrowDown,
ArrowLeft,
ArrowRight,
Home,
End,
PageUp,
PageDown,
F(u8),
}
impl Key {
pub(crate) fn validate(&self) -> Result<()> {
if let Key::Char(c) = self {
if c.is_ascii_uppercase() {
return Err(Error::InvalidActionData {
message: format!(
"Key::Char('{c}') is uppercase; use Key::Char('{}') \
with Key::Shift held to produce an uppercase letter",
c.to_ascii_lowercase()
),
});
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ClickOptions {
pub button: MouseButton,
pub count: u32,
pub held: Vec<Key>,
pub anchor: Anchor,
}
impl Default for ClickOptions {
fn default() -> Self {
Self {
button: MouseButton::Left,
count: 1,
held: Vec::new(),
anchor: Anchor::Center,
}
}
}
#[derive(Debug, Clone)]
pub struct DragOptions {
pub button: MouseButton,
pub held: Vec<Key>,
pub duration: Duration,
}
impl Default for DragOptions {
fn default() -> Self {
Self {
button: MouseButton::Left,
held: Vec::new(),
duration: Duration::from_millis(150),
}
}
}
pub trait InputProvider: Send + Sync {
fn pointer_move(&self, to: Point) -> Result<()>;
fn pointer_down(&self, button: MouseButton) -> Result<()>;
fn pointer_up(&self, button: MouseButton) -> Result<()>;
fn pointer_click(&self, at: Point, button: MouseButton, count: u32) -> Result<()>;
fn pointer_scroll(&self, at: Point, delta: ScrollDelta) -> Result<()>;
fn key_down(&self, key: &Key) -> Result<()>;
fn key_up(&self, key: &Key) -> Result<()>;
fn type_text(&self, text: &str) -> Result<()>;
fn pointer_drag(
&self,
from: Point,
to: Point,
button: MouseButton,
duration: Duration,
) -> Result<()> {
const STEP: Duration = Duration::from_millis(16);
self.pointer_move(from)?;
self.pointer_down(button)?;
let steps = (duration.as_millis() / STEP.as_millis().max(1)).max(1) as i32;
for i in 1..=steps {
let t = i as f64 / steps as f64;
let x = from.x + ((to.x - from.x) as f64 * t).round() as i32;
let y = from.y + ((to.y - from.y) as f64 * t).round() as i32;
self.pointer_move(Point::new(x, y))?;
if i < steps {
std::thread::sleep(STEP);
}
}
self.pointer_up(button)
}
}
#[derive(Clone)]
pub struct InputSim {
backend: Arc<dyn InputProvider>,
}
impl InputSim {
pub fn new(backend: Arc<dyn InputProvider>) -> Self {
Self { backend }
}
pub fn backend(&self) -> &Arc<dyn InputProvider> {
&self.backend
}
pub fn mouse(&self) -> Mouse<'_> {
Mouse {
backend: &self.backend,
}
}
pub fn keyboard(&self) -> Keyboard<'_> {
Keyboard {
backend: &self.backend,
}
}
pub fn point_for(&self, element: &Element, anchor: Anchor) -> Result<Point> {
point_for(element, anchor)
}
}
pub struct Mouse<'a> {
backend: &'a Arc<dyn InputProvider>,
}
impl Mouse<'_> {
pub fn click(&self, target: impl IntoPoint) -> Result<()> {
let pt = target.into_point()?;
self.backend.pointer_click(pt, MouseButton::Left, 1)
}
pub fn click_with(&self, target: ClickTarget<'_>, opts: ClickOptions) -> Result<()> {
for k in &opts.held {
k.validate()?;
}
let pt = match target {
ClickTarget::Point(p) => p,
ClickTarget::Element(el) => point_for(el, opts.anchor)?,
};
with_keys_held(self.backend.as_ref(), &opts.held, || {
self.backend.pointer_click(pt, opts.button, opts.count)
})
}
pub fn double_click(&self, target: impl IntoPoint) -> Result<()> {
let pt = target.into_point()?;
self.backend.pointer_click(pt, MouseButton::Left, 2)
}
pub fn right_click(&self, target: impl IntoPoint) -> Result<()> {
let pt = target.into_point()?;
self.backend.pointer_click(pt, MouseButton::Right, 1)
}
pub fn down(&self, button: MouseButton) -> Result<()> {
self.backend.pointer_down(button)
}
pub fn up(&self, button: MouseButton) -> Result<()> {
self.backend.pointer_up(button)
}
pub fn move_to(&self, target: impl IntoPoint) -> Result<()> {
let pt = target.into_point()?;
self.backend.pointer_move(pt)
}
pub fn drag(&self, from: impl IntoPoint, to: impl IntoPoint) -> Result<()> {
let from = from.into_point()?;
let to = to.into_point()?;
self.backend
.pointer_drag(from, to, MouseButton::Left, Duration::from_millis(150))
}
pub fn drag_with(
&self,
from: impl IntoPoint,
to: impl IntoPoint,
opts: DragOptions,
) -> Result<()> {
for k in &opts.held {
k.validate()?;
}
let from = from.into_point()?;
let to = to.into_point()?;
with_keys_held(self.backend.as_ref(), &opts.held, || {
self.backend
.pointer_drag(from, to, opts.button, opts.duration)
})
}
pub fn scroll(&self, target: impl IntoPoint, delta: ScrollDelta) -> Result<()> {
let pt = target.into_point()?;
self.backend.pointer_scroll(pt, delta)
}
}
pub struct Keyboard<'a> {
backend: &'a Arc<dyn InputProvider>,
}
impl Keyboard<'_> {
pub fn press(&self, key: Key) -> Result<()> {
key.validate()?;
self.backend.key_down(&key)?;
self.backend.key_up(&key)
}
pub fn chord(&self, key: Key, held: &[Key]) -> Result<()> {
key.validate()?;
for k in held {
k.validate()?;
}
with_keys_held(self.backend.as_ref(), held, || {
self.backend.key_down(&key)?;
self.backend.key_up(&key)
})
}
pub fn down(&self, key: Key) -> Result<()> {
key.validate()?;
self.backend.key_down(&key)
}
pub fn up(&self, key: Key) -> Result<()> {
key.validate()?;
self.backend.key_up(&key)
}
pub fn type_text(&self, text: &str) -> Result<()> {
self.backend.type_text(text)
}
}
pub enum ClickTarget<'a> {
Point(Point),
Element(&'a Element),
}
impl From<Point> for ClickTarget<'_> {
fn from(p: Point) -> Self {
Self::Point(p)
}
}
impl From<(i32, i32)> for ClickTarget<'_> {
fn from(t: (i32, i32)) -> Self {
Self::Point(Point::new(t.0, t.1))
}
}
impl<'a> From<&'a Element> for ClickTarget<'a> {
fn from(el: &'a Element) -> Self {
Self::Element(el)
}
}
fn with_keys_held<F>(backend: &dyn InputProvider, keys: &[Key], body: F) -> Result<()>
where
F: FnOnce() -> Result<()>,
{
for k in keys {
backend.key_down(k)?;
}
let result = body();
let mut release_err: Option<Error> = None;
for k in keys.iter().rev() {
if let Err(e) = backend.key_up(k) {
if release_err.is_none() {
release_err = Some(e);
}
}
}
match (result, release_err) {
(Err(e), _) => Err(e),
(Ok(()), Some(e)) => Err(e),
(Ok(()), None) => Ok(()),
}
}