mouse-rs icon indicating copy to clipboard operation
mouse-rs copied to clipboard

Animation?

Open alloc33 opened this issue 2 years ago • 3 comments

I know that this crate doesn't have any animation logic, however I'd like to find a way to use this lib (move mouse cursor) with animation. Is there any crates to do this?

alloc33 avatar Sep 16 '22 15:09 alloc33

Not the author, but just chiming in with my two cents.

If you simply want to linearly move the mouse (e.g., with constant speed and no acceleration) given some time t, you can just use a standard lerp (common function name for linear interpolation) over a loop. The loop would run while an elapsed value keeps track of the target time (say, 1 second), and you run each loop based on how "smooth" you want the animation (say, 60 frames per second). The loop time would then be: t (as milliseconds) / fps, which is about 16.67ms.

Your lerp might look something like:

struct PointF64 {
    x: f64,
    y: f64
}

impl PointF64 {
    pub fn lerp(p1: Self, p2: Self, t: f64) -> Self {
        let t_clamp = t.clamp(0., 1.);

        Self {
            (p1.x + (p2.x - p1.x) * t_clamp),
            (p1.y + (p2.y - p1.y) * t_clamp),
        }
}

This will give you the PointF64 to move the mouse to, given a t in the range [0, 1]. If you use the above, you will need to make sure you round off the x and y values and convert them to i32 to use with Mouse::move_to from this library.

Then the general implementation would be (you need a given interval_time in milliseconds and end point ep):

  • Calculate the loop time (e.g., loop_time = 1000ms / 60 ~= 16.67ms)
  • Get the mouse start point (sp)
  • While elapsed_time < interval_time:
    • Keep track of the loop start time (start_time)
    • Get the current mouse position (current_pos)
    • Normalize/map the elapsed_time in the range [0, 1] (t = elapsed_time / interval_time)
    • Interpolate the next position (next_pos = PointF64::lerp(sp, ep, t))
    • Check if the current_pos != next_pos (otherwise you can end up firing off a ton of events) -- if they are not equal, move the mouse to next_pos
    • Sleep for remainder of loop time (sleep(loop_time - start_time.elapsed()))

Note on sleeping for the last step: I found the the standard library thread::sleep function was not accurate enough for what I was trying to do on Windows. The crate spin_sleep solves that problem cross-platform which is really nice. Basically just a drop in replacement for thread::sleep.

For reference, you can check out my implementation (just from a toy project, definitely take the details with a grain of salt). This one eases the movement in and out from point to point: https://github.com/bwpge/mouse-jiggler/blob/8609a1914dd25ec10b3df5ddf4716e0ec134c853/src/mouse.rs#L95-L134

If you want to get more clever than this, such as rounding off the movements to look like the mouse is waving around, you might want to look into Bezier Curves (think of the "pen" tool in Photoshop or GIMP where you can drag around anchors to make curves). I also recommend this article which has a clever way on implementing continuous or composite bezier curves (this eliminates the jagged edges when combining curves).

bwpge avatar Jan 26 '23 21:01 bwpge

@AltF02 I can submit a PR for a (simple linear) animate function if you'd like, I'm just not sure if that functionality is within the scope for this crate. I would probably gate it behind a feature animate or something since I believe it would need at least spin_sleep, as well as possibly chrono or time.

bwpge avatar Jan 26 '23 21:01 bwpge

I'm not sure, I personally planned to create an alternative https://crates.io/crates/enigo with mouse-rs under the hood at some point. But unfortunately life got in the way. I think a hypothetical library like that would be a great fit for something like this.

I personally think its out of scope, but I am more than happy to provide a note to a possible external library that implements this feature.

AltF02 avatar Feb 03 '23 17:02 AltF02