mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 01:51:47 +00:00
176 lines
6.4 KiB
Java
176 lines
6.4 KiB
Java
/*
|
|
* SPDX-FileCopyrightText: 2018 Chansol Yang <CosmicSubspace@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
*/
|
|
|
|
package org.kde.kdeconnect.Plugins.MousePadPlugin;
|
|
|
|
|
|
public class PointerAccelerationProfileFactory {
|
|
|
|
/* The simplest profile. Merely adds the mouse deltas without any processing. */
|
|
private static class DefaultProfile extends PointerAccelerationProfile {
|
|
float accumulatedX = 0.0f;
|
|
float accumulatedY = 0.0f;
|
|
|
|
@Override
|
|
public void touchMoved(float deltaX, float deltaY, long eventTime) {
|
|
accumulatedX += deltaX;
|
|
accumulatedY += deltaY;
|
|
}
|
|
|
|
@Override
|
|
public MouseDelta commitAcceleratedMouseDelta(MouseDelta reusedObject) {
|
|
MouseDelta result;
|
|
if (reusedObject == null) result = new MouseDelta();
|
|
else result = reusedObject;
|
|
|
|
result.x = accumulatedX;
|
|
result.y = accumulatedY;
|
|
accumulatedY = 0;
|
|
accumulatedX = 0;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
|
|
/* Base class for acceleration profiles that takes touch movement speed into account.
|
|
* To calculate the speed, a history of 32 touch events are stored
|
|
* and then later used to calculate the speed. */
|
|
private static abstract class SpeedBasedAccelerationProfile extends PointerAccelerationProfile {
|
|
float accumulatedX = 0.0f;
|
|
float accumulatedY = 0.0f;
|
|
|
|
// Higher values will reduce the amount of noise in the speed calculation
|
|
// but will also increase latency until the acceleration kicks in.
|
|
// 150ms seemed like a nice middle ground.
|
|
final long freshThreshold = 150;
|
|
|
|
private static class TouchDeltaEvent {
|
|
final float x;
|
|
final float y;
|
|
final long time;
|
|
TouchDeltaEvent(float x, float y, long t) {
|
|
this.x = x;
|
|
this.y = y;
|
|
this.time = t;
|
|
}
|
|
}
|
|
|
|
private final TouchDeltaEvent[] touchEventHistory = new TouchDeltaEvent[32];
|
|
|
|
/* add an event to the touchEventHistory array, shifting everything else in the array. */
|
|
private void addHistory(float deltaX, float deltaY, long eventTime) {
|
|
System.arraycopy(touchEventHistory, 0, touchEventHistory, 1, touchEventHistory.length - 1);
|
|
touchEventHistory[0] = new TouchDeltaEvent(deltaX, deltaY, eventTime);
|
|
}
|
|
|
|
@Override
|
|
public void touchMoved(float deltaX, float deltaY, long eventTime) {
|
|
|
|
// To calculate the touch movement speed,
|
|
// we iterate through the touchEventHistory array,
|
|
// adding up the distance moved for each event.
|
|
// Breaks if a touchEventHistory entry is too "stale".
|
|
float distanceMoved = 0.0f;
|
|
long deltaT = 0;
|
|
for (TouchDeltaEvent aTouchEventHistory : touchEventHistory) {
|
|
if (aTouchEventHistory == null) break;
|
|
if (eventTime - aTouchEventHistory.time > freshThreshold) break;
|
|
|
|
distanceMoved += (float) Math.sqrt(
|
|
aTouchEventHistory.x * aTouchEventHistory.x
|
|
+ aTouchEventHistory.y * aTouchEventHistory.y);
|
|
deltaT = eventTime - aTouchEventHistory.time;
|
|
}
|
|
|
|
|
|
float multiplier;
|
|
|
|
if (deltaT == 0) {
|
|
// Edge case when there are no historical touch data to calculate speed from.
|
|
multiplier = 0;
|
|
} else {
|
|
float speed = distanceMoved / (deltaT / 1000.0f); // units: px/sec
|
|
|
|
multiplier = calculateMultiplier(speed);
|
|
}
|
|
|
|
if (multiplier < 0.01f) multiplier = 0.01f;
|
|
|
|
accumulatedX += deltaX * multiplier;
|
|
accumulatedY += deltaY * multiplier;
|
|
|
|
addHistory(deltaX, deltaY, eventTime);
|
|
}
|
|
|
|
/* Should be overridden by the child class.
|
|
* Given the current touch movement speed, this method should return a multiplier
|
|
* for the touch delta. ( mouse_delta = touch_delta * multiplier ) */
|
|
abstract float calculateMultiplier(float speed);
|
|
|
|
@Override
|
|
public MouseDelta commitAcceleratedMouseDelta(MouseDelta reusedObject) {
|
|
MouseDelta result;
|
|
if (reusedObject == null) result = new MouseDelta();
|
|
else result = reusedObject;
|
|
|
|
/* This makes sure that only the integer components of the deltas are sent,
|
|
* since the coordinates are converted to integers in the desktop client anyway.
|
|
* The leftover fractional part is stored and added later; this makes
|
|
* the cursor move much smoother in slow speeds. */
|
|
result.x = (int) accumulatedX;
|
|
result.y = (int) accumulatedY;
|
|
accumulatedY = accumulatedY % 1.0f;
|
|
accumulatedX = accumulatedX % 1.0f;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/* Pointer acceleration with mouse_delta = touch_delta * ( touch_speed ^ exponent )
|
|
* It is similar to x.org server's Polynomial pointer acceleration profile. */
|
|
private static class PolynomialProfile extends SpeedBasedAccelerationProfile {
|
|
final float exponent;
|
|
|
|
PolynomialProfile(float exponent) {
|
|
this.exponent = exponent;
|
|
}
|
|
|
|
@Override
|
|
float calculateMultiplier(float speed) {
|
|
// The value 600 was chosen arbitrarily.
|
|
return (float) (Math.pow(speed / 600, exponent));
|
|
}
|
|
}
|
|
|
|
|
|
/* Mimics the behavior of xorg's Power profile.
|
|
* Currently not visible to the user since it is rather hard to control. */
|
|
private static class PowerProfile extends SpeedBasedAccelerationProfile {
|
|
@Override
|
|
float calculateMultiplier(float speed) {
|
|
return (float) (Math.pow(2, speed / 1000)) - 1;
|
|
}
|
|
}
|
|
|
|
|
|
public static PointerAccelerationProfile getProfileWithName(String name) {
|
|
switch (name) {
|
|
case "weaker":
|
|
return new PolynomialProfile(0.25f);
|
|
case "weak":
|
|
return new PolynomialProfile(0.5f);
|
|
case "medium":
|
|
return new PolynomialProfile(1.0f);
|
|
case "strong":
|
|
return new PolynomialProfile(1.5f);
|
|
case "stronger":
|
|
return new PolynomialProfile(2.0f);
|
|
case "noacceleration":
|
|
default:
|
|
return new DefaultProfile();
|
|
}
|
|
}
|
|
}
|