1
0
Fork 0
mutter-performance-source/src/core/boxes.c
Robert Mader 2ade26ebf8 boxes: Swap 90 and 270 degree transforms in meta_rectangle_transform()
`meta_rectangle_transform()` is used in the stack to *compensate* for a
`MetaMonitorTransform` applied to a output, not to apply it again.
Change the function accordingly.

Context:
Experimenting with direct scanout on offscreen-rotated outputs revealed
that the 90/270 degree cases were actually interchanged.
Further digging revealed that we use `meta_rectangle_transform()` with
those values swapped in every single case, papering over the issue.

Either a unintuitive and unexplained `meta_monitor_transform_invert()`
was added, in which case "flipped" values would be wrong, or, in case
of Wayland buffer transforms, the values were swapped by interpreting
the Wayland enums accordingly, see commit 8d9bbe10.

Swapping the 90/270 degree values in `meta_rectangle_transform()`:
1. fixes hardware cursor positioning with flipped output transforms
2. fixes rendering issues with offscreen-rotated flipped output transforms
3. allows us to drop unexplained `meta_monitor_transform_invert()`s in
follow-up commits
4. allows us to make `META_MONITOR_TRANSFORM` and `WL_OUTPUT_TRANSFORM`
enums match again (reverting 8d9bbe10, as already done)

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2459>
2022-07-26 18:41:43 +00:00

