#include #include #include #define max(a, b) ((a > b) ? a : b) #define min(a, b) ((a < b) ? a : b) struct device_coords { int x; int y; }; /* from comment 12 in https://bugs.freedesktop.org/show_bug.cgi?id=105108#c12 */ static inline struct device_coords evdev_hysteresis_105108(const struct device_coords point, const struct device_coords center, const struct device_coords margin) { struct device_coords diff; struct device_coords p; int offset = 0; float ratio; diff.x = point.x - center.x; /* -3 */ diff.y = point.y - center.y; /* -9 */ /* do we need to test for a real circle here? */ if (abs(diff.x) <= margin.x && abs(diff.y) <= margin.y) return center; /* If we're outside the margin, we always move back by * the margin, never any other value. So we can use a few tricks to * make this easier to calculate because there are a finite number * of vectors we use to move back (2*margin). And the margin is * usually small, so there's rounding anyway to meet the integer * coordinates. Thus the algorithm is: * * For a movement vector (x, y), calculate the ratio y:x. * Multiply that ratio by the margin, the vector * (-margin, -int(margin * ratio)) is now the vector to subtract * from (x, y). Add the right x/y swapping and the infinity * exceptions and we're good. * * Example for margin 8: * (20, 1) -> ratio 0.05 -> vec (-8, 0) -> final position (12, 1) * (20, 10) -> ratio 0.5 -> vec (-8, -4) -> final position (12, 6) * (20, 20) -> ratio 1.0 -> vec (-8, -8) -> final position (12, 12) * * And if y > x we swap things around: * (10, 20) -> ratio 0.5 -> vec (-4, -8) -> final position (6, 12) * ... */ p = point; if (diff.x == 0 || diff.y == 0) goto out; else ratio = 1.0 * max(abs(diff.x), abs(diff.y))/ min(abs(diff.x), abs(diff.y)); offset = (int)(margin.x/ratio); out: if (abs(diff.x) >= abs(diff.y)) { p.x -= margin.x * ((diff.x > 0) ? 1 : -1); p.y -= offset * ((diff.y > 0) ? 1 : -1); } else { p.x -= offset * ((diff.x > 0) ? 1 : -1); p.y -= margin.x * ((diff.y > 0) ? 1 : -1); } return p; } /* From attachment in bug https://bugs.freedesktop.org/show_bug.cgi?id=105306 */ static inline struct device_coords evdev_hysteresis_105306(const struct device_coords *in, const struct device_coords *center, const struct device_coords *margin) { int dx = in->x - center->x; int dy = in->y - center->y; int dx2 = dx * dx; int dy2 = dy * dy; int a = margin->x; int b = margin->y; double normalized_finger_distance, finger_distance, margin_distance; double lag_x, lag_y; struct device_coords result; if (!a || !b) return *in; /* * Basic equation for an ellipse of radii a,b: * x²/a² + y²/b² = 1 * But we start by making a scaled ellipse passing through the * relative finger location (dx,dy). So the scale of this ellipse is * the ratio of finger_distance to margin_distance: * dx²/a² + dy²/b² = normalized_finger_distance² */ normalized_finger_distance = sqrt((double)dx2 / (a * a) + (double)dy2 / (b * b)); /* Which means anything less than 1 is within the elliptical margin */ if (normalized_finger_distance < 1.0) return *center; finger_distance = sqrt(dx2 + dy2); margin_distance = finger_distance / normalized_finger_distance; /* * Now calculate the x,y coordinates on the edge of the margin ellipse * where it intersects the finger vector. Shortcut: We achieve this by * finding the point with the same gradient as dy/dx. */ if (dx) { double gradient = (double)dy / dx; lag_x = margin_distance / sqrt(gradient * gradient + 1); lag_y = sqrt((margin_distance + lag_x) * (margin_distance - lag_x)); } else { /* Infinite gradient */ lag_x = 0.0; lag_y = margin_distance; } /* * 'result' is the centre of an ellipse (radii a,b) which has been * dragged by the finger moving inside it to 'in'. The finger is now * touching the margin ellipse at some point: (±lag_x,±lag_y) */ result.x = (dx >= 0) ? in->x - lag_x : in->x + lag_x; result.y = (dy >= 0) ? in->y - lag_y : in->y + lag_y; return result; } const struct device_coords margin = { 8, 8 }; const struct device_coords center = { 0, 0 }; void print_one(int x, int y) { struct device_coords point = {x, y}; struct device_coords result; //result = _evdev_hysteresis_105306(&point, ¢er, &margin); result = evdev_hysteresis_105108(point, center, margin); printf("%3d %3d to %3d %3d\n", point.x, point.y, result.x, result.y); } int main(void) { //print_one(-3, 9); for (int x = -margin.x * 2; x <= margin.x * 2; x++) { for (int y = -margin.y * 2; y <= margin.y * 2; y++) { struct device_coords point = {x, y}; struct device_coords result; result = evdev_hysteresis_105306(&point, ¢er, &margin); //result = evdev_hysteresis_105108(point, center, margin); printf("%3d %3d to %3d %3d\n", point.x, point.y, result.x, result.y); } } return 0; }