File indexing completed on 2024-12-22 04:41:39

0001 /*
0002  * SPDX-FileCopyrightText: 2018 Chansol Yang <CosmicSubspace@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 package org.kde.kdeconnect.Plugins.MousePadPlugin;
0008 
0009 
0010 public class PointerAccelerationProfileFactory {
0011 
0012     /* The simplest profile. Merely adds the mouse deltas without any processing. */
0013     private static class DefaultProfile extends PointerAccelerationProfile {
0014         float accumulatedX = 0.0f;
0015         float accumulatedY = 0.0f;
0016 
0017         @Override
0018         public void touchMoved(float deltaX, float deltaY, long eventTime) {
0019             accumulatedX += deltaX;
0020             accumulatedY += deltaY;
0021         }
0022 
0023         @Override
0024         public MouseDelta commitAcceleratedMouseDelta(MouseDelta reusedObject) {
0025             MouseDelta result;
0026             if (reusedObject == null) result = new MouseDelta();
0027             else result = reusedObject;
0028 
0029             result.x = accumulatedX;
0030             result.y = accumulatedY;
0031             accumulatedY = 0;
0032             accumulatedX = 0;
0033             return result;
0034         }
0035     }
0036 
0037 
0038     /* Base class for acceleration profiles that takes touch movement speed into account.
0039      * To calculate the speed, a history of 32 touch events are stored
0040      * and then later used to calculate the speed. */
0041     private static abstract class SpeedBasedAccelerationProfile extends PointerAccelerationProfile {
0042         float accumulatedX = 0.0f;
0043         float accumulatedY = 0.0f;
0044 
0045         // Higher values will reduce the amount of noise in the speed calculation
0046         // but will also increase latency until the acceleration kicks in.
0047         // 150ms seemed like a nice middle ground.
0048         final long freshThreshold = 150;
0049 
0050         private static class TouchDeltaEvent {
0051             final float x;
0052             final float y;
0053             final long time;
0054             TouchDeltaEvent(float x, float y, long t) {
0055                 this.x = x;
0056                 this.y = y;
0057                 this.time = t;
0058             }
0059         }
0060 
0061         private final TouchDeltaEvent[] touchEventHistory = new TouchDeltaEvent[32];
0062 
0063         /* add an event to the touchEventHistory array, shifting everything else in the array. */
0064         private void addHistory(float deltaX, float deltaY, long eventTime) {
0065             System.arraycopy(touchEventHistory, 0, touchEventHistory, 1, touchEventHistory.length - 1);
0066             touchEventHistory[0] = new TouchDeltaEvent(deltaX, deltaY, eventTime);
0067         }
0068 
0069         @Override
0070         public void touchMoved(float deltaX, float deltaY, long eventTime) {
0071 
0072             // To calculate the touch movement speed,
0073             // we iterate through the touchEventHistory array,
0074             // adding up the distance moved for each event.
0075             // Breaks if a touchEventHistory entry is too "stale".
0076             float distanceMoved = 0.0f;
0077             long deltaT = 0;
0078             for (TouchDeltaEvent aTouchEventHistory : touchEventHistory) {
0079                 if (aTouchEventHistory == null) break;
0080                 if (eventTime - aTouchEventHistory.time > freshThreshold) break;
0081 
0082                 distanceMoved += (float) Math.sqrt(
0083                         aTouchEventHistory.x * aTouchEventHistory.x
0084                                 + aTouchEventHistory.y * aTouchEventHistory.y);
0085                 deltaT = eventTime - aTouchEventHistory.time;
0086             }
0087 
0088 
0089             float multiplier;
0090 
0091             if (deltaT == 0) {
0092                 // Edge case when there are no historical touch data to calculate speed from.
0093                 multiplier = 0;
0094             } else {
0095                 float speed = distanceMoved / (deltaT / 1000.0f); // units: px/sec
0096 
0097                 multiplier = calculateMultiplier(speed);
0098             }
0099 
0100             if (multiplier < 0.01f) multiplier = 0.01f;
0101 
0102             accumulatedX += deltaX * multiplier;
0103             accumulatedY += deltaY * multiplier;
0104 
0105             addHistory(deltaX, deltaY, eventTime);
0106         }
0107 
0108         /* Should be overridden by the child class.
0109          * Given the current touch movement speed, this method should return a multiplier
0110          * for the touch delta. ( mouse_delta = touch_delta * multiplier ) */
0111         abstract float calculateMultiplier(float speed);
0112 
0113         @Override
0114         public MouseDelta commitAcceleratedMouseDelta(MouseDelta reusedObject) {
0115             MouseDelta result;
0116             if (reusedObject == null) result = new MouseDelta();
0117             else result = reusedObject;
0118 
0119             /* This makes sure that only the integer components of the deltas are sent,
0120              * since the coordinates are converted to integers in the desktop client anyway.
0121              * The leftover fractional part is stored and added later; this makes
0122              * the cursor move much smoother in slow speeds. */
0123             result.x = (int) accumulatedX;
0124             result.y = (int) accumulatedY;
0125             accumulatedY = accumulatedY % 1.0f;
0126             accumulatedX = accumulatedX % 1.0f;
0127             return result;
0128         }
0129     }
0130 
0131     /* Pointer acceleration with mouse_delta = touch_delta * ( touch_speed ^ exponent )
0132      * It is similar to x.org server's Polynomial pointer acceleration profile. */
0133     private static class PolynomialProfile extends SpeedBasedAccelerationProfile {
0134         final float exponent;
0135 
0136         PolynomialProfile(float exponent) {
0137             this.exponent = exponent;
0138         }
0139 
0140         @Override
0141         float calculateMultiplier(float speed) {
0142             // The value 600 was chosen arbitrarily.
0143             return (float) (Math.pow(speed / 600, exponent));
0144         }
0145     }
0146 
0147 
0148     /* Mimics the behavior of xorg's Power profile.
0149      * Currently not visible to the user since it is rather hard to control. */
0150     private static class PowerProfile extends SpeedBasedAccelerationProfile {
0151         @Override
0152         float calculateMultiplier(float speed) {
0153             return (float) (Math.pow(2, speed / 1000)) - 1;
0154         }
0155     }
0156 
0157 
0158     public static PointerAccelerationProfile getProfileWithName(String name) {
0159         switch (name) {
0160             case "noacceleration":
0161             default:
0162                 return new DefaultProfile();
0163             case "weaker":
0164                 return new PolynomialProfile(0.25f);
0165             case "weak":
0166                 return new PolynomialProfile(0.5f);
0167             case "medium":
0168                 return new PolynomialProfile(1.0f);
0169             case "strong":
0170                 return new PolynomialProfile(1.5f);
0171             case "stronger":
0172                 return new PolynomialProfile(2.0f);
0173         }
0174     }
0175 }