Untitled

 avatar
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