File: move-drag-interface.cpp

package info (click to toggle)
wayfire 0.10.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,764 kB
  • sloc: cpp: 52,464; xml: 2,987; ansic: 699; makefile: 161
file content (669 lines) | stat: -rw-r--r-- 20,900 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
#include "wayfire/plugins/common/move-drag-interface.hpp"
#include "wayfire/debug.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 <wayfire/util/log.hpp>
#include <cmath>
#include <wayfire/view-transform.hpp>
#include <wayfire/util/duration.hpp>
#include <wayfire/nonstd/wlroots-full.hpp>
#include <wayfire/window-manager.hpp>
#include <wayfire/nonstd/reverse.hpp>
#include <wayfire/plugins/common/util.hpp>
#include <wayfire/plugins/wobbly/wobbly-signal.hpp>
#include <wayfire/output-layout.hpp>
#include <wayfire/render-manager.hpp>
#include <wayfire/nonstd/observer_ptr.h>
#include <wayfire/workspace-set.hpp>
#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<scale_around_grab_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::scene::render_instruction_t& data) override
        {
            auto bbox = self->get_bounding_box();
            auto tex  = this->get_texture(data.target.scale);
            data.pass->add_texture(tex, data.target, bbox, data.damage, self->alpha_factor);
        }
    };

    void gen_render_instances(std::vector<scene::render_instance_uptr>& instances,
        scene::damage_callback push_damage, wf::output_t *shown_on) override
    {
        instances.push_back(std::make_unique<render_instance_t>(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<scale_around_grab_t> 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<dragged_view_t> views;
    dragged_view_node_t(std::vector<dragged_view_t> views) : node_t(false)
    {
        this->views = views;
    }

    std::string stringify() const override
    {
        return "move-drag-view " + stringify_flags();
    }

    void gen_render_instances(std::vector<scene::render_instance_uptr>& instances,
        scene::damage_callback push_damage, wf::output_t *output = nullptr) override
    {
        instances.push_back(std::make_unique<dragged_view_render_instance_t>(
            std::dynamic_pointer_cast<dragged_view_node_t>(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;
        wf::output_t *shown_on = nullptr;
        std::weak_ptr<dragged_view_node_t> self;
        std::unique_ptr<wf::scene::render_instance_manager_t> children_manager;

        wf::signal::connection_t<scene::node_damage_signal> on_node_damage =
            [=] (scene::node_damage_signal *data)
        {
            push_damage(data->region);
        };

      public:
        dragged_view_render_instance_t(std::shared_ptr<dragged_view_node_t> self,
            wf::scene::damage_callback push_damage, wf::output_t *shown_on)
        {
            this->self = self;
            this->push_damage = push_damage;

            std::vector<wf::scene::node_ptr> all_rendered;
            for (auto& view : self->views)
            {
                all_rendered.push_back(view.view->get_transformed_node());
            }

            auto push_damage_child = [=] (wf::region_t child_damage)
            {
                push_damage(last_bbox);
                last_bbox = this->self.lock()->get_bounding_box();
                push_damage(last_bbox);
            };

            this->shown_on = shown_on;
            this->children_manager = std::make_unique<wf::scene::render_instance_manager_t>(all_rendered,
                push_damage_child,
                shown_on);

            const int BIG_NUMBER    = 1e5;
            wf::region_t big_region =
                wf::geometry_t{-BIG_NUMBER, -BIG_NUMBER, 2 * BIG_NUMBER, 2 * BIG_NUMBER};
            children_manager->set_visibility_region(big_region);
        }

        void schedule_instructions(std::vector<scene::render_instruction_t>& instructions,
            const wf::render_target_t& target, wf::region_t& damage) override
        {
            for (auto& inst : children_manager->get_instances())
            {
                inst->schedule_instructions(instructions, target, damage);
            }
        }

        void presentation_feedback(wf::output_t *output) override
        {
            for (auto& instance : children_manager->get_instances())
            {
                instance->presentation_feedback(output);
            }
        }
    };
};

struct core_drag_t::impl
{
    // All views being dragged, more than one in case of join_views.
    std::vector<dragged_view_t> 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<dragged_view_node_t> 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<view_unmapped_signal> on_view_unmap;
    wf::signal::connection_t<output_removed_signal> on_output_removed;
};

core_drag_t::core_drag_t()
{
    this->priv = std::make_unique<impl>();

    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<scale_around_grab_t>();
        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<dragged_view_node_t>(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);
            }

            update_current_output(to);
            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<scale_around_grab_t>();

        // 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);
    }
}
}
}