--- a/metadata/move.xml +++ b/metadata/move.xml @@ -17,7 +17,7 @@ <_short>Snap - <_long>Enables or disables snapping the window being moved to an edge of the screen. + <_long>Snap a dragged window at screen edges and corners. Physical-scope windows use the monitor under the pointer, including internal monitor seams; virtual-scope windows use the combined desktop. Left/right edges select halves, corners select quarters, the top edge maximizes, and the bottom edge selects the bottom half. The preview shows the placement on release. true --- a/plugins/grid/grid.cpp +++ b/plugins/grid/grid.cpp @@ -90,6 +90,7 @@ std::shared_ptr preview; wf::grid::slot_t slot_id = wf::grid::SLOT_NONE; std::string zone; + wf::geometry_t geometry = {0, 0, 0, 0}; } slot; wf::plugin_activation_data_t grab_interface{ @@ -231,6 +232,10 @@ { if (can_adjust_view(ev->view) && get_view_wset(ev->view)) { + if (wf::window_scope_policy(ev->view) == "physical") + { + wf::select_window_scope_monitor(ev->view, zone->name); + } place_in_zone(ev->view, *zone, get_view_wset(ev->view)->get_current_workspace()); } @@ -383,37 +388,82 @@ [=] (wf::grid::grid_handle_move_signal *ev) { ev->carried_out = true; + if (ev->operation == wf::grid::MOVE_OP_CLEAR_PREVIEW || !ev->view || + !can_adjust_view(ev->view) || ev->view->pending_fullscreen()) + { + clear_zone_preview(); + slot.zone.clear(); + slot.slot_id = wf::grid::SLOT_NONE; + return; + } + if (handle_zone_move(ev)) { return; } auto drag_area = ev->output->workarea->get_workarea(); - if (wf::window_scope_policy(ev->view) != "virtual") + auto policy = wf::window_scope_policy(ev->view); + std::optional drag_zone; + if (policy != "virtual") { if (auto layout = ev->output->get_data()) + { for (auto& zone : layout->zones) - if (zone.geometry & wf::pointf_t{double(ev->input.x), double(ev->input.y)}) - drag_area = wf::physical_zone_workarea(ev->output, zone); - } - wf::grid::slot_t new_slot_id = ev->operation == wf::grid::MOVE_OP_CLEAR_PREVIEW ? - wf::grid::slot_t::SLOT_NONE : + { + // Half-open bounds give each seam to exactly one monitor. + auto& g = zone.geometry; + if (ev->input.x >= g.x && ev->input.x < g.x + g.width && + ev->input.y >= g.y && ev->input.y < g.y + g.height && + (policy == "physical" || zone.name == policy)) + { + drag_zone = zone; + break; + } + } + + // Empty space in an uneven layout is not a VIRTUAL snap target. + if (!drag_zone) + { + clear_zone_preview(); + slot.slot_id = wf::grid::SLOT_NONE; + return; + } + + drag_area = wf::physical_zone_workarea(ev->output, *drag_zone); + } + } + wf::grid::slot_t new_slot_id = wf::grid::calc_slot(drag_area, ev->input, snap_threshold, quarter_snap_threshold); + auto slot_geometry = wf::grid::get_slot_dimensions(drag_area, new_slot_id); if ((ev->operation == wf::grid::MOVE_OP_DROP) && new_slot_id) { + if (!get_view_wset(ev->view)) + { + clear_zone_preview(); + slot.slot_id = wf::grid::SLOT_NONE; + return; + } + + // The pointer chooses the drop monitor, even when the grabbed + // window's center is still on the other side of a seam. + if (drag_zone && policy == "physical") + { + wf::select_window_scope_monitor(ev->view, drag_zone->name); + } ev->view->toplevel()->pending().tiled_edges = wf::grid::get_tiled_edges_for_slot(new_slot_id); - auto desired_size = slot_dimensions(ev->view, new_slot_id); ev->view->get_data_safe()->slot = new_slot_id; ev->view->get_data_safe()->zone.clear(); ensure_grid_view(ev->view)->adjust_target_geometry( - adjust_for_workspace(get_view_wset(ev->view), desired_size, + adjust_for_workspace(get_view_wset(ev->view), slot_geometry, get_view_wset(ev->view)->get_current_workspace()), ev->view->toplevel()->pending().tiled_edges); new_slot_id = wf::grid::slot_t::SLOT_NONE; } /* No changes in the slot, just return */ - if (slot.slot_id == new_slot_id) + if (slot.slot_id == new_slot_id && (!new_slot_id || + (slot.preview && slot.preview->get_output() == ev->output && slot.geometry == slot_geometry))) { return; } @@ -428,11 +478,11 @@ } slot.slot_id = new_slot_id; + slot.geometry = slot_geometry; /* Show a preview overlay */ if (new_slot_id) { - wf::geometry_t slot_geometry = wf::grid::get_slot_dimensions(drag_area, new_slot_id); /* Unknown slot geometry, can't show a preview */ if ((slot_geometry.width <= 0) || (slot_geometry.height <= 0)) { --- a/plugins/grid/wayfire/plugins/grid.hpp +++ b/plugins/grid/wayfire/plugins/grid.hpp @@ -146,12 +146,12 @@ bool is_left = point.x - g.x <= threshold; bool is_right = g.x + g.width - point.x <= threshold; bool is_top = point.y - g.y < threshold; - bool is_bottom = g.x + g.height - point.y < threshold; + bool is_bottom = g.y + g.height - point.y < threshold; bool is_far_left = point.x - g.x <= quarter_snap_threshold; bool is_far_right = g.x + g.width - point.x <= quarter_snap_threshold; bool is_far_top = point.y - g.y < quarter_snap_threshold; - bool is_far_bottom = g.x + g.height - point.y < quarter_snap_threshold; + bool is_far_bottom = g.y + g.height - point.y < quarter_snap_threshold; wf::grid::slot_t slot = wf::grid::SLOT_NONE; if ((is_left && is_far_top) || (is_far_left && is_top)) --- a/plugins/single_plugins/move.cpp +++ b/plugins/single_plugins/move.cpp @@ -28,6 +28,10 @@ #include #include +namespace +{ +// Keep implementation symbols local so a replacement plugin cannot bind to +// an older Move implementation still cached by the running compositor. class wayfire_move : public wf::per_output_plugin_instance_t, public wf::pointer_interaction_t, public wf::touch_interaction_t { @@ -481,6 +485,7 @@ grid_signal.output = output; grid_signal.input = input; grid_signal.operation = operation; + grid_signal.view = drag_helper->view; wf::get_core().emit(&grid_signal); if (grid_signal.carried_out) @@ -591,5 +596,6 @@ output->rem_binding(&activate_zone_binding); } }; +} DECLARE_WAYFIRE_PLUGIN(wf::per_output_plugin_t);