diff --git a/plugins/common/meson.build b/plugins/common/meson.build index d401eed7c..7402fa52f 100644 --- a/plugins/common/meson.build +++ b/plugins/common/meson.build @@ -1,2 +1,16 @@ plugins_common_inc = include_directories('.') install_subdir('wayfire', install_dir: get_option('includedir')) + +workspace_wall = static_library('wayfire-workspace-wall', + ['workspace-wall.cpp'], + include_directories: [wayfire_api_inc, wayfire_conf_inc], + dependencies: [wlroots, pixman, wfconfig, plugin_pch_dep], + override_options: ['b_lundef=false'], + install: true) + +move_drag_interface = static_library('wayfire-move-drag-interface', + ['move-drag-interface.cpp'], + include_directories: [wayfire_api_inc, wayfire_conf_inc, wobbly_inc], + dependencies: [wlroots, pixman, wfconfig, plugin_pch_dep], + override_options: ['b_lundef=false'], + install: true) diff --git a/plugins/common/move-drag-interface.cpp b/plugins/common/move-drag-interface.cpp new file mode 100644 index 000000000..7085d642c --- /dev/null +++ b/plugins/common/move-drag-interface.cpp @@ -0,0 +1,673 @@ +#include "wayfire/plugins/common/move-drag-interface.hpp" +#include "wayfire/debug.hpp" +#include "wayfire/opengl.hpp" +#include "wayfire/region.hpp" +#include "wayfire/scene-input.hpp" +#include "wayfire/scene-operations.hpp" +#include "wayfire/seat.hpp" +#include "wayfire/signal-definitions.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wayfire/scene-render.hpp" +#include "wayfire/scene.hpp" +#include "wayfire/view-helpers.hpp" + +namespace wf +{ +namespace move_drag +{ +static wf::geometry_t find_geometry_around(wf::dimensions_t size, wf::point_t grab, wf::pointf_t relative) +{ + return wf::geometry_t{ + grab.x - (int)std::floor(relative.x * size.width), + grab.y - (int)std::floor(relative.y * size.height), + size.width, + size.height, + }; +} + +/** + * A transformer used while dragging. + * + * It is primarily used to scale the view is a plugin needs it, and also to keep it + * centered around the `grab_position`. + */ +class scale_around_grab_t : public wf::scene::transformer_base_node_t +{ + public: + /** + * Factor for scaling down the view. + * A factor 2.0 means that the view will have half of its width and height. + */ + wf::animation::simple_animation_t scale_factor{wf::create_option(300)}; + + wf::animation::simple_animation_t alpha_factor{wf::create_option(300)}; + + /** + * A place relative to the view, where it is grabbed. + * + * Coordinates are [0, 1]. A grab at (0.5, 0.5) means that the view is grabbed + * at its center. + */ + wf::pointf_t relative_grab; + + /** + * The position where the grab appears on the outputs, in output-layout + * coordinates. + */ + wf::point_t grab_position; + + scale_around_grab_t() : transformer_base_node_t(false) + {} + + std::string stringify() const override + { + return "move-drag"; + } + + wf::pointf_t scale_around_grab(wf::pointf_t point, double factor) + { + auto bbox = get_children_bounding_box(); + auto gx = bbox.x + bbox.width * relative_grab.x; + auto gy = bbox.y + bbox.height * relative_grab.y; + + return { + (point.x - gx) * factor + gx, + (point.y - gy) * factor + gy, + }; + } + + wf::pointf_t to_local(const wf::pointf_t& point) override + { + return scale_around_grab(point, scale_factor); + } + + wf::pointf_t to_global(const wf::pointf_t& point) override + { + return scale_around_grab(point, 1.0 / scale_factor); + } + + wf::geometry_t get_bounding_box() override + { + auto bbox = get_children_bounding_box(); + int w = std::floor(bbox.width / scale_factor); + int h = std::floor(bbox.height / scale_factor); + return find_geometry_around({w, h}, grab_position, relative_grab); + } + + class render_instance_t : + public scene::transformer_render_instance_t + { + public: + using transformer_render_instance_t::transformer_render_instance_t; + + void transform_damage_region(wf::region_t& region) override + { + region |= self->get_bounding_box(); + } + + void render(const wf::render_target_t& target, + const wf::region_t& region) override + { + auto bbox = self->get_bounding_box(); + auto tex = this->get_texture(target.scale); + + OpenGL::render_begin(target); + for (auto& rect : region) + { + target.logic_scissor(wlr_box_from_pixman_box(rect)); + OpenGL::render_texture(tex, target, bbox, glm::vec4{1, 1, 1, (double)self->alpha_factor}); + } + + OpenGL::render_end(); + } + }; + + void gen_render_instances(std::vector& instances, + scene::damage_callback push_damage, wf::output_t *shown_on) override + { + instances.push_back(std::make_unique(this, + push_damage, shown_on)); + } +}; + +static const std::string move_drag_transformer = "move-drag-transformer"; + +/** + * Represents a view which is being dragged. + * Multiple views exist only if join_views is set to true. + */ +struct dragged_view_t +{ + // The view being dragged + wayfire_toplevel_view view; + + // Its transformer + std::shared_ptr transformer; + + // The last bounding box used for damage. + // This is needed in case the view resizes or something like that, in which + // case we don't have access to the previous bbox. + wf::geometry_t last_bbox; +}; + +// A node to render the dragged views in global coordinates. +// The assumption is that all nodes have a view transformer which transforms them to global (not output-local) +// coordinates and thus we just need to schedule them for rendering. +class dragged_view_node_t : public wf::scene::node_t +{ + public: + std::vector views; + dragged_view_node_t(std::vector views) : node_t(false) + { + this->views = views; + } + + std::string stringify() const override + { + return "move-drag-view " + stringify_flags(); + } + + void gen_render_instances(std::vector& instances, + scene::damage_callback push_damage, wf::output_t *output = nullptr) override + { + instances.push_back(std::make_unique( + std::dynamic_pointer_cast(shared_from_this()), push_damage, output)); + } + + wf::geometry_t get_bounding_box() override + { + wf::region_t bounding; + for (auto& view : views) + { + // Note: bbox will be in output layout coordinates now, since this is + // how the transformer works + auto bbox = view.view->get_transformed_node()->get_bounding_box(); + bounding |= bbox; + } + + return wlr_box_from_pixman_box(bounding.get_extents()); + } + + class dragged_view_render_instance_t : public wf::scene::render_instance_t + { + wf::geometry_t last_bbox = {0, 0, 0, 0}; + wf::scene::damage_callback push_damage; + std::vector children; + wf::signal::connection_t on_node_damage = + [=] (scene::node_damage_signal *data) + { + push_damage(data->region); + }; + + public: + dragged_view_render_instance_t(std::shared_ptr self, + wf::scene::damage_callback push_damage, wf::output_t *shown_on) + { + auto push_damage_child = [=] (wf::region_t child_damage) + { + push_damage(last_bbox); + last_bbox = self->get_bounding_box(); + push_damage(last_bbox); + }; + + for (auto& view : self->views) + { + auto node = view.view->get_transformed_node(); + node->gen_render_instances(children, push_damage_child, shown_on); + } + } + + void schedule_instructions(std::vector& instructions, + const wf::render_target_t& target, wf::region_t& damage) override + { + for (auto& inst : children) + { + inst->schedule_instructions(instructions, target, damage); + } + } + + void presentation_feedback(wf::output_t *output) override + { + for (auto& instance : children) + { + instance->presentation_feedback(output); + } + } + + void compute_visibility(wf::output_t *output, wf::region_t& visible) override + { + for (auto& instance : children) + { + const int BIG_NUMBER = 1e5; + wf::region_t big_region = + wf::geometry_t{-BIG_NUMBER, -BIG_NUMBER, 2 * BIG_NUMBER, 2 * BIG_NUMBER}; + instance->compute_visibility(output, big_region); + } + } + }; +}; + +struct core_drag_t::impl +{ + // All views being dragged, more than one in case of join_views. + std::vector all_views; + + // Current parameters + drag_options_t params; + + // View is held in place, waiting for snap-off + bool view_held_in_place = false; + + std::shared_ptr render_node; + + wf::effect_hook_t on_pre_frame = [=] () + { + for (auto& v : this->all_views) + { + if (v.transformer->scale_factor.running()) + { + v.view->damage(); + } + } + }; + + wf::signal::connection_t on_view_unmap; + wf::signal::connection_t on_output_removed; +}; + +core_drag_t::core_drag_t() +{ + this->priv = std::make_unique(); + + priv->on_view_unmap = [=] (auto *ev) + { + handle_input_released(); + }; + + priv->on_output_removed = [=] (wf::output_removed_signal *ev) + { + if (current_output == ev->output) + { + update_current_output(nullptr); + } + }; + + wf::get_core().output_layout->connect(&priv->on_output_removed); +} + +core_drag_t::~core_drag_t() = default; + +void core_drag_t::rebuild_wobbly(wayfire_toplevel_view view, wf::point_t grab, wf::pointf_t relative) +{ + auto dim = wf::dimensions(wf::view_bounding_box_up_to(view, "wobbly")); + modify_wobbly(view, find_geometry_around(dim, grab, relative)); +} + +bool core_drag_t::should_start_pending_drag(wf::point_t current_position) +{ + if (!tentative_grab_position.has_value()) + { + return false; + } + + return distance_to_grab_origin(current_position) > 5; +} + +void core_drag_t::start_drag(wayfire_toplevel_view grab_view, wf::pointf_t relative, + const drag_options_t& options) +{ + wf::dassert(tentative_grab_position.has_value(), + "First, the drag operation should be set as pending!"); + wf::dassert(grab_view->is_mapped(), "Dragged view should be mapped!"); + wf::dassert(!this->view, "Drag operation already in progress!"); + + auto bbox = wf::view_bounding_box_up_to(grab_view, "wobbly"); + wf::point_t rel_grab_pos = { + int(bbox.x + relative.x * bbox.width), + int(bbox.y + relative.y * bbox.height), + }; + + if (options.join_views) + { + grab_view = wf::find_topmost_parent(grab_view); + } + + this->view = grab_view; + priv->params = options; + wf::get_core().default_wm->set_view_grabbed(view, true); + + auto target_views = get_target_views(grab_view, options.join_views); + for (auto& v : target_views) + { + dragged_view_t dragged; + dragged.view = v; + + // Setup view transform + + auto tr = std::make_shared(); + dragged.transformer = {tr}; + + tr->relative_grab = find_relative_grab( + wf::view_bounding_box_up_to(v, "wobbly"), rel_grab_pos); + tr->grab_position = *tentative_grab_position; + tr->scale_factor.animate(options.initial_scale, options.initial_scale); + tr->alpha_factor.animate(1, 1); + v->get_transformed_node()->add_transformer( + tr, wf::TRANSFORMER_HIGHLEVEL - 1); + + // Hide the view, we will render it as an overlay + wf::scene::set_node_enabled(v->get_transformed_node(), false); + v->damage(); + + // Make sure that wobbly has the correct geometry from the start! + rebuild_wobbly(v, *tentative_grab_position, dragged.transformer->relative_grab); + + // TODO: make this configurable! + start_wobbly_rel(v, dragged.transformer->relative_grab); + + priv->all_views.push_back(dragged); + v->connect(&priv->on_view_unmap); + } + + // Setup overlay hooks + priv->render_node = std::make_shared(priv->all_views); + wf::scene::add_front(wf::get_core().scene(), priv->render_node); + wf::get_core().set_cursor("grabbing"); + + // Set up snap-off + if (priv->params.enable_snap_off) + { + for (auto& v : priv->all_views) + { + set_tiled_wobbly(v.view, true); + } + + priv->view_held_in_place = true; + } +} + +void core_drag_t::start_drag(wayfire_toplevel_view view, const drag_options_t& options) +{ + wf::dassert(tentative_grab_position.has_value(), + "First, the drag operation should be set as pending!"); + + if (options.join_views) + { + view = wf::find_topmost_parent(view); + } + + auto bbox = view->get_transformed_node()->get_bounding_box() + + wf::origin(view->get_output()->get_layout_geometry()); + start_drag(view, find_relative_grab(bbox, *tentative_grab_position), options); +} + +void core_drag_t::handle_motion(wf::point_t to) +{ + if (priv->view_held_in_place) + { + if (distance_to_grab_origin(to) >= (double)priv->params.snap_off_threshold) + { + priv->view_held_in_place = false; + for (auto& v : priv->all_views) + { + set_tiled_wobbly(v.view, false); + } + + snap_off_signal data; + data.focus_output = current_output; + emit(&data); + } + } + + // Update wobbly independently of the grab position. + // This is because while held in place, wobbly is anchored to its edges + // so we can still move the grabbed point without moving the view. + for (auto& v : priv->all_views) + { + move_wobbly(v.view, to.x, to.y); + if (!priv->view_held_in_place) + { + v.view->get_transformed_node()->begin_transform_update(); + v.transformer->grab_position = to; + v.view->get_transformed_node()->end_transform_update(); + } + } + + update_current_output(to); + + drag_motion_signal data; + data.current_position = to; + emit(&data); +} + +double core_drag_t::distance_to_grab_origin(wf::point_t to) const +{ + return abs(to - *tentative_grab_position); +} + +void core_drag_t::handle_input_released() +{ + if (!view || priv->all_views.empty()) + { + this->tentative_grab_position = {}; + // Input already released => don't do anything + return; + } + + // Store data for the drag done signal + drag_done_signal data; + data.grab_position = priv->all_views.front().transformer->grab_position; + for (auto& v : priv->all_views) + { + data.all_views.push_back( + {v.view, v.transformer->relative_grab}); + } + + data.main_view = this->view; + data.focused_output = current_output; + data.join_views = priv->params.join_views; + + // Remove overlay hooks and damage outputs BEFORE popping the transformer + wf::scene::remove_child(priv->render_node); + priv->render_node->views.clear(); + priv->render_node = nullptr; + + for (auto& v : priv->all_views) + { + auto grab_position = v.transformer->grab_position; + auto rel_pos = v.transformer->relative_grab; + + // Restore view to where it was before + wf::scene::set_node_enabled(v.view->get_transformed_node(), true); + v.view->get_transformed_node()->rem_transformer(); + + // Reset wobbly and leave it in output-LOCAL coordinates + end_wobbly(v.view); + + // Important! If the view scale was not 1.0, the wobbly model needs to be + // updated with the new size. Since this is an artificial resize, we need + // to make sure that the resize happens smoothly. + rebuild_wobbly(v.view, grab_position, rel_pos); + + // Put wobbly back in output-local space, the plugins will take it from + // here. + translate_wobbly(v.view, + -wf::origin(v.view->get_output()->get_layout_geometry())); + } + + // Reset our state + wf::get_core().default_wm->set_view_grabbed(view, false); + view = nullptr; + priv->all_views.clear(); + if (current_output) + { + current_output->render->rem_effect(&priv->on_pre_frame); + current_output = nullptr; + } + + wf::get_core().set_cursor("default"); + + // Lastly, let the plugins handle what happens on drag end. + emit(&data); + priv->view_held_in_place = false; + priv->on_view_unmap.disconnect(); + + this->tentative_grab_position = {}; +} + +void core_drag_t::set_scale(double new_scale, double alpha) +{ + for (auto& view : priv->all_views) + { + view.transformer->scale_factor.animate(new_scale); + view.transformer->alpha_factor.animate(alpha); + } +} + +bool core_drag_t::is_view_held_in_place() +{ + return priv->view_held_in_place; +} + +void core_drag_t::update_current_output(wf::point_t grab) +{ + wf::pointf_t origin = {1.0 * grab.x, 1.0 * grab.y}; + auto output = wf::get_core().output_layout->get_output_coords_at(origin, origin); + update_current_output(output); +} + +void core_drag_t::update_current_output(wf::output_t *output) +{ + if (output != current_output) + { + if (current_output) + { + current_output->render->rem_effect(&priv->on_pre_frame); + } + + drag_focus_output_signal data; + data.previous_focus_output = current_output; + current_output = output; + data.focus_output = output; + if (output) + { + wf::get_core().seat->focus_output(output); + output->render->add_effect(&priv->on_pre_frame, OUTPUT_EFFECT_PRE); + } + + emit(&data); + } +} + +/** + * Move the view to the target output and put it at the coordinates of the grab. + * Also take into account view's fullscreen and tiled state. + * + * Unmapped views are ignored. + */ +void adjust_view_on_output(drag_done_signal *ev) +{ + // Any one of the views that are being dragged. + // They are all part of the same view tree. + auto parent = wf::find_topmost_parent(ev->main_view); + if (!parent->is_mapped()) + { + return; + } + + const bool change_output = parent->get_output() != ev->focused_output; + auto old_wset = parent->get_wset(); + if (change_output) + { + start_move_view_to_wset(parent, ev->focused_output->wset()); + } + + // Calculate the position we're leaving the view on + auto output_delta = -wf::origin(ev->focused_output->get_layout_geometry()); + auto grab = ev->grab_position + output_delta; + + auto output_geometry = ev->focused_output->get_relative_geometry(); + auto current_ws = ev->focused_output->wset()->get_current_workspace(); + wf::point_t target_ws{ + (int)std::floor(1.0 * grab.x / output_geometry.width), + (int)std::floor(1.0 * grab.y / output_geometry.height), + }; + target_ws = target_ws + current_ws; + + auto gsize = ev->focused_output->wset()->get_workspace_grid_size(); + target_ws.x = wf::clamp(target_ws.x, 0, gsize.width - 1); + target_ws.y = wf::clamp(target_ws.y, 0, gsize.height - 1); + + // view to focus at the end of drag + auto focus_view = ev->main_view; + + for (auto& v : ev->all_views) + { + if (!v.view->is_mapped()) + { + // Maybe some dialog got unmapped + continue; + } + + auto bbox = wf::view_bounding_box_up_to(v.view, "wobbly"); + auto wm = v.view->get_geometry(); + + wf::point_t wm_offset = wf::origin(wm) + -wf::origin(bbox); + bbox = wf::move_drag::find_geometry_around( + wf::dimensions(bbox), grab, v.relative_grab); + + wf::point_t target = wf::origin(bbox) + wm_offset; + v.view->move(target.x, target.y); + if (v.view->pending_fullscreen()) + { + wf::get_core().default_wm->fullscreen_request(v.view, ev->focused_output, true, target_ws); + } else if (v.view->pending_tiled_edges()) + { + wf::get_core().default_wm->tile_request(v.view, v.view->pending_tiled_edges(), target_ws); + } + + // check focus timestamp and select the last focused view to (re)focus + if (get_focus_timestamp(v.view) > get_focus_timestamp(focus_view)) + { + focus_view = v.view; + } + } + + // Ensure that every view is visible on parent's main workspace + for (auto& v : parent->enumerate_views()) + { + ev->focused_output->wset()->move_to_workspace(v, target_ws); + } + + if (change_output) + { + emit_view_moved_to_wset(parent, old_wset, ev->focused_output->wset()); + } + + wf::get_core().default_wm->focus_raise_view(focus_view); +} + +/** + * Adjust the view's state after snap-off. + */ +void adjust_view_on_snap_off(wayfire_toplevel_view view) +{ + if (view->pending_tiled_edges() && !view->pending_fullscreen()) + { + wf::get_core().default_wm->tile_request(view, 0); + } +} +} +} diff --git a/plugins/common/wayfire/plugins/common/move-drag-interface.hpp b/plugins/common/wayfire/plugins/common/move-drag-interface.hpp index fc187d014..77aab4a22 100644 --- a/plugins/common/wayfire/plugins/common/move-drag-interface.hpp +++ b/plugins/common/wayfire/plugins/common/move-drag-interface.hpp @@ -1,33 +1,10 @@ #pragma once -#include "wayfire/core.hpp" -#include "wayfire/debug.hpp" +#include "wayfire/toplevel-view.hpp" #include "wayfire/geometry.hpp" -#include "wayfire/opengl.hpp" -#include "wayfire/region.hpp" -#include "wayfire/scene-input.hpp" -#include "wayfire/scene-operations.hpp" -#include "wayfire/scene-render.hpp" -#include "wayfire/scene.hpp" -#include "wayfire/seat.hpp" -#include "wayfire/signal-definitions.hpp" -#include "wayfire/view-helpers.hpp" #include -#include -#include -#include #include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include namespace wf @@ -127,21 +104,6 @@ struct drag_done_signal wf::point_t grab_position; }; -/** - * Find the geometry of a view, if it has size @size, it is grabbed at point @grab, - * and the grab is at position @relative relative to the view. - */ -inline static wf::geometry_t find_geometry_around( - wf::dimensions_t size, wf::point_t grab, wf::pointf_t relative) -{ - return wf::geometry_t{ - grab.x - (int)std::floor(relative.x * size.width), - grab.y - (int)std::floor(relative.y * size.height), - size.width, - size.height, - }; -} - /** * Find the position of grab relative to the view. * Example: returns [0.5, 0.5] if the grab is the midpoint of the view. @@ -155,133 +117,7 @@ inline static wf::pointf_t find_relative_grab( }; } -/** - * A transformer used while dragging. - * - * It is primarily used to scale the view is a plugin needs it, and also to keep it - * centered around the `grab_position`. - */ -class scale_around_grab_t : public wf::scene::transformer_base_node_t -{ - public: - /** - * Factor for scaling down the view. - * A factor 2.0 means that the view will have half of its width and height. - */ - wf::animation::simple_animation_t scale_factor{wf::create_option(300)}; - - wf::animation::simple_animation_t alpha_factor{wf::create_option(300)}; - - /** - * A place relative to the view, where it is grabbed. - * - * Coordinates are [0, 1]. A grab at (0.5, 0.5) means that the view is grabbed - * at its center. - */ - wf::pointf_t relative_grab; - - /** - * The position where the grab appears on the outputs, in output-layout - * coordinates. - */ - wf::point_t grab_position; - - scale_around_grab_t() : transformer_base_node_t(false) - {} - - std::string stringify() const override - { - return "move-drag"; - } - - wf::pointf_t scale_around_grab(wf::pointf_t point, double factor) - { - auto bbox = get_children_bounding_box(); - auto gx = bbox.x + bbox.width * relative_grab.x; - auto gy = bbox.y + bbox.height * relative_grab.y; - - return { - (point.x - gx) * factor + gx, - (point.y - gy) * factor + gy, - }; - } - - wf::pointf_t to_local(const wf::pointf_t& point) override - { - return scale_around_grab(point, scale_factor); - } - - wf::pointf_t to_global(const wf::pointf_t& point) override - { - return scale_around_grab(point, 1.0 / scale_factor); - } - - wf::geometry_t get_bounding_box() override - { - auto bbox = get_children_bounding_box(); - int w = std::floor(bbox.width / scale_factor); - int h = std::floor(bbox.height / scale_factor); - return find_geometry_around({w, h}, grab_position, relative_grab); - } - - class render_instance_t : - public scene::transformer_render_instance_t - { - public: - using transformer_render_instance_t::transformer_render_instance_t; - - void transform_damage_region(wf::region_t& region) override - { - region |= self->get_bounding_box(); - } - - void render(const wf::render_target_t& target, - const wf::region_t& region) override - { - auto bbox = self->get_bounding_box(); - auto tex = this->get_texture(target.scale); - - OpenGL::render_begin(target); - for (auto& rect : region) - { - target.logic_scissor(wlr_box_from_pixman_box(rect)); - OpenGL::render_texture(tex, target, bbox, glm::vec4{1, 1, 1, (double)self->alpha_factor}); - } - - OpenGL::render_end(); - } - }; - - void gen_render_instances(std::vector& instances, - scene::damage_callback push_damage, wf::output_t *shown_on) override - { - instances.push_back(std::make_unique(this, - push_damage, shown_on)); - } -}; - -static const std::string move_drag_transformer = "move-drag-transformer"; - -/** - * Represents a view which is being dragged. - * Multiple views exist only if join_views is set to true. - */ -struct dragged_view_t -{ - // The view being dragged - wayfire_toplevel_view view; - - // Its transformer - std::shared_ptr transformer; - - // The last bounding box used for damage. - // This is needed in case the view resizes or something like that, in which - // case we don't have access to the previous bbox. - wf::geometry_t last_bbox; -}; - -inline std::vector get_target_views(wayfire_toplevel_view grabbed, - bool join_views) +inline std::vector get_target_views(wayfire_toplevel_view grabbed, bool join_views) { std::vector r = {grabbed}; if (join_views) @@ -292,103 +128,6 @@ inline std::vector get_target_views(wayfire_toplevel_view return r; } -// A node to render the dragged views in global coordinates. -// The assumption is that all nodes have a view transformer which transforms them to global (not output-local) -// coordinates and thus we just need to schedule them for rendering. -class dragged_view_node_t : public wf::scene::node_t -{ - public: - std::vector views; - dragged_view_node_t(std::vector views) : node_t(false) - { - this->views = views; - } - - std::string stringify() const override - { - return "move-drag-view " + stringify_flags(); - } - - void gen_render_instances(std::vector& instances, - scene::damage_callback push_damage, wf::output_t *output = nullptr) override - { - instances.push_back(std::make_unique( - std::dynamic_pointer_cast(shared_from_this()), push_damage, output)); - } - - wf::geometry_t get_bounding_box() override - { - wf::region_t bounding; - for (auto& view : views) - { - // Note: bbox will be in output layout coordinates now, since this is - // how the transformer works - auto bbox = view.view->get_transformed_node()->get_bounding_box(); - bounding |= bbox; - } - - return wlr_box_from_pixman_box(bounding.get_extents()); - } - - class dragged_view_render_instance_t : public wf::scene::render_instance_t - { - wf::geometry_t last_bbox = {0, 0, 0, 0}; - wf::scene::damage_callback push_damage; - std::vector children; - wf::signal::connection_t on_node_damage = - [=] (scene::node_damage_signal *data) - { - push_damage(data->region); - }; - - public: - dragged_view_render_instance_t(std::shared_ptr self, - wf::scene::damage_callback push_damage, wf::output_t *shown_on) - { - auto push_damage_child = [=] (wf::region_t child_damage) - { - push_damage(last_bbox); - last_bbox = self->get_bounding_box(); - push_damage(last_bbox); - }; - - for (auto& view : self->views) - { - auto node = view.view->get_transformed_node(); - node->gen_render_instances(children, push_damage_child, shown_on); - } - } - - void schedule_instructions(std::vector& instructions, - const wf::render_target_t& target, wf::region_t& damage) override - { - for (auto& inst : children) - { - inst->schedule_instructions(instructions, target, damage); - } - } - - void presentation_feedback(wf::output_t *output) override - { - for (auto& instance : children) - { - instance->presentation_feedback(output); - } - } - - void compute_visibility(wf::output_t *output, wf::region_t& visible) override - { - for (auto& instance : children) - { - const int BIG_NUMBER = 1e5; - wf::region_t big_region = - wf::geometry_t{-BIG_NUMBER, -BIG_NUMBER, 2 * BIG_NUMBER, 2 * BIG_NUMBER}; - instance->compute_visibility(output, big_region); - } - } - }; -}; - struct drag_options_t { /** @@ -422,19 +161,12 @@ class core_drag_t : public signal::provider_t * Rebuild the wobbly model after a change in the scaling, so that the wobbly * model does not try to animate the scaling change itself. */ - void rebuild_wobbly(wayfire_toplevel_view view, wf::point_t grab, wf::pointf_t relative) - { - auto dim = wf::dimensions(wf::view_bounding_box_up_to(view, "wobbly")); - modify_wobbly(view, find_geometry_around(dim, grab, relative)); - } + void rebuild_wobbly(wayfire_toplevel_view view, wf::point_t grab, wf::pointf_t relative); public: std::optional tentative_grab_position; - - core_drag_t() - { - wf::get_core().output_layout->connect(&on_output_removed); - } + core_drag_t(); + ~core_drag_t(); /** * A button has been pressed which might start a drag action. @@ -451,15 +183,7 @@ class core_drag_t : public signal::provider_t * Note that in some cases this functionality is not used at all, if the action for example was triggered * by a binding. */ - bool should_start_pending_drag(wf::point_t current_position) - { - if (!tentative_grab_position.has_value()) - { - return false; - } - - return distance_to_grab_origin(current_position) > 5; - } + bool should_start_pending_drag(wf::point_t current_position); /** * Start the actual dragging operation. Note: this should be called **after** set_pending_drag(). @@ -468,220 +192,16 @@ class core_drag_t : public signal::provider_t * @param grab_position The position of the input, in output-layout coordinates. * @param relative The position of the grab_position relative to view. */ - void start_drag(wayfire_toplevel_view grab_view, wf::pointf_t relative, const drag_options_t& options) - { - wf::dassert(tentative_grab_position.has_value(), - "First, the drag operation should be set as pending!"); - wf::dassert(grab_view->is_mapped(), "Dragged view should be mapped!"); - wf::dassert(!this->view, "Drag operation already in progress!"); - - auto bbox = wf::view_bounding_box_up_to(grab_view, "wobbly"); - wf::point_t rel_grab_pos = { - int(bbox.x + relative.x * bbox.width), - int(bbox.y + relative.y * bbox.height), - }; - - if (options.join_views) - { - grab_view = find_topmost_parent(grab_view); - } - - this->view = grab_view; - this->params = options; - wf::get_core().default_wm->set_view_grabbed(view, true); - - auto target_views = get_target_views(grab_view, options.join_views); - for (auto& v : target_views) - { - dragged_view_t dragged; - dragged.view = v; - - // Setup view transform - - auto tr = std::make_shared(); - dragged.transformer = {tr}; - - tr->relative_grab = find_relative_grab( - wf::view_bounding_box_up_to(v, "wobbly"), rel_grab_pos); - tr->grab_position = *tentative_grab_position; - tr->scale_factor.animate(options.initial_scale, options.initial_scale); - tr->alpha_factor.animate(1, 1); - v->get_transformed_node()->add_transformer( - tr, wf::TRANSFORMER_HIGHLEVEL - 1); - - // Hide the view, we will render it as an overlay - wf::scene::set_node_enabled(v->get_transformed_node(), false); - v->damage(); - - // Make sure that wobbly has the correct geometry from the start! - rebuild_wobbly(v, *tentative_grab_position, dragged.transformer->relative_grab); - - // TODO: make this configurable! - start_wobbly_rel(v, dragged.transformer->relative_grab); - - this->all_views.push_back(dragged); - v->connect(&on_view_unmap); - } - - // Setup overlay hooks - render_node = std::make_shared(all_views); - wf::scene::add_front(wf::get_core().scene(), render_node); - wf::get_core().set_cursor("grabbing"); - - // Set up snap-off - if (params.enable_snap_off) - { - for (auto& v : all_views) - { - set_tiled_wobbly(v.view, true); - } - - view_held_in_place = true; - } - } - - void start_drag(wayfire_toplevel_view view, const drag_options_t& options) - { - wf::dassert(tentative_grab_position.has_value(), - "First, the drag operation should be set as pending!"); - - if (options.join_views) - { - view = find_topmost_parent(view); - } - - auto bbox = view->get_transformed_node()->get_bounding_box() + - wf::origin(view->get_output()->get_layout_geometry()); - start_drag(view, find_relative_grab(bbox, *tentative_grab_position), options); - } - - void handle_motion(wf::point_t to) - { - if (view_held_in_place) - { - if (distance_to_grab_origin(to) >= (double)params.snap_off_threshold) - { - view_held_in_place = false; - for (auto& v : all_views) - { - set_tiled_wobbly(v.view, false); - } - - snap_off_signal data; - data.focus_output = current_output; - emit(&data); - } - } - - // Update wobbly independently of the grab position. - // This is because while held in place, wobbly is anchored to its edges - // so we can still move the grabbed point without moving the view. - for (auto& v : all_views) - { - move_wobbly(v.view, to.x, to.y); - if (!view_held_in_place) - { - v.view->get_transformed_node()->begin_transform_update(); - v.transformer->grab_position = to; - v.view->get_transformed_node()->end_transform_update(); - } - } - - update_current_output(to); - - drag_motion_signal data; - data.current_position = to; - emit(&data); - } + void start_drag(wayfire_toplevel_view grab_view, wf::pointf_t relative, const drag_options_t& options); + void start_drag(wayfire_toplevel_view view, const drag_options_t& options); - double distance_to_grab_origin(wf::point_t to) const - { - return abs(to - *tentative_grab_position); - } + void handle_motion(wf::point_t to); - void handle_input_released() - { - if (!view || all_views.empty()) - { - this->tentative_grab_position = {}; - // Input already released => don't do anything - return; - } - - // Store data for the drag done signal - drag_done_signal data; - data.grab_position = all_views.front().transformer->grab_position; - for (auto& v : all_views) - { - data.all_views.push_back( - {v.view, v.transformer->relative_grab}); - } - - data.main_view = this->view; - data.focused_output = current_output; - data.join_views = params.join_views; - - // Remove overlay hooks and damage outputs BEFORE popping the transformer - wf::scene::remove_child(render_node); - render_node->views.clear(); - render_node = nullptr; - - for (auto& v : all_views) - { - auto grab_position = v.transformer->grab_position; - auto rel_pos = v.transformer->relative_grab; - - // Restore view to where it was before - wf::scene::set_node_enabled(v.view->get_transformed_node(), true); - v.view->get_transformed_node()->rem_transformer(); - - // Reset wobbly and leave it in output-LOCAL coordinates - end_wobbly(v.view); - - // Important! If the view scale was not 1.0, the wobbly model needs to be - // updated with the new size. Since this is an artificial resize, we need - // to make sure that the resize happens smoothly. - rebuild_wobbly(v.view, grab_position, rel_pos); - - // Put wobbly back in output-local space, the plugins will take it from - // here. - translate_wobbly(v.view, - -wf::origin(v.view->get_output()->get_layout_geometry())); - } - - // Reset our state - wf::get_core().default_wm->set_view_grabbed(view, false); - view = nullptr; - all_views.clear(); - if (current_output) - { - current_output->render->rem_effect(&on_pre_frame); - current_output = nullptr; - } - - wf::get_core().set_cursor("default"); - - // Lastly, let the plugins handle what happens on drag end. - emit(&data); - view_held_in_place = false; - on_view_unmap.disconnect(); - - this->tentative_grab_position = {}; - } - - void set_scale(double new_scale, double alpha = 1.0) - { - for (auto& view : all_views) - { - view.transformer->scale_factor.animate(new_scale); - view.transformer->alpha_factor.animate(alpha); - } - } + double distance_to_grab_origin(wf::point_t to) const; + void handle_input_released(); + void set_scale(double new_scale, double alpha = 1.0); - bool is_view_held_in_place() - { - return view_held_in_place; - } + bool is_view_held_in_place(); // View currently being moved. wayfire_toplevel_view view; @@ -690,70 +210,12 @@ class core_drag_t : public signal::provider_t wf::output_t *current_output = NULL; private: - // All views being dragged, more than one in case of join_views. - std::vector all_views; - - // Current parameters - drag_options_t params; + struct impl; + std::unique_ptr priv; - // View is held in place, waiting for snap-off - bool view_held_in_place = false; - std::shared_ptr render_node; - - void update_current_output(wf::point_t grab) - { - wf::pointf_t origin = {1.0 * grab.x, 1.0 * grab.y}; - auto output = wf::get_core().output_layout->get_output_coords_at(origin, origin); - update_current_output(output); - } - - void update_current_output(wf::output_t *output) - { - if (output != current_output) - { - if (current_output) - { - current_output->render->rem_effect(&on_pre_frame); - } - - drag_focus_output_signal data; - data.previous_focus_output = current_output; - current_output = output; - data.focus_output = output; - if (output) - { - wf::get_core().seat->focus_output(output); - output->render->add_effect(&on_pre_frame, OUTPUT_EFFECT_PRE); - } - - emit(&data); - } - } - - wf::effect_hook_t on_pre_frame = [=] () - { - for (auto& v : this->all_views) - { - if (v.transformer->scale_factor.running()) - { - v.view->damage(); - } - } - }; - - wf::signal::connection_t on_view_unmap = [=] (auto *ev) - { - handle_input_released(); - }; - - wf::signal::connection_t on_output_removed = [=] (wf::output_removed_signal *ev) - { - if (current_output == ev->output) - { - update_current_output(nullptr); - } - }; + void update_current_output(wf::point_t grab); + void update_current_output(wf::output_t *output); }; /** @@ -762,97 +224,11 @@ class core_drag_t : public signal::provider_t * * Unmapped views are ignored. */ -inline void adjust_view_on_output(drag_done_signal *ev) -{ - // Any one of the views that are being dragged. - // They are all part of the same view tree. - auto parent = find_topmost_parent(ev->main_view); - if (!parent->is_mapped()) - { - return; - } - - const bool change_output = parent->get_output() != ev->focused_output; - auto old_wset = parent->get_wset(); - if (change_output) - { - start_move_view_to_wset(parent, ev->focused_output->wset()); - } - - // Calculate the position we're leaving the view on - auto output_delta = -wf::origin(ev->focused_output->get_layout_geometry()); - auto grab = ev->grab_position + output_delta; - - auto output_geometry = ev->focused_output->get_relative_geometry(); - auto current_ws = ev->focused_output->wset()->get_current_workspace(); - wf::point_t target_ws{ - (int)std::floor(1.0 * grab.x / output_geometry.width), - (int)std::floor(1.0 * grab.y / output_geometry.height), - }; - target_ws = target_ws + current_ws; - - auto gsize = ev->focused_output->wset()->get_workspace_grid_size(); - target_ws.x = wf::clamp(target_ws.x, 0, gsize.width - 1); - target_ws.y = wf::clamp(target_ws.y, 0, gsize.height - 1); - - // view to focus at the end of drag - auto focus_view = ev->main_view; - - for (auto& v : ev->all_views) - { - if (!v.view->is_mapped()) - { - // Maybe some dialog got unmapped - continue; - } - - auto bbox = wf::view_bounding_box_up_to(v.view, "wobbly"); - auto wm = v.view->get_geometry(); - - wf::point_t wm_offset = wf::origin(wm) + -wf::origin(bbox); - bbox = wf::move_drag::find_geometry_around( - wf::dimensions(bbox), grab, v.relative_grab); - - wf::point_t target = wf::origin(bbox) + wm_offset; - v.view->move(target.x, target.y); - if (v.view->pending_fullscreen()) - { - wf::get_core().default_wm->fullscreen_request(v.view, ev->focused_output, true, target_ws); - } else if (v.view->pending_tiled_edges()) - { - wf::get_core().default_wm->tile_request(v.view, v.view->pending_tiled_edges(), target_ws); - } - - // check focus timestamp and select the last focused view to (re)focus - if (get_focus_timestamp(v.view) > get_focus_timestamp(focus_view)) - { - focus_view = v.view; - } - } - - // Ensure that every view is visible on parent's main workspace - for (auto& v : parent->enumerate_views()) - { - ev->focused_output->wset()->move_to_workspace(v, target_ws); - } - - if (change_output) - { - emit_view_moved_to_wset(parent, old_wset, ev->focused_output->wset()); - } - - wf::get_core().default_wm->focus_raise_view(focus_view); -} +void adjust_view_on_output(drag_done_signal *ev); /** * Adjust the view's state after snap-off. */ -inline void adjust_view_on_snap_off(wayfire_toplevel_view view) -{ - if (view->pending_tiled_edges() && !view->pending_fullscreen()) - { - wf::get_core().default_wm->tile_request(view, 0); - } -} +void adjust_view_on_snap_off(wayfire_toplevel_view view); } } diff --git a/plugins/common/wayfire/plugins/common/workspace-wall.hpp b/plugins/common/wayfire/plugins/common/workspace-wall.hpp index ceee1cf7a..6a4330da6 100644 --- a/plugins/common/wayfire/plugins/common/workspace-wall.hpp +++ b/plugins/common/wayfire/plugins/common/workspace-wall.hpp @@ -1,19 +1,16 @@ #pragma once - #include "wayfire/workspace-set.hpp" // IWYU pragma: keep #include #include +#include #include "wayfire/core.hpp" #include "wayfire/geometry.hpp" #include "wayfire/opengl.hpp" #include "wayfire/region.hpp" -#include "wayfire/scene-input.hpp" -#include "wayfire/scene-operations.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/signal-provider.hpp" -#include "wayfire/workspace-stream.hpp" #include "wayfire/output.hpp" namespace wf @@ -40,25 +37,16 @@ class workspace_wall_t : public wf::signal::provider_t /** * Create a new workspace wall on the given output. */ - workspace_wall_t(wf::output_t *_output) : output(_output) - { - this->viewport = get_wall_rectangle(); - } + workspace_wall_t(wf::output_t *_output); - ~workspace_wall_t() - { - stop_output_renderer(false); - } + ~workspace_wall_t(); /** * Set the color of the background outside of workspaces. * * @param color The new background color. */ - void set_background_color(const wf::color_t& color) - { - this->background_color = color; - } + void set_background_color(const wf::color_t& color); /** * Set the size of the gap between adjacent workspaces, both horizontally @@ -66,10 +54,7 @@ class workspace_wall_t : public wf::signal::provider_t * * @param size The new gap size, in pixels. */ - void set_gap_size(int size) - { - this->gap_size = size; - } + void set_gap_size(int size); /** * Set which part of the workspace wall to render. @@ -82,20 +67,9 @@ class workspace_wall_t : public wf::signal::provider_t * * @param viewport_geometry The part of the workspace wall to render. */ - void set_viewport(const wf::geometry_t& viewport_geometry) - { - this->viewport = viewport_geometry; - if (render_node) - { - scene::damage_node(this->render_node, - this->render_node->get_bounding_box()); - } - } + void set_viewport(const wf::geometry_t& viewport_geometry); - wf::geometry_t get_viewport() const - { - return viewport; - } + wf::geometry_t get_viewport() const; /** * Render the selected viewport on the framebuffer. @@ -104,22 +78,13 @@ class workspace_wall_t : public wf::signal::provider_t * @param geometry The rectangle in fb to draw to, in the same coordinate * system as the framebuffer's geometry. */ - void render_wall(const wf::render_target_t& fb, const wf::region_t& damage) - { - wall_frame_event_t data{fb}; - this->emit(&data); - } + void render_wall(const wf::render_target_t& fb, const wf::region_t& damage); /** * Register a render hook and paint the whole output as a desktop wall * with the set parameters. */ - void start_output_renderer() - { - wf::dassert(render_node == nullptr, "Starting workspace-wall twice?"); - render_node = std::make_shared(this); - scene::add_front(wf::get_core().scene(), render_node); - } + void start_output_renderer(); /** * Stop repainting the whole output. @@ -127,21 +92,7 @@ class workspace_wall_t : public wf::signal::provider_t * @param reset_viewport If true, the viewport will be reset to {0, 0, 0, 0} * and thus all workspace streams will be stopped. */ - void stop_output_renderer(bool reset_viewport) - { - if (!render_node) - { - return; - } - - scene::remove_child(render_node); - render_node = nullptr; - - if (reset_viewport) - { - set_viewport({0, 0, 0, 0}); - } - } + void stop_output_renderer(bool reset_viewport); /** * Calculate the geometry of a particular workspace, as described in @@ -149,45 +100,17 @@ class workspace_wall_t : public wf::signal::provider_t * * @param ws The workspace whose geometry is to be computed. */ - wf::geometry_t get_workspace_rectangle(const wf::point_t& ws) const - { - auto size = this->output->get_screen_size(); - - return { - ws.x * (size.width + gap_size), - ws.y * (size.height + gap_size), - size.width, - size.height - }; - } + wf::geometry_t get_workspace_rectangle(const wf::point_t& ws) const; /** * Calculate the whole workspace wall region, including gaps around it. */ - wf::geometry_t get_wall_rectangle() const - { - auto size = this->output->get_screen_size(); - auto workspace_size = this->output->wset()->get_workspace_grid_size(); - - return { - -gap_size, - -gap_size, - workspace_size.width * (size.width + gap_size) + gap_size, - workspace_size.height * (size.height + gap_size) + gap_size - }; - } + wf::geometry_t get_wall_rectangle() const; /** * Get/set the dimming factor for a given workspace. */ - void set_ws_dim(const wf::point_t& ws, float value) - { - render_colors[{ws.x, ws.y}] = value; - if (render_node) - { - scene::damage_node(render_node, render_node->get_bounding_box()); - } - } + void set_ws_dim(const wf::point_t& ws, float value); protected: wf::output_t *output; @@ -195,362 +118,16 @@ class workspace_wall_t : public wf::signal::provider_t wf::color_t background_color = {0, 0, 0, 0}; int gap_size = 0; wf::geometry_t viewport = {0, 0, 0, 0}; - std::map, float> render_colors; - - float get_color_for_workspace(wf::point_t ws) - { - auto it = render_colors.find({ws.x, ws.y}); - if (it == render_colors.end()) - { - return 1.0; - } - - return it->second; - } + float get_color_for_workspace(wf::point_t ws); /** * Get a list of workspaces visible in the viewport. */ - std::vector get_visible_workspaces(wf::geometry_t viewport) const - { - std::vector visible; - auto wsize = output->wset()->get_workspace_grid_size(); - for (int i = 0; i < wsize.width; i++) - { - for (int j = 0; j < wsize.height; j++) - { - if (viewport & get_workspace_rectangle({i, j})) - { - visible.push_back({i, j}); - } - } - } - - return visible; - } + std::vector get_visible_workspaces(wf::geometry_t viewport) const; protected: - template using per_workspace_map_t = std::map>; - - class workspace_wall_node_t : public scene::node_t - { - class wwall_render_instance_t : public scene::render_instance_t - { - std::shared_ptr self; - per_workspace_map_t> instances; - - scene::damage_callback push_damage; - wf::signal::connection_t on_wall_damage = - [=] (scene::node_damage_signal *ev) - { - push_damage(ev->region); - }; - - wf::geometry_t get_workspace_rect(wf::point_t ws) - { - auto output_size = self->wall->output->get_screen_size(); - return { - .x = ws.x * (output_size.width + self->wall->gap_size), - .y = ws.y * (output_size.height + self->wall->gap_size), - .width = output_size.width, - .height = output_size.height, - }; - } - - public: - wwall_render_instance_t(workspace_wall_node_t *self, - scene::damage_callback push_damage) - { - this->self = std::dynamic_pointer_cast(self->shared_from_this()); - this->push_damage = push_damage; - self->connect(&on_wall_damage); - - for (int i = 0; i < (int)self->workspaces.size(); i++) - { - for (int j = 0; j < (int)self->workspaces[i].size(); j++) - { - auto push_damage_child = [=] (const wf::region_t& damage) - { - // Store the damage because we'll have to update the buffers - self->aux_buffer_damage[i][j] |= damage; - - wf::region_t our_damage; - for (auto& rect : damage) - { - wf::geometry_t box = wlr_box_from_pixman_box(rect); - box = box + wf::origin(get_workspace_rect({i, j})); - auto A = self->wall->viewport; - auto B = self->get_bounding_box(); - our_damage |= scale_box(A, B, box); - } - - // Also damage the 'screen' after transforming damage - push_damage(our_damage); - }; - - self->workspaces[i][j]->gen_render_instances(instances[i][j], - push_damage_child, self->wall->output); - } - } - } - - static int damage_sum_area(const wf::region_t& damage) - { - int sum = 0; - for (const auto& rect : damage) - { - sum += (rect.y2 - rect.y1) * (rect.x2 - rect.x1); - } - - return sum; - } - - bool consider_rescale_workspace_buffer(int i, int j, wf::region_t& visible_damage) - { - // In general, when rendering the auxilliary buffers for each workspace, we can render the - // workspace thumbnails in a lower resolution, because at the end they are shown scaled. - // This helps with performance and uses less GPU power. - // - // However, the situation is tricky because during the Expo animation the optimal render - // scale constantly changes. Thus, in some cases it is actually far from optimal to rescale - // on every frame - it is often better to just keep the buffers from the old scale. - // - // Nonetheless, we need to make sure to rescale when this makes sense, and to avoid visual - // artifacts. - auto bbox = self->workspaces[i][j]->get_bounding_box(); - const float render_scale = std::max( - 1.0 * bbox.width / self->wall->viewport.width, - 1.0 * bbox.height / self->wall->viewport.height); - const float current_scale = self->aux_buffer_current_scale[i][j]; - - // Avoid keeping a low resolution if we are going up in the scale (for example, expo exit - // animation) and we're close to the 1.0 scale. Otherwise, we risk popping artifacts as we - // suddenly switch from low to high resolution. - const bool rescale_magnification = (render_scale > 0.5) && - (render_scale > current_scale * 1.1); - - // In general, it is worth changing the buffer scale if we have a lot of damage to the old - // buffer, so that for ex. a full re-scale is actually cheaper than repaiting the old buffer. - // This could easily happen for example if we have a video player during Expo start animation. - const int repaint_cost_current_scale = - damage_sum_area(visible_damage) * (current_scale * current_scale); - const int repaint_rescale_cost = (bbox.width * bbox.height) * (render_scale * render_scale); - - if ((repaint_cost_current_scale > repaint_rescale_cost) || rescale_magnification) - { - self->aux_buffer_current_scale[i][j] = render_scale; - self->aux_buffers[i][j].subbuffer = wf::geometry_t{ - 0, 0, - int(std::ceil(render_scale * self->aux_buffers[i][j].viewport_width)), - int(std::ceil(render_scale * self->aux_buffers[i][j].viewport_height)), - }; - - self->aux_buffer_damage[i][j] |= self->workspaces[i][j]->get_bounding_box(); - return true; - } - - return false; - } - - void schedule_instructions( - std::vector& instructions, - const wf::render_target_t& target, wf::region_t& damage) override - { - // Update workspaces in a render pass - for (int i = 0; i < (int)self->workspaces.size(); i++) - { - for (int j = 0; j < (int)self->workspaces[i].size(); j++) - { - const auto ws_bbox = self->wall->get_workspace_rectangle({i, j}); - const auto visible_box = - geometry_intersection(self->wall->viewport, ws_bbox) - wf::origin(ws_bbox); - wf::region_t visible_damage = self->aux_buffer_damage[i][j] & visible_box; - if (consider_rescale_workspace_buffer(i, j, visible_damage)) - { - visible_damage |= visible_box; - } - - if (!visible_damage.empty()) - { - scene::render_pass_params_t params; - params.instances = &instances[i][j]; - params.damage = std::move(visible_damage); - params.reference_output = self->wall->output; - params.target = self->aux_buffers[i][j]; - scene::run_render_pass(params, scene::RPASS_EMIT_SIGNALS); - self->aux_buffer_damage[i][j] ^= visible_damage; - } - } - } - - // Render the wall - instructions.push_back(scene::render_instruction_t{ - .instance = this, - .target = target, - .damage = damage & self->get_bounding_box(), - }); - - damage ^= self->get_bounding_box(); - } - - static gl_geometry scale_fbox(wf::geometry_t A, wf::geometry_t B, wf::geometry_t box) - { - const float px = 1.0 * (box.x - A.x) / A.width; - const float py = 1.0 * (box.y - A.y) / A.height; - const float px2 = 1.0 * (box.x + box.width - A.x) / A.width; - const float py2 = 1.0 * (box.y + box.height - A.y) / A.height; - return gl_geometry{ - B.x + B.width * px, - B.y + B.height * py, - B.x + B.width * px2, - B.y + B.height * py2, - }; - } - - void render(const wf::render_target_t& target, const wf::region_t& region) override - { - OpenGL::render_begin(target); - for (auto& box : region) - { - target.logic_scissor(wlr_box_from_pixman_box(box)); - OpenGL::clear(self->wall->background_color); - for (int i = 0; i < (int)self->workspaces.size(); i++) - { - for (int j = 0; j < (int)self->workspaces[i].size(); j++) - { - auto box = get_workspace_rect({i, j}); - auto A = self->wall->viewport; - auto B = self->get_bounding_box(); - gl_geometry render_geometry = scale_fbox(A, B, box); - auto& buffer = self->aux_buffers[i][j]; - - float dim = self->wall->get_color_for_workspace({i, j}); - const glm::vec4 color = glm::vec4(dim, dim, dim, 1.0); - - if (!buffer.subbuffer.has_value()) - { - OpenGL::render_transformed_texture({buffer.tex}, - render_geometry, {}, target.get_orthographic_projection(), color); - } else - { - // The 0.999f come from trying to avoid floating-point artifacts - const gl_geometry tex_geometry = { - 0.0f, - 1.0f - 0.999f * buffer.subbuffer->height / buffer.viewport_height, - 0.999f * buffer.subbuffer->width / buffer.viewport_width, - 1.0f, - }; - - OpenGL::render_transformed_texture({buffer.tex}, - render_geometry, tex_geometry, - target.get_orthographic_projection(), - color, OpenGL::TEXTURE_USE_TEX_GEOMETRY); - } - } - } - } - - OpenGL::render_end(); - self->wall->render_wall(target, region); - } - - void compute_visibility(wf::output_t *output, wf::region_t& visible) override - { - for (int i = 0; i < (int)self->workspaces.size(); i++) - { - for (int j = 0; j < (int)self->workspaces[i].size(); j++) - { - wf::region_t ws_region = self->workspaces[i][j]->get_bounding_box(); - for (auto& ch : this->instances[i][j]) - { - ch->compute_visibility(output, ws_region); - } - } - } - } - }; - - public: - workspace_wall_node_t(workspace_wall_t *wall) : node_t(false) - { - this->wall = wall; - auto [w, h] = wall->output->wset()->get_workspace_grid_size(); - workspaces.resize(w); - for (int i = 0; i < w; i++) - { - for (int j = 0; j < h; j++) - { - auto node = std::make_shared( - wall->output, wf::point_t{i, j}); - workspaces[i].push_back(node); - - aux_buffers[i][j].geometry = workspaces[i][j]->get_bounding_box(); - aux_buffers[i][j].scale = wall->output->handle->scale; - aux_buffers[i][j].wl_transform = WL_OUTPUT_TRANSFORM_NORMAL; - aux_buffers[i][j].transform = get_output_matrix_from_transform( - aux_buffers[i][j].wl_transform); - - auto size = - aux_buffers[i][j].framebuffer_box_from_geometry_box(aux_buffers[i][j].geometry); - OpenGL::render_begin(); - aux_buffers[i][j].allocate(size.width, size.height); - OpenGL::render_end(); - - aux_buffer_damage[i][j] |= aux_buffers[i][j].geometry; - aux_buffer_current_scale[i][j] = 1.0; - } - } - } - - ~workspace_wall_node_t() - { - OpenGL::render_begin(); - for (auto& [_, buffers] : aux_buffers) - { - for (auto& [_, buffer] : buffers) - { - buffer.release(); - } - } - - OpenGL::render_end(); - } - - virtual void gen_render_instances( - std::vector& instances, - scene::damage_callback push_damage, wf::output_t *shown_on) override - { - if (shown_on != this->wall->output) - { - return; - } - - instances.push_back(std::make_unique( - this, push_damage)); - } - - std::string stringify() const override - { - return "workspace-wall " + stringify_flags(); - } - - wf::geometry_t get_bounding_box() override - { - return wall->output->get_layout_geometry(); - } - - private: - workspace_wall_t *wall; - std::vector>> workspaces; - - // Buffers keeping the contents of almost-static workspaces - per_workspace_map_t aux_buffers; - // Damage accumulated for those buffers - per_workspace_map_t aux_buffer_damage; - // Current rendering scale for the workspace - per_workspace_map_t aux_buffer_current_scale; - }; + class workspace_wall_node_t; std::shared_ptr render_node; }; } diff --git a/plugins/common/workspace-wall.cpp b/plugins/common/workspace-wall.cpp new file mode 100644 index 000000000..823d14a0d --- /dev/null +++ b/plugins/common/workspace-wall.cpp @@ -0,0 +1,460 @@ +#include "wayfire/plugins/common/workspace-wall.hpp" +#include "wayfire/scene-input.hpp" +#include "wayfire/scene-operations.hpp" +#include "wayfire/workspace-stream.hpp" +#include "wayfire/opengl.hpp" +#include "wayfire/scene-render.hpp" +#include "wayfire/scene.hpp" +#include "wayfire/region.hpp" +#include "wayfire/core.hpp" + +#include + +namespace wf +{ +template using per_workspace_map_t = std::map>; + +class workspace_wall_t::workspace_wall_node_t : public scene::node_t +{ + class wwall_render_instance_t : public scene::render_instance_t + { + std::shared_ptr self; + per_workspace_map_t> instances; + + scene::damage_callback push_damage; + wf::signal::connection_t on_wall_damage = + [=] (scene::node_damage_signal *ev) + { + push_damage(ev->region); + }; + + wf::geometry_t get_workspace_rect(wf::point_t ws) + { + auto output_size = self->wall->output->get_screen_size(); + return { + .x = ws.x * (output_size.width + self->wall->gap_size), + .y = ws.y * (output_size.height + self->wall->gap_size), + .width = output_size.width, + .height = output_size.height, + }; + } + + public: + wwall_render_instance_t(workspace_wall_node_t *self, + scene::damage_callback push_damage) + { + this->self = std::dynamic_pointer_cast(self->shared_from_this()); + this->push_damage = push_damage; + self->connect(&on_wall_damage); + + for (int i = 0; i < (int)self->workspaces.size(); i++) + { + for (int j = 0; j < (int)self->workspaces[i].size(); j++) + { + auto push_damage_child = [=] (const wf::region_t& damage) + { + // Store the damage because we'll have to update the buffers + self->aux_buffer_damage[i][j] |= damage; + + wf::region_t our_damage; + for (auto& rect : damage) + { + wf::geometry_t box = wlr_box_from_pixman_box(rect); + box = box + wf::origin(get_workspace_rect({i, j})); + auto A = self->wall->viewport; + auto B = self->get_bounding_box(); + our_damage |= scale_box(A, B, box); + } + + // Also damage the 'screen' after transforming damage + push_damage(our_damage); + }; + + self->workspaces[i][j]->gen_render_instances(instances[i][j], + push_damage_child, self->wall->output); + } + } + } + + static int damage_sum_area(const wf::region_t& damage) + { + int sum = 0; + for (const auto& rect : damage) + { + sum += (rect.y2 - rect.y1) * (rect.x2 - rect.x1); + } + + return sum; + } + + bool consider_rescale_workspace_buffer(int i, int j, wf::region_t& visible_damage) + { + // In general, when rendering the auxilliary buffers for each workspace, we can render the + // workspace thumbnails in a lower resolution, because at the end they are shown scaled. + // This helps with performance and uses less GPU power. + // + // However, the situation is tricky because during the Expo animation the optimal render + // scale constantly changes. Thus, in some cases it is actually far from optimal to rescale + // on every frame - it is often better to just keep the buffers from the old scale. + // + // Nonetheless, we need to make sure to rescale when this makes sense, and to avoid visual + // artifacts. + auto bbox = self->workspaces[i][j]->get_bounding_box(); + const float render_scale = std::max( + 1.0 * bbox.width / self->wall->viewport.width, + 1.0 * bbox.height / self->wall->viewport.height); + const float current_scale = self->aux_buffer_current_scale[i][j]; + + // Avoid keeping a low resolution if we are going up in the scale (for example, expo exit + // animation) and we're close to the 1.0 scale. Otherwise, we risk popping artifacts as we + // suddenly switch from low to high resolution. + const bool rescale_magnification = (render_scale > 0.5) && + (render_scale > current_scale * 1.1); + + // In general, it is worth changing the buffer scale if we have a lot of damage to the old + // buffer, so that for ex. a full re-scale is actually cheaper than repaiting the old buffer. + // This could easily happen for example if we have a video player during Expo start animation. + const int repaint_cost_current_scale = + damage_sum_area(visible_damage) * (current_scale * current_scale); + const int repaint_rescale_cost = (bbox.width * bbox.height) * (render_scale * render_scale); + + if ((repaint_cost_current_scale > repaint_rescale_cost) || rescale_magnification) + { + self->aux_buffer_current_scale[i][j] = render_scale; + self->aux_buffers[i][j].subbuffer = wf::geometry_t{ + 0, 0, + int(std::ceil(render_scale * self->aux_buffers[i][j].viewport_width)), + int(std::ceil(render_scale * self->aux_buffers[i][j].viewport_height)), + }; + + self->aux_buffer_damage[i][j] |= self->workspaces[i][j]->get_bounding_box(); + return true; + } + + return false; + } + + void schedule_instructions( + std::vector& instructions, + const wf::render_target_t& target, wf::region_t& damage) override + { + // Update workspaces in a render pass + for (int i = 0; i < (int)self->workspaces.size(); i++) + { + for (int j = 0; j < (int)self->workspaces[i].size(); j++) + { + const auto ws_bbox = self->wall->get_workspace_rectangle({i, j}); + const auto visible_box = + geometry_intersection(self->wall->viewport, ws_bbox) - wf::origin(ws_bbox); + wf::region_t visible_damage = self->aux_buffer_damage[i][j] & visible_box; + if (consider_rescale_workspace_buffer(i, j, visible_damage)) + { + visible_damage |= visible_box; + } + + if (!visible_damage.empty()) + { + scene::render_pass_params_t params; + params.instances = &instances[i][j]; + params.damage = std::move(visible_damage); + params.reference_output = self->wall->output; + params.target = self->aux_buffers[i][j]; + scene::run_render_pass(params, scene::RPASS_EMIT_SIGNALS); + self->aux_buffer_damage[i][j] ^= visible_damage; + } + } + } + + // Render the wall + instructions.push_back(scene::render_instruction_t{ + .instance = this, + .target = target, + .damage = damage & self->get_bounding_box(), + }); + + damage ^= self->get_bounding_box(); + } + + static gl_geometry scale_fbox(wf::geometry_t A, wf::geometry_t B, wf::geometry_t box) + { + const float px = 1.0 * (box.x - A.x) / A.width; + const float py = 1.0 * (box.y - A.y) / A.height; + const float px2 = 1.0 * (box.x + box.width - A.x) / A.width; + const float py2 = 1.0 * (box.y + box.height - A.y) / A.height; + return gl_geometry{ + B.x + B.width * px, + B.y + B.height * py, + B.x + B.width * px2, + B.y + B.height * py2, + }; + } + + void render(const wf::render_target_t& target, const wf::region_t& region) override + { + OpenGL::render_begin(target); + for (auto& box : region) + { + target.logic_scissor(wlr_box_from_pixman_box(box)); + OpenGL::clear(self->wall->background_color); + for (int i = 0; i < (int)self->workspaces.size(); i++) + { + for (int j = 0; j < (int)self->workspaces[i].size(); j++) + { + auto box = get_workspace_rect({i, j}); + auto A = self->wall->viewport; + auto B = self->get_bounding_box(); + gl_geometry render_geometry = scale_fbox(A, B, box); + auto& buffer = self->aux_buffers[i][j]; + + float dim = self->wall->get_color_for_workspace({i, j}); + const glm::vec4 color = glm::vec4(dim, dim, dim, 1.0); + + if (!buffer.subbuffer.has_value()) + { + OpenGL::render_transformed_texture({buffer.tex}, + render_geometry, {}, target.get_orthographic_projection(), color); + } else + { + // The 0.999f come from trying to avoid floating-point artifacts + const gl_geometry tex_geometry = { + 0.0f, + 1.0f - 0.999f * buffer.subbuffer->height / buffer.viewport_height, + 0.999f * buffer.subbuffer->width / buffer.viewport_width, + 1.0f, + }; + + OpenGL::render_transformed_texture({buffer.tex}, + render_geometry, tex_geometry, + target.get_orthographic_projection(), + color, OpenGL::TEXTURE_USE_TEX_GEOMETRY); + } + } + } + } + + OpenGL::render_end(); + self->wall->render_wall(target, region); + } + + void compute_visibility(wf::output_t *output, wf::region_t& visible) override + { + for (int i = 0; i < (int)self->workspaces.size(); i++) + { + for (int j = 0; j < (int)self->workspaces[i].size(); j++) + { + wf::region_t ws_region = self->workspaces[i][j]->get_bounding_box(); + for (auto& ch : this->instances[i][j]) + { + ch->compute_visibility(output, ws_region); + } + } + } + } + }; + + public: + std::map, float> render_colors; + + workspace_wall_node_t(workspace_wall_t *wall) : node_t(false) + { + this->wall = wall; + auto [w, h] = wall->output->wset()->get_workspace_grid_size(); + workspaces.resize(w); + for (int i = 0; i < w; i++) + { + for (int j = 0; j < h; j++) + { + auto node = std::make_shared( + wall->output, wf::point_t{i, j}); + workspaces[i].push_back(node); + + aux_buffers[i][j].geometry = workspaces[i][j]->get_bounding_box(); + aux_buffers[i][j].scale = wall->output->handle->scale; + aux_buffers[i][j].wl_transform = WL_OUTPUT_TRANSFORM_NORMAL; + aux_buffers[i][j].transform = get_output_matrix_from_transform( + aux_buffers[i][j].wl_transform); + + auto size = + aux_buffers[i][j].framebuffer_box_from_geometry_box(aux_buffers[i][j].geometry); + OpenGL::render_begin(); + aux_buffers[i][j].allocate(size.width, size.height); + OpenGL::render_end(); + + aux_buffer_damage[i][j] |= aux_buffers[i][j].geometry; + aux_buffer_current_scale[i][j] = 1.0; + } + } + } + + ~workspace_wall_node_t() + { + OpenGL::render_begin(); + for (auto& [_, buffers] : aux_buffers) + { + for (auto& [_, buffer] : buffers) + { + buffer.release(); + } + } + + OpenGL::render_end(); + } + + virtual void gen_render_instances( + std::vector& instances, + scene::damage_callback push_damage, wf::output_t *shown_on) override + { + if (shown_on != this->wall->output) + { + return; + } + + instances.push_back(std::make_unique( + this, push_damage)); + } + + std::string stringify() const override + { + return "workspace-wall " + stringify_flags(); + } + + wf::geometry_t get_bounding_box() override + { + return wall->output->get_layout_geometry(); + } + + private: + workspace_wall_t *wall; + std::vector>> workspaces; + + // Buffers keeping the contents of almost-static workspaces + per_workspace_map_t aux_buffers; + // Damage accumulated for those buffers + per_workspace_map_t aux_buffer_damage; + // Current rendering scale for the workspace + per_workspace_map_t aux_buffer_current_scale; +}; + +workspace_wall_t::workspace_wall_t(wf::output_t *_output) : output(_output) +{ + this->viewport = get_wall_rectangle(); +} + +workspace_wall_t::~workspace_wall_t() +{ + stop_output_renderer(false); +} + +void workspace_wall_t::set_background_color(const wf::color_t& color) +{ + this->background_color = color; +} + +void workspace_wall_t::set_gap_size(int size) +{ + this->gap_size = size; +} + +void workspace_wall_t::set_viewport(const wf::geometry_t& viewport_geometry) +{ + this->viewport = viewport_geometry; + if (render_node) + { + scene::damage_node( + this->render_node, this->render_node->get_bounding_box()); + } +} + +wf::geometry_t workspace_wall_t::get_viewport() const +{ + return viewport; +} + +void workspace_wall_t::render_wall( + const wf::render_target_t& fb, const wf::region_t& damage) +{ + wall_frame_event_t data{fb}; + this->emit(&data); +} + +void workspace_wall_t::start_output_renderer() +{ + wf::dassert(render_node == nullptr, "Starting workspace-wall twice?"); + render_node = std::make_shared(this); + scene::add_front(wf::get_core().scene(), render_node); +} + +void workspace_wall_t::stop_output_renderer(bool reset_viewport) +{ + if (!render_node) + { + return; + } + + scene::remove_child(render_node); + render_node = nullptr; + + if (reset_viewport) + { + set_viewport({0, 0, 0, 0}); + } +} + +wf::geometry_t workspace_wall_t::get_workspace_rectangle( + const wf::point_t& ws) const +{ + auto size = this->output->get_screen_size(); + + return {ws.x * (size.width + gap_size), ws.y * (size.height + gap_size), + size.width, size.height}; +} + +wf::geometry_t workspace_wall_t::get_wall_rectangle() const +{ + auto size = this->output->get_screen_size(); + auto workspace_size = this->output->wset()->get_workspace_grid_size(); + + return {-gap_size, -gap_size, + workspace_size.width * (size.width + gap_size) + gap_size, + workspace_size.height * (size.height + gap_size) + gap_size}; +} + +void workspace_wall_t::set_ws_dim(const wf::point_t& ws, float value) +{ + render_colors[{ws.x, ws.y}] = value; + if (render_node) + { + scene::damage_node(render_node, render_node->get_bounding_box()); + } +} + +float workspace_wall_t::get_color_for_workspace(wf::point_t ws) +{ + auto it = render_colors.find({ws.x, ws.y}); + if (it == render_colors.end()) + { + return 1.0; + } + + return it->second; +} + +std::vector workspace_wall_t::get_visible_workspaces( + wf::geometry_t viewport) const +{ + std::vector visible; + auto wsize = output->wset()->get_workspace_grid_size(); + for (int i = 0; i < wsize.width; i++) + { + for (int j = 0; j < wsize.height; j++) + { + if (viewport & get_workspace_rectangle({i, j})) + { + visible.push_back({i, j}); + } + } + } + + return visible; +} +} // namespace wf diff --git a/plugins/meson.build b/plugins/meson.build index bd9824dec..8de769658 100644 --- a/plugins/meson.build +++ b/plugins/meson.build @@ -1,5 +1,3 @@ -subdir('common') - if get_option('custom_pch') # Add a workaround for https://github.com/mesonbuild/meson/issues/4350 # We create a PCH ourselves and manage it for all plugins. @@ -51,6 +49,9 @@ else plugin_pch_dep = declare_dependency() endif +wobbly_inc = include_directories('wobbly/') +subdir('common') + subdir('ipc') subdir('protocols') subdir('vswitch') diff --git a/plugins/scale/meson.build b/plugins/scale/meson.build index aeafee0c4..b725faa3b 100644 --- a/plugins/scale/meson.build +++ b/plugins/scale/meson.build @@ -4,6 +4,7 @@ all_deps = [wlroots, pixman, wfconfig, wftouch, cairo, pango, pangocairo, json, shared_module('scale', ['scale.cpp', 'scale-title-overlay.cpp'], include_directories: all_include_dirs, dependencies: all_deps, + link_with: [move_drag_interface], install: true, install_dir: conf_data.get('PLUGIN_PATH')) diff --git a/plugins/scale/scale.cpp b/plugins/scale/scale.cpp index de445d5bc..7c6b8f1f5 100644 --- a/plugins/scale/scale.cpp +++ b/plugins/scale/scale.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include diff --git a/plugins/single_plugins/expo.cpp b/plugins/single_plugins/expo.cpp index 486dfa0f1..e5e54cd38 100644 --- a/plugins/single_plugins/expo.cpp +++ b/plugins/single_plugins/expo.cpp @@ -12,6 +12,7 @@ #include #include #include +#include "wayfire/plugins/wobbly/wobbly-signal.hpp" #include #include diff --git a/plugins/single_plugins/meson.build b/plugins/single_plugins/meson.build index c828f745b..8e8a68014 100644 --- a/plugins/single_plugins/meson.build +++ b/plugins/single_plugins/meson.build @@ -8,10 +8,17 @@ plugins = [ all_include_dirs = [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, vswitch_inc, wobbly_inc, grid_inc] all_deps = [wlroots, pixman, wfconfig, wftouch, cairo, pango, pangocairo, json, plugin_pch_dep] +extra_libs = { + 'move': [move_drag_interface], + 'expo': [workspace_wall, move_drag_interface], + 'vswipe': [workspace_wall], +} + foreach plugin : plugins shared_module(plugin, plugin + '.cpp', include_directories: all_include_dirs, dependencies: all_deps, + link_with: extra_libs.get(plugin, []), install: true, install_dir: conf_data.get('PLUGIN_PATH')) endforeach diff --git a/plugins/tile/meson.build b/plugins/tile/meson.build index be6e721ea..d39b97773 100644 --- a/plugins/tile/meson.build +++ b/plugins/tile/meson.build @@ -2,6 +2,7 @@ tile = shared_module('simple-tile', ['tile-plugin.cpp', 'tree.cpp', 'tree-controller.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, grid_inc, wobbly_inc, ipc_include_dirs], dependencies: [wlroots, pixman, wfconfig, json, plugin_pch_dep], + link_with: [move_drag_interface], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) diff --git a/plugins/vswitch/meson.build b/plugins/vswitch/meson.build index dfc64159c..9b440870d 100644 --- a/plugins/vswitch/meson.build +++ b/plugins/vswitch/meson.build @@ -4,6 +4,7 @@ vswitch = shared_module('vswitch', ['vswitch.cpp'], include_directories: [wayfire_api_inc, wayfire_conf_inc, plugins_common_inc, vswitch_inc, ipc_include_dirs], dependencies: [wlroots, pixman, wfconfig, json, plugin_pch_dep], + link_with: [workspace_wall], install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) diff --git a/plugins/vswitch/vswitch.cpp b/plugins/vswitch/vswitch.cpp index d6b35178c..adc4906ef 100644 --- a/plugins/vswitch/vswitch.cpp +++ b/plugins/vswitch/vswitch.cpp @@ -7,6 +7,316 @@ #include #include "plugins/ipc/ipc-method-repository.hpp" #include "wayfire/plugins/common/shared-core-data.hpp" +#include "wayfire/unstable/translation-node.hpp" +#include "wayfire/scene-operations.hpp" +#include "wayfire/render-manager.hpp" + +namespace wf +{ +namespace vswitch +{ +using namespace animation; +class workspace_animation_t : public duration_t +{ + public: + using duration_t::duration_t; + timed_transition_t dx{*this}; + timed_transition_t dy{*this}; +}; + +/** + * A simple scenegraph node which draws a view at a fixed position and as an overlay over the workspace wall. + */ +class vswitch_overlay_node_t : public wf::scene::node_t +{ + std::weak_ptr _view; + + public: + vswitch_overlay_node_t(wayfire_toplevel_view view) : node_t(true) + { + _view = view->weak_from_this(); + } + + // Since we do not grab focus via a grab node, route focus to the view being rendered as an overlay + wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output) + { + if (auto view = _view.lock()) + { + return view->get_transformed_node()->keyboard_refocus(output); + } + + return wf::keyboard_focus_node_t{}; + } + + virtual void gen_render_instances(std::vector& instances, + scene::damage_callback push_damage, wf::output_t *output = nullptr) + { + if (auto view = _view.lock()) + { + view->get_transformed_node()->gen_render_instances(instances, push_damage, output); + } + } + + virtual wf::geometry_t get_bounding_box() + { + if (auto view = _view.lock()) + { + return view->get_transformed_node()->get_bounding_box(); + } + + return {0, 0, 0, 0}; + } +}; + +/** + * Represents the action of switching workspaces with the vswitch algorithm. + * + * The workspace is actually switched at the end of the animation + */ +class workspace_switch_t +{ + public: + /** + * Initialize the workspace switch process. + * + * @param output The output the workspace switch happens on. + */ + workspace_switch_t(output_t *output) + { + this->output = output; + wall = std::make_unique(output); + animation = workspace_animation_t{ + wf::option_wrapper_t{"vswitch/duration"} + }; + } + + /** + * Initialize switching animation. + * At this point, the calling plugin needs to have the custom renderer + * ability set. + */ + virtual void start_switch() + { + /* Setup wall */ + wall->set_gap_size(gap); + wall->set_viewport(wall->get_workspace_rectangle( + output->wset()->get_current_workspace())); + wall->set_background_color(background_color); + wall->start_output_renderer(); + + if (overlay_view_node) + { + wf::scene::readd_front(wf::get_core().scene(), overlay_view_node); + } + + output->render->add_effect(&post_render, OUTPUT_EFFECT_POST); + + running = true; + + /* Setup animation */ + animation.dx.set(0, 0); + animation.dy.set(0, 0); + animation.start(); + } + + /** + * Start workspace switch animation towards the given workspace, + * and set that workspace as current. + * + * @param workspace The new target workspace. + */ + virtual void set_target_workspace(point_t workspace) + { + point_t cws = output->wset()->get_current_workspace(); + + animation.dx.set(animation.dx + cws.x - workspace.x, 0); + animation.dy.set(animation.dy + cws.y - workspace.y, 0); + animation.start(); + + std::vector fixed_views; + if (overlay_view) + { + fixed_views.push_back(overlay_view); + } + + output->wset()->set_workspace(workspace, fixed_views); + } + + /** + * Set the overlay view. It will be hidden from the normal workspace layers + * and shown on top of the workspace wall. The overlay view's position is + * not animated together with the workspace transition, but its alpha is. + * + * Note: if the view disappears, the caller is responsible for resetting the + * overlay view. + * + * @param view The desired overlay view, or NULL if the overlay view needs + * to be unset. + */ + virtual void set_overlay_view(wayfire_toplevel_view view) + { + if (this->overlay_view == view) + { + /* Nothing to do */ + return; + } + + /* Reset old view */ + if (this->overlay_view) + { + wf::scene::set_node_enabled(overlay_view->get_transformed_node(), true); + overlay_view->get_transformed_node()->rem_transformer( + vswitch_view_transformer_name); + + wf::scene::remove_child(overlay_view_node); + overlay_view_node.reset(); + } + + /* Set new view */ + this->overlay_view = view; + if (view) + { + view->get_transformed_node()->add_transformer( + std::make_shared(view), + wf::TRANSFORMER_2D, vswitch_view_transformer_name); + wf::scene::set_node_enabled(view->get_transformed_node(), false); + + // Render as an overlay, but make sure it is translated to the local output + auto vswitch_overlay = std::make_shared(view); + + overlay_view_node = std::make_shared(); + overlay_view_node->set_children_list({vswitch_overlay}); + overlay_view_node->set_offset(origin(output->get_layout_geometry())); + + wf::scene::add_front(wf::get_core().scene(), overlay_view_node); + } + } + + /** @return the current overlay view, might be NULL. */ + virtual wayfire_view get_overlay_view() + { + return this->overlay_view; + } + + /** + * Called automatically when the workspace switch animation is done. + * By default, this stops the animation. + * + * @param normal_exit Whether the operation has ended because of animation + * running out, in which case the workspace and the overlay view are + * adjusted, and otherwise not. + */ + virtual void stop_switch(bool normal_exit) + { + if (normal_exit) + { + auto old_ws = output->wset()->get_current_workspace(); + adjust_overlay_view_switch_done(old_ws); + } + + wall->stop_output_renderer(true); + output->render->rem_effect(&post_render); + running = false; + } + + virtual bool is_running() const + { + return running; + } + + virtual ~workspace_switch_t() + {} + + protected: + option_wrapper_t gap{"vswitch/gap"}; + option_wrapper_t background_color{"vswitch/background"}; + workspace_animation_t animation; + + output_t *output; + std::unique_ptr wall; + + const std::string vswitch_view_transformer_name = "vswitch-transformer"; + wayfire_toplevel_view overlay_view; + std::shared_ptr overlay_view_node; + + bool running = false; + void update_overlay_fb() + { + if (!overlay_view) + { + return; + } + + double progress = animation.progress(); + + auto tmanager = overlay_view->get_transformed_node(); + auto tr = tmanager->get_transformer( + vswitch_view_transformer_name); + + static constexpr double smoothing_in = 0.4; + static constexpr double smoothing_out = 0.2; + static constexpr double smoothing_amount = 0.5; + + tmanager->begin_transform_update(); + if (progress <= smoothing_in) + { + tr->alpha = 1.0 - (smoothing_amount / smoothing_in) * progress; + } else if (progress >= 1.0 - smoothing_out) + { + tr->alpha = 1.0 - (smoothing_amount / smoothing_out) * (1.0 - progress); + } else + { + tr->alpha = smoothing_amount; + } + + tmanager->end_transform_update(); + } + + wf::effect_hook_t post_render = [=] () + { + auto start = wall->get_workspace_rectangle( + output->wset()->get_current_workspace()); + auto size = output->get_screen_size(); + geometry_t viewport = { + (int)std::round(animation.dx * (size.width + gap) + start.x), + (int)std::round(animation.dy * (size.height + gap) + start.y), + start.width, + start.height, + }; + wall->set_viewport(viewport); + update_overlay_fb(); + + output->render->damage_whole(); + output->render->schedule_redraw(); + if (!animation.running()) + { + stop_switch(true); + } + }; + + /** + * Emit the view-change-workspace signal from the old workspace to the current + * workspace and unset the view. + */ + virtual void adjust_overlay_view_switch_done(wf::point_t old_workspace) + { + if (!overlay_view) + { + return; + } + + wf::view_change_workspace_signal data; + data.view = overlay_view; + data.from = old_workspace; + data.to = output->wset()->get_current_workspace(); + output->emit(&data); + + set_overlay_view(nullptr); + wf::get_core().seat->refocus(); + } +}; +} +} class vswitch : public wf::per_output_plugin_instance_t { diff --git a/plugins/vswitch/wayfire/plugins/vswitch.hpp b/plugins/vswitch/wayfire/plugins/vswitch.hpp index 306153f24..69a44ac9e 100644 --- a/plugins/vswitch/wayfire/plugins/vswitch.hpp +++ b/plugins/vswitch/wayfire/plugins/vswitch.hpp @@ -1,14 +1,11 @@ #pragma once #include "wayfire/scene-input.hpp" -#include "wayfire/scene-operations.hpp" #include "wayfire/seat.hpp" #include "wayfire/core.hpp" -#include "wayfire/render-manager.hpp" #include "wayfire/scene-render.hpp" #include "wayfire/scene.hpp" #include "wayfire/toplevel-view.hpp" -#include "wayfire/unstable/translation-node.hpp" #include "wayfire/view-helpers.hpp" #include #include @@ -25,307 +22,6 @@ namespace wf { namespace vswitch { -using namespace animation; -class workspace_animation_t : public duration_t -{ - public: - using duration_t::duration_t; - timed_transition_t dx{*this}; - timed_transition_t dy{*this}; -}; - -/** - * A simple scenegraph node which draws a view at a fixed position and as an overlay over the workspace wall. - */ -class vswitch_overlay_node_t : public wf::scene::node_t -{ - std::weak_ptr _view; - - public: - vswitch_overlay_node_t(wayfire_toplevel_view view) : node_t(true) - { - _view = view->weak_from_this(); - } - - // Since we do not grab focus via a grab node, route focus to the view being rendered as an overlay - wf::keyboard_focus_node_t keyboard_refocus(wf::output_t *output) - { - if (auto view = _view.lock()) - { - return view->get_transformed_node()->keyboard_refocus(output); - } - - return wf::keyboard_focus_node_t{}; - } - - virtual void gen_render_instances(std::vector& instances, - scene::damage_callback push_damage, wf::output_t *output = nullptr) - { - if (auto view = _view.lock()) - { - view->get_transformed_node()->gen_render_instances(instances, push_damage, output); - } - } - - virtual wf::geometry_t get_bounding_box() - { - if (auto view = _view.lock()) - { - return view->get_transformed_node()->get_bounding_box(); - } - - return {0, 0, 0, 0}; - } -}; - -/** - * Represents the action of switching workspaces with the vswitch algorithm. - * - * The workspace is actually switched at the end of the animation - */ -class workspace_switch_t -{ - public: - /** - * Initialize the workspace switch process. - * - * @param output The output the workspace switch happens on. - */ - workspace_switch_t(output_t *output) - { - this->output = output; - wall = std::make_unique(output); - animation = workspace_animation_t{ - wf::option_wrapper_t{"vswitch/duration"} - }; - } - - /** - * Initialize switching animation. - * At this point, the calling plugin needs to have the custom renderer - * ability set. - */ - virtual void start_switch() - { - /* Setup wall */ - wall->set_gap_size(gap); - wall->set_viewport(wall->get_workspace_rectangle( - output->wset()->get_current_workspace())); - wall->set_background_color(background_color); - wall->start_output_renderer(); - - if (overlay_view_node) - { - wf::scene::readd_front(wf::get_core().scene(), overlay_view_node); - } - - output->render->add_effect(&post_render, OUTPUT_EFFECT_POST); - - running = true; - - /* Setup animation */ - animation.dx.set(0, 0); - animation.dy.set(0, 0); - animation.start(); - } - - /** - * Start workspace switch animation towards the given workspace, - * and set that workspace as current. - * - * @param workspace The new target workspace. - */ - virtual void set_target_workspace(point_t workspace) - { - point_t cws = output->wset()->get_current_workspace(); - - animation.dx.set(animation.dx + cws.x - workspace.x, 0); - animation.dy.set(animation.dy + cws.y - workspace.y, 0); - animation.start(); - - std::vector fixed_views; - if (overlay_view) - { - fixed_views.push_back(overlay_view); - } - - output->wset()->set_workspace(workspace, fixed_views); - } - - /** - * Set the overlay view. It will be hidden from the normal workspace layers - * and shown on top of the workspace wall. The overlay view's position is - * not animated together with the workspace transition, but its alpha is. - * - * Note: if the view disappears, the caller is responsible for resetting the - * overlay view. - * - * @param view The desired overlay view, or NULL if the overlay view needs - * to be unset. - */ - virtual void set_overlay_view(wayfire_toplevel_view view) - { - if (this->overlay_view == view) - { - /* Nothing to do */ - return; - } - - /* Reset old view */ - if (this->overlay_view) - { - wf::scene::set_node_enabled(overlay_view->get_transformed_node(), true); - overlay_view->get_transformed_node()->rem_transformer( - vswitch_view_transformer_name); - - wf::scene::remove_child(overlay_view_node); - overlay_view_node.reset(); - } - - /* Set new view */ - this->overlay_view = view; - if (view) - { - view->get_transformed_node()->add_transformer( - std::make_shared(view), - wf::TRANSFORMER_2D, vswitch_view_transformer_name); - wf::scene::set_node_enabled(view->get_transformed_node(), false); - - // Render as an overlay, but make sure it is translated to the local output - auto vswitch_overlay = std::make_shared(view); - - overlay_view_node = std::make_shared(); - overlay_view_node->set_children_list({vswitch_overlay}); - overlay_view_node->set_offset(origin(output->get_layout_geometry())); - - wf::scene::add_front(wf::get_core().scene(), overlay_view_node); - } - } - - /** @return the current overlay view, might be NULL. */ - virtual wayfire_view get_overlay_view() - { - return this->overlay_view; - } - - /** - * Called automatically when the workspace switch animation is done. - * By default, this stops the animation. - * - * @param normal_exit Whether the operation has ended because of animation - * running out, in which case the workspace and the overlay view are - * adjusted, and otherwise not. - */ - virtual void stop_switch(bool normal_exit) - { - if (normal_exit) - { - auto old_ws = output->wset()->get_current_workspace(); - adjust_overlay_view_switch_done(old_ws); - } - - wall->stop_output_renderer(true); - output->render->rem_effect(&post_render); - running = false; - } - - virtual bool is_running() const - { - return running; - } - - virtual ~workspace_switch_t() - {} - - protected: - option_wrapper_t gap{"vswitch/gap"}; - option_wrapper_t background_color{"vswitch/background"}; - workspace_animation_t animation; - - output_t *output; - std::unique_ptr wall; - - const std::string vswitch_view_transformer_name = "vswitch-transformer"; - wayfire_toplevel_view overlay_view; - std::shared_ptr overlay_view_node; - - bool running = false; - void update_overlay_fb() - { - if (!overlay_view) - { - return; - } - - double progress = animation.progress(); - - auto tmanager = overlay_view->get_transformed_node(); - auto tr = tmanager->get_transformer( - vswitch_view_transformer_name); - - static constexpr double smoothing_in = 0.4; - static constexpr double smoothing_out = 0.2; - static constexpr double smoothing_amount = 0.5; - - tmanager->begin_transform_update(); - if (progress <= smoothing_in) - { - tr->alpha = 1.0 - (smoothing_amount / smoothing_in) * progress; - } else if (progress >= 1.0 - smoothing_out) - { - tr->alpha = 1.0 - (smoothing_amount / smoothing_out) * (1.0 - progress); - } else - { - tr->alpha = smoothing_amount; - } - - tmanager->end_transform_update(); - } - - wf::effect_hook_t post_render = [=] () - { - auto start = wall->get_workspace_rectangle( - output->wset()->get_current_workspace()); - auto size = output->get_screen_size(); - geometry_t viewport = { - (int)std::round(animation.dx * (size.width + gap) + start.x), - (int)std::round(animation.dy * (size.height + gap) + start.y), - start.width, - start.height, - }; - wall->set_viewport(viewport); - update_overlay_fb(); - - output->render->damage_whole(); - output->render->schedule_redraw(); - if (!animation.running()) - { - stop_switch(true); - } - }; - - /** - * Emit the view-change-workspace signal from the old workspace to the current - * workspace and unset the view. - */ - virtual void adjust_overlay_view_switch_done(wf::point_t old_workspace) - { - if (!overlay_view) - { - return; - } - - wf::view_change_workspace_signal data; - data.view = overlay_view; - data.from = old_workspace; - data.to = output->wset()->get_current_workspace(); - output->emit(&data); - - set_overlay_view(nullptr); - wf::get_core().seat->refocus(); - } -}; - /** * A simple class to register the vswitch bindings and get a custom callback called. */ diff --git a/plugins/wobbly/meson.build b/plugins/wobbly/meson.build index e9cc44970..4d3f5ef2a 100644 --- a/plugins/wobbly/meson.build +++ b/plugins/wobbly/meson.build @@ -8,5 +8,4 @@ wobbly = shared_module('wobbly', install: true, install_dir: join_paths(get_option('libdir'), 'wayfire')) -wobbly_inc = include_directories('.') install_headers(['wayfire/plugins/wobbly/wobbly-signal.hpp'], subdir: 'wayfire/plugins/wobbly') diff --git a/src/api/wayfire/plugin.hpp b/src/api/wayfire/plugin.hpp index d75581181..518102513 100644 --- a/src/api/wayfire/plugin.hpp +++ b/src/api/wayfire/plugin.hpp @@ -105,7 +105,7 @@ class plugin_interface_t using wayfire_plugin_load_func = wf::plugin_interface_t * (*)(); /** The version of Wayfire's API/ABI */ -constexpr uint32_t WAYFIRE_API_ABI_VERSION = 2025'02'26; +constexpr uint32_t WAYFIRE_API_ABI_VERSION = 2025'02'27; /** * Each plugin must also provide a function which returns the Wayfire API/ABI diff --git a/src/api/wayfire/toplevel.hpp b/src/api/wayfire/toplevel.hpp index a20541e5f..3c6fd6b31 100644 --- a/src/api/wayfire/toplevel.hpp +++ b/src/api/wayfire/toplevel.hpp @@ -5,6 +5,7 @@ #include "wayfire/geometry.hpp" #include "wayfire/object.hpp" #include +#include "wayfire/nonstd/wlroots.hpp" // IWYU pragma: keep namespace wf { diff --git a/src/core/seat/hotspot-manager.hpp b/src/core/seat/hotspot-manager.hpp index c1d0e3322..9e136b8e2 100644 --- a/src/core/seat/hotspot-manager.hpp +++ b/src/core/seat/hotspot-manager.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include "wayfire/util.hpp" #include #include