Move out generic math parts out of the native barrier implementation

In order to reuse some vector math for pointer confinement, move out
those parts to its own file, introducing the types old types
"MetaVector2" and "MetaLine2" outside of meta-barrier-native.c, as well
as introducing MetaBorder which is a line, with a blocking direction.

https://bugzilla.gnome.org/show_bug.cgi?id=744104
This commit is contained in:
Jonas Ådahl 2015-07-03 15:01:58 +08:00
parent bc8ec2d90d
commit f0f638d2bd
7 changed files with 292 additions and 178 deletions

View File

@ -112,6 +112,8 @@ libmutter_la_SOURCES = \
core/boxes.c \ core/boxes.c \
core/boxes-private.h \ core/boxes-private.h \
meta/boxes.h \ meta/boxes.h \
core/meta-border.c \
core/meta-border.h \
compositor/clutter-utils.c \ compositor/clutter-utils.c \
compositor/clutter-utils.h \ compositor/clutter-utils.h \
compositor/cogl-utils.c \ compositor/cogl-utils.c \

View File

@ -26,6 +26,8 @@
#ifndef META_BARRIER_PRIVATE_H #ifndef META_BARRIER_PRIVATE_H
#define META_BARRIER_PRIVATE_H #define META_BARRIER_PRIVATE_H
#include "core/meta-border.h"
G_BEGIN_DECLS G_BEGIN_DECLS
#define META_TYPE_BARRIER_IMPL (meta_barrier_impl_get_type ()) #define META_TYPE_BARRIER_IMPL (meta_barrier_impl_get_type ())
@ -67,14 +69,7 @@ G_END_DECLS
struct _MetaBarrierPrivate struct _MetaBarrierPrivate
{ {
MetaDisplay *display; MetaDisplay *display;
MetaBorder border;
int x1;
int y1;
int x2;
int y2;
MetaBarrierDirection directions;
MetaBarrierImpl *impl; MetaBarrierImpl *impl;
}; };

View File

