mirror of
https://gitlab.freedesktop.org/libinput/libinput.git
synced 2025-12-25 21:00:06 +01:00
Introduce omnidirectional (elliptical) hysteresis
This changes the hysteresis region to an ellipse (usually a circle), where
previously it was a rectangle (usually square).
Using an ellipse means the algorithm is no longer more sensitive in some
directions than others. It is now omnidirectional, which solves a few
problems:
* Moving a finger in small circles now creates circles, not squares.
* Moving a finger in a curve no longer snaps the cursor to vertical
or horizontal lines. The cursor now follows a similar curve to the
finger.
https://bugs.freedesktop.org/page.cgi?id=splinter.html&bug=105306
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
(cherry picked from commit 6936a15558)
This commit is contained in:
parent
1c15e162b8
commit
b437b2f196
3 changed files with 70 additions and 54 deletions
|
|
@ -127,12 +127,9 @@ fallback_filter_defuzz_touch(struct fallback_dispatch *dispatch,
|
|||
if (!dispatch->mt.want_hysteresis)
|
||||
return false;
|
||||
|
||||
point.x = evdev_hysteresis(slot->point.x,
|
||||
slot->hysteresis_center.x,
|
||||
dispatch->mt.hysteresis_margin.x);
|
||||
point.y = evdev_hysteresis(slot->point.y,
|
||||
slot->hysteresis_center.y,
|
||||
dispatch->mt.hysteresis_margin.y);
|
||||
point = evdev_hysteresis(&slot->point,
|
||||
&slot->hysteresis_center,
|
||||
&dispatch->mt.hysteresis_margin);
|
||||
|
||||
slot->hysteresis_center = slot->point;
|
||||
if (point.x == slot->point.x && point.y == slot->point.y)
|
||||
|
|
|
|||
|
|
@ -194,26 +194,15 @@ static inline void
|
|||
tp_motion_hysteresis(struct tp_dispatch *tp,
|
||||
struct tp_touch *t)
|
||||
{
|
||||
int x = t->point.x,
|
||||
y = t->point.y;
|
||||
|
||||
if (!tp->hysteresis.enabled)
|
||||
return;
|
||||
|
||||
if (t->history.count == 0) {
|
||||
t->hysteresis.center = t->point;
|
||||
} else {
|
||||
x = evdev_hysteresis(x,
|
||||
t->hysteresis.center.x,
|
||||
tp->hysteresis.margin.x);
|
||||
y = evdev_hysteresis(y,
|
||||
t->hysteresis.center.y,
|
||||
tp->hysteresis.margin.y);
|
||||
t->hysteresis.center.x = x;
|
||||
t->hysteresis.center.y = y;
|
||||
t->point.x = x;
|
||||
t->point.y = y;
|
||||
}
|
||||
if (t->history.count > 0)
|
||||
t->point = evdev_hysteresis(&t->point,
|
||||
&t->hysteresis.center,
|
||||
&tp->hysteresis.margin);
|
||||
|
||||
t->hysteresis.center = t->point;
|
||||
}
|
||||
|
||||
static inline void
|
||||
|
|
|
|||
92
src/evdev.h
92
src/evdev.h
|
|
@ -600,11 +600,11 @@ evdev_to_left_handed(struct evdev_device *device,
|
|||
* Apply a hysteresis filtering to the coordinate in, based on the current
|
||||
* hysteresis center and the margin. If 'in' is within 'margin' of center,
|
||||
* return the center (and thus filter the motion). If 'in' is outside,
|
||||
* return a point on the edge of the new margin. So for a point x in the
|
||||
* space outside c + margin we return r:
|
||||
* +---+ +---+
|
||||
* return a point on the edge of the new margin (which is an ellipse, usually
|
||||
* a circle). So for a point x in the space outside c + margin we return r:
|
||||
* ,---. ,---.
|
||||
* | c | x → | r x
|
||||
* +---+ +---+
|
||||
* `---' `---'
|
||||
*
|
||||
* The effect of this is that initial small motions are filtered. Once we
|
||||
* move into one direction we lag the real coordinates by 'margin' but any
|
||||
|
|
@ -617,41 +617,71 @@ evdev_to_left_handed(struct evdev_device *device,
|
|||
* Otherwise, the center has a dead zone of size margin around it and the
|
||||
* first reachable point is the margin edge.
|
||||
*
|
||||
* Hysteresis is handled separately per axis (and the window is thus
|
||||
* rectangular, not circular). It is unkown if that's an issue, but the
|
||||
* calculation to do circular hysteresis are nontrivial, especially since
|
||||
* many touchpads have uneven x/y resolutions.
|
||||
*
|
||||
* Given coordinates, 0, 1, 2, ... this is what we return for a margin of 3
|
||||
* and a center of 0:
|
||||
*
|
||||
* Input: 1 2 3 4 5 6 5 4 3 2 1 0 -1
|
||||
* Coord: 0 0 0 1 2 3 3 3 3 3 3 3 2
|
||||
* Center: 0 0 0 1 2 3 3 3 3 3 3 3 2
|
||||
*
|
||||
* Problem: viewed from a stationary finger that starts moving, the
|
||||
* hysteresis margin is M in both directions. Once we start moving
|
||||
* continuously though, the margin is 0 in the movement direction and 2*M to
|
||||
* change direction. That makes the finger less responsive to directional
|
||||
* changes than to the original movement.
|
||||
*
|
||||
* @param in The input coordinate
|
||||
* @param center Current center of the hysteresis
|
||||
* @param margin Hysteresis width (on each side)
|
||||
*
|
||||
* @return The new center of the hysteresis
|
||||
*/
|
||||
static inline int
|
||||
evdev_hysteresis(int in, int center, int margin)
|
||||
static inline struct device_coords
|
||||
evdev_hysteresis(const struct device_coords *in,
|
||||
const struct device_coords *center,
|
||||
const struct device_coords *margin)
|
||||
{
|
||||
int diff = in - center;
|
||||
if (abs(diff) <= margin)
|
||||
return center;
|
||||
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 (diff > 0)
|
||||
return in - margin;
|
||||
else
|
||||
return in + margin;
|
||||
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;
|
||||
}
|
||||
|
||||
static inline struct libinput *
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue