use std::time::Duration;
use serde_json::json;
use tokio::time::sleep;
use crate::Result;
use crate::browser::element::Element;
use crate::browser::tab::Tab;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MouseButton {
Left,
Middle,
Right,
}
impl MouseButton {
fn id(self) -> i64 {
match self {
MouseButton::Left => 0,
MouseButton::Middle => 1,
MouseButton::Right => 2,
}
}
fn bit(self) -> i64 {
match self {
MouseButton::Left => 1,
MouseButton::Right => 2,
MouseButton::Middle => 4,
}
}
}
enum Act {
MoveAbs(f64, f64, f64), MoveEle(Box<Element>, f64, f64, f64), MoveBy(f64, f64, f64), Down(MouseButton),
Up(MouseButton),
Click(MouseButton, u32), Scroll(f64, f64), KeyDown(String),
KeyUp(String),
Type(String),
Wait(f64),
}
pub struct Actions {
tab: Tab,
steps: Vec<Act>,
default_move_secs: f64,
}
impl Actions {
pub(crate) fn new(tab: Tab) -> Self {
Self {
tab,
steps: Vec::new(),
default_move_secs: 0.4,
}
}
pub fn move_to_ele(self, ele: &Element) -> Self {
let d = self.default_move_secs;
self.move_to_ele_offset(ele, 0.0, 0.0, d)
}
pub fn move_to_ele_offset(mut self, ele: &Element, ox: f64, oy: f64, duration: f64) -> Self {
self.steps
.push(Act::MoveEle(Box::new(ele.clone()), ox, oy, duration));
self
}
pub fn move_to(mut self, x: f64, y: f64, duration: f64) -> Self {
self.steps.push(Act::MoveAbs(x, y, duration));
self
}
pub fn move_by(mut self, dx: f64, dy: f64, duration: f64) -> Self {
self.steps.push(Act::MoveBy(dx, dy, duration));
self
}
pub fn up(self, pixel: f64) -> Self {
let d = self.default_move_secs;
self.move_by(0.0, -pixel, d)
}
pub fn down(self, pixel: f64) -> Self {
let d = self.default_move_secs;
self.move_by(0.0, pixel, d)
}
pub fn left(self, pixel: f64) -> Self {
let d = self.default_move_secs;
self.move_by(-pixel, 0.0, d)
}
pub fn right(self, pixel: f64) -> Self {
let d = self.default_move_secs;
self.move_by(pixel, 0.0, d)
}
pub fn hold(mut self) -> Self {
self.steps.push(Act::Down(MouseButton::Left));
self
}
pub fn hold_on(self, ele: &Element) -> Self {
self.move_to_ele(ele).hold()
}
pub fn release(mut self) -> Self {
self.steps.push(Act::Up(MouseButton::Left));
self
}
pub fn release_on(self, ele: &Element) -> Self {
self.move_to_ele(ele).release()
}
pub fn click(mut self) -> Self {
self.steps.push(Act::Click(MouseButton::Left, 1));
self
}
pub fn double_click(mut self) -> Self {
self.steps.push(Act::Click(MouseButton::Left, 2));
self
}
pub fn right_click(mut self) -> Self {
self.steps.push(Act::Click(MouseButton::Right, 1));
self
}
pub fn middle_click(mut self) -> Self {
self.steps.push(Act::Click(MouseButton::Middle, 1));
self
}
pub fn mouse_down(mut self, button: MouseButton) -> Self {
self.steps.push(Act::Down(button));
self
}
pub fn mouse_up(mut self, button: MouseButton) -> Self {
self.steps.push(Act::Up(button));
self
}
pub fn scroll(mut self, dx: f64, dy: f64) -> Self {
self.steps.push(Act::Scroll(dx, dy));
self
}
pub fn key_down(mut self, key: &str) -> Self {
self.steps.push(Act::KeyDown(key.to_string()));
self
}
pub fn key_up(mut self, key: &str) -> Self {
self.steps.push(Act::KeyUp(key.to_string()));
self
}
pub fn type_text(mut self, text: &str) -> Self {
self.steps.push(Act::Type(text.to_string()));
self
}
pub fn wait(mut self, secs: f64) -> Self {
self.steps.push(Act::Wait(secs));
self
}
pub async fn perform(self) -> Result<()> {
let core = &self.tab.core;
let mut cur = (0.0_f64, 0.0_f64);
let mut held = 0i64;
for act in self.steps {
match act {
Act::MoveAbs(x, y, d) => {
glide(&self.tab, cur, (x, y), d, held).await?;
cur = (x, y);
}
Act::MoveEle(ele, ox, oy, d) => {
ele.scroll_into_view().await?;
let (cx, cy) = ele.center_point().await?;
let to = (cx + ox, cy + oy);
glide(&self.tab, cur, to, d, held).await?;
cur = to;
}
Act::MoveBy(dx, dy, d) => {
let to = (cur.0 + dx, cur.1 + dy);
glide(&self.tab, cur, to, d, held).await?;
cur = to;
}
Act::Down(b) => {
held |= b.bit();
core.dispatch_mouse_ex("mousedown", cur.0, cur.1, b.id(), held, 1)
.await?;
sleep(Duration::from_millis(60)).await;
}
Act::Up(b) => {
held &= !b.bit();
core.dispatch_mouse_ex("mouseup", cur.0, cur.1, b.id(), held, 1)
.await?;
sleep(Duration::from_millis(30)).await;
}
Act::Click(b, count) => {
core.dispatch_mouse("mousemove", cur.0, cur.1, held).await?;
for n in 1..=count {
let down_buttons = held | b.bit();
core.dispatch_mouse_ex(
"mousedown",
cur.0,
cur.1,
b.id(),
down_buttons,
n as i64,
)
.await?;
sleep(Duration::from_millis(40)).await;
core.dispatch_mouse_ex("mouseup", cur.0, cur.1, b.id(), held, n as i64)
.await?;
sleep(Duration::from_millis(40)).await;
}
}
Act::Scroll(dx, dy) => {
core.send_page(
"Page.dispatchWheelEvent",
json!({ "x": cur.0.floor(), "y": cur.1.floor(),
"deltaX": dx, "deltaY": dy, "deltaZ": 0, "modifiers": 0 }),
)
.await?;
}
Act::KeyDown(k) => {
core.send_page(
"Page.dispatchKeyEvent",
json!({ "type": "keydown", "key": k }),
)
.await?;
}
Act::KeyUp(k) => {
core.send_page(
"Page.dispatchKeyEvent",
json!({ "type": "keyup", "key": k }),
)
.await?;
}
Act::Type(t) => {
core.send_page("Page.insertText", json!({ "text": t }))
.await?;
}
Act::Wait(s) => {
sleep(Duration::from_secs_f64(s.max(0.0))).await;
}
}
}
Ok(())
}
}
async fn glide(
tab: &Tab,
from: (f64, f64),
to: (f64, f64),
duration: f64,
held: i64,
) -> Result<()> {
let dx = to.0 - from.0;
let dy = to.1 - from.1;
let dist = (dx * dx + dy * dy).sqrt();
let steps = ((dist / 8.0).round() as usize).clamp(6, 50);
let per = if duration > 0.0 {
((duration * 1000.0) / steps as f64) as u64
} else {
4
};
for i in 1..=steps {
let t = i as f64 / steps as f64;
let eased = if t < 0.5 {
4.0 * t * t * t
} else {
1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
};
let jx = (i as f64 * 1.3).sin() * 0.8;
let jy = (i as f64 * 1.7).cos() * 0.8;
let x = from.0 + dx * eased + jx;
let y = from.1 + dy * eased + jy;
tab.core.dispatch_mouse("mousemove", x, y, held).await?;
if per > 0 {
sleep(Duration::from_millis(per)).await;
}
}
tab.core
.dispatch_mouse("mousemove", to.0, to.1, held)
.await?;
Ok(())
}