Untitled
unknown
plain_text
a year ago
10 kB
16
Indexable
#include <cmath>
#include <set>
#include <wayfire/core.hpp>
#include <wayfire/seat.hpp>
#include <wayfire/plugin.hpp>
#include <wayfire/view-helpers.hpp>
#include <wayfire/toplevel-view.hpp>
#include <wayfire/util/duration.hpp>
#include <wayfire/view-transform.hpp>
#include <wayfire/render-manager.hpp>
#include <wayfire/window-manager.hpp>
#include <wayfire/signal-definitions.hpp>
#include <wayfire/scene-operations.hpp>
namespace wf
{
namespace dodge
{
static std::string dodge_transformer_name = "dodge";
// Simple structure for each dodging view
struct dodge_view_data
{
wayfire_view view;
std::shared_ptr<wf::scene::view_2d_transformer_t> transformer;
wf::pointf_t direction;
double delay; // Simple delay 0.0 to 0.3
};
// Helper function to check if two boxes intersect
bool boxes_intersect(const wlr_box &a, const wlr_box &b)
{
return !(a.x >= b.x + b.width ||
b.x >= a.x + a.width ||
a.y >= b.y + b.height ||
b.y >= a.y + a.height);
}
class wayfire_dodge : public wf::plugin_interface_t
{
std::vector<dodge_view_data> views_from;
wayfire_view view_to, last_focused_view;
wf::animation::simple_animation_t progression{wf::create_option(2000)};
bool view_to_focused;
wf::pointf_t direction;
std::set<wf::output_t*> active_outputs;
public:
void init() override
{
wf::get_core().connect(&view_mapped);
wf::get_core().connect(&view_unmapped);
this->progression.set(0, 0);
}
wf::signal::connection_t<wf::view_activated_state_signal> view_activated =
[=] (wf::view_activated_state_signal *ev)
{
if (ev->view == wf::get_core().seat->get_active_view())
{
last_focused_view = wf::get_core().seat->get_active_view();
return;
}
if (progression.running())
{
return;
}
view_to = ev->view;
if (!last_focused_view || !view_to || last_focused_view == view_to)
{
return;
}
auto to_bb = view_to->get_bounding_box();
auto all_views = wf::get_core().get_all_views();
// Find overlapping views
std::vector<wayfire_view> overlapping_views;
for (auto& view : all_views)
{
if (view == view_to || !view->is_mapped())
{
continue;
}
auto toplevel = wf::toplevel_cast(view);
if (!toplevel)
{
continue;
}
auto view_bb = view->get_bounding_box();
if (boxes_intersect(to_bb, view_bb))
{
overlapping_views.push_back(view);
}
}
if (overlapping_views.empty())
{
// No overlapping views, just focus normally
wf::get_core().seat->focus_view(view_to);
view_bring_to_front(view_to);
view_to = nullptr;
return;
}
// Keep the current focused view in front initially (this prevents jumping)
view_bring_to_front(last_focused_view);
// Calculate main direction
compute_direction(last_focused_view, view_to);
// Setup overlapping views with fan directions
for (size_t i = 0; i < overlapping_views.size(); ++i)
{
dodge_view_data view_data;
view_data.view = overlapping_views[i];
view_data.transformer = std::make_shared<wf::scene::view_2d_transformer_t>(view_data.view);
// Create fan effect: spread views in arc around main direction
double angle_offset = 0.0;
if (overlapping_views.size() > 1)
{
// Spread over 90 degrees total
double spread = M_PI / 2.0;
double step = spread / overlapping_views.size();
angle_offset = (double(i) * step) - (spread / 2.0);
}
// Apply angle offset to main direction
double cos_a = std::cos(angle_offset);
double sin_a = std::sin(angle_offset);
view_data.direction.x = direction.x * cos_a - direction.y * sin_a;
view_data.direction.y = direction.x * sin_a + direction.y * cos_a;
// Simple cascade delay
view_data.delay = double(i) * 0.1; // Max 0.3 seconds delay
view_data.view->get_transformed_node()->add_transformer(view_data.transformer,
wf::TRANSFORMER_2D,
dodge_transformer_name);
if (active_outputs.find(view_data.view->get_output()) == active_outputs.end())
{
view_data.view->get_output()->render->add_effect(&dodge_animation_hook, wf::OUTPUT_EFFECT_PRE);
active_outputs.insert(view_data.view->get_output());
}
views_from.push_back(view_data);
}
view_to_focused = false;
this->progression.animate(0, 1);
};
wf::signal::connection_t<wf::view_mapped_signal> view_mapped =
[=] (wf::view_mapped_signal *ev)
{
ev->view->connect(&view_activated);
};
wf::signal::connection_t<wf::view_unmapped_signal> view_unmapped =
[=] (wf::view_unmapped_signal *ev)
{
last_focused_view = wf::get_core().seat->get_active_view();
if (ev->view == view_to)
{
view_to = nullptr;
}
views_from.erase(std::remove_if(views_from.begin(), views_from.end(),
[ev](const dodge_view_data& data) {
return data.view == ev->view;
}), views_from.end());
};
double magnitude(int x, int y)
{
return std::sqrt(x * x + y * y);
}
void compute_direction(wayfire_view from, wayfire_view to)
{
auto from_bb = from->get_bounding_box();
auto to_bb = to->get_bounding_box();
auto from_center = wf::point_t{from_bb.x + from_bb.width / 2, from_bb.y + from_bb.height / 2};
auto to_center = wf::point_t{to_bb.x + to_bb.width / 2, to_bb.y + to_bb.height / 2};
auto x = double(from_center.x - to_center.x);
auto y = double(from_center.y - to_center.y);
auto m = magnitude(x, y);
if (m == 0)
{
direction = {1.0, 0.0};
} else
{
x /= m;
y /= m;
direction = wf::pointf_t{x, y};
}
}
void damage_views()
{
for (auto& view_data : views_from)
{
view_data.view->damage();
}
if (view_to)
{
view_to->damage();
}
}
void finish_animation()
{
for (auto& view_data : views_from)
{
view_data.view->get_transformed_node()->rem_transformer(dodge_transformer_name);
}
for (auto& output : active_outputs)
{
output->render->rem_effect(&dodge_animation_hook);
}
active_outputs.clear();
views_from.clear();
view_to = nullptr;
}
// Perfectly smooth animation curve using smoothstep
double smooth_bump(double t)
{
if (t <= 0.0) return 0.0;
if (t >= 1.0) return 0.0;
// Create a bump that goes 0 -> 1 -> 0 smoothly
// Use smoothstep twice to create a bump function
double rising = t * 2.0; // 0 to 1 in first half
double falling = (1.0 - t) * 2.0; // 1 to 0 in second half
// Clamp to [0,1] range
rising = std::max(0.0, std::min(1.0, rising));
falling = std::max(0.0, std::min(1.0, falling));
// Smoothstep function: 3t² - 2t³ (smooth at both ends)
auto smoothstep = [](double x) {
return x * x * (3.0 - 2.0 * x);
};
// Combine rising and falling with smooth transitions
return smoothstep(rising) * smoothstep(falling);
}
bool step_animation()
{
if (!view_to || !last_focused_view)
{
return false;
}
auto from_bb = last_focused_view->get_bounding_box();
auto to_bb = view_to->get_bounding_box();
auto move_dist = std::max(from_bb.width, to_bb.width) * 1.0; // Good movement distance
double progress = progression.progress();
// Animate overlapping views with speed-adjusted timing
for (auto& view_data : views_from)
{
// Calculate when this view should start and finish
double start_time = view_data.delay;
double end_time = 1.0; // All views finish at the same time
double duration = end_time - start_time;
// Calculate progress for this specific view
double view_progress = 0.0;
if (progress >= start_time && duration > 0.0)
{
view_progress = (progress - start_time) / duration;
view_progress = std::min(1.0, view_progress);
}
double move_x = move_dist * view_data.direction.x;
double move_y = move_dist * view_data.direction.y;
double animation_value = smooth_bump(view_progress);
view_data.transformer->translation_x = animation_value * move_x;
view_data.transformer->translation_y = animation_value * move_y;
}
// Target view stays stationary - no animation for it
// Keep it in background until halfway through animation
if (progress > 0.5 && !view_to_focused)
{
wf::get_core().seat->focus_view(view_to);
view_bring_to_front(view_to);
view_to_focused = true;
}
return progression.running();
}
wf::effect_hook_t dodge_animation_hook = [=] ()
{
damage_views();
bool result = step_animation();
damage_views();
if (!result)
{
finish_animation();
}
};
void fini() override
{
finish_animation();
wf::get_core().disconnect(&view_mapped);
wf::get_core().disconnect(&view_unmapped);
}
};
}
}
DECLARE_WAYFIRE_PLUGIN(wf::dodge::wayfire_dodge);Editor is loading...
Leave a Comment