window: Add new tiling code

The new tiling code, instead of based around "tiling states", is instead
based around constrained edges. This allows us to have windows that have
three constrained edges, but keep one free-floating, e.g. a window tiled
to the left has the left, top, and bottom edges constrained, but the
right edge can be left resizable.

This system also is easily extended to support corner tiling. We also,
using the new "size state" system, also keep normal, tiled, and
maximized sizes independently, allowing the maximize button to bounce
between maximized and tiled states without reverting to normal in
between. Dragging from the top will always restore the normal state,
though.

https://bugzilla.gnome.org/show_bug.cgi?id=751857
This commit is contained in:
Jasper St. Pierre 2015-06-30 18:10:58 -07:00
parent 2c7ef2269f
commit 50e3e3b929
8 changed files with 353 additions and 55 deletions

View File

@ -97,7 +97,7 @@ typedef enum
PRIORITY_ENTIRELY_VISIBLE_ON_WORKAREA = 1,
PRIORITY_SIZE_HINTS_INCREMENTS = 1,
PRIORITY_MAXIMIZATION = 2,
PRIORITY_TILING = 2,
PRIORITY_CONSTRAINED_EDGES = 2,
PRIORITY_FULLSCREEN = 2,
PRIORITY_SIZE_HINTS_LIMITS = 3,
PRIORITY_TITLEBAR_VISIBLE = 4,
@ -152,6 +152,10 @@ static gboolean constrain_maximization (MetaWindow *window,
ConstraintInfo *info,
ConstraintPriority priority,
gboolean check_only);
static gboolean constrain_constrained_edges (MetaWindow *window,
ConstraintInfo *info,
ConstraintPriority priority,
gboolean check_only);
static gboolean constrain_fullscreen (MetaWindow *window,
ConstraintInfo *info,
ConstraintPriority priority,
@ -209,6 +213,7 @@ typedef struct {
static const Constraint all_constraints[] = {
{constrain_modal_dialog, "constrain_modal_dialog"},
{constrain_maximization, "constrain_maximization"},
{constrain_constrained_edges, "constrain_constrained_edges"},
{constrain_fullscreen, "constrain_fullscreen"},
{constrain_size_increments, "constrain_size_increments"},
{constrain_size_limits, "constrain_size_limits"},
@ -798,6 +803,45 @@ constrain_fullscreen (MetaWindow *window,
return TRUE;
}
static gboolean
constrain_constrained_edges (MetaWindow *window,
ConstraintInfo *info,
ConstraintPriority priority,
gboolean check_only)
{
MetaRectangle monitor, new_rectangle;
gboolean constraint_already_satisfied;
if (priority > PRIORITY_CONSTRAINED_EDGES)
return TRUE;
/* Determine whether constraint applies; exit if it doesn't */
if (!window->constrained_edges)
return TRUE;
new_rectangle = info->current;
monitor = info->work_area_monitor;
if (window->constrained_edges & META_DIRECTION_LEFT)
new_rectangle.x = monitor.x;
if (window->constrained_edges & META_DIRECTION_RIGHT)
new_rectangle.x = monitor.x + monitor.width - new_rectangle.width;
if (window->constrained_edges & META_DIRECTION_TOP)
new_rectangle.y = monitor.y;
if (window->constrained_edges & META_DIRECTION_BOTTOM)
new_rectangle.y = monitor.y + monitor.height - new_rectangle.height;
constraint_already_satisfied =
meta_rectangle_equal (&info->current, &new_rectangle);
if (check_only || constraint_already_satisfied)
return constraint_already_satisfied;
/*** Enforce constraint ***/
info->current = new_rectangle;
return TRUE;
}
static gboolean
constrain_size_increments (MetaWindow *window,
ConstraintInfo *info,

View File

@ -2936,6 +2936,7 @@ handle_toggle_tiled_left (MetaDisplay *display,
MetaKeyBinding *binding,
gpointer dummy)
{
meta_window_tile (window, META_TILE_ZONE_W);
}
static void
@ -2946,6 +2947,7 @@ handle_toggle_tiled_right (MetaDisplay *display,
MetaKeyBinding *binding,
gpointer dummy)
{
meta_window_tile (window, META_TILE_ZONE_E);
}
static void

View File

@ -57,7 +57,13 @@ struct _MetaScreen
MetaRectangle rect; /* Size of screen; rect.x & rect.y are always 0 */
MetaUI *ui;
guint tile_preview_timeout_id;
struct {
gboolean exists;
guint timeout_id;
MetaWindow *window;
MetaRectangle area;
int monitor;
} tile_preview;
MetaWorkspace *active_workspace;
@ -136,9 +142,12 @@ void meta_screen_foreach_window (MetaScreen *scree
void meta_screen_update_cursor (MetaScreen *screen);
void meta_screen_update_tile_preview (MetaScreen *screen,
gboolean delay);
void meta_screen_hide_tile_preview (MetaScreen *screen);
void meta_screen_update_tile_preview (MetaScreen *screen,
MetaWindow *window,
MetaRectangle area,
int monitor,
gboolean delay);
void meta_screen_hide_tile_preview (MetaScreen *screen);
MetaWindow* meta_screen_get_mouse_window (MetaScreen *screen,
MetaWindow *not_this_one);

View File

@ -723,8 +723,6 @@ meta_screen_new (MetaDisplay *display,
screen->ui = meta_ui_new (screen->display->xdisplay,
screen->xscreen);
screen->tile_preview_timeout_id = 0;
screen->stack = meta_stack_new (screen);
screen->stack_tracker = meta_stack_tracker_new (screen);
@ -841,8 +839,8 @@ meta_screen_free (MetaScreen *screen,
g_free (screen->monitor_infos);
if (screen->tile_preview_timeout_id)
g_source_remove (screen->tile_preview_timeout_id);
if (screen->tile_preview.timeout_id)
g_source_remove (screen->tile_preview.timeout_id);
g_free (screen->screen_name);
@ -1320,43 +1318,69 @@ meta_screen_set_cursor (MetaScreen *screen,
static gboolean
meta_screen_update_tile_preview_timeout (gpointer data)
{
MetaScreen *screen = data;
screen->tile_preview.timeout_id = 0;
if (screen->tile_preview.exists)
{
meta_compositor_show_tile_preview (screen->display->compositor,
screen->tile_preview.window,
&screen->tile_preview.area,
screen->tile_preview.monitor);
}
else
{
meta_compositor_hide_tile_preview (screen->display->compositor);
}
return FALSE;
}
#define TILE_PREVIEW_TIMEOUT_MS 200
void
meta_screen_update_tile_preview (MetaScreen *screen,
gboolean delay)
meta_screen_update_tile_preview (MetaScreen *screen,
MetaWindow *window,
MetaRectangle area,
int monitor,
gboolean delay)
{
screen->tile_preview.exists = TRUE;
screen->tile_preview.window = window;
screen->tile_preview.area = area;
screen->tile_preview.monitor = monitor;
if (delay)
{
if (screen->tile_preview_timeout_id > 0)
if (screen->tile_preview.timeout_id > 0)
return;
screen->tile_preview_timeout_id =
screen->tile_preview.timeout_id =
g_timeout_add (TILE_PREVIEW_TIMEOUT_MS,
meta_screen_update_tile_preview_timeout,
screen);
g_source_set_name_by_id (screen->tile_preview_timeout_id,
g_source_set_name_by_id (screen->tile_preview.timeout_id,
"[mutter] meta_screen_update_tile_preview_timeout");
}
else
{
if (screen->tile_preview_timeout_id > 0)
g_source_remove (screen->tile_preview_timeout_id);
if (screen->tile_preview.timeout_id > 0)
g_source_remove (screen->tile_preview.timeout_id);
meta_screen_update_tile_preview_timeout ((gpointer)screen);
meta_screen_update_tile_preview_timeout ((gpointer) screen);
}
}
void
meta_screen_hide_tile_preview (MetaScreen *screen)
{
if (screen->tile_preview_timeout_id > 0)
g_source_remove (screen->tile_preview_timeout_id);
screen->tile_preview.exists = FALSE;
meta_compositor_hide_tile_preview (screen->display->compositor);
if (screen->tile_preview.timeout_id > 0)
g_source_remove (screen->tile_preview.timeout_id);
meta_screen_update_tile_preview_timeout ((gpointer) screen);
}
MetaWindow*

View File

@ -450,8 +450,10 @@ struct _MetaWindow
/* This is where we store data while in another state. The information
* above in MetaWindow is always the current state of the window. */
struct {
MetaWindowSizeState normal, maximized;
MetaWindowSizeState normal, tiled, maximized;
} size_states;
MetaDirection constrained_edges;
};
struct _MetaWindowClass
@ -617,7 +619,6 @@ void meta_window_update_for_monitors_changed (MetaWindow *window);
void meta_window_on_all_workspaces_changed (MetaWindow *window);
gboolean meta_window_should_attach_to_parent (MetaWindow *window);
gboolean meta_window_can_tile_side_by_side (MetaWindow *window);
gboolean meta_window_updates_are_frozen (MetaWindow *window);

View File

@ -2609,6 +2609,8 @@ get_current_size_state (MetaWindow *window)
{
if (META_WINDOW_MAXIMIZED (window))
return &window->size_states.maximized;
else if (window->constrained_edges != 0)
return &window->size_states.tiled;
else
return &window->size_states.normal;
}
@ -2880,35 +2882,48 @@ meta_window_requested_dont_bypass_compositor (MetaWindow *window)
return window->bypass_compositor == _NET_WM_BYPASS_COMPOSITOR_HINT_OFF;
}
static gboolean
meta_window_can_tile_maximized (MetaWindow *window)
static void
get_tile_zone_area (MetaWindow *window,
MetaTileZone tile_zone,
MetaRectangle *rect_p)
{
return window->has_maximize_func;
}
MetaRectangle work_area;
meta_window_get_work_area_for_monitor (window, window->monitor->number, &work_area);
gboolean
meta_window_can_tile_side_by_side (MetaWindow *window)
{
int monitor;
MetaRectangle tile_area;
MetaRectangle client_rect;
MetaRectangle rect = {
.x = work_area.x,
.y = work_area.y,
};
if (!meta_window_can_tile_maximized (window))
return FALSE;
switch (tile_zone & META_DIRECTION_HORIZONTAL)
{
case META_DIRECTION_HORIZONTAL:
rect.width = work_area.width;
break;
case META_DIRECTION_LEFT:
rect.width = work_area.width / 2;
break;
case META_DIRECTION_RIGHT:
rect.x += work_area.width / 2;
rect.width = work_area.width / 2;
break;
}
monitor = meta_screen_get_current_monitor (window->screen);
meta_window_get_work_area_for_monitor (window, monitor, &tile_area);
switch (tile_zone & META_DIRECTION_VERTICAL)
{
case META_DIRECTION_VERTICAL:
rect.height = work_area.height;
break;
case META_DIRECTION_TOP:
rect.height = work_area.height / 2;
break;
case META_DIRECTION_BOTTOM:
rect.y += work_area.height / 2;
rect.height = work_area.height / 2;
break;
}
/* Do not allow tiling in portrait orientation */
if (tile_area.height > tile_area.width)
return FALSE;
tile_area.width /= 2;
meta_window_frame_rect_to_client_rect (window, &tile_area, &client_rect);
return client_rect.width >= window->size_hints.min_width &&
client_rect.height >= window->size_hints.min_height;
*rect_p = rect;
}
static void
@ -3022,7 +3037,10 @@ meta_window_unmaximize (MetaWindow *window,
g_assert (unmaximize_horizontally || unmaximize_vertically);
MetaWindowSizeState *size_state;
size_state = &window->size_states.normal;
if (window->constrained_edges != 0)
size_state = &window->size_states.tiled;
else
size_state = &window->size_states.normal;
/* Special-case unmaximizing both directions to restoring the
* last state. This makes the unmaximize buttons go back to the
@ -3085,6 +3103,12 @@ meta_window_unmaximize (MetaWindow *window,
new_size_state.rect = target_rect;
/* Clear any constrained edges when unmaximizing */
if (unmaximize_horizontally)
window->constrained_edges &= ~META_DIRECTION_HORIZONTAL;
if (unmaximize_vertically)
window->constrained_edges &= ~META_DIRECTION_VERTICAL;
meta_window_unmaximize_internal (window, &new_size_state);
}
}
@ -5485,6 +5509,119 @@ update_move_timeout (gpointer data)
return FALSE;
}
typedef enum {
META_RECT_EDGE_W = 1 << 0, /* west */
META_RECT_EDGE_E = 1 << 1, /* east */
META_RECT_EDGE_H = 1 << 2, /* horizontal (middle) */
META_RECT_EDGE_N = 1 << 3, /* north */
META_RECT_EDGE_S = 1 << 4, /* south */
META_RECT_EDGE_V = 1 << 5, /* vertical (middle) */
} MetaRectEdges;
typedef struct {
int x1, y1, x2, y2;
} Box;
static MetaRectEdges
get_rect_edges (Box outer, Box inner, int x, int y)
{
MetaDirection edges = 0;
if (x >= outer.x1 && x < inner.x1)
edges |= META_RECT_EDGE_W;
else if (x >= inner.x2 && x < outer.x2)
edges |= META_RECT_EDGE_E;
else if (x >= inner.x1 && x < inner.x2)
edges |= META_RECT_EDGE_H;
if (y >= outer.y1 && y < inner.y1)
edges |= META_RECT_EDGE_N;
else if (y >= inner.y2 && y < outer.y2)
edges |= META_RECT_EDGE_S;
else if (y >= inner.y1 && y < inner.y2)
edges |= META_RECT_EDGE_V;
return edges;
}
static inline Box
box_from_rect (MetaRectangle rect)
{
Box box = { .x1 = rect.x, .y1 = rect.y, .x2 = rect.x + rect.width, .y2 = rect.y + rect.height };
return box;
}
static inline int
get_drag_shake_threshold (void)
{
/* Originally for detaching maximized windows, but we use this
* for the zones at the sides of the monitor where trigger tiling
* because it's about the right size
*/
#define DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR 6
return meta_prefs_get_drag_threshold () * DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR;
}
static MetaTileZone
get_tile_zone_for_pointer (MetaWindow *window,
int x, int y)
{
if (!window->has_maximize_func)
return 0;
const MetaMonitorInfo *monitor;
MetaRectangle work_area;
monitor = meta_screen_get_current_monitor_info_for_pos (window->screen, x, y);
meta_window_get_work_area_for_monitor (window, monitor->number, &work_area);
MetaRectangle client_rect;
meta_window_frame_rect_to_client_rect (window, &work_area, &client_rect);
gboolean can_tile_horz = ((client_rect.width / 2) >= window->size_hints.min_width);
gboolean can_tile_vert = ((client_rect.height / 2) >= window->size_hints.min_height);
gboolean can_max_horz = (client_rect.width >= window->size_hints.min_width);
gboolean can_max_vert = (client_rect.height >= window->size_hints.min_height);
int shake_threshold = get_drag_shake_threshold ();
Box outer = box_from_rect (monitor->rect);
/* fudge the work area with the shake threshold */
Box inner = box_from_rect (work_area);
inner.x1 = MAX (inner.x1, shake_threshold);
inner.x2 = MIN (outer.x2 - shake_threshold, inner.x2);
inner.y1 = MAX (inner.y1, shake_threshold);
inner.y2 = MIN (outer.y2 - shake_threshold, inner.y2);
MetaRectEdges edges = get_rect_edges (outer, inner, x, y);
/* Simple cases: outside of a monitor, or in the inner rectangle
* entirely aren't tile zones. */
if (edges == 0 ||
edges == (META_RECT_EDGE_H | META_RECT_EDGE_V))
return 0;
/* Special case: the top border is maximization */
if (edges == (META_RECT_EDGE_N | META_RECT_EDGE_H))
return META_TILE_ZONE_MAXIMIZED;
MetaTileZone tile_zone = 0;
if ((edges & META_RECT_EDGE_W) && can_tile_horz)
tile_zone |= META_DIRECTION_LEFT;
else if ((edges & META_RECT_EDGE_E) && can_tile_horz)
tile_zone |= META_DIRECTION_RIGHT;
else if ((edges & META_RECT_EDGE_H) && can_max_horz)
tile_zone |= META_DIRECTION_HORIZONTAL;
if ((edges & META_RECT_EDGE_N) && can_tile_vert)
tile_zone |= META_DIRECTION_TOP;
else if ((edges & META_RECT_EDGE_S) && can_tile_vert)
tile_zone |= META_DIRECTION_BOTTOM;
else if ((edges & META_RECT_EDGE_V) && can_max_vert)
tile_zone |= META_DIRECTION_VERTICAL;
return tile_zone;
}
static void
update_move (MetaWindow *window,
gboolean snap,
@ -5521,20 +5658,36 @@ update_move (MetaWindow *window,
if (dx == 0 && dy == 0)
return;
/* Originally for detaching maximized windows, but we use this
* for the zones at the sides of the monitor where trigger tiling
* because it's about the right size
*/
#define DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR 6
shake_threshold = meta_prefs_get_drag_threshold () *
DRAG_THRESHOLD_TO_SHAKE_THRESHOLD_FACTOR;
shake_threshold = get_drag_shake_threshold ();
/* shake loose (unmaximize) maximized or tiled window if dragged beyond
* the threshold in the Y direction. Tiled windows can also be pulled
* loose via X motion.
*/
int shake_x_threshold = G_MAXINT;
if ((META_WINDOW_MAXIMIZED (window) && ABS (dy) >= shake_threshold))
if ((window->constrained_edges & META_DIRECTION_HORIZONTAL) &&
(window->constrained_edges & META_DIRECTION_HORIZONTAL) != META_DIRECTION_HORIZONTAL)
shake_x_threshold = shake_threshold;
if (meta_prefs_get_edge_tiling ())
{
MetaTileZone tile_zone = get_tile_zone_for_pointer (window, x, y);
if (tile_zone != 0)
{
MetaRectangle tile_area;
get_tile_zone_area (window, tile_zone, &tile_area);
const MetaMonitorInfo *monitor = meta_screen_get_current_monitor_info_for_pos (window->screen, x, y);
meta_screen_update_tile_preview (window->screen, window, tile_area, monitor->number, TRUE);
}
else
{
meta_screen_hide_tile_preview (window->screen);
}
}
if ((META_WINDOW_MAXIMIZED (window) || window->constrained_edges) &&
(ABS (dy) >= shake_threshold || ABS (dx) >= shake_x_threshold))
{
double prop;
@ -5564,6 +5717,8 @@ update_move (MetaWindow *window,
window->size_states.normal.rect.x = display->grab_initial_window_pos.x;
window->size_states.normal.rect.y = display->grab_initial_window_pos.y;
window->constrained_edges = 0;
meta_window_unmaximize_internal (window, &window->size_states.normal);
return;
}
@ -5818,6 +5973,10 @@ end_grab_op (MetaWindow *window,
update_move (window,
modifiers & CLUTTER_SHIFT_MASK,
x, y);
MetaTileZone tile_zone = get_tile_zone_for_pointer (window, x, y);
if (tile_zone != 0)
meta_window_tile (window, tile_zone);
}
else if (meta_grab_op_is_resizing (window->display->grab_op))
{
@ -5827,6 +5986,9 @@ end_grab_op (MetaWindow *window,
TRUE);
}
}
meta_screen_hide_tile_preview (window->screen);
meta_display_end_grab_op (window->display, clutter_event_get_time (event));
}
@ -7643,3 +7805,36 @@ meta_window_emit_size_changed (MetaWindow *window)
{
g_signal_emit (window, window_signals[SIZE_CHANGED], 0);
}
void
meta_window_tile (MetaWindow *window,
MetaTileZone tile_zone)
{
/* Special case: Tile maximizing is the same as maximizing. */
if (tile_zone == META_TILE_ZONE_MAXIMIZED)
{
meta_window_maximize (window, META_MAXIMIZE_BOTH);
return;
}
MetaWindowSizeState *size_state = get_current_size_state (window);
save_size_state (window, size_state, &window->rect);
window->constrained_edges = tile_zone;
if ((window->constrained_edges & META_DIRECTION_VERTICAL) == META_DIRECTION_VERTICAL)
window->maximized_vertically = TRUE;
if ((window->constrained_edges & META_DIRECTION_HORIZONTAL) == META_DIRECTION_HORIZONTAL)
window->maximized_horizontally = TRUE;
meta_window_recalc_features (window);
set_net_wm_state (window);
MetaRectangle target_rect;
get_tile_zone_area (window, tile_zone, &target_rect);
meta_window_move_resize_internal (window,
META_MOVE_RESIZE_MOVE_ACTION | META_MOVE_RESIZE_RESIZE_ACTION | META_MOVE_RESIZE_STATE_CHANGED,
NorthWestGravity,
target_rect);
}

View File

@ -317,6 +317,26 @@ typedef enum
META_DIRECTION_VERTICAL = META_DIRECTION_UP | META_DIRECTION_DOWN,
} MetaDirection;
/* Tile zones are specified in terms of their constrained edges.
* So, "top left corner" is top and left, which should be obvious,
* but "left side of the screen" is top, bottom, and left
*/
typedef enum {
META_TILE_ZONE_MAXIMIZED_HORZ = META_DIRECTION_HORIZONTAL,
META_TILE_ZONE_MAXIMIZED_VERT = META_DIRECTION_VERTICAL,
META_TILE_ZONE_MAXIMIZED = META_TILE_ZONE_MAXIMIZED_HORZ | META_TILE_ZONE_MAXIMIZED_VERT,
META_TILE_ZONE_W = META_TILE_ZONE_MAXIMIZED_VERT | META_DIRECTION_LEFT,
META_TILE_ZONE_E = META_TILE_ZONE_MAXIMIZED_VERT | META_DIRECTION_RIGHT,
META_TILE_ZONE_N = META_TILE_ZONE_MAXIMIZED_HORZ | META_DIRECTION_TOP,
META_TILE_ZONE_S = META_TILE_ZONE_MAXIMIZED_HORZ | META_DIRECTION_BOTTOM,
META_TILE_ZONE_NW = META_DIRECTION_TOP | META_DIRECTION_LEFT,
META_TILE_ZONE_NE = META_DIRECTION_TOP | META_DIRECTION_RIGHT,
META_TILE_ZONE_SW = META_DIRECTION_BOTTOM | META_DIRECTION_LEFT,
META_TILE_ZONE_SE = META_DIRECTION_BOTTOM | META_DIRECTION_RIGHT,
} MetaTileZone;
/**
* MetaMotionDirection:
* @META_MOTION_UP: Upwards motion

View File

@ -258,4 +258,7 @@ gboolean meta_window_is_client_decorated (MetaWindow *window);
gboolean meta_window_titlebar_is_onscreen (MetaWindow *window);
void meta_window_shove_titlebar_onscreen (MetaWindow *window);
void meta_window_tile (MetaWindow *window,
MetaTileZone tile_zone);
#endif