diff --git a/src/core/constraints.c b/src/core/constraints.c index 9247ce6cd..e0e3490cf 100644 --- a/src/core/constraints.c +++ b/src/core/constraints.c @@ -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, diff --git a/src/core/keybindings.c b/src/core/keybindings.c index adeb9bd8e..6bfa69f67 100644 --- a/src/core/keybindings.c +++ b/src/core/keybindings.c @@ -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 diff --git a/src/core/screen-private.h b/src/core/screen-private.h index 438d1f4cc..76ce2f274 100644 --- a/src/core/screen-private.h +++ b/src/core/screen-private.h @@ -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); diff --git a/src/core/screen.c b/src/core/screen.c index 14193d41b..6a5eebfbd 100644 --- a/src/core/screen.c +++ b/src/core/screen.c @@ -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* diff --git a/src/core/window-private.h b/src/core/window-private.h index 7a2e5d890..f4eb4685a 100644 --- a/src/core/window-private.h +++ b/src/core/window-private.h @@ -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); diff --git a/src/core/window.c b/src/core/window.c index 34a36259f..976deda01 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -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); +} diff --git a/src/meta/common.h b/src/meta/common.h index a18be7f45..19bbc3c56 100644 --- a/src/meta/common.h +++ b/src/meta/common.h @@ -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 diff --git a/src/meta/window.h b/src/meta/window.h index a3a4b768a..a5b8da9dd 100644 --- a/src/meta/window.h +++ b/src/meta/window.h @@ -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