2215 lines
72 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/**
* SECTION:boxes
* @Title: MetaRectangle
* @Short_Description: Simple box operations
*/
/*
* Copyright (C) 2005, 2006 Elijah Newren
* [meta_rectangle_intersect() is copyright the GTK+ Team according to Havoc,
* see gdkrectangle.c. As far as Havoc knows, he probably wrote
* meta_rectangle_equal(), and I'm guessing it's (C) Red Hat. So...]
* Copyright (C) 1995-2000 GTK+ Team
* Copyright (C) 2002 Red Hat, Inc.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "backends/meta-monitor-transform.h"
#include "core/boxes-private.h"
#include <math.h>
#include <X11/Xutil.h>
#include "meta/util.h"
/* It would make sense to use GSlice here, but until we clean up the
* rest of this file and the internal API to use these functions, we
* leave it using g_malloc()/g_free() for consistency.
*/
MetaRectangle *
meta_rectangle_copy (const MetaRectangle *rect)
{
return g_memdup2 (rect, sizeof (MetaRectangle));
}
void
meta_rectangle_free (MetaRectangle *rect)
{
g_free (rect);
}
G_DEFINE_BOXED_TYPE (MetaRectangle, meta_rectangle,
meta_rectangle_copy, meta_rectangle_free);
char*
meta_rectangle_to_string (const MetaRectangle *rect,
char *output)
{
/* 25 chars: 2 commas, space, plus, trailing \0 + 5 for each digit.
* Should be more than enough space. Note that of this space, the
* trailing \0 will be overwritten for all but the last rectangle.
*/
g_snprintf (output, RECT_LENGTH, "%d,%d +%d,%d",
rect->x, rect->y, rect->width, rect->height);
return output;
}
char*
meta_rectangle_region_to_string (GList *region,
const char *separator_string,
char *output)
{
/* 27 chars: 2 commas, 2 square brackets, space, plus, trailing \0 + 5
* for each digit. Should be more than enough space. Note that of this
* space, the trailing \0 will be overwritten for all but the last
* rectangle.
*/
char rect_string[RECT_LENGTH];
GList *tmp = region;
char *cur = output;
if (region == NULL)
g_snprintf (output, 10, "(EMPTY)");
while (tmp)
{
MetaRectangle *rect = tmp->data;
g_snprintf (rect_string, RECT_LENGTH, "[%d,%d +%d,%d]",
rect->x, rect->y, rect->width, rect->height);
cur = g_stpcpy (cur, rect_string);
tmp = tmp->next;
if (tmp)
cur = g_stpcpy (cur, separator_string);
}
return output;
}
char*
meta_rectangle_edge_to_string (const MetaEdge *edge,
char *output)
{
/* 25 chars: 2 commas, space, plus, trailing \0 + 5 for each digit.
* Should be more than enough space. Note that of this space, the
* trailing \0 will be overwritten for all but the last rectangle.
*
* Plus 2 for parenthesis, 4 for 2 more numbers, 2 more commas, and
* 2 more spaces, for a total of 10 more.
*/
g_snprintf (output, EDGE_LENGTH, "[%d,%d +%d,%d], %2d, %2d",
edge->rect.x, edge->rect.y, edge->rect.width, edge->rect.height,
edge->side_type, edge->edge_type);
return output;
}
char*
meta_rectangle_edge_list_to_string (GList *edge_list,
const char *separator_string,
char *output)
{
/* 27 chars: 2 commas, 2 square brackets, space, plus, trailing \0 + 5 for
* each digit. Should be more than enough space. Note that of this
* space, the trailing \0 will be overwritten for all but the last
* rectangle.
*
* Plus 2 for parenthesis, 4 for 2 more numbers, 2 more commas, and
* 2 more spaces, for a total of 10 more.
*/
char rect_string[EDGE_LENGTH];
char *cur = output;
GList *tmp = edge_list;
if (edge_list == NULL)
g_snprintf (output, 10, "(EMPTY)");
while (tmp)
{
MetaEdge *edge = tmp->data;
MetaRectangle *rect = &edge->rect;
g_snprintf (rect_string, EDGE_LENGTH, "([%d,%d +%d,%d], %2d, %2d)",
rect->x, rect->y, rect->width, rect->height,
edge->side_type, edge->edge_type);
cur = g_stpcpy (cur, rect_string);
tmp = tmp->next;
if (tmp)
cur = g_stpcpy (cur, separator_string);
}
return output;
}
MetaRectangle
meta_rect (int x, int y, int width, int height)
{
MetaRectangle temporary;
temporary.x = x;
temporary.y = y;
temporary.width = width;
temporary.height = height;
return temporary;
}
int
meta_rectangle_area (const MetaRectangle *rect)
{
g_return_val_if_fail (rect != NULL, 0);
return rect->width * rect->height;
}
/**
* meta_rectangle_intersect:
* @src1: a #MetaRectangle
* @src2: another #MetaRectangle
* @dest: (out caller-allocates): an empty #MetaRectangle, to be filled
* with the coordinates of the intersection.
*
* Returns: TRUE is some intersection exists and is not degenerate, FALSE
* otherwise.
*/
gboolean
meta_rectangle_intersect (const MetaRectangle *src1,
const MetaRectangle *src2,
MetaRectangle *dest)
{
int dest_x, dest_y;
int dest_w, dest_h;
int return_val;
g_return_val_if_fail (src1 != NULL, FALSE);
g_return_val_if_fail (src2 != NULL, FALSE);
g_return_val_if_fail (dest != NULL, FALSE);
return_val = FALSE;
dest_x = MAX (src1->x, src2->x);
dest_y = MAX (src1->y, src2->y);
dest_w = MIN (src1->x + src1->width, src2->x + src2->width) - dest_x;
dest_h = MIN (src1->y + src1->height, src2->y + src2->height) - dest_y;
if (dest_w > 0 && dest_h > 0)
{
dest->x = dest_x;
dest->y = dest_y;
dest->width = dest_w;
dest->height = dest_h;
return_val = TRUE;
}
else
{
dest->width = 0;
dest->height = 0;
}
return return_val;
}
gboolean
meta_rectangle_equal (const MetaRectangle *src1,
const MetaRectangle *src2)
{
return ((src1->x == src2->x) &&
(src1->y == src2->y) &&
(src1->width == src2->width) &&
(src1->height == src2->height));
}
/**
* meta_rectangle_union:
* @rect1: a #MetaRectangle
* @rect2: another #MetaRectangle
* @dest: (out caller-allocates): an empty #MetaRectangle, to be filled
* with the coordinates of the bounding box.
*/
void
meta_rectangle_union (const MetaRectangle *rect1,
const MetaRectangle *rect2,
MetaRectangle *dest)
{
int dest_x, dest_y;
int dest_w, dest_h;
dest_x = rect1->x;
dest_y = rect1->y;
dest_w = rect1->width;
dest_h = rect1->height;
if (rect2->x < dest_x)
{
dest_w += dest_x - rect2->x;
dest_x = rect2->x;
}
if (rect2->y < dest_y)
{
dest_h += dest_y - rect2->y;
dest_y = rect2->y;
}
if (rect2->x + rect2->width > dest_x + dest_w)
dest_w = rect2->x + rect2->width - dest_x;
if (rect2->y + rect2->height > dest_y + dest_h)
dest_h = rect2->y + rect2->height - dest_y;
dest->x = dest_x;
dest->y = dest_y;
dest->width = dest_w;
dest->height = dest_h;
}
gboolean
meta_rectangle_overlap (const MetaRectangle *rect1,
const MetaRectangle *rect2)
{
g_return_val_if_fail (rect1 != NULL, FALSE);
g_return_val_if_fail (rect2 != NULL, FALSE);
return !((rect1->x + rect1->width <= rect2->x) ||
(rect2->x + rect2->width <= rect1->x) ||
(rect1->y + rect1->height <= rect2->y) ||
(rect2->y + rect2->height <= rect1->y));
}
gboolean
meta_rectangle_vert_overlap (const MetaRectangle *rect1,
const MetaRectangle *rect2)
{
return (rect1->y < rect2->y + rect2->height &&
rect2->y < rect1->y + rect1->height);
}
gboolean
meta_rectangle_horiz_overlap (const MetaRectangle *rect1,
const MetaRectangle *rect2)
{
return (rect1->x < rect2->x + rect2->width &&
rect2->x < rect1->x + rect1->width);
}
gboolean
meta_rectangle_could_fit_rect (const MetaRectangle *outer_rect,
const MetaRectangle *inner_rect)
{
return (outer_rect->width >= inner_rect->width &&
outer_rect->height >= inner_rect->height);
}
gboolean
meta_rectangle_contains_rect (const MetaRectangle *outer_rect,
const MetaRectangle *inner_rect)
{
return
inner_rect->x >= outer_rect->x &&
inner_rect->y >= outer_rect->y &&
inner_rect->x + inner_rect->width <= outer_rect->x + outer_rect->width &&
inner_rect->y + inner_rect->height <= outer_rect->y + outer_rect->height;
}
void
meta_rectangle_resize_with_gravity (const MetaRectangle *old_rect,
MetaRectangle *rect,
MetaGravity gravity,
int new_width,
int new_height)
{
/* FIXME: I'm too deep into this to know whether the below comment is
* still clear or not now that I've moved it out of constraints.c.
* boxes.h has a good comment, but I'm not sure if the below info is also
* helpful on top of that (or whether it has superfluous info).
*/
/* These formulas may look overly simplistic at first but you can work
* everything out with a left_frame_with, right_frame_width,
* border_width, and old and new client area widths (instead of old total
* width and new total width) and you come up with the same formulas.
*
* Also, note that the reason we can treat META_GRAVITY_NORTH_WEST and
* META_GRAVITY_STATIC the same is because we're not given a location at
* which to place the window--the window was already placed
* appropriately before. So, META_GRAVITY_NORTH_WEST for this function
* means to just leave the upper left corner of the outer window
* where it already is, and META_GRAVITY_STATIC for this function means to
* just leave the upper left corner of the inner window where it
* already is. But leaving either of those two corners where they
* already are will ensure that the other corner is fixed as well
* (since frame size doesn't change)--thus making the two
* equivalent.
*/
/* First, the x direction */
switch (gravity)
{
case META_GRAVITY_NORTH_WEST:
case META_GRAVITY_WEST:
case META_GRAVITY_SOUTH_WEST:
rect->x = old_rect->x;
break;
case META_GRAVITY_NORTH:
case META_GRAVITY_CENTER:
case META_GRAVITY_SOUTH:
/* FIXME: Needing to adjust new_width kind of sucks, but not doing so
* would cause drift.
*/
new_width -= (old_rect->width - new_width) % 2;
rect->x = old_rect->x + (old_rect->width - new_width)/2;
break;
case META_GRAVITY_NORTH_EAST:
case META_GRAVITY_EAST:
case META_GRAVITY_SOUTH_EAST:
rect->x = old_rect->x + (old_rect->width - new_width);
break;
case META_GRAVITY_STATIC:
default:
rect->x = old_rect->x;
break;
}
rect->width = new_width;
/* Next, the y direction */
switch (gravity)
{
case META_GRAVITY_NORTH_WEST:
case META_GRAVITY_NORTH:
case META_GRAVITY_NORTH_EAST:
rect->y = old_rect->y;
break;
case META_GRAVITY_WEST:
case META_GRAVITY_CENTER:
case META_GRAVITY_EAST:
/* FIXME: Needing to adjust new_height kind of sucks, but not doing so
* would cause drift.
*/
new_height -= (old_rect->height - new_height) % 2;
rect->y = old_rect->y + (old_rect->height - new_height)/2;
break;
case META_GRAVITY_SOUTH_WEST:
case META_GRAVITY_SOUTH:
case META_GRAVITY_SOUTH_EAST:
rect->y = old_rect->y + (old_rect->height - new_height);
break;
case META_GRAVITY_STATIC:
default:
rect->y = old_rect->y;
break;
}
rect->height = new_height;
}
/* Not so simple helper function for get_minimal_spanning_set_for_region() */
static GList*
merge_spanning_rects_in_region (GList *region)
{
/* NOTE FOR ANY OPTIMIZATION PEOPLE OUT THERE: Please see the
* documentation of get_minimal_spanning_set_for_region() for performance
* considerations that also apply to this function.
*/
GList *compare;
compare = region;
if (region == NULL)
{
g_warning ("Region to merge was empty! Either you have some "
"pathological STRUT list or there's a bug somewhere!");
return NULL;
}
while (compare && compare->next)
{
MetaRectangle *a = compare->data;
GList *other = compare->next;
g_assert (a->width > 0 && a->height > 0);
while (other)
{
MetaRectangle *b = other->data;
GList *delete_me = NULL;
g_assert (b->width > 0 && b->height > 0);
/* If a contains b, just remove b */
if (meta_rectangle_contains_rect (a, b))
{
delete_me = other;
}
/* If b contains a, just remove a */
else if (meta_rectangle_contains_rect (b, a))
{
delete_me = compare;
}
/* If a and b might be mergeable horizontally */
else if (a->y == b->y && a->height == b->height)
{
/* If a and b overlap */
if (meta_rectangle_overlap (a, b))
{
int new_x = MIN (a->x, b->x);
a->width = MAX (a->x + a->width, b->x + b->width) - new_x;
a->x = new_x;
delete_me = other;
}
/* If a and b are adjacent */
else if (a->x + a->width == b->x || a->x == b->x + b->width)
{
int new_x = MIN (a->x, b->x);
a->width = MAX (a->x + a->width, b->x + b->width) - new_x;
a->x = new_x;
delete_me = other;
}
}
/* If a and b might be mergeable vertically */
else if (a->x == b->x && a->width == b->width)
{
/* If a and b overlap */
if (meta_rectangle_overlap (a, b))
{
int new_y = MIN (a->y, b->y);
a->height = MAX (a->y + a->height, b->y + b->height) - new_y;
a->y = new_y;
delete_me = other;
}
/* If a and b are adjacent */
else if (a->y + a->height == b->y || a->y == b->y + b->height)
{
int new_y = MIN (a->y, b->y);
a->height = MAX (a->y + a->height, b->y + b->height) - new_y;
a->y = new_y;
delete_me = other;
}
}
other = other->next;
/* Delete any rectangle in the list that is no longer wanted */
if (delete_me != NULL)
{
/* Deleting the rect we compare others to is a little tricker */
if (compare == delete_me)
{
compare = compare->next;
other = compare->next;
a = compare->data;
}
/* Okay, we can free it now */
g_free (delete_me->data);
region = g_list_delete_link (region, delete_me);
}
}
compare = compare->next;
}
return region;
}
/* Simple helper function for get_minimal_spanning_set_for_region()... */
static gint
compare_rect_areas (gconstpointer a, gconstpointer b)
{
const MetaRectangle *a_rect = (gconstpointer) a;
const MetaRectangle *b_rect = (gconstpointer) b;
int a_area = meta_rectangle_area (a_rect);
int b_area = meta_rectangle_area (b_rect);
return b_area - a_area; /* positive ret value denotes b > a, ... */
}
/* ... and another helper for get_minimal_spanning_set_for_region()... */
static gboolean
check_strut_align (MetaStrut *strut, const MetaRectangle *rect)
{
/* Check whether @strut actually aligns to the side of @rect it claims */
switch (strut->side)
{
case META_SIDE_TOP:
return BOX_TOP (strut->rect) <= BOX_TOP (*rect);
case META_SIDE_BOTTOM:
return BOX_BOTTOM (strut->rect) >= BOX_BOTTOM (*rect);
case META_SIDE_LEFT:
return BOX_LEFT (strut->rect) <= BOX_LEFT (*rect);
case META_SIDE_RIGHT:
return BOX_RIGHT (strut->rect) >= BOX_RIGHT (*rect);
default:
return FALSE;
}
}
/**
* meta_rectangle_get_minimal_spanning_set_for_region:
* @basic_rect: Input rectangle
* @all_struts: (element-type Meta.Rectangle): List of struts
*
* This function is trying to find a "minimal spanning set (of rectangles)"
* for a given region.
*
* The region is given by taking basic_rect, then removing the areas
* covered by all the rectangles in the all_struts list, and then expanding
* the resulting region by the given number of pixels in each direction.
*
* A "minimal spanning set (of rectangles)" is the best name I could come
* up with for the concept I had in mind. Basically, for a given region, I
* want a set of rectangles with the property that a window is contained in
* the region if and only if it is contained within at least one of the
* rectangles.
*
* Returns: (transfer full) (element-type Meta.Rectangle): Minimal spanning set
*/
GList*
meta_rectangle_get_minimal_spanning_set_for_region (
const MetaRectangle *basic_rect,
const GSList *all_struts)
{
/* NOTE FOR OPTIMIZERS: This function *might* be somewhat slow,
* especially due to the call to merge_spanning_rects_in_region() (which
* is O(n^2) where n is the size of the list generated in this function).
* This is made more onerous due to the fact that it involves a fair
* number of memory allocation and deallocation calls. However, n is 1
* for default installations of Gnome (because partial struts aren't used
* by default and only partial struts increase the size of the spanning
* set generated). With one partial strut, n will be 2 or 3. With 2
* partial struts, n will probably be 4 or 5. So, n probably isn't large
* enough to make this worth bothering. Further, it is only called from
* workspace.c:ensure_work_areas_validated (at least as of the time of
* writing this comment), which in turn should only be called if the
* strut list changes or the screen or monitor size changes. If it ever
* does show up on profiles (most likely because people start using
* ridiculously huge numbers of partial struts), possible optimizations
* include:
*
* (1) rewrite merge_spanning_rects_in_region() to be O(n) or O(nlogn).
* I'm not totally sure it's possible, but with a couple copies of
* the list and sorting them appropriately, I believe it might be.
* (2) only call merge_spanning_rects_in_region() with a subset of the
* full list of rectangles. I believe from some of my preliminary
* debugging and thinking about it that it is possible to figure out
* apriori groups of rectangles which are only merge candidates with
* each other. (See testboxes.c:get_screen_region() when which==2
* and track the steps of this function carefully to see what gave
* me the hint that this might work)
* (3) figure out how to avoid merge_spanning_rects_in_region(). I think
* it might be possible to modify this function to make that
* possible, and I spent just a little while thinking about it, but n
* wasn't large enough to convince me to care yet.
* (4) Some of the stuff Rob mentioned at http://mail.gnome.org/archives\
* /metacity-devel-list/2005-November/msg00028.html. (Sorry for the
* URL splitting.)
*/
GList *ret;
GList *tmp_list;
const GSList *strut_iter;
MetaRectangle *temp_rect;
/* The algorithm is basically as follows:
* Initialize rectangle_set to basic_rect
* Foreach strut:
* Foreach rectangle in rectangle_set:
* - Split the rectangle into new rectangles that don't overlap the
* strut (but which are as big as possible otherwise)
* - Remove the old (pre-split) rectangle from the rectangle_set,
* and replace it with the new rectangles generated from the
* splitting
*/
temp_rect = g_new (MetaRectangle, 1);
*temp_rect = *basic_rect;
ret = g_list_prepend (NULL, temp_rect);
for (strut_iter = all_struts; strut_iter; strut_iter = strut_iter->next)
{
GList *rect_iter;
MetaStrut *strut = (MetaStrut*)strut_iter->data;
MetaRectangle *strut_rect = &strut->rect;
tmp_list = ret;
ret = NULL;
rect_iter = tmp_list;
while (rect_iter)
{
MetaRectangle *rect = (MetaRectangle*) rect_iter->data;
if (!meta_rectangle_overlap (strut_rect, rect) ||
!check_strut_align (strut, basic_rect))
ret = g_list_prepend (ret, rect);
else
{
/* If there is area in rect left of strut */
if (BOX_LEFT (*rect) < BOX_LEFT (*strut_rect))
{
temp_rect = g_new (MetaRectangle, 1);
*temp_rect = *rect;
temp_rect->width = BOX_LEFT (*strut_rect) - BOX_LEFT (*rect);
ret = g_list_prepend (ret, temp_rect);
}
/* If there is area in rect right of strut */
if (BOX_RIGHT (*rect) > BOX_RIGHT (*strut_rect))
{
int new_x;
temp_rect = g_new (MetaRectangle, 1);
*temp_rect = *rect;
new_x = BOX_RIGHT (*strut_rect);
temp_rect->width = BOX_RIGHT(*rect) - new_x;
temp_rect->x = new_x;
ret = g_list_prepend (ret, temp_rect);
}
/* If there is area in rect above strut */
if (BOX_TOP (*rect) < BOX_TOP (*strut_rect))
{
temp_rect = g_new (MetaRectangle, 1);
*temp_rect = *rect;
temp_rect->height = BOX_TOP (*strut_rect) - BOX_TOP (*rect);
ret = g_list_prepend (ret, temp_rect);
}
/* If there is area in rect below strut */
if (BOX_BOTTOM (*rect) > BOX_BOTTOM (*strut_rect))
{
int new_y;
temp_rect = g_new (MetaRectangle, 1);
*temp_rect = *rect;
new_y = BOX_BOTTOM (*strut_rect);
temp_rect->height = BOX_BOTTOM (*rect) - new_y;
temp_rect->y = new_y;
ret = g_list_prepend (ret, temp_rect);
}
g_free (rect);
}
rect_iter = rect_iter->next;
}
g_list_free (tmp_list);
}
/* Sort by maximal area, just because I feel like it... */
ret = g_list_sort (ret, compare_rect_areas);
/* Merge rectangles if possible so that the list really is minimal */
ret = merge_spanning_rects_in_region (ret);
return ret;
}
/**
* meta_rectangle_expand_region: (skip)
*
*/
GList*
meta_rectangle_expand_region (GList *region,
const int left_expand,
const int right_expand,
const int top_expand,
const int bottom_expand)
{
return meta_rectangle_expand_region_conditionally (region,
left_expand,
right_expand,
top_expand,
bottom_expand,
0,
0);
}
/**
* meta_rectangle_expand_region_conditionally: (skip)
*
*/
GList*
meta_rectangle_expand_region_conditionally (GList *region,
const int left_expand,
const int right_expand,
const int top_expand,
const int bottom_expand,
const int min_x,
const int min_y)
{
GList *tmp_list = region;
while (tmp_list)
{
MetaRectangle *rect = (MetaRectangle*) tmp_list->data;
if (rect->width >= min_x)
{
rect->x -= left_expand;
rect->width += (left_expand + right_expand);
}
if (rect->height >= min_y)
{
rect->y -= top_expand;
rect->height += (top_expand + bottom_expand);
}
tmp_list = tmp_list->next;
}
return region;
}
void
meta_rectangle_expand_to_avoiding_struts (MetaRectangle *rect,
const MetaRectangle *expand_to,
const MetaDirection direction,
const GSList *all_struts)
{
const GSList *strut_iter;
/* If someone wants this function to handle more fine-grained
* direction expanding in the future (e.g. only left, or fully
* horizontal plus upward), feel free. But I'm hard-coding for both
* horizontal directions (exclusive-)or both vertical directions.
*/
g_assert ((direction == META_DIRECTION_HORIZONTAL) ^
(direction == META_DIRECTION_VERTICAL ));
if (direction == META_DIRECTION_HORIZONTAL)
{
rect->x = expand_to->x;
rect->width = expand_to->width;
}
else
{
rect->y = expand_to->y;
rect->height = expand_to->height;
}
/* Run over all struts */
for (strut_iter = all_struts; strut_iter; strut_iter = strut_iter->next)
{
MetaStrut *strut = (MetaStrut*) strut_iter->data;
/* Skip struts that don't overlap */
if (!meta_rectangle_overlap (&strut->rect, rect))
continue;
if (direction == META_DIRECTION_HORIZONTAL)
{
if (strut->side == META_SIDE_LEFT)
{
int offset = BOX_RIGHT(strut->rect) - BOX_LEFT(*rect);
rect->x += offset;
rect->width -= offset;
}
else if (strut->side == META_SIDE_RIGHT)
{
int offset = BOX_RIGHT (*rect) - BOX_LEFT(strut->rect);
rect->width -= offset;
}
/* else ignore the strut */
}
else /* direction == META_DIRECTION_VERTICAL */
{
if (strut->side == META_SIDE_TOP)
{
int offset = BOX_BOTTOM(strut->rect) - BOX_TOP(*rect);
rect->y += offset;
rect->height -= offset;
}
else if (strut->side == META_SIDE_BOTTOM)
{
int offset = BOX_BOTTOM(*rect) - BOX_TOP(strut->rect);
rect->height -= offset;
}
/* else ignore the strut */
}
} /* end loop over struts */
} /* end meta_rectangle_expand_to_avoiding_struts */
void
meta_rectangle_free_list_and_elements (GList *filled_list)
{
g_list_free_full (filled_list, g_free);
}
gboolean
meta_rectangle_could_fit_in_region (const GList *spanning_rects,
const MetaRectangle *rect)
{
const GList *temp;
gboolean could_fit;
temp = spanning_rects;
could_fit = FALSE;
while (!could_fit && temp != NULL)
{
could_fit = could_fit || meta_rectangle_could_fit_rect (temp->data, rect);
temp = temp->next;
}
return could_fit;
}
gboolean
meta_rectangle_contained_in_region (const GList *spanning_rects,
const MetaRectangle *rect)
{
const GList *temp;
gboolean contained;
temp = spanning_rects;
contained = FALSE;
while (!contained && temp != NULL)
{
contained = contained || meta_rectangle_contains_rect (temp->data, rect);
temp = temp->next;
}
return contained;
}
gboolean
meta_rectangle_overlaps_with_region (const GList *spanning_rects,
const MetaRectangle *rect)
{
const GList *temp;
gboolean overlaps;
temp = spanning_rects;
overlaps = FALSE;
while (!overlaps && temp != NULL)
{
overlaps = overlaps || meta_rectangle_overlap (temp->data, rect);
temp = temp->next;
}
return overlaps;
}
gboolean
meta_rectangle_is_adjacent_to_any_in_region (const GList *spanning_rects,
MetaRectangle *rect)
{
const GList *l;
for (l = spanning_rects; l; l = l->next)
{
MetaRectangle *other = (MetaRectangle *) l->data;
if (rect == other || meta_rectangle_equal (rect, other))
continue;
if (meta_rectangle_is_adjacent_to (rect, other))
return TRUE;
}
return FALSE;
}
void
meta_rectangle_clamp_to_fit_into_region (const GList *spanning_rects,
FixedDirections fixed_directions,
MetaRectangle *rect,
const MetaRectangle *min_size)
{
const GList *temp;
const MetaRectangle *best_rect = NULL;
int best_overlap = 0;
/* First, find best rectangle from spanning_rects to which we can clamp
* rect to fit into.
*/
for (temp = spanning_rects; temp; temp = temp->next)
{
MetaRectangle *compare_rect = temp->data;
int maximal_overlap_amount_for_compare;
/* If x is fixed and the entire width of rect doesn't fit in compare,
* skip this rectangle.
*/
if ((fixed_directions & FIXED_DIRECTION_X) &&
(compare_rect->x > rect->x ||
compare_rect->x + compare_rect->width < rect->x + rect->width))
continue;
/* If y is fixed and the entire height of rect doesn't fit in compare,
* skip this rectangle.
*/
if ((fixed_directions & FIXED_DIRECTION_Y) &&
(compare_rect->y > rect->y ||
compare_rect->y + compare_rect->height < rect->y + rect->height))
continue;
/* If compare can't hold the min_size window, skip this rectangle. */
if (compare_rect->width < min_size->width ||
compare_rect->height < min_size->height)
continue;
/* Determine maximal overlap amount */
maximal_overlap_amount_for_compare =
MIN (rect->width, compare_rect->width) *
MIN (rect->height, compare_rect->height);
/* See if this is the best rect so far */
if (maximal_overlap_amount_for_compare > best_overlap)
{
best_rect = compare_rect;
best_overlap = maximal_overlap_amount_for_compare;
}
}
/* Clamp rect appropriately */
if (best_rect == NULL)
{
g_warning ("No rect whose size to clamp to found!");
/* If it doesn't fit, at least make it no bigger than it has to be */
if (!(fixed_directions & FIXED_DIRECTION_X))
rect->width = min_size->width;
if (!(fixed_directions & FIXED_DIRECTION_Y))
rect->height = min_size->height;
}
else
{
rect->width = MIN (rect->width, best_rect->width);
rect->height = MIN (rect->height, best_rect->height);
}
}
void
meta_rectangle_clip_to_region (const GList *spanning_rects,
FixedDirections fixed_directions,
MetaRectangle *rect)
{
const GList *temp;
const MetaRectangle *best_rect = NULL;
int best_overlap = 0;
/* First, find best rectangle from spanning_rects to which we will clip
* rect into.
*/
for (temp = spanning_rects; temp; temp = temp->next)
{
MetaRectangle *compare_rect = temp->data;
MetaRectangle overlap;
int maximal_overlap_amount_for_compare;
/* If x is fixed and the entire width of rect doesn't fit in compare,
* skip the rectangle.
*/
if ((fixed_directions & FIXED_DIRECTION_X) &&
(compare_rect->x > rect->x ||
compare_rect->x + compare_rect->width < rect->x + rect->width))
continue;
/* If y is fixed and the entire height of rect doesn't fit in compare,
* skip the rectangle.
*/
if ((fixed_directions & FIXED_DIRECTION_Y) &&
(compare_rect->y > rect->y ||
compare_rect->y + compare_rect->height < rect->y + rect->height))
continue;
/* Determine maximal overlap amount */
meta_rectangle_intersect (rect, compare_rect, &overlap);
maximal_overlap_amount_for_compare = meta_rectangle_area (&overlap);
/* See if this is the best rect so far */
if (maximal_overlap_amount_for_compare > best_overlap)
{
best_rect = compare_rect;
best_overlap = maximal_overlap_amount_for_compare;
}
}
/* Clip rect appropriately */
if (best_rect == NULL)
{
g_warning ("No rect to clip to found!");
}
else
{
/* Extra precaution with checking fixed direction shouldn't be needed
* due to logic above, but it shouldn't hurt either.
*/
if (!(fixed_directions & FIXED_DIRECTION_X))
{
/* Find the new left and right */
int new_x = MAX (rect->x, best_rect->x);
rect->width = MIN ((rect->x + rect->width) - new_x,
(best_rect->x + best_rect->width) - new_x);
rect->x = new_x;
}
/* Extra precaution with checking fixed direction shouldn't be needed
* due to logic above, but it shouldn't hurt either.
*/
if (!(fixed_directions & FIXED_DIRECTION_Y))
{
/* Clip the top, if needed */
int new_y = MAX (rect->y, best_rect->y);
rect->height = MIN ((rect->y + rect->height) - new_y,
(best_rect->y + best_rect->height) - new_y);
rect->y = new_y;
}
}
}
void
meta_rectangle_shove_into_region (const GList *spanning_rects,
FixedDirections fixed_directions,
MetaRectangle *rect)
{
const GList *temp;
const MetaRectangle *best_rect = NULL;
int best_overlap = 0;
int shortest_distance = G_MAXINT;
/* First, find best rectangle from spanning_rects to which we will shove
* rect into.
*/
for (temp = spanning_rects; temp; temp = temp->next)
{
MetaRectangle *compare_rect = temp->data;
int maximal_overlap_amount_for_compare;
int dist_to_compare;
/* If x is fixed and the entire width of rect doesn't fit in compare,
* skip this rectangle.
*/
if ((fixed_directions & FIXED_DIRECTION_X) &&
(compare_rect->x > rect->x ||
compare_rect->x + compare_rect->width < rect->x + rect->width))
continue;
/* If y is fixed and the entire height of rect doesn't fit in compare,
* skip this rectangle.
*/
if ((fixed_directions & FIXED_DIRECTION_Y) &&
(compare_rect->y > rect->y ||
compare_rect->y + compare_rect->height < rect->y + rect->height))
continue;
/* Determine maximal overlap amount between rect & compare_rect */
maximal_overlap_amount_for_compare =
MIN (rect->width, compare_rect->width) *
MIN (rect->height, compare_rect->height);
/* Determine distance necessary to put rect into compare_rect */
dist_to_compare = 0;
if (compare_rect->x > rect->x)
dist_to_compare += compare_rect->x - rect->x;
if (compare_rect->x + compare_rect->width < rect->x + rect->width)
dist_to_compare += (rect->x + rect->width) -
(compare_rect->x + compare_rect->width);
if (compare_rect->y > rect->y)
dist_to_compare += compare_rect->y - rect->y;
if (compare_rect->y + compare_rect->height < rect->y + rect->height)
dist_to_compare += (rect->y + rect->height) -
(compare_rect->y + compare_rect->height);
/* See if this is the best rect so far */
if ((maximal_overlap_amount_for_compare > best_overlap) ||
(maximal_overlap_amount_for_compare == best_overlap &&
dist_to_compare < shortest_distance))
{
best_rect = compare_rect;
best_overlap = maximal_overlap_amount_for_compare;
shortest_distance = dist_to_compare;
}
}
/* Shove rect appropriately */
if (best_rect == NULL)
{
g_warning ("No rect to shove into found!");
}
else
{
/* Extra precaution with checking fixed direction shouldn't be needed
* due to logic above, but it shouldn't hurt either.
*/
if (!(fixed_directions & FIXED_DIRECTION_X))
{
/* Shove to the right, if needed */
if (best_rect->x > rect->x)
rect->x = best_rect->x;
/* Shove to the left, if needed */
if (best_rect->x + best_rect->width < rect->x + rect->width)
rect->x = (best_rect->x + best_rect->width) - rect->width;
}
/* Extra precaution with checking fixed direction shouldn't be needed
* due to logic above, but it shouldn't hurt either.
*/
if (!(fixed_directions & FIXED_DIRECTION_Y))
{
/* Shove down, if needed */
if (best_rect->y > rect->y)
rect->y = best_rect->y;
/* Shove up, if needed */
if (best_rect->y + best_rect->height < rect->y + rect->height)
rect->y = (best_rect->y + best_rect->height) - rect->height;
}
}
}
void
meta_rectangle_find_linepoint_closest_to_point (double x1,
double y1,
double x2,
double y2,
double px,
double py,
double *valx,
double *valy)
{
/* I'll use the shorthand rx, ry for the return values, valx & valy.
* Now, we need (rx,ry) to be on the line between (x1,y1) and (x2,y2).
* For that to happen, we first need the slope of the line from (x1,y1)
* to (rx,ry) must match the slope of (x1,y1) to (x2,y2), i.e.:
* (ry-y1) (y2-y1)
* ------- = -------
* (rx-x1) (x2-x1)
* If x1==x2, though, this gives divide by zero errors, so we want to
* rewrite the equation by multiplying both sides by (rx-x1)*(x2-x1):
* (ry-y1)(x2-x1) = (y2-y1)(rx-x1)
* This is a valid requirement even when x1==x2 (when x1==x2, this latter
* equation will basically just mean that rx must be equal to both x1 and
* x2)
*
* The other requirement that we have is that the line from (rx,ry) to
* (px,py) must be perpendicular to the line from (x1,y1) to (x2,y2). So
* we just need to get a vector in the direction of each line, take the
* dot product of the two, and ensure that the result is 0:
* (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0.
*
* This gives us two equations and two unknowns:
*
* (ry-y1)(x2-x1) = (y2-y1)(rx-x1)
* (rx-px)*(x2-x1) + (ry-py)*(y2-y1) = 0.
*
* This particular pair of equations is always solvable so long as
* (x1,y1) and (x2,y2) are not the same point (and note that anyone who
* calls this function that way is braindead because it means that they
* really didn't specify a line after all). However, the caller should
* be careful to avoid making (x1,y1) and (x2,y2) too close (e.g. like
* 10^{-8} apart in each coordinate), otherwise roundoff error could
* cause issues. Solving these equations by hand (or using Maple(TM) or
* Mathematica(TM) or whatever) results in slightly messy expressions,
* but that's all the below few lines do.
*/
double diffx, diffy, den;
diffx = x2 - x1;
diffy = y2 - y1;
den = diffx * diffx + diffy * diffy;
*valx = (py * diffx * diffy + px * diffx * diffx +
y2 * x1 * diffy - y1 * x2 * diffy) / den;
*valy = (px * diffx * diffy + py * diffy * diffy +
x2 * y1 * diffx - x1 * y2 * diffx) / den;
}
/***************************************************************************/
/* */
/* Switching gears to code for edges instead of just rectangles */
/* */
/***************************************************************************/
gboolean
meta_rectangle_edge_aligns (const MetaRectangle *rect, const MetaEdge *edge)
{
/* The reason for the usage of <= below instead of < is because we are
* interested in in-the-way-or-adject'ness. So, a left (i.e. vertical
* edge) occupying y positions 0-9 (which has a y of 0 and a height of
* 10) and a rectangle with top at y=10 would be considered to "align" by
* this function.
*/
switch (edge->side_type)
{
case META_SIDE_LEFT:
case META_SIDE_RIGHT:
return BOX_TOP (*rect) <= BOX_BOTTOM (edge->rect) &&
BOX_TOP (edge->rect) <= BOX_BOTTOM (*rect);
case META_SIDE_TOP:
case META_SIDE_BOTTOM:
return BOX_LEFT (*rect) <= BOX_RIGHT (edge->rect) &&
BOX_LEFT (edge->rect) <= BOX_RIGHT (*rect);
default:
g_assert_not_reached ();
return FALSE;
}
}
static GList*
get_rect_minus_overlap (const GList *rect_in_list,
MetaRectangle *overlap)
{
MetaRectangle *temp;
MetaRectangle *rect = rect_in_list->data;
GList *ret = NULL;
if (BOX_LEFT (*rect) < BOX_LEFT (*overlap))
{
temp = g_new (MetaRectangle, 1);
*temp = *rect;
temp->width = BOX_LEFT (*overlap) - BOX_LEFT (*rect);
ret = g_list_prepend (ret, temp);
}
if (BOX_RIGHT (*rect) > BOX_RIGHT (*overlap))
{
temp = g_new (MetaRectangle, 1);
*temp = *rect;
temp->x = BOX_RIGHT (*overlap);
temp->width = BOX_RIGHT (*rect) - BOX_RIGHT (*overlap);
ret = g_list_prepend (ret, temp);
}
if (BOX_TOP (*rect) < BOX_TOP (*overlap))
{
temp = g_new (MetaRectangle, 1);
temp->x = overlap->x;
temp->width = overlap->width;
temp->y = BOX_TOP (*rect);
temp->height = BOX_TOP (*overlap) - BOX_TOP (*rect);
ret = g_list_prepend (ret, temp);
}
if (BOX_BOTTOM (*rect) > BOX_BOTTOM (*overlap))
{
temp = g_new (MetaRectangle, 1);
temp->x = overlap->x;
temp->width = overlap->width;
temp->y = BOX_BOTTOM (*overlap);
temp->height = BOX_BOTTOM (*rect) - BOX_BOTTOM (*overlap);
ret = g_list_prepend (ret, temp);
}
return ret;
}
static GList*
replace_rect_with_list (GList *old_element,
GList *new_list)
{
GList *ret;
g_assert (old_element != NULL);
if (!new_list)
{
/* If there is no new list, just remove the old_element */
ret = g_list_remove_link (old_element, old_element);
}
else
{
/* Fix up the prev and next pointers everywhere */
ret = new_list;
if (old_element->prev)
{
old_element->prev->next = new_list;
new_list->prev = old_element->prev;
}
if (old_element->next)
{
GList *tmp = g_list_last (new_list);
old_element->next->prev = tmp;
tmp->next = old_element->next;
}
}
/* Free the old_element and return the appropriate "next" point */
g_free (old_element->data);
g_list_free_1 (old_element);
return ret;
}
/* Make a copy of the strut list, make sure that copy only contains parts
* of the old_struts that intersect with the region rect, and then do some
* magic to make all the new struts disjoint (okay, we we break up struts
* that aren't disjoint in a way that the overlapping part is only included
* once, so it's not really magic...).
*/
static GList*
get_disjoint_strut_rect_list_in_region (const GSList *old_struts,
const MetaRectangle *region)
{
GList *strut_rects;
GList *tmp;
/* First, copy the list */
strut_rects = NULL;
while (old_struts)
{
MetaRectangle *cur = &((MetaStrut*)old_struts->data)->rect;
MetaRectangle *copy = g_new (MetaRectangle, 1);
*copy = *cur;
if (meta_rectangle_intersect (copy, region, copy))
strut_rects = g_list_prepend (strut_rects, copy);
else
g_free (copy);
old_struts = old_struts->next;
}
/* Now, loop over the list and check for intersections, fixing things up
* where they do intersect.
*/
tmp = strut_rects;
while (tmp)
{
GList *compare;
MetaRectangle *cur = tmp->data;
compare = tmp->next;
while (compare)
{
MetaRectangle *comp = compare->data;
MetaRectangle overlap;
if (meta_rectangle_intersect (cur, comp, &overlap))
{
/* Get a list of rectangles for each strut that don't overlap
* the intersection region.
*/
GList *cur_leftover = get_rect_minus_overlap (tmp, &overlap);
GList *comp_leftover = get_rect_minus_overlap (compare, &overlap);
/* Add the intersection region to cur_leftover */
MetaRectangle *overlap_allocated = g_new (MetaRectangle, 1);
*overlap_allocated = overlap;
cur_leftover = g_list_prepend (cur_leftover, overlap_allocated);
/* Fix up tmp, compare, and cur -- maybe struts too */
if (strut_rects == tmp)
{
strut_rects = replace_rect_with_list (tmp, cur_leftover);
tmp = strut_rects;
}
else
tmp = replace_rect_with_list (tmp, cur_leftover);
compare = replace_rect_with_list (compare, comp_leftover);
if (compare == NULL)
break;
cur = tmp->data;
}
compare = compare->next;
}
tmp = tmp->next;
}
return strut_rects;
}
gint
meta_rectangle_edge_cmp_ignore_type (gconstpointer a, gconstpointer b)
{
const MetaEdge *a_edge_rect = (gconstpointer) a;
const MetaEdge *b_edge_rect = (gconstpointer) b;
int a_compare, b_compare;
/* Edges must be both vertical or both horizontal, or it doesn't make
* sense to compare them.
*/
g_assert ((a_edge_rect->rect.width == 0 && b_edge_rect->rect.width == 0) ||
(a_edge_rect->rect.height == 0 && b_edge_rect->rect.height == 0));
a_compare = b_compare = 0; /* gcc-3.4.2 sucks at figuring initialized'ness */
if (a_edge_rect->side_type == META_SIDE_LEFT ||
a_edge_rect->side_type == META_SIDE_RIGHT)
{
a_compare = a_edge_rect->rect.x;
b_compare = b_edge_rect->rect.x;
if (a_compare == b_compare)
{
a_compare = a_edge_rect->rect.y;
b_compare = b_edge_rect->rect.y;
}
}
else if (a_edge_rect->side_type == META_SIDE_TOP ||
a_edge_rect->side_type == META_SIDE_BOTTOM)
{
a_compare = a_edge_rect->rect.y;
b_compare = b_edge_rect->rect.y;
if (a_compare == b_compare)
{
a_compare = a_edge_rect->rect.x;
b_compare = b_edge_rect->rect.x;
}
}
else
g_assert ("Some idiot wanted to sort sides of different types.");
return a_compare - b_compare; /* positive value denotes a > b ... */
}
/* To make things easily testable, provide a nice way of sorting edges */
gint
meta_rectangle_edge_cmp (gconstpointer a, gconstpointer b)
{
const MetaEdge *a_edge_rect = (gconstpointer) a;
const MetaEdge *b_edge_rect = (gconstpointer) b;
int a_compare, b_compare;
a_compare = a_edge_rect->side_type;
b_compare = b_edge_rect->side_type;
if (a_compare == b_compare)
return meta_rectangle_edge_cmp_ignore_type (a, b);
return a_compare - b_compare; /* positive value denotes a > b ... */
}
/* Determine whether two given edges overlap */
static gboolean
edges_overlap (const MetaEdge *edge1,
const MetaEdge *edge2)
{
if (edge1->rect.width == 0 && edge2->rect.width == 0)
{
return meta_rectangle_vert_overlap (&edge1->rect, &edge2->rect) &&
edge1->rect.x == edge2->rect.x;
}
else if (edge1->rect.height == 0 && edge2->rect.height == 0)
{
return meta_rectangle_horiz_overlap (&edge1->rect, &edge2->rect) &&
edge1->rect.y == edge2->rect.y;
}
else
{
return FALSE;
}
}
static gboolean
rectangle_and_edge_intersection (const MetaRectangle *rect,
const MetaEdge *edge,
MetaEdge *overlap,
int *handle_type)
{
const MetaRectangle *rect2 = &edge->rect;
MetaRectangle *result = &overlap->rect;
gboolean intersect = TRUE;
/* We don't know how to set these, so set them to invalid values */
overlap->edge_type = -1;
overlap->side_type = -1;
/* Figure out what the intersection is */
result->x = MAX (rect->x, rect2->x);
result->y = MAX (rect->y, rect2->y);
result->width = MIN (BOX_RIGHT (*rect), BOX_RIGHT (*rect2)) - result->x;
result->height = MIN (BOX_BOTTOM (*rect), BOX_BOTTOM (*rect2)) - result->y;
/* Find out if the intersection is empty; have to do it this way since
* edges have a thickness of 0
*/
if ((result->width < 0 || result->height < 0) ||
(result->width == 0 && result->height == 0))
{
result->width = 0;
result->height = 0;
intersect = FALSE;
}
else
{
/* Need to figure out the handle_type, a somewhat weird quantity:
* 0 - overlap is in middle of rect
* -1 - overlap is at the side of rect, and is on the opposite side
* of rect than the edge->side_type side
* 1 - overlap is at the side of rect, and the side of rect it is
* on is the edge->side_type side
*/
switch (edge->side_type)
{
case META_SIDE_LEFT:
if (result->x == rect->x)
*handle_type = 1;
else if (result->x == BOX_RIGHT (*rect))
*handle_type = -1;
else
*handle_type = 0;
break;
case META_SIDE_RIGHT:
if (result->x == rect->x)
*handle_type = -1;
else if (result->x == BOX_RIGHT (*rect))
*handle_type = 1;
else
*handle_type = 0;
break;
case META_SIDE_TOP:
if (result->y == rect->y)
*handle_type = 1;
else if (result->y == BOX_BOTTOM (*rect))
*handle_type = -1;
else
*handle_type = 0;
break;
case META_SIDE_BOTTOM:
if (result->y == rect->y)
*handle_type = -1;
else if (result->y == BOX_BOTTOM (*rect))
*handle_type = 1;
else
*handle_type = 0;
break;
default:
g_assert_not_reached ();
}
}
return intersect;
}
/* Add all edges of the given rect to cur_edges and return the result. If
* rect_is_internal is false, the side types are switched (LEFT<->RIGHT and
* TOP<->BOTTOM).
*/
static GList*
add_edges (GList *cur_edges,
const MetaRectangle *rect,
gboolean rect_is_internal)
{
MetaEdge *temp_edge;
int i;
for (i=0; i<4; i++)
{
temp_edge = g_new (MetaEdge, 1);
temp_edge->rect = *rect;
switch (i)
{
case 0:
temp_edge->side_type =
rect_is_internal ? META_SIDE_LEFT : META_SIDE_RIGHT;
temp_edge->rect.width = 0;
break;
case 1:
temp_edge->side_type =
rect_is_internal ? META_SIDE_RIGHT : META_SIDE_LEFT;
temp_edge->rect.x += temp_edge->rect.width;
temp_edge->rect.width = 0;
break;
case 2:
temp_edge->side_type =
rect_is_internal ? META_SIDE_TOP : META_SIDE_BOTTOM;
temp_edge->rect.height = 0;
break;
case 3:
temp_edge->side_type =
rect_is_internal ? META_SIDE_BOTTOM : META_SIDE_TOP;
temp_edge->rect.y += temp_edge->rect.height;
temp_edge->rect.height = 0;
break;
}
temp_edge->edge_type = META_EDGE_SCREEN;
cur_edges = g_list_prepend (cur_edges, temp_edge);
}
return cur_edges;
}
/* Remove any part of old_edge that intersects remove and add any resulting
* edges to cur_list. Return cur_list when finished.
*/
static GList*
split_edge (GList *cur_list,
const MetaEdge *old_edge,
const MetaEdge *remove)
{
MetaEdge *temp_edge;
switch (old_edge->side_type)
{
case META_SIDE_LEFT:
case META_SIDE_RIGHT:
g_assert (meta_rectangle_vert_overlap (&old_edge->rect, &remove->rect));
if (BOX_TOP (old_edge->rect) < BOX_TOP (remove->rect))
{
temp_edge = g_new (MetaEdge, 1);
*temp_edge = *old_edge;
temp_edge->rect.height = BOX_TOP (remove->rect)
- BOX_TOP (old_edge->rect);
cur_list = g_list_prepend (cur_list, temp_edge);
}
if (BOX_BOTTOM (old_edge->rect) > BOX_BOTTOM (remove->rect))
{
temp_edge = g_new (MetaEdge, 1);
*temp_edge = *old_edge;
temp_edge->rect.y = BOX_BOTTOM (remove->rect);
temp_edge->rect.height = BOX_BOTTOM (old_edge->rect)
- BOX_BOTTOM (remove->rect);
cur_list = g_list_prepend (cur_list, temp_edge);
}
break;
case META_SIDE_TOP:
case META_SIDE_BOTTOM:
g_assert (meta_rectangle_horiz_overlap (&old_edge->rect, &remove->rect));
if (BOX_LEFT (old_edge->rect) < BOX_LEFT (remove->rect))
{
temp_edge = g_new (MetaEdge, 1);
*temp_edge = *old_edge;
temp_edge->rect.width = BOX_LEFT (remove->rect)
- BOX_LEFT (old_edge->rect);
cur_list = g_list_prepend (cur_list, temp_edge);
}
if (BOX_RIGHT (old_edge->rect) > BOX_RIGHT (remove->rect))
{
temp_edge = g_new (MetaEdge, 1);
*temp_edge = *old_edge;
temp_edge->rect.x = BOX_RIGHT (remove->rect);
temp_edge->rect.width = BOX_RIGHT (old_edge->rect)
- BOX_RIGHT (remove->rect);
cur_list = g_list_prepend (cur_list, temp_edge);
}
break;
default:
g_assert_not_reached ();
}
return cur_list;
}
/* Split up edge and remove preliminary edges from strut_edges depending on
* if and how rect and edge intersect.
*/
static void
fix_up_edges (MetaRectangle *rect, MetaEdge *edge,
GList **strut_edges, GList **edge_splits,
gboolean *edge_needs_removal)
{
MetaEdge overlap;
int handle_type;
if (!rectangle_and_edge_intersection (rect, edge, &overlap, &handle_type))
return;
if (handle_type == 0 || handle_type == 1)
{
/* Put the result of removing overlap from edge into edge_splits */
*edge_splits = split_edge (*edge_splits, edge, &overlap);
*edge_needs_removal = TRUE;
}
if (handle_type == -1 || handle_type == 1)
{
/* Remove the overlap from strut_edges */
/* First, loop over the edges of the strut */
GList *tmp = *strut_edges;
while (tmp)
{
MetaEdge *cur = tmp->data;
/* If this is the edge that overlaps, then we need to split it */
if (edges_overlap (cur, &overlap))
{
GList *delete_me = tmp;
/* Split this edge into some new ones */
*strut_edges = split_edge (*strut_edges, cur, &overlap);
/* Delete the old one */
tmp = tmp->next;
g_free (cur);
*strut_edges = g_list_delete_link (*strut_edges, delete_me);
}
else
tmp = tmp->next;
}
}
}
/**
* meta_rectangle_remove_intersections_with_boxes_from_edges: (skip)
*
* This function removes intersections of edges with the rectangles from the
* list of edges.
*/
GList*
meta_rectangle_remove_intersections_with_boxes_from_edges (
GList *edges,
const GSList *rectangles)
{
const GSList *rect_iter;
const int opposing = 1;
/* Now remove all intersections of rectangles with the edge list */
rect_iter = rectangles;
while (rect_iter)
{
MetaRectangle *rect = rect_iter->data;
GList *edge_iter = edges;
while (edge_iter)
{
MetaEdge *edge = edge_iter->data;
MetaEdge overlap;
int handle;
gboolean edge_iter_advanced = FALSE;
/* If this edge overlaps with this rect... */
if (rectangle_and_edge_intersection (rect, edge, &overlap, &handle))
{
/* "Intersections" where the edges touch but are opposite
* sides (e.g. a left edge against the right edge) should not
* be split. Note that the comments in
* rectangle_and_edge_intersection() say that opposing edges
* occur when handle is -1, BUT you need to remember that we
* treat the left side of a window as a right edge because
* it's what the right side of the window being moved should
* be-resisted-by/snap-to. So opposing is really 1. Anyway,
* we just keep track of it in the opposing constant set up
* above and if handle isn't equal to that, then we know the
* edge should be split.
*/
if (handle != opposing)
{
/* Keep track of this edge so we can delete it below */
GList *delete_me = edge_iter;
edge_iter = edge_iter->next;
edge_iter_advanced = TRUE;
/* Split the edge and add the result to beginning of edges */
edges = split_edge (edges, edge, &overlap);
/* Now free the edge... */
g_free (edge);
edges = g_list_delete_link (edges, delete_me);
}
}
if (!edge_iter_advanced)
edge_iter = edge_iter->next;
}
rect_iter = rect_iter->next;
}
return edges;
}
/**
* meta_rectangle_find_onscreen_edges: (skip)
*
* This function is trying to find all the edges of an onscreen region.
*/
GList*
meta_rectangle_find_onscreen_edges (const MetaRectangle *basic_rect,
const GSList *all_struts)
{
GList *ret;
GList *fixed_strut_rects;
GList *edge_iter;
const GList *strut_rect_iter;
/* The algorithm is basically as follows:
* Make sure the struts are disjoint
* Initialize the edge_set to the edges of basic_rect
* Foreach strut:
* Put together a preliminary new edge from the edges of the strut
* Foreach edge in edge_set:
* - Split the edge if it is partially contained inside the strut
* - If the edge matches an edge of the strut (i.e. a strut just
* against the edge of the screen or a not-next-to-edge-of-screen
* strut adjacent to another), then both the edge from the
* edge_set and the preliminary edge for the strut will need to
* be split
* Add any remaining "preliminary" strut edges to the edge_set
*/
/* Make sure the struts are disjoint */
fixed_strut_rects =
get_disjoint_strut_rect_list_in_region (all_struts, basic_rect);
/* Start off the list with the edges of basic_rect */
ret = add_edges (NULL, basic_rect, TRUE);
strut_rect_iter = fixed_strut_rects;
while (strut_rect_iter)
{
MetaRectangle *strut_rect = (MetaRectangle*) strut_rect_iter->data;
/* Get the new possible edges we may need to add from the strut */
GList *new_strut_edges = add_edges (NULL, strut_rect, FALSE);
edge_iter = ret;
while (edge_iter)
{
MetaEdge *cur_edge = edge_iter->data;
GList *splits_of_cur_edge = NULL;
gboolean edge_needs_removal = FALSE;
fix_up_edges (strut_rect, cur_edge,
&new_strut_edges, &splits_of_cur_edge,
&edge_needs_removal);
if (edge_needs_removal)
{
/* Delete the old edge */
GList *delete_me = edge_iter;
edge_iter = edge_iter->next;
g_free (cur_edge);
ret = g_list_delete_link (ret, delete_me);
/* Add the new split parts of the edge */
ret = g_list_concat (splits_of_cur_edge, ret);
}
else
{
edge_iter = edge_iter->next;
}
/* edge_iter was already advanced above */
}
ret = g_list_concat (new_strut_edges, ret);
strut_rect_iter = strut_rect_iter->next;
}
/* Sort the list */
ret = g_list_sort (ret, meta_rectangle_edge_cmp);
/* Free the fixed struts list */
meta_rectangle_free_list_and_elements (fixed_strut_rects);
return ret;
}
/**
* meta_rectangle_find_nonintersected_monitor_edges: (skip)
*
*/
GList*
meta_rectangle_find_nonintersected_monitor_edges (
const GList *monitor_rects,
const GSList *all_struts)
{
/* This function cannot easily be merged with
* meta_rectangle_find_onscreen_edges() because real screen edges
* and strut edges both are of the type "there ain't anything
* immediately on the other side"; monitor edges are different.
*/
GList *ret;
const GList *cur;
GSList *temp_rects;
/* Initialize the return list to be empty */
ret = NULL;
/* start of ret with all the edges of monitors that are adjacent to
* another monitor.
*/
cur = monitor_rects;
while (cur)
{
MetaRectangle *cur_rect = cur->data;
const GList *compare = monitor_rects;
while (compare)
{
MetaRectangle *compare_rect = compare->data;
/* Check if cur might be horizontally adjacent to compare */
if (meta_rectangle_vert_overlap(cur_rect, compare_rect))
{
MetaSide side_type;
int y = MAX (cur_rect->y, compare_rect->y);
int height = MIN (BOX_BOTTOM (*cur_rect) - y,
BOX_BOTTOM (*compare_rect) - y);
int width = 0;
int x;
if (BOX_LEFT (*cur_rect) == BOX_RIGHT (*compare_rect))
{
/* compare_rect is to the left of cur_rect */
x = BOX_LEFT (*cur_rect);
side_type = META_SIDE_LEFT;
}
else if (BOX_RIGHT (*cur_rect) == BOX_LEFT (*compare_rect))
{
/* compare_rect is to the right of cur_rect */
x = BOX_RIGHT (*cur_rect);
side_type = META_SIDE_RIGHT;
}
else
/* These rectangles aren't adjacent after all */
x = INT_MIN;
/* If the rectangles really are adjacent */
if (x != INT_MIN)
{
/* We need a left edge for the monitor on the right, and
* a right edge for the monitor on the left. Just fill
* up the edges and stick 'em on the list.
*/
MetaEdge *new_edge = g_new (MetaEdge, 1);
new_edge->rect = meta_rect (x, y, width, height);
new_edge->side_type = side_type;
new_edge->edge_type = META_EDGE_MONITOR;
ret = g_list_prepend (ret, new_edge);
}
}
/* Check if cur might be vertically adjacent to compare */
if (meta_rectangle_horiz_overlap(cur_rect, compare_rect))
{
MetaSide side_type;
int x = MAX (cur_rect->x, compare_rect->x);
int width = MIN (BOX_RIGHT (*cur_rect) - x,
BOX_RIGHT (*compare_rect) - x);
int height = 0;
int y;
if (BOX_TOP (*cur_rect) == BOX_BOTTOM (*compare_rect))
{
/* compare_rect is to the top of cur_rect */
y = BOX_TOP (*cur_rect);
side_type = META_SIDE_TOP;
}
else if (BOX_BOTTOM (*cur_rect) == BOX_TOP (*compare_rect))
{
/* compare_rect is to the bottom of cur_rect */
y = BOX_BOTTOM (*cur_rect);
side_type = META_SIDE_BOTTOM;
}
else
/* These rectangles aren't adjacent after all */
y = INT_MIN;
/* If the rectangles really are adjacent */
if (y != INT_MIN)
{
/* We need a top edge for the monitor on the bottom, and
* a bottom edge for the monitor on the top. Just fill
* up the edges and stick 'em on the list.
*/
MetaEdge *new_edge = g_new (MetaEdge, 1);
new_edge->rect = meta_rect (x, y, width, height);
new_edge->side_type = side_type;
new_edge->edge_type = META_EDGE_MONITOR;
ret = g_list_prepend (ret, new_edge);
}
}
compare = compare->next;
}
cur = cur->next;
}
temp_rects = NULL;
for (; all_struts; all_struts = all_struts->next)
temp_rects = g_slist_prepend (temp_rects,
&((MetaStrut*)all_struts->data)->rect);
ret = meta_rectangle_remove_intersections_with_boxes_from_edges (ret,
temp_rects);
g_slist_free (temp_rects);
/* Sort the list */
ret = g_list_sort (ret, meta_rectangle_edge_cmp);
return ret;
}
gboolean
meta_rectangle_is_adjacent_to (MetaRectangle *rect,
MetaRectangle *other)
{
int rect_x1 = rect->x;
int rect_y1 = rect->y;
int rect_x2 = rect->x + rect->width;
int rect_y2 = rect->y + rect->height;
int other_x1 = other->x;
int other_y1 = other->y;
int other_x2 = other->x + other->width;
int other_y2 = other->y + other->height;
if ((rect_x1 == other_x2 || rect_x2 == other_x1) &&
!(rect_y2 <= other_y1 || rect_y1 >= other_y2))
return TRUE;
else if ((rect_y1 == other_y2 || rect_y2 == other_y1) &&
!(rect_x2 <= other_x1 || rect_x1 >= other_x2))
return TRUE;
else
return FALSE;
}
void
meta_rectangle_scale_double (const MetaRectangle *rect,
double scale,
MetaRoundingStrategy rounding_strategy,
MetaRectangle *dest)
{
graphene_rect_t tmp = GRAPHENE_RECT_INIT (rect->x, rect->y,
rect->width, rect->height);
graphene_rect_scale (&tmp, scale, scale, &tmp);
meta_rectangle_from_graphene_rect (&tmp, rounding_strategy, dest);
}
/**
* meta_rectangle_transform:
* @rect: the #MetaRectangle to be transformed
* @transform: the #MetaMonitorTransform
* @width: the width of the target space
* @height: the height of the target space
* @dest: the transformed #MetaRectangle
*
* This function transforms the values in @rect in order to compensate for
* @transform applied to a #MetaMonitor, making them match the viewport. Note
* that compensating implies that for a clockwise rotation of the #MetaMonitor
* an anti-clockwise rotation has to be applied to @rect.
*/
void
meta_rectangle_transform (const MetaRectangle *rect,
MetaMonitorTransform transform,
int width,
int height,
MetaRectangle *dest)
{
switch (transform)
{
case META_MONITOR_TRANSFORM_NORMAL:
*dest = *rect;
break;
case META_MONITOR_TRANSFORM_90:
*dest = (MetaRectangle) {
.x = rect->y,
.y = height - (rect->x + rect->width),
.width = rect->height,
.height = rect->width,
};
break;
case META_MONITOR_TRANSFORM_180:
*dest = (MetaRectangle) {
.x = width - (rect->x + rect->width),
.y = height - (rect->y + rect->height),
.width = rect->width,
.height = rect->height,
};
break;
case META_MONITOR_TRANSFORM_270:
*dest = (MetaRectangle) {
.x = width - (rect->y + rect->height),
.y = rect->x,
.width = rect->height,
.height = rect->width,
};
break;
case META_MONITOR_TRANSFORM_FLIPPED:
*dest = (MetaRectangle) {
.x = width - (rect->x + rect->width),
.y = rect->y,
.width = rect->width,
.height = rect->height,
};
break;
case META_MONITOR_TRANSFORM_FLIPPED_90:
*dest = (MetaRectangle) {
.x = rect->y,
.y = rect->x,
.width = rect->height,
.height = rect->width,
};
break;
case META_MONITOR_TRANSFORM_FLIPPED_180:
*dest = (MetaRectangle) {
.x = rect->x,
.y = height - (rect->y + rect->height),
.width = rect->width,
.height = rect->height,
};
break;
case META_MONITOR_TRANSFORM_FLIPPED_270:
*dest = (MetaRectangle) {
.x = width - (rect->y + rect->height),
.y = height - (rect->x + rect->width),
.width = rect->height,
.height = rect->width,
};
break;
}
}
void
meta_rectangle_from_graphene_rect (const graphene_rect_t *rect,
MetaRoundingStrategy rounding_strategy,
MetaRectangle *dest)
{
switch (rounding_strategy)
{
case META_ROUNDING_STRATEGY_SHRINK:
{
*dest = (MetaRectangle) {
.x = ceilf (rect->origin.x),
.y = ceilf (rect->origin.y),
.width = floorf (rect->size.width),
.height = floorf (rect->size.height),
};
}
break;
case META_ROUNDING_STRATEGY_GROW:
{
graphene_rect_t clamped = *rect;
graphene_rect_round_extents (&clamped, &clamped);
*dest = (MetaRectangle) {
.x = clamped.origin.x,
.y = clamped.origin.y,
.width = clamped.size.width,
.height = clamped.size.height,
};
}
break;
case META_ROUNDING_STRATEGY_ROUND:
{
*dest = (MetaRectangle) {
.x = roundf (rect->origin.x),
.y = roundf (rect->origin.y),
.width = roundf (rect->size.width),
.height = roundf (rect->size.height),
};
}
}
}
void
meta_rectangle_crop_and_scale (const MetaRectangle *rect,
graphene_rect_t *src_rect,
int dst_width,
int dst_height,
MetaRectangle *dest)
{
graphene_rect_t tmp = GRAPHENE_RECT_INIT (rect->x, rect->y,
rect->width, rect->height);
graphene_rect_scale (&tmp,
src_rect->size.width / dst_width,
src_rect->size.height / dst_height,
&tmp);
graphene_rect_offset (&tmp, src_rect->origin.x, src_rect->origin.y);
meta_rectangle_from_graphene_rect (&tmp, META_ROUNDING_STRATEGY_GROW, dest);
}