@ -61,19 +61,20 @@ meta_barrier_get_property (GObject *object,
g_value_set_object (value, priv->display); g_value_set_object (value, priv->display);
break; break;
case PROP_X1: case PROP_X1:
g_value_set_int (value, priv->x1); g_value_set_int (value, priv->border.line.a.x);
break; break;
case PROP_Y1: case PROP_Y1:
g_value_set_int (value, priv->y1); g_value_set_int (value, priv->border.line.a.y);
break; break;
case PROP_X2: case PROP_X2:
g_value_set_int (value, priv->x2); g_value_set_int (value, priv->border.line.b.x);
break; break;
case PROP_Y2: case PROP_Y2:
g_value_set_int (value, priv->y2); g_value_set_int (value, priv->border.line.b.y);
break; break;
case PROP_DIRECTIONS: case PROP_DIRECTIONS:
g_value_set_flags (value, priv->directions); g_value_set_flags (value,
meta_border_get_allows_directions (&priv->border));
break; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@ -95,19 +96,20 @@ meta_barrier_set_property (GObject *object,
priv->display = g_value_get_object (value); priv->display = g_value_get_object (value);
break; break;
case PROP_X1: case PROP_X1:
priv->x1 = g_value_get_int (value); priv->border.line.a.x = g_value_get_int (value);
break; break;
case PROP_Y1: case PROP_Y1:
priv->y1 = g_value_get_int (value); priv->border.line.a.y = g_value_get_int (value);
break; break;
case PROP_X2: case PROP_X2:
priv->x2 = g_value_get_int (value); priv->border.line.b.x = g_value_get_int (value);
break; break;
case PROP_Y2: case PROP_Y2:
priv->y2 = g_value_get_int (value); priv->border.line.b.y = g_value_get_int (value);
break; break;
case PROP_DIRECTIONS: case PROP_DIRECTIONS:
priv->directions = g_value_get_flags (value); meta_border_set_allows_directions (&priv->border,
g_value_get_flags (value));
break; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@ -166,7 +168,8 @@ meta_barrier_constructed (GObject *object)
MetaBarrier *barrier = META_BARRIER (object); MetaBarrier *barrier = META_BARRIER (object);
MetaBarrierPrivate *priv = barrier->priv; MetaBarrierPrivate *priv = barrier->priv;
g_return_if_fail (priv->x1 == priv->x2 || priv->y1 == priv->y2); g_return_if_fail (priv->border.line.a.x == priv->border.line.b.x ||
priv->border.line.a.y == priv->border.line.b.y);
#if defined(HAVE_NATIVE_BACKEND) #if defined(HAVE_NATIVE_BACKEND)
if (META_IS_BACKEND_NATIVE (meta_get_backend ())) if (META_IS_BACKEND_NATIVE (meta_get_backend ()))

View File

@ -93,126 +93,18 @@ next_serial (void)
return barrier_serial; return barrier_serial;
} }
typedef struct _Vector2
{
float x, y;
} Vector2;
static float
vector2_cross_product (Vector2 a, Vector2 b)
{
return a.x * b.y - a.y * b.x;
}
static Vector2
vector2_add (Vector2 a, Vector2 b)
{
return (Vector2) {
.x = a.x + b.x,
.y = a.y + b.y,
};
}
static Vector2
vector2_subtract (Vector2 a, Vector2 b)
{
return (Vector2) {
.x = a.x - b.x,
.y = a.y - b.y,
};
}
static Vector2
vector2_multiply_constant (float c, Vector2 a)
{
return (Vector2) {
.x = c * a.x,
.y = c * a.y,
};
}
typedef struct _Line2
{
Vector2 a;
Vector2 b;
} Line2;
static gboolean
lines_intersect (Line2 *line1, Line2 *line2, Vector2 *intersection)
{
Vector2 p = line1->a;
Vector2 r = vector2_subtract (line1->b, line1->a);
Vector2 q = line2->a;
Vector2 s = vector2_subtract (line2->b, line2->a);
float rxs;
float sxr;
float t;
float u;
/*
* The line (p, r) and (q, s) intersects where
*
* p + t r = q + u s
*
* Calculate t:
*
* (p + t r) × s = (q + u s) × s
* p × s + t (r × s) = q × s + u (s × s)
* p × s + t (r × s) = q × s
* t (r × s) = q × s - p × s
* t (r × s) = (q - p) × s
* t = ((q - p) × s) / (r × s)
*
* Using the same method, for u we get:
*
* u = ((p - q) × r) / (s × r)
*/
rxs = vector2_cross_product (r, s);
sxr = vector2_cross_product (s, r);
/* If r × s = 0 then the lines are either parallel or collinear. */
if (fabs ( rxs) < DBL_MIN)
return FALSE;
t = vector2_cross_product (vector2_subtract (q, p), s) / rxs;
u = vector2_cross_product (vector2_subtract (p, q), r) / sxr;
/* The lines only intersect if 0 ≤ t ≤ 1 and 0 ≤ u ≤ 1. */
if (t < 0.0 || t > 1.0 || u < 0.0 || u > 1.0)
return FALSE;
*intersection = vector2_add (p, vector2_multiply_constant (t, r));
return TRUE;
}
static gboolean static gboolean
is_barrier_horizontal (MetaBarrier *barrier) is_barrier_horizontal (MetaBarrier *barrier)
{ {
return barrier->priv->y1 == barrier->priv->y2; return meta_border_is_horizontal (&barrier->priv->border);
} }
static gboolean static gboolean
is_barrier_blocking_directions (MetaBarrier *barrier, is_barrier_blocking_directions (MetaBarrier *barrier,
MetaBarrierDirection directions) MetaBarrierDirection directions)
{ {
/* Barriers doesn't block parallel motions. */ return meta_border_is_blocking_directions (&barrier->priv->border,
if (is_barrier_horizontal (barrier)) directions);
{
if ((directions & (META_BARRIER_DIRECTION_POSITIVE_Y |
META_BARRIER_DIRECTION_NEGATIVE_Y)) == 0)
return FALSE;
}
else
{
if ((directions & (META_BARRIER_DIRECTION_POSITIVE_X |
META_BARRIER_DIRECTION_NEGATIVE_X)) == 0)
return FALSE;
}
return (barrier->priv->directions & directions) != directions;
} }
static void static void
@ -224,31 +116,16 @@ dismiss_pointer (MetaBarrierImplNative *self)
priv->state = META_BARRIER_STATE_LEFT; priv->state = META_BARRIER_STATE_LEFT;
} }
static Line2
barrier_to_line (MetaBarrier *barrier)
{
return (Line2) {
.a = (Vector2) {
.x = MIN (barrier->priv->x1, barrier->priv->x2),
.y = MIN (barrier->priv->y1, barrier->priv->y2),
},
.b = (Vector2) {
.x = MAX (barrier->priv->x1, barrier->priv->x2),
.y = MAX (barrier->priv->y1, barrier->priv->y2),
},
};
}
/* /*
* Calculate the hit box for a held motion. The hit box is a 2 px wide region * Calculate the hit box for a held motion. The hit box is a 2 px wide region
* in the opposite direction of every direction the barrier blocks. The purpose * in the opposite direction of every direction the barrier blocks. The purpose
* of this is to allow small movements without receiving a "left" signal. This * of this is to allow small movements without receiving a "left" signal. This
* heuristic comes from the X.org pointer barrier implementation. * heuristic comes from the X.org pointer barrier implementation.
*/ */
static Line2 static MetaLine2
calculate_barrier_hit_box (MetaBarrier *barrier) calculate_barrier_hit_box (MetaBarrier *barrier)
{ {
Line2 hit_box = barrier_to_line (barrier); MetaLine2 hit_box = barrier->priv->border.line;
if (is_barrier_horizontal (barrier)) if (is_barrier_horizontal (barrier))
{ {
@ -273,7 +150,8 @@ calculate_barrier_hit_box (MetaBarrier *barrier)
} }
static gboolean static gboolean
is_within_box (Line2 box, Vector2 point) is_within_box (MetaLine2 box,
MetaVector2 point)
{ {
return (point.x >= box.a.x && point.x < box.b.x && return (point.x >= box.a.x && point.x < box.b.x &&
point.y >= box.a.y && point.y < box.b.y); point.y >= box.a.y && point.y < box.b.y);
@ -288,8 +166,8 @@ maybe_release_barrier (gpointer key,
MetaBarrierImplNativePrivate *priv = MetaBarrierImplNativePrivate *priv =
meta_barrier_impl_native_get_instance_private (self); meta_barrier_impl_native_get_instance_private (self);
MetaBarrier *barrier = priv->barrier; MetaBarrier *barrier = priv->barrier;
Line2 *motion = user_data; MetaLine2 *motion = user_data;
Line2 hit_box; MetaLine2 hit_box;
if (priv->state != META_BARRIER_STATE_HELD) if (priv->state != META_BARRIER_STATE_HELD)
return; return;
@ -297,8 +175,10 @@ maybe_release_barrier (gpointer key,
/* Release if we end up outside barrier end points. */ /* Release if we end up outside barrier end points. */
if (is_barrier_horizontal (barrier)) if (is_barrier_horizontal (barrier))
{ {
if (motion->b.x > MAX (barrier->priv->x1, barrier->priv->x2) || if (motion->b.x > MAX (barrier->priv->border.line.a.x,
motion->b.x < MIN (barrier->priv->x1, barrier->priv->x2)) barrier->priv->border.line.b.x) ||
motion->b.x < MIN (barrier->priv->border.line.a.x,
barrier->priv->border.line.b.x))
{ {
dismiss_pointer (self); dismiss_pointer (self);
return; return;
@ -306,8 +186,10 @@ maybe_release_barrier (gpointer key,
} }
else else
{ {
if (motion->b.y > MAX (barrier->priv->y1, barrier->priv->y2) || if (motion->b.y > MAX (barrier->priv->border.line.a.y,
motion->b.y < MIN (barrier->priv->y1, barrier->priv->y2)) barrier->priv->border.line.b.y) ||
motion->b.y < MIN (barrier->priv->border.line.a.y,
barrier->priv->border.line.b.y))
{ {
dismiss_pointer (self); dismiss_pointer (self);
return; return;
@ -330,7 +212,7 @@ maybe_release_barriers (MetaBarrierManagerNative *manager,
float x, float x,
float y) float y)
{ {
Line2 motion = { MetaLine2 motion = {
.a = { .a = {
.x = prev_x, .x = prev_x,
.y = prev_y, .y = prev_y,
@ -350,7 +232,7 @@ typedef struct _MetaClosestBarrierData
{ {
struct struct
{ {
Line2 motion; MetaLine2 motion;
MetaBarrierDirection directions; MetaBarrierDirection directions;
} in; } in;
@ -371,8 +253,7 @@ update_closest_barrier (gpointer key,
meta_barrier_impl_native_get_instance_private (self); meta_barrier_impl_native_get_instance_private (self);
MetaBarrier *barrier = priv->barrier; MetaBarrier *barrier = priv->barrier;
MetaClosestBarrierData *data = user_data; MetaClosestBarrierData *data = user_data;
Line2 barrier_line; MetaVector2 intersection;
Vector2 intersection;
float dx, dy; float dx, dy;
float distance_2; float distance_2;
@ -391,17 +272,9 @@ update_closest_barrier (gpointer key,
/* Check if the motion intersects with the barrier, and retrieve the /* Check if the motion intersects with the barrier, and retrieve the
* intersection point if any. */ * intersection point if any. */
barrier_line = (Line2) { if (!meta_line2_intersects_with (&barrier->priv->border.line,
.a = { &data->in.motion,
.x = barrier->priv->x1, &intersection))
.y = barrier->priv->y1
},
.b = {
.x = barrier->priv->x2,
.y = barrier->priv->y2
},
};
if (!lines_intersect (&barrier_line, &data->in.motion, &intersection))
return; return;
/* Calculate the distance to the barrier and keep track of the closest /* Calculate the distance to the barrier and keep track of the closest
@ -570,9 +443,9 @@ clamp_to_barrier (MetaBarrierImplNative *self,
if (is_barrier_horizontal (barrier)) if (is_barrier_horizontal (barrier))
{ {
if (*motion_dir & META_BARRIER_DIRECTION_POSITIVE_Y) if (*motion_dir & META_BARRIER_DIRECTION_POSITIVE_Y)
*y = barrier->priv->y1; *y = barrier->priv->border.line.a.y;
else if (*motion_dir & META_BARRIER_DIRECTION_NEGATIVE_Y) else if (*motion_dir & META_BARRIER_DIRECTION_NEGATIVE_Y)
*y = barrier->priv->y1; *y = barrier->priv->border.line.a.y;
priv->blocked_dir = *motion_dir & (META_BARRIER_DIRECTION_POSITIVE_Y | priv->blocked_dir = *motion_dir & (META_BARRIER_DIRECTION_POSITIVE_Y |
META_BARRIER_DIRECTION_NEGATIVE_Y); META_BARRIER_DIRECTION_NEGATIVE_Y);
@ -582,9 +455,9 @@ clamp_to_barrier (MetaBarrierImplNative *self,
else else
{ {
if (*motion_dir & META_BARRIER_DIRECTION_POSITIVE_X) if (*motion_dir & META_BARRIER_DIRECTION_POSITIVE_X)
*x = barrier->priv->x1; *x = barrier->priv->border.line.a.x;
else if (*motion_dir & META_BARRIER_DIRECTION_NEGATIVE_X) else if (*motion_dir & META_BARRIER_DIRECTION_NEGATIVE_X)
*x = barrier->priv->x1; *x = barrier->priv->border.line.a.x;
priv->blocked_dir = *motion_dir & (META_BARRIER_DIRECTION_POSITIVE_X | priv->blocked_dir = *motion_dir & (META_BARRIER_DIRECTION_POSITIVE_X |
META_BARRIER_DIRECTION_NEGATIVE_X); META_BARRIER_DIRECTION_NEGATIVE_X);

View File

@ -107,6 +107,7 @@ meta_barrier_impl_x11_new (MetaBarrier *barrier)
MetaDisplay *display = barrier->priv->display; MetaDisplay *display = barrier->priv->display;
Display *dpy; Display *dpy;
Window root; Window root;
unsigned int allowed_motion_dirs;
if (display == NULL) if (display == NULL)
{ {
@ -121,12 +122,14 @@ meta_barrier_impl_x11_new (MetaBarrier *barrier)
dpy = display->xdisplay; dpy = display->xdisplay;
root = DefaultRootWindow (dpy); root = DefaultRootWindow (dpy);
allowed_motion_dirs =
meta_border_get_allows_directions (&barrier->priv->border);
priv->xbarrier = XFixesCreatePointerBarrier (dpy, root, priv->xbarrier = XFixesCreatePointerBarrier (dpy, root,
barrier->priv->x1, barrier->priv->border.line.a.x,
barrier->priv->y1, barrier->priv->border.line.a.y,
barrier->priv->x2, barrier->priv->border.line.b.x,
barrier->priv->y2, barrier->priv->border.line.b.y,
barrier->priv->directions, allowed_motion_dirs,
0, NULL); 0, NULL);
g_hash_table_insert (display->xids, &priv->xbarrier, barrier); g_hash_table_insert (display->xids, &priv->xbarrier, barrier);

154
src/core/meta-border.c Normal file
View File

@ -0,0 +1,154 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2015 Red Hat
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Written by:
* Jonas Ådahl <jadahl@gmail.com>
*/
#include "config.h"
#include "core/meta-border.h"
#include <math.h>
static inline float
meta_vector2_cross_product (const MetaVector2 a,
const MetaVector2 b)
{
return a.x * b.y - a.y * b.x;
}
static inline MetaVector2
meta_vector2_add (const MetaVector2 a,
const MetaVector2 b)
{
return (MetaVector2) {
.x = a.x + b.x,
.y = a.y + b.y,
};
}
static inline MetaVector2
meta_vector2_multiply_constant (const float c,
const MetaVector2 a)
{
return (MetaVector2) {
.x = c * a.x,
.y = c * a.y,
};
}
gboolean
meta_line2_intersects_with (const MetaLine2 *line1,
const MetaLine2 *line2,
MetaVector2 *intersection)
{
MetaVector2 p = line1->a;
MetaVector2 r = meta_vector2_subtract (line1->b, line1->a);
MetaVector2 q = line2->a;
MetaVector2 s = meta_vector2_subtract (line2->b, line2->a);
float rxs;
float sxr;
float t;
float u;
/*
* The line (p, r) and (q, s) intersects where
*
* p + t r = q + u s
*
* Calculate t:
*
* (p + t r) × s = (q + u s) × s
* p × s + t (r × s) = q × s + u (s × s)
* p × s + t (r × s) = q × s
* t (r × s) = q × s - p × s
* t (r × s) = (q - p) × s
* t = ((q - p) × s) / (r × s)
*
* Using the same method, for u we get:
*
* u = ((p - q) × r) / (s × r)
*/
rxs = meta_vector2_cross_product (r, s);
sxr = meta_vector2_cross_product (s, r);
/* If r × s = 0 then the lines are either parallel or collinear. */
if (fabs (rxs) < DBL_MIN)
return FALSE;
t = meta_vector2_cross_product (meta_vector2_subtract (q, p), s) / rxs;
u = meta_vector2_cross_product (meta_vector2_subtract (p, q), r) / sxr;
/* The lines only intersect if 0 ≤ t ≤ 1 and 0 ≤ u ≤ 1. */
if (t < 0.0 || t > 1.0 || u < 0.0 || u > 1.0)
return FALSE;
*intersection = meta_vector2_add (p, meta_vector2_multiply_constant (t, r));
return TRUE;
}
gboolean
meta_border_is_horizontal (MetaBorder *border)
{
return border->line.a.y == border->line.b.y;
}
gboolean
meta_border_is_blocking_directions (MetaBorder *border,
MetaBorderMotionDirection directions)
{
if (meta_border_is_horizontal (border))
{
if ((directions & (META_BORDER_MOTION_DIRECTION_POSITIVE_Y |
META_BORDER_MOTION_DIRECTION_NEGATIVE_Y)) == 0)
return FALSE;
}
else
{
if ((directions & (META_BORDER_MOTION_DIRECTION_POSITIVE_X |
META_BORDER_MOTION_DIRECTION_NEGATIVE_X)) == 0)
return FALSE;
}
return (~border->blocking_directions & directions) != directions;
}
unsigned int
meta_border_get_allows_directions (MetaBorder *border)
{
return ~border->blocking_directions &
(META_BORDER_MOTION_DIRECTION_POSITIVE_X |
META_BORDER_MOTION_DIRECTION_POSITIVE_Y |
META_BORDER_MOTION_DIRECTION_NEGATIVE_X |
META_BORDER_MOTION_DIRECTION_NEGATIVE_Y);
}
void
meta_border_set_allows_directions (MetaBorder *border, unsigned int directions)
{
border->blocking_directions =
~directions & (META_BORDER_MOTION_DIRECTION_POSITIVE_X |
META_BORDER_MOTION_DIRECTION_POSITIVE_Y |
META_BORDER_MOTION_DIRECTION_NEGATIVE_X |
META_BORDER_MOTION_DIRECTION_NEGATIVE_Y);
}

84
src/core/meta-border.h Normal file
View File

@ -0,0 +1,84 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
* Copyright (C) 2015 Red Hat
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Written by:
* Jonas Ådahl <jadahl@gmail.com>
*/
#ifndef META_BORDER_H
#define META_BORDER_H
#include <glib.h>
typedef enum
{
META_BORDER_MOTION_DIRECTION_POSITIVE_X = 1 << 0,
META_BORDER_MOTION_DIRECTION_POSITIVE_Y = 1 << 1,
META_BORDER_MOTION_DIRECTION_NEGATIVE_X = 1 << 2,
META_BORDER_MOTION_DIRECTION_NEGATIVE_Y = 1 << 3,
} MetaBorderMotionDirection;
typedef struct _MetaVector2
{
float x;
float y;
} MetaVector2;
typedef struct _MetaLine2
{
MetaVector2 a;
MetaVector2 b;
} MetaLine2;
typedef struct _MetaBorder
{
MetaLine2 line;
MetaBorderMotionDirection blocking_directions;
} MetaBorder;
static inline MetaVector2
meta_vector2_subtract (const MetaVector2 a,
const MetaVector2 b)
{
return (MetaVector2) {
.x = a.x - b.x,
.y = a.y - b.y,
};
}
gboolean
meta_line2_intersects_with (const MetaLine2 *line1,
const MetaLine2 *line2,
MetaVector2 *intersection);
gboolean
meta_border_is_horizontal (MetaBorder *border);
gboolean
meta_border_is_blocking_directions (MetaBorder *border,
MetaBorderMotionDirection directions);
unsigned int
meta_border_get_allows_directions (MetaBorder *border);
void
meta_border_set_allows_directions (MetaBorder *border, unsigned int directions);
#endif /* META_BORDER_H */