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 }