...and unify region overlay and snap-to-edge overlay into overlay.c.
Snap-to-edge overlay is delayed for 500ms to prevent flickering when
the view is dragged from an output to another (demo in discussion labwc#1613).
This also fixes a bug that region overlay is not shown when a modifier
key is re-pressed.
SnapToEdge action for that edge. A *range* of 0 disables snapping via
interactive moves. Default is 1.
+*<snapping><preview><enabled>* [yes|no]
+ Show a preview when snaping to a window to an edge. Default is yes.
+
+*<snapping><preview><delay><inner>*++
+*<snapping><preview><delay><outer>*
+ Sets the delay to show a preview when snapping a window to each type of edges.
+ Defaults are 500 ms.
+ *inner* edges are edges with an adjacent output and *outer* edges are edges
+ without an adjacent output.
+
*<snapping><topMaximize>* [yes|no]
If *yes*, an interactive move that snaps a window to the top edge will
maximize the window. If *no*, snapping will behave as it does with other
<snapping>
<!-- Set range to 0 to disable window snapping completely -->
<range>1</range>
+ <preview enabled="yes">
+ <delay inner="500" outer="500" />
+ </preview>
<topMaximize>yes</topMaximize>
<notifyClient>always</notifyClient>
</snapping>
/* window snapping */
int snap_edge_range;
+ bool snap_preview_enabled;
+ int snap_preview_delay_inner;
+ int snap_preview_delay_outer;
bool snap_top_maximize;
enum tiling_events_mode snap_tiling_events_mode;
#include "config/rcxml.h"
#include "input/cursor.h"
#include "input/ime.h"
+#include "overlay.h"
#include "regions.h"
#include "session-lock.h"
#if HAVE_NLS
struct wlr_scene_tree *icons;
} drag;
- /* Private use by regions.c */
- struct region *region_active;
- struct region_overlay region_overlay;
+ struct overlay overlay;
/* Used to prevent region snapping when starting a move with A-Left */
bool region_prevent_snap;
void interactive_begin(struct view *view, enum input_mode mode, uint32_t edges);
void interactive_finish(struct view *view);
void interactive_cancel(struct view *view);
+/* Possibly returns VIEW_EDGE_CENTER if <topMaximize> is yes */
+enum view_edge edge_from_cursor(struct seat *seat, struct output **dest_output);
void output_init(struct server *server);
void output_manager_init(struct server *server);
--- /dev/null
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef LABWC_OVERLAY_H
+#define LABWC_OVERLAY_H
+
+#include <wlr/util/box.h>
+#include "common/graphic-helpers.h"
+#include "regions.h"
+#include "view.h"
+
+struct overlay {
+ struct wlr_scene_tree *tree;
+ union {
+ struct wlr_scene_rect *rect;
+ struct multi_rect *pixman_rect;
+ };
+
+ /* Represents currently shown or delayed overlay */
+ struct {
+ /* Region overlay */
+ struct region *region;
+
+ /* Snap-to-edge overlay */
+ enum view_edge edge;
+ struct output *output;
+ } active;
+
+ /* For delayed snap-to-edge overlay */
+ struct wl_event_source *timer;
+};
+
+/* Calls overlay_hide() internally if there's no overlay to show */
+void overlay_update(struct seat *seat);
+/* This function must be called when server->grabbed_view is destroyed */
+void overlay_hide(struct seat *seat);
+
+#endif
} center;
};
-struct region_overlay {
- struct wlr_scene_tree *tree;
- union {
- struct wlr_scene_rect *overlay;
- struct multi_rect *pixman_overlay;
- };
-};
-
/* Returns true if we should show the region overlay or snap to region */
bool regions_should_snap(struct server *server);
struct region *regions_from_cursor(struct server *server);
struct region *regions_from_name(const char *region_name, struct output *output);
-void regions_show_overlay(struct view *view, struct seat *seat, struct region *region);
-void regions_hide_overlay(struct seat *seat);
-
#endif /* LABWC_REGIONS_H */
rc.window_edge_strength = atoi(content);
} else if (!strcasecmp(nodename, "range.snapping")) {
rc.snap_edge_range = atoi(content);
+ } else if (!strcasecmp(nodename, "enabled.preview.snapping")) {
+ set_bool(content, &rc.snap_preview_enabled);
+ } else if (!strcasecmp(nodename, "inner.delay.preview.snapping")) {
+ rc.snap_preview_delay_inner = atoi(content);
+ } else if (!strcasecmp(nodename, "outer.delay.preview.snapping")) {
+ rc.snap_preview_delay_outer = atoi(content);
} else if (!strcasecmp(nodename, "topMaximize.snapping")) {
set_bool(content, &rc.snap_top_maximize);
} else if (!strcasecmp(nodename, "notifyClient.snapping")) {
rc.window_edge_strength = 20;
rc.snap_edge_range = 1;
+ rc.snap_preview_enabled = true;
+ rc.snap_preview_delay_inner = 500;
+ rc.snap_preview_delay_outer = 500;
rc.snap_top_maximize = true;
rc.snap_tiling_events_mode = LAB_TILING_EVENTS_ALWAYS;
if (node == &server->seat.drag.icons->node) {
return "seat->drag.icons";
}
- if (server->seat.region_overlay.tree
- && node == &server->seat.region_overlay.tree->node) {
+ if (server->seat.overlay.tree
+ && node == &server->seat.overlay.tree->node) {
/* Created on-demand */
- return "seat->region_overlay";
+ return "seat->overlay";
}
if (server->seat.input_method_relay->popup_tree
&& node == &server->seat.input_method_relay->popup_tree->node) {
resistance_move_apply(view, &dx, &dy);
view_move(view, dx, dy);
- /* Region overlay */
- if (!regions_should_snap(server)) {
- return;
- }
- struct region *region = regions_from_cursor(server);
- if (region) {
- regions_show_overlay(view, &server->seat, region);
- } else {
- regions_hide_overlay(&server->seat);
- }
+ overlay_update(&server->seat);
}
static void
if (server->input_mode == LAB_INPUT_STATE_MOVE) {
/* Any change to the modifier state re-enable region snap */
seat->region_prevent_snap = false;
+ /* Pressing/releasing modifier key may show/hide region overlay */
+ overlay_update(seat);
}
- if (server->osd_state.cycle_view || server->grabbed_view
+ if (server->osd_state.cycle_view
|| seat->workspace_osd_shown_by_modifier) {
- if (!keyboard_any_modifiers_pressed(wlr_keyboard)) {
+ if (!keyboard_any_modifiers_pressed(wlr_keyboard)) {
if (server->osd_state.cycle_view) {
if (key_state_nr_bound_keys()) {
should_cancel_cycling_on_next_key_release = true;
if (seat->workspace_osd_shown_by_modifier) {
workspaces_osd_hide(seat);
}
- if (server->grabbed_view) {
- regions_hide_overlay(seat);
- }
}
}
}
}
-/* Returns true if view was snapped to any edge */
-static bool
-snap_to_edge(struct view *view)
+enum view_edge
+edge_from_cursor(struct seat *seat, struct output **dest_output)
{
int snap_range = rc.snap_edge_range;
if (!snap_range) {
- return false;
+ return VIEW_EDGE_INVALID;
}
- struct output *output = output_nearest_to_cursor(view->server);
+ struct output *output = output_nearest_to_cursor(seat->server);
if (!output_is_usable(output)) {
wlr_log(WLR_ERROR, "output at cursor is unusable");
- return false;
+ return VIEW_EDGE_INVALID;
}
+ *dest_output = output;
/* Translate into output local coordinates */
- double cursor_x = view->server->seat.cursor->x;
- double cursor_y = view->server->seat.cursor->y;
- wlr_output_layout_output_coords(view->server->output_layout,
+ double cursor_x = seat->cursor->x;
+ double cursor_y = seat->cursor->y;
+ wlr_output_layout_output_coords(seat->server->output_layout,
output->wlr_output, &cursor_x, &cursor_y);
struct wlr_box *area = &output->usable_area;
- enum view_edge edge;
if (cursor_x <= area->x + snap_range) {
- edge = VIEW_EDGE_LEFT;
+ return VIEW_EDGE_LEFT;
} else if (cursor_x >= area->x + area->width - snap_range) {
- edge = VIEW_EDGE_RIGHT;
+ return VIEW_EDGE_RIGHT;
} else if (cursor_y <= area->y + snap_range) {
- edge = VIEW_EDGE_UP;
+ if (rc.snap_top_maximize) {
+ return VIEW_EDGE_CENTER;
+ } else {
+ return VIEW_EDGE_UP;
+ }
} else if (cursor_y >= area->y + area->height - snap_range) {
- edge = VIEW_EDGE_DOWN;
+ return VIEW_EDGE_DOWN;
} else {
/* Not close to any edge */
+ return VIEW_EDGE_INVALID;
+ }
+}
+
+/* Returns true if view was snapped to any edge */
+static bool
+snap_to_edge(struct view *view)
+{
+ struct output *output;
+ enum view_edge edge = edge_from_cursor(&view->server->seat, &output);
+ if (edge == VIEW_EDGE_INVALID) {
return false;
}
* Don't store natural geometry here (it was
* stored already in interactive_begin())
*/
- if (edge == VIEW_EDGE_UP && rc.snap_top_maximize) {
+ if (edge == VIEW_EDGE_CENTER) {
+ /* <topMaximize> */
view_maximize(view, VIEW_AXIS_BOTH,
/*store_natural_geometry*/ false);
} else {
return;
}
- regions_hide_overlay(&view->server->seat);
+ overlay_hide(&view->server->seat);
resize_indicator_hide(view);
'osd.c',
'output.c',
'output-virtual.c',
+ 'overlay.c',
'placement.c',
'regions.c',
'resistance.c',
output_destroy_notify(struct wl_listener *listener, void *data)
{
struct output *output = wl_container_of(listener, output, destroy);
+ struct seat *seat = &output->server->seat;
regions_evacuate_output(output);
- regions_destroy(&output->server->seat, &output->regions);
+ regions_destroy(seat, &output->regions);
+ if (seat->overlay.active.output == output) {
+ overlay_hide(seat);
+ }
wl_list_remove(&output->link);
wl_list_remove(&output->frame.link);
wl_list_remove(&output->destroy.link);
wl_list_remove(&output->request_state.link);
- seat_output_layout_changed(&output->server->seat);
+ seat_output_layout_changed(seat);
for (size_t i = 0; i < ARRAY_SIZE(output->layer_tree); i++) {
wlr_scene_node_destroy(&output->layer_tree[i]->node);
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+#include <assert.h>
+#include <wlr/render/pixman.h>
+#include "labwc.h"
+#include "overlay.h"
+#include "view.h"
+
+static void
+create_overlay(struct seat *seat)
+{
+ assert(!seat->overlay.tree);
+
+ struct server *server = seat->server;
+ struct wlr_scene_tree *parent = wlr_scene_tree_create(&server->scene->tree);
+
+ seat->overlay.tree = parent;
+ wlr_scene_node_set_enabled(&parent->node, false);
+ if (!wlr_renderer_is_pixman(server->renderer)) {
+ /* Hardware assisted rendering: Half transparent overlay */
+ float color[4] = { 0.25, 0.25, 0.35, 0.5 };
+ seat->overlay.rect = wlr_scene_rect_create(parent, 0, 0, color);
+ } else {
+ /* Software rendering: Outlines */
+ int line_width = server->theme->osd_border_width;
+ float *colors[3] = {
+ server->theme->osd_bg_color,
+ server->theme->osd_label_text_color,
+ server->theme->osd_bg_color
+ };
+ seat->overlay.pixman_rect = multi_rect_create(parent, colors, line_width);
+ }
+}
+
+static void
+show_overlay(struct seat *seat, struct wlr_box *box)
+{
+ struct server *server = seat->server;
+ struct view *view = server->grabbed_view;
+ assert(view);
+
+ if (!seat->overlay.tree) {
+ create_overlay(seat);
+ }
+
+ struct wlr_scene_node *node = &seat->overlay.tree->node;
+ if (!wlr_renderer_is_pixman(server->renderer)) {
+ /* Hardware assisted rendering: Half transparent overlay */
+ wlr_scene_rect_set_size(seat->overlay.rect,
+ box->width, box->height);
+ } else {
+ /* Software rendering: Outlines */
+ multi_rect_set_size(seat->overlay.pixman_rect,
+ box->width, box->height);
+ }
+ if (node->parent != view->scene_tree->node.parent) {
+ wlr_scene_node_reparent(node, view->scene_tree->node.parent);
+ wlr_scene_node_place_below(node, &view->scene_tree->node);
+ }
+ wlr_scene_node_set_position(node, box->x, box->y);
+ wlr_scene_node_set_enabled(node, true);
+
+ if (seat->overlay.timer) {
+ wl_event_source_timer_update(seat->overlay.timer, 0);
+ }
+}
+
+static void
+show_region_overlay(struct seat *seat, struct region *region)
+{
+ if (region == seat->overlay.active.region) {
+ return;
+ }
+ seat->overlay.active.region = region;
+ seat->overlay.active.edge = VIEW_EDGE_INVALID;
+ seat->overlay.active.output = NULL;
+
+ show_overlay(seat, ®ion->geo);
+}
+
+/* TODO: share logic with view_get_edge_snap_box() */
+static struct wlr_box get_edge_snap_box(enum view_edge edge, struct output *output)
+{
+ struct wlr_box box = output_usable_area_in_layout_coords(output);
+ switch (edge) {
+ case VIEW_EDGE_RIGHT:
+ box.x += box.width / 2;
+ /* fallthrough */
+ case VIEW_EDGE_LEFT:
+ box.width /= 2;
+ break;
+ case VIEW_EDGE_DOWN:
+ box.y += box.height / 2;
+ /* fallthrough */
+ case VIEW_EDGE_UP:
+ box.height /= 2;
+ break;
+ case VIEW_EDGE_CENTER:
+ /* <topMaximize> */
+ break;
+ default:
+ /* not reached */
+ assert(false);
+ }
+ return box;
+}
+
+static int
+handle_edge_overlay_timeout(void *data)
+{
+ struct seat *seat = data;
+ assert(seat->overlay.active.edge != VIEW_EDGE_INVALID
+ && seat->overlay.active.output);
+ struct wlr_box box = get_edge_snap_box(seat->overlay.active.edge,
+ seat->overlay.active.output);
+ show_overlay(seat, &box);
+ return 0;
+}
+
+static enum wlr_direction
+get_wlr_direction(enum view_edge edge)
+{
+ switch (edge) {
+ case VIEW_EDGE_LEFT:
+ return WLR_DIRECTION_LEFT;
+ case VIEW_EDGE_RIGHT:
+ return WLR_DIRECTION_RIGHT;
+ case VIEW_EDGE_UP:
+ case VIEW_EDGE_CENTER:
+ return WLR_DIRECTION_UP;
+ case VIEW_EDGE_DOWN:
+ return WLR_DIRECTION_DOWN;
+ default:
+ /* not reached */
+ assert(false);
+ return 0;
+ }
+}
+
+static bool
+edge_has_adjacent_output_from_cursor(struct seat *seat, struct output *output,
+ enum view_edge edge)
+{
+ return wlr_output_layout_adjacent_output(
+ seat->server->output_layout, get_wlr_direction(edge),
+ output->wlr_output, seat->cursor->x, seat->cursor->y);
+}
+
+static void
+show_edge_overlay(struct seat *seat, enum view_edge edge,
+ struct output *output)
+{
+ if (!rc.snap_preview_enabled) {
+ return;
+ }
+ if (seat->overlay.active.edge == edge
+ && seat->overlay.active.output == output) {
+ return;
+ }
+ seat->overlay.active.region = NULL;
+ seat->overlay.active.edge = edge;
+ seat->overlay.active.output = output;
+
+ int delay;
+ if (edge_has_adjacent_output_from_cursor(seat, output, edge)) {
+ delay = rc.snap_preview_delay_inner;
+ } else {
+ delay = rc.snap_preview_delay_outer;
+ }
+
+ if (delay > 0) {
+ if (!seat->overlay.timer) {
+ seat->overlay.timer = wl_event_loop_add_timer(
+ seat->server->wl_event_loop,
+ handle_edge_overlay_timeout, seat);
+ }
+ /* Show overlay <snapping><preview><delay>ms later */
+ wl_event_source_timer_update(seat->overlay.timer, delay);
+ } else {
+ /* Show overlay now */
+ struct wlr_box box = get_edge_snap_box(seat->overlay.active.edge,
+ seat->overlay.active.output);
+ show_overlay(seat, &box);
+ }
+}
+
+void
+overlay_update(struct seat *seat)
+{
+ struct server *server = seat->server;
+
+ /* Region-snapping overlay */
+ if (regions_should_snap(server)) {
+ struct region *region = regions_from_cursor(server);
+ if (region) {
+ show_region_overlay(seat, region);
+ return;
+ }
+ }
+
+ /* Edge-snapping overlay */
+ struct output *output;
+ enum view_edge edge = edge_from_cursor(seat, &output);
+ if (edge != VIEW_EDGE_INVALID) {
+ show_edge_overlay(seat, edge, output);
+ return;
+ }
+
+ overlay_hide(seat);
+}
+
+void
+overlay_hide(struct seat *seat)
+{
+ seat->overlay.active.region = NULL;
+ seat->overlay.active.edge = VIEW_EDGE_INVALID;
+ seat->overlay.active.output = NULL;
+ if (seat->overlay.timer) {
+ wl_event_source_timer_update(seat->overlay.timer, 0);
+ }
+
+ if (!seat->overlay.tree) {
+ return;
+ }
+ struct server *server = seat->server;
+ struct wlr_scene_node *node = &seat->overlay.tree->node;
+
+ wlr_scene_node_set_enabled(node, false);
+ if (node->parent != &server->scene->tree) {
+ wlr_scene_node_reparent(node, &server->scene->tree);
+ }
+}
#include <float.h>
#include <math.h>
#include <string.h>
-#include <wlr/render/pixman.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/util/box.h>
#include <wlr/util/log.h>
-#include "common/graphic-helpers.h"
#include "common/list.h"
#include "common/mem.h"
#include "input/keyboard.h"
return keyboard_any_modifiers_pressed(keyboard);
}
-static void
-overlay_create(struct seat *seat)
-{
- assert(!seat->region_overlay.tree);
-
- struct server *server = seat->server;
- struct wlr_scene_tree *parent = wlr_scene_tree_create(&server->scene->tree);
-
- seat->region_overlay.tree = parent;
- wlr_scene_node_set_enabled(&parent->node, false);
- if (!wlr_renderer_is_pixman(server->renderer)) {
- /* Hardware assisted rendering: Half transparent overlay */
- float color[4] = { 0.25, 0.25, 0.35, 0.5 };
- seat->region_overlay.overlay = wlr_scene_rect_create(parent, 0, 0, color);
- } else {
- /* Software rendering: Outlines */
- int line_width = server->theme->osd_border_width;
- float *colors[3] = {
- server->theme->osd_bg_color,
- server->theme->osd_label_text_color,
- server->theme->osd_bg_color
- };
- seat->region_overlay.pixman_overlay = multi_rect_create(parent, colors, line_width);
- }
-}
-
struct region *
regions_from_name(const char *region_name, struct output *output)
{
return closest_region;
}
-void
-regions_show_overlay(struct view *view, struct seat *seat, struct region *region)
-{
- assert(view);
- assert(seat);
- assert(region);
-
- /* Don't show active region */
- if (seat->region_active == region) {
- return;
- }
-
- if (!seat->region_overlay.tree) {
- overlay_create(seat);
- }
-
- /* Update overlay */
- struct server *server = seat->server;
- struct wlr_scene_node *node = &seat->region_overlay.tree->node;
- if (!wlr_renderer_is_pixman(server->renderer)) {
- /* Hardware assisted rendering: Half transparent overlay */
- wlr_scene_rect_set_size(seat->region_overlay.overlay,
- region->geo.width, region->geo.height);
- } else {
- /* Software rendering: Outlines */
- multi_rect_set_size(seat->region_overlay.pixman_overlay,
- region->geo.width, region->geo.height);
- }
- if (node->parent != view->scene_tree->node.parent) {
- wlr_scene_node_reparent(node, view->scene_tree->node.parent);
- wlr_scene_node_place_below(node, &view->scene_tree->node);
- }
- wlr_scene_node_set_position(node, region->geo.x, region->geo.y);
- wlr_scene_node_set_enabled(node, true);
- seat->region_active = region;
-}
-
-void
-regions_hide_overlay(struct seat *seat)
-{
- assert(seat);
- if (!seat->region_active) {
- return;
- }
-
- struct server *server = seat->server;
- struct wlr_scene_node *node = &seat->region_overlay.tree->node;
-
- wlr_scene_node_set_enabled(node, false);
- if (node->parent != &server->scene->tree) {
- wlr_scene_node_reparent(node, &server->scene->tree);
- }
- seat->region_active = NULL;
-}
-
void
regions_reconfigure_output(struct output *output)
{
struct region *region, *region_tmp;
wl_list_for_each_safe(region, region_tmp, regions, link) {
wl_list_remove(®ion->link);
- zfree(region->name);
- if (seat && seat->region_active == region) {
- seat->region_active = NULL;
+ if (seat && seat->overlay.active.region == region) {
+ overlay_hide(seat);
}
+ zfree(region->name);
zfree(region);
}
}
/* Application got killed while moving around */
server->input_mode = LAB_INPUT_STATE_PASSTHROUGH;
server->grabbed_view = NULL;
- regions_hide_overlay(&server->seat);
+ overlay_hide(&server->seat);
}
if (server->active_view == view) {