From 489ba880f18bc527c4d56a6aa7c333d780fe7cc7 Mon Sep 17 00:00:00 2001 From: Olivier Brunel Date: Sun, 8 Mar 2015 13:22:12 +0100 Subject: [PATCH 1/3] Rewrote smartPlacement() Instead of trying to find the position with the least overlaps, including all stacked windows with the same weight, we now try to determine the place where the lowest window on stack is visible, to put the new window there. In effect, this means all windows hidden by another one are ignored, only windows visible to the user should matter. We put the new window on top of the window the lowest on stack, i.e. the last one to be activated/(fully) visible. Signed-off-by: Olivier Brunel --- src/placement.c | 414 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 270 insertions(+), 144 deletions(-) diff --git a/src/placement.c b/src/placement.c index 9090a3f2..0d94313a 100644 --- a/src/placement.c +++ b/src/placement.c @@ -542,17 +542,140 @@ clientAutoMaximize (Client * c, int full_w, int full_h) } } +static void +expand_horizontal (cairo_region_t *region, + cairo_rectangle_int_t *rect, + gint full_x, + gint full_y, + gint full_w, + gint full_h) +{ + cairo_rectangle_int_t r; + cairo_region_overlap_t overlap; + + /* a chance to expand on the left? */ + if (rect->x > full_x) + { + /* try the full expansion first */ + r.x = full_x; + r.y = rect->y; + r.width = rect->x; + r.height = rect->height; + overlap = cairo_region_contains_rectangle (region, &r); + if (overlap == CAIRO_REGION_OVERLAP_IN) + rect->x = r.x; + else if (overlap == CAIRO_REGION_OVERLAP_PART) + { + /* there might be some expansion possible */ + r.x = rect->x; + r.width = 0; + do + { + --r.x; + ++r.width; + overlap = cairo_region_contains_rectangle (region, &r); + } while (overlap == CAIRO_REGION_OVERLAP_IN); + if (r.width - 1 > 0) + rect->x = r.x + 1; + } + } + /* a chance to expand on the right? */ + if (rect->x + rect->width < full_x + full_w) + { + /* try the full expansion first */ + r.x = rect->x + rect->width; + r.y = rect->y; + r.width = full_x + full_w - r.x; + r.height = rect->height; + overlap = cairo_region_contains_rectangle (region, &r); + if (overlap == CAIRO_REGION_OVERLAP_IN) + rect->width += r.width; + else if (overlap == CAIRO_REGION_OVERLAP_PART) + { + /* there might be some expansion possible */ + r.width = 0; + do + { + ++r.width; + overlap = cairo_region_contains_rectangle (region, &r); + } while (overlap == CAIRO_REGION_OVERLAP_IN); + if (--r.width > 0) + rect->width += r.width; + } + } +} + +static void +expand_vertical (cairo_region_t *region, + cairo_rectangle_int_t *rect, + gint full_x, + gint full_y, + gint full_w, + gint full_h) +{ + cairo_rectangle_int_t r; + cairo_region_overlap_t overlap; + + /* a chance to expand on top? */ + if (rect->y > full_y) + { + /* try the full expansion first */ + r.x = rect->x; + r.y = full_y; + r.width = rect->width; + r.height = rect->y - r.y; + overlap = cairo_region_contains_rectangle (region, &r); + if (overlap == CAIRO_REGION_OVERLAP_IN) + rect->y = r.y; + else if (overlap == CAIRO_REGION_OVERLAP_PART) + { + /* there might be some expansion possible */ + r.height = 0; + r.y = rect->y; + do + { + --r.y; + ++r.height; + overlap = cairo_region_contains_rectangle (region, &r); + } while (overlap == CAIRO_REGION_OVERLAP_IN); + if (r.height - 1 > 0) + rect->y = r.y + 1; + } + } + /* a chance to expand on bottom? */ + if (rect->y + rect->height < full_y + full_h) + { + /* try the full expansion first */ + r.x = rect->x; + r.y = rect->y + rect->height; + r.width = rect->width; + r.height = full_y + full_h - r.y; + overlap = cairo_region_contains_rectangle (region, &r); + if (overlap == CAIRO_REGION_OVERLAP_IN) + rect->height += r.height; + else if (overlap == CAIRO_REGION_OVERLAP_PART) + { + /* there might be some expansion possible */ + r.height = 0; + do + { + ++r.height; + overlap = cairo_region_contains_rectangle (region, &r); + } while (overlap == CAIRO_REGION_OVERLAP_IN); + if (--r.height > 0) + rect->height += r.height; + } + } +} + static void smartPlacement (Client * c, int full_x, int full_y, int full_w, int full_h) { - Client *c2; ScreenInfo *screen_info; - gfloat best_overlaps; - guint i; - gint test_x, test_y, xmax, ymax, best_x, best_y; gint frame_height, frame_width, frame_left, frame_top; - gint c2_x, c2_y; - gint xmin, ymin; + cairo_region_t *region_monitor, *region_used, *region_hole, *region_tmp; + cairo_rectangle_int_t rect; + GList *list; g_return_if_fail (c != NULL); TRACE ("entering smartPlacement"); @@ -563,165 +686,168 @@ smartPlacement (Client * c, int full_x, int full_y, int full_w, int full_h) frame_left = frameExtentLeft(c); frame_top = frameExtentTop (c); - /* max coordinates (bottom-right) */ - xmax = full_x + full_w - c->width - frameExtentRight (c); - ymax = full_y + full_h - c->height - frameExtentBottom (c); - - /* min coordinates (top-left) */ - xmin = full_x + frameExtentLeft (c); - ymin = full_y + frameExtentTop (c); - - /* start with worst-case position at top-left */ - best_overlaps = G_MAXFLOAT; - best_x = xmin; - best_y = ymin; - - TRACE ("analyzing %i clients", screen_info->client_count); - - test_y = ymin; - do + /* region of the monitor (i.e. where we can/want to put the window) */ + rect.x = full_x; + rect.y = full_y; + rect.width = full_w; + rect.height = full_h; + region_monitor = cairo_region_create_rectangle (&rect); + /* region where we'll add visible windows (i.e. used space) */ + region_used = cairo_region_create (); + /* region of monitor we can use, i.e. monitor - used */ + region_hole = NULL; + + for (list = g_list_last (screen_info->windows_stack); list; list = g_list_previous (list)) { - gint next_test_y = G_MAXINT; - gboolean first_test_x = TRUE; + Client *c2 = list->data; + gint i, n; + gboolean done; + gint best_x, best_y; + gdouble best_surface = 0; + gboolean can_window_fit = FALSE; + + if (!clientSelectMask (c2, NULL, SEARCH_INCLUDE_SKIP_PAGER | SEARCH_INCLUDE_SKIP_TASKBAR, WINDOW_REGULAR_FOCUSABLE)) + continue; - TRACE ("testing y position %d", test_y); + /* rectangle for the window */ + rect.x = frameExtentX (c2); + rect.y = frameExtentY (c2); + rect.width = frameExtentWidth (c2); + rect.height = frameExtentHeight (c2); - test_x = xmin; - do + switch (cairo_region_contains_rectangle (region_monitor, &rect)) { - gfloat count_overlaps = 0.0; - gint next_test_x = G_MAXINT; - gint c2_next_test_x; - gint c2_next_test_y; - gint c2_frame_height; - gint c2_frame_width; - - TRACE ("testing x position %d", test_x); - - for (c2 = screen_info->clients, i = 0; i < screen_info->client_count; c2 = c2->next, i++) - { - if ((c2 != c) && (c2->type != WINDOW_DESKTOP) - && (c->win_workspace == c2->win_workspace) - && FLAG_TEST (c2->xfwm_flags, XFWM_FLAG_VISIBLE)) + case CAIRO_REGION_OVERLAP_OUT: + /* another monitor, ignore it */ + continue; + case CAIRO_REGION_OVERLAP_IN: + /* add the window's rect to region_used */ + cairo_region_union_rectangle (region_used, &rect); + break; + case CAIRO_REGION_OVERLAP_PART: { - c2_x = frameExtentX (c2); - c2_frame_width = frameExtentWidth (c2); - if (c2_x >= full_x + full_w - || c2_x + c2_frame_width < full_x) - { - /* skip clients on right-of or left-of monitor */ - continue; - } - - c2_y = frameExtentY (c2); - c2_frame_height = frameExtentHeight (c2); - if (c2_y >= full_y + full_h - || c2_y + c2_frame_height < full_y) - { - /* skip clients on above-of or below-of monitor */ - continue; - } - - count_overlaps += overlap (test_x - frame_left, - test_y - frame_top, - test_x - frame_left + frame_width, - test_y - frame_top + frame_height, - c2_x, - c2_y, - c2_x + c2_frame_width, - c2_y + c2_frame_height); - - /* find the next x boundy for the step */ - if (test_x > c2_x) - { - /* test location is beyond the x of the window, - * take the window right corner as next target */ - c2_x += c2_frame_width; - } - c2_next_test_x = MIN (c2_x, xmax); - if (c2_next_test_x < next_test_x - && c2_next_test_x > test_x) - { - /* set new optimal next x step position */ - next_test_x = c2_next_test_x; - } - - if (first_test_x) - { - /* find the next y boundry step */ - if (test_y > c2_y) - { - /* test location is beyond the y of the window, - * take the window bottom corner as next target */ - c2_y += c2_frame_height; - } - c2_next_test_y = MIN (c2_y, ymax); - if (c2_next_test_y < next_test_y - && c2_next_test_y > test_y) - { - /* set new optimal next y step position */ - next_test_y = c2_next_test_y; - } - } + /* get only the part of the window on our monitor */ + region_tmp = cairo_region_copy (region_monitor); + cairo_region_intersect_rectangle (region_tmp, &rect); + /* and add it to region_used */ + cairo_region_union (region_used, region_tmp); + cairo_region_destroy (region_tmp); } - } + } - /* don't look for the next y boundry this x row */ - first_test_x = FALSE; + /* get a region of the monitor - visible window, i.e. leaving space + * where we can put out window. The goal is to reach a place where there + * isn't such space, and then go back one iteration, so we use the space + * of the visible (to the user) window the lowest on the stack */ + region_tmp = cairo_region_copy (region_monitor); + cairo_region_subtract (region_tmp, region_used); + + /* is there still free space left? */ + n = cairo_region_num_rectangles (region_tmp); + done = TRUE; + for (i = 0; i < n; ++i) + { + cairo_region_get_rectangle (region_tmp, i, &rect); + /* ignore little areas between windows */ + if (rect.width <= 15 || rect.height <= 15) + continue; + done = FALSE; + break; + } + if (!done) + { + if (region_hole) + cairo_region_destroy (region_hole); + region_hole = region_tmp; + continue; + } + cairo_region_destroy (region_tmp); - if (count_overlaps < best_overlaps) - { - /* found position with less overlap */ - best_x = test_x; - best_y = test_y; - best_overlaps = count_overlaps; + /* got nothing, use defaults */ + if (!region_hole) + break; - if (count_overlaps == 0.0f) - { - /* overlap is ideal, stop searching */ - TRACE ("found position without overlap"); - goto found_best; - } - } + cairo_region_destroy (region_monitor); + cairo_region_destroy (region_used); - if (G_LIKELY (next_test_x != G_MAXINT)) - { - test_x = MAX (next_test_x, next_test_x + frameExtentLeft (c)); - if (test_x > xmax) - { - /* always clamp on the monitor */ - test_x = xmax; - } - } - else + n = cairo_region_num_rectangles (region_hole); + for (i = 0; i < n; ++i) + { + cairo_rectangle_int_t r; + gint exp_x, exp_y; + gdouble exp_surface; + gboolean exp_can_window_fit; + gdouble surface; + gboolean can_fit; + + cairo_region_get_rectangle (region_hole, i, &rect); + + /* expand horizontally, then vertically */ + expand_horizontal (region_hole, &rect, full_x, full_y, full_w, full_h); + expand_vertical (region_hole, &rect, full_x, full_y, full_w, full_h); + exp_x = rect.x; + exp_y = rect.y; + exp_surface = rect.width * rect.height; + exp_can_window_fit = frame_width <= rect.width && frame_height <= rect.height; + + /* expand vertically, then horizontally */ + expand_vertical (region_hole, &rect, full_x, full_y, full_w, full_h); + expand_horizontal (region_hole, &rect, full_x, full_y, full_w, full_h); + /* keep the best one */ + surface = rect.width * rect.height; + can_fit = frame_width <= rect.width && frame_height <= rect.height; + /* if we can now fit the window, or still can't but in a larger + * rect; or still can but in a smaller rect */ + if ((!exp_can_window_fit && (can_fit || surface > exp_surface)) + || (exp_can_window_fit && can_fit && surface < exp_surface)) { - test_x++; + exp_can_window_fit = can_fit; + exp_surface = surface; + exp_x = rect.x; + exp_y = rect.y; } - } - while (test_x <= xmax); - if (G_LIKELY (next_test_y != G_MAXINT)) - { - test_y = MAX (next_test_y, next_test_y + frameExtentTop (c)); - if (test_y > ymax) + /* is this the new best result ? (same criteria) */ + if (best_surface == 0 /* our first result */ + || (!can_window_fit && (exp_can_window_fit || exp_surface > best_surface)) + || (can_window_fit && exp_can_window_fit && exp_surface < best_surface)) { - /* always clamp on the monitor */ - test_y = ymax; + can_window_fit = exp_can_window_fit; + best_surface = exp_surface; + best_x = exp_x; + best_y = exp_y; } } - else + cairo_region_destroy (region_hole); + + c->x = best_x; + c->y = best_y; + + /* unless it could fit, make sure it's fully within monitor */ + if (!can_window_fit) { - test_y++; + /* move to the left if needed */ + n = c->x + frame_width - full_x - full_w; + if (n > 0) + c->x -= n; + /* move to the top if needed */ + n = c->y + frame_height - full_y - full_h; + if (n > 0) + c->y -= n; } - } - while (test_y <= ymax); - found_best: + /* add frames */ + c->x += frame_left; + c->y += frame_top; + + return; + } - TRACE ("overlaps %f at %d,%d (x,y)", best_overlaps, best_x, best_y); + cairo_region_destroy (region_monitor); + cairo_region_destroy (region_used); - c->x = best_x; - c->y = best_y; + c->x = full_x + frame_left; + c->y = full_y + frame_top; } static void -- 2.15.1