--- a/plugins/grid/grid.cpp +++ b/plugins/grid/grid.cpp @@ -83,6 +83,14 @@ std::string zone; }; +// Keep this separate from slot data: existing views can survive a plugin reload. +class grid_size_cycle_data_t : public wf::custom_data_t +{ + public: + int slot = 0; + int step = 0; +}; + nonstd::observer_ptr ensure_grid_view(wayfire_toplevel_view view) { if (!view->has_data()) @@ -118,7 +126,21 @@ wf::option_wrapper_t default_scope{"core/window_scope"}, scope_rules{"core/window_scope_rules"}; wf::geometry_t slot_dimensions(wayfire_toplevel_view view, int n) { - return wf::grid::get_slot_dimensions(wf::window_scope_workarea(view), n); + auto normal = wf::grid::get_slot_dimensions(wf::window_scope_workarea(view), n); + auto cycle = view->get_data(); + if (!cycle || cycle->slot != n || !cycle->step) + { + return normal; + } + + const double scale = cycle->step == 1 ? 0.8 : 0.6; + auto scaled = normal; + scaled.width = std::max(1.0, std::round(normal.width * scale)); + scaled.height = std::max(1.0, std::round(normal.height * scale)); + // Corners stay anchored; side slots center along their outer edge. + scaled.x += (normal.width - scaled.width) * (n % 3 == 1 ? 0 : n % 3 == 0 ? 1 : 0.5); + scaled.y += (normal.height - scaled.height) * (n >= 7 ? 0 : n <= 3 ? 1 : 0.5); + return scaled; } wf::option_wrapper_t zone_shift{"grid/zone_shift"}; wf::option_wrapper_t zone_gap{"grid/zone_gap"}; @@ -218,6 +240,7 @@ void place_in_zone(wayfire_toplevel_view view, const wf::output_stitch_zone_t& zone, wf::point_t workspace) { + view->erase_data(); auto data = view->get_data_safe(); data->slot = wf::grid::SLOT_CENTER; data->zone = zone.name; @@ -402,7 +425,7 @@ if (auto toplevel = toplevel_cast(view)) { - handle_slot(toplevel, i); + cycle_slot(toplevel, i); return true; } @@ -482,6 +505,7 @@ 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); + ev->view->erase_data(); ev->view->get_data_safe()->slot = new_slot_id; ev->view->get_data_safe()->zone.clear(); ensure_grid_view(ev->view)->adjust_target_geometry( @@ -550,6 +574,23 @@ return is_floating && (view->get_output() != nullptr) && view->toplevel()->pending().mapped; } + void cycle_slot(wayfire_toplevel_view view, int slot) + { + if (!can_adjust_view(view)) + { + return; + } + + auto previous = view->get_data(); + auto cycle = view->get_data_safe(); + const bool repeat = view->pending_tiled_edges() == wf::grid::get_tiled_edges_for_slot(slot) && + (!previous || (previous->slot == slot && previous->zone.empty())); + cycle->step = repeat ? ((cycle->slot == slot ? cycle->step : 0) + 1) % 3 : 0; + cycle->slot = slot; + handle_slot(view, slot); + } + + // Reflows reuse the selected size; only the activator advances the cycle. void handle_slot(wayfire_toplevel_view view, int slot, wf::pointf_t delta = {0, 0}) { if (!can_adjust_view(view)) @@ -699,6 +740,11 @@ } uint32_t slot = wf::grid::get_slot_from_tiled_edges(data->edges); + if (auto cycle = data->view->get_data(); + cycle && (!slot || cycle->slot != (int)slot)) + { + data->view->erase_data(); + } if (slot > 0) { data->desired_size = slot_dimensions(data->view, slot); @@ -752,9 +798,12 @@ } auto data = ev->view->get_data_safe(); - if (ev->new_edges != wf::grid::get_tiled_edges_for_slot(data->slot)) + // A superseded transaction must not erase the newer requested slot. + if (ev->new_edges == ev->view->pending_tiled_edges() && + ev->new_edges != wf::grid::get_tiled_edges_for_slot(data->slot)) { ev->view->erase_data(); + ev->view->erase_data(); } }; }; --- a/plugins/grid/wayfire/plugins/crossfade.hpp +++ b/plugins/grid/wayfire/plugins/crossfade.hpp @@ -26,6 +26,9 @@ { namespace grid { +// Distinct types keep cached older plugin implementations out of a live upgrade. +namespace transition +{ /** * A transformer used for a simple crossfade + scale animation. * @@ -46,14 +49,13 @@ crossfade_node_t(wayfire_toplevel_view view) : view_2d_transformer_t(view) { this->view = view; - capture_current_contents(); - } - - void capture_current_contents() - { displayed_geometry = view->get_geometry(); overlay_alpha = 0.0; - + capture_current_contents(); + } + + void capture_current_contents() + { auto root_node = view->get_surface_root_node(); const wf::geometry_t bbox = root_node->get_bounding_box(); const wf::geometry_t g = view->get_geometry(); @@ -151,16 +153,10 @@ scene::damage_callback push_damage) { this->self = std::dynamic_pointer_cast(self->shared_from_this()); - scene::damage_callback push_damage_child = [=] (const wf::regionf_t&) - { - // XXX: we could attempt to calculate a meaningful damage, but - // we update on each frame anyway so .. - push_damage(self->get_bounding_box()); - }; - - on_damage = [=] (auto) - { - push_damage(self->get_bounding_box()); + on_damage = [=] (scene::node_damage_signal *ev) + { + // Damage can include pixels vacated by the preceding geometry. + push_damage(ev->region); }; self->connect(&on_damage); } @@ -367,18 +363,25 @@ void start_crossfade(wf::geometry_t start_geometry, wf::geometry_t target_geometry) { - const auto current_displayed = animation.running() ? wf::geometry_t(animation) : start_geometry; - auto tr = view->get_transformed_node()->get_transformer(); + auto transforms = view->get_transformed_node(); + auto tr = transforms->get_transformer(); + // Continue from the last displayed frame, even while waiting for a + // client to commit. Recapturing must not reset the displayed rectangle + // to the previous, possibly still distant, destination. + const auto current_displayed = tr ? tr->displayed_geometry : start_geometry; + transforms->begin_transform_update(); if (!tr) { - view->get_transformed_node()->add_transformer( - std::make_shared(view), - wf::TRANSFORMER_2D); + tr = std::make_shared(view); + transforms->add_transformer(tr, wf::TRANSFORMER_2D); } else { tr->capture_current_contents(); } + tr->displayed_geometry = current_displayed; + tr->overlay_alpha = 0.0; + transforms->end_transform_update(); waiting_for_transaction = false; original = current_displayed; animation.set_start(current_displayed); @@ -403,4 +406,7 @@ type_t type; }; } +using transition::crossfade_node_t; +using transition::grid_animation_t; } +} --- a/metadata/grid.xml +++ b/metadata/grid.xml @@ -2,7 +2,7 @@ <_short>Grid - <_long>A plugin to position the windows in certain regions of the output. + <_long>Position windows within their physical or VIRTUAL workarea. Repeating a position shortcut cycles through 100%, 80%, and 60% of that slot's normal width and height. Corners remain anchored; sides and center stay centered. A different position starts at 100%. Window Management