1 /**
2  * Original code by: Scott Moreau, Daniel Kondor
3  */
4 #include <map>
5 #include <wayfire/plugin.hpp>
6 #include <wayfire/output.hpp>
7 #include <wayfire/util/duration.hpp>
8 #include <wayfire/view-transform.hpp>
9 #include <wayfire/render-manager.hpp>
10 #include <wayfire/workspace-manager.hpp>
11 #include <wayfire/signal-definitions.hpp>
12 #include <wayfire/plugins/vswitch.hpp>
13 #include <wayfire/touch/touch.hpp>
14 #include <wayfire/plugins/scale-signal.hpp>
15 #include <wayfire/plugins/scale-transform.hpp>
16 #include <wayfire/plugins/wobbly/wobbly-signal.hpp>
17 
18 #include <wayfire/plugins/common/move-drag-interface.hpp>
19 #include <wayfire/plugins/common/shared-core-data.hpp>
20 
21 #include <linux/input-event-codes.h>
22 
23 #include "scale-title-overlay.hpp"
24 
25 using namespace wf::animation;
26 
27 class scale_animation_t : public duration_t
28 {
29   public:
30     using duration_t::duration_t;
31     timed_transition_t scale_x{*this};
32     timed_transition_t scale_y{*this};
33     timed_transition_t translation_x{*this};
34     timed_transition_t translation_y{*this};
35 };
36 
37 struct wf_scale_animation_attribs
38 {
39     wf::option_wrapper_t<int> duration{"scale/duration"};
40     scale_animation_t scale_animation{duration};
41 };
42 
43 struct view_scale_data
44 {
45     int row, col;
46     wf::scale_transformer_t *transformer = nullptr;
47     wf::animation::simple_animation_t fade_animation;
48     wf_scale_animation_attribs animation;
49     enum class view_visibility_t
50     {
51         VISIBLE, /*  view is shown in position determined by layout_slots() */
52         HIDING, /* view is in the process of hiding (due to filters)      */
53         HIDDEN, /* view is hidden by a filter (with set_visible(false))   */
54     };
55 
56     view_visibility_t visibility = view_visibility_t::VISIBLE;
57 };
58 
59 class wayfire_scale : public wf::plugin_interface_t
60 {
61     /* helper class for optionally showing title overlays */
62     scale_show_title_t show_title;
63     std::vector<int> current_row_sizes;
64     wf::point_t initial_workspace;
65     bool active, hook_set;
66     /* View that was active before scale began. */
67     wayfire_view initial_focus_view;
68     /* View that has active focus. */
69     wayfire_view current_focus_view;
70     // View over which the last input press happened, might become dangling
71     wayfire_view last_selected_view;
72     std::map<wayfire_view, view_scale_data> scale_data;
73     wf::option_wrapper_t<int> spacing{"scale/spacing"};
74     /* If interact is true, no grab is acquired and input events are sent
75      * to the scaled surfaces. If it is false, the hard coded bindings
76      * are as follows:
77      * KEY_ENTER:
78      * - Ends scale, switching to the workspace of the focused view
79      * KEY_ESC:
80      * - Ends scale, switching to the workspace where scale was started,
81      *   and focuses the initially active view
82      * KEY_UP:
83      * KEY_DOWN:
84      * KEY_LEFT:
85      * KEY_RIGHT:
86      * - When scale is active, change focus of the views
87      *
88      * BTN_LEFT:
89      * - Ends scale, switching to the workspace of the surface clicked
90      * BTN_MIDDLE:
91      * - If middle_click_close is true, closes the view clicked
92      */
93     wf::option_wrapper_t<bool> interact{"scale/interact"};
94     wf::option_wrapper_t<bool> middle_click_close{"scale/middle_click_close"};
95     wf::option_wrapper_t<double> inactive_alpha{"scale/inactive_alpha"};
96     wf::option_wrapper_t<bool> allow_scale_zoom{"scale/allow_zoom"};
97 
98     /* maximum scale -- 1.0 means we will not "zoom in" on a view */
99     const double max_scale_factor = 1.0;
100     /* maximum scale for child views (relative to their parents)
101      * zero means unconstrained, 1.0 means child cannot be scaled
102      * "larger" than the parent */
103     const double max_scale_child = 1.0;
104 
105     /* true if the currently running scale should include views from
106      * all workspaces */
107     bool all_workspaces;
108     std::unique_ptr<wf::vswitch::control_bindings_t> workspace_bindings;
109 
110     wf::shared_data::ref_ptr_t<wf::move_drag::core_drag_t> drag_helper;
111 
112   public:
init()113     void init() override
114     {
115         grab_interface->name = "scale";
116         grab_interface->capabilities =
117             wf::CAPABILITY_MANAGE_DESKTOP | wf::CAPABILITY_GRAB_INPUT;
118         active = hook_set = false;
119 
120         output->add_activator(
121             wf::option_wrapper_t<wf::activatorbinding_t>{"scale/toggle"},
122             &toggle_cb);
123         output->add_activator(
124             wf::option_wrapper_t<wf::activatorbinding_t>{"scale/toggle_all"},
125             &toggle_all_cb);
126         output->connect_signal("scale-update", &update_cb);
127 
128         grab_interface->callbacks.keyboard.key = [=] (uint32_t key, uint32_t state)
129         {
130             process_key(key, state);
131         };
132 
133         grab_interface->callbacks.cancel = [=] ()
134         {
135             finalize();
136         };
137 
138         grab_interface->callbacks.pointer.motion = [=] (int32_t x, int32_t y)
139         {
140             auto offset = wf::origin(output->get_layout_geometry());
141             process_motion(offset + wf::point_t{x, y});
142         };
143 
144         interact.set_callback(interact_option_changed);
145         allow_scale_zoom.set_callback(allow_scale_zoom_option_changed);
146 
147         setup_workspace_switching();
148 
149         drag_helper->connect_signal("focus-output", &on_drag_output_focus);
150         drag_helper->connect_signal("done", &on_drag_done);
151 
152         show_title.init(output);
153     }
154 
setup_workspace_switching()155     void setup_workspace_switching()
156     {
157         workspace_bindings =
158             std::make_unique<wf::vswitch::control_bindings_t>(output);
159         workspace_bindings->setup([&] (wf::point_t delta, wayfire_view view)
160         {
161             if (!output->is_plugin_active(grab_interface->name))
162             {
163                 return false;
164             }
165 
166             if (delta == wf::point_t{0, 0})
167             {
168                 // Consume input event
169                 return true;
170             }
171 
172             auto ws = output->workspace->get_current_workspace() + delta;
173 
174             // vswitch picks the top view, we want the focused one
175             std::vector<wayfire_view> fixed_views;
176             if (view && !all_workspaces)
177             {
178                 fixed_views.push_back(current_focus_view);
179             }
180 
181             output->workspace->request_workspace(ws, fixed_views);
182 
183             return true;
184         });
185     }
186 
187     /* Add a transformer that will be used to scale the view */
add_transformer(wayfire_view view)188     bool add_transformer(wayfire_view view)
189     {
190         if (view->get_transformer(wf::scale_transformer_t::transformer_name()))
191         {
192             return false;
193         }
194 
195         wf::scale_transformer_t *tr = new wf::scale_transformer_t(view);
196         scale_data[view].transformer = tr;
197         view->add_transformer(std::unique_ptr<wf::scale_transformer_t>(tr),
198             wf::scale_transformer_t::transformer_name());
199         /* Transformers are added only once when scale is activated so
200          * this is a good place to connect the geometry-changed handler */
201         view->connect_signal("geometry-changed", &view_geometry_changed);
202 
203         set_tiled_wobbly(view, true);
204 
205         /* signal that a transformer was added to this view */
206         scale_transformer_added_signal data;
207         data.transformer = tr;
208         output->emit_signal("scale-transformer-added", &data);
209 
210         return true;
211     }
212 
213     /* Remove the scale transformer from the view */
pop_transformer(wayfire_view view)214     void pop_transformer(wayfire_view view)
215     {
216         view->pop_transformer(wf::scale_transformer_t::transformer_name());
217         set_tiled_wobbly(view, false);
218     }
219 
220     /* Remove scale transformers from all views */
remove_transformers()221     void remove_transformers()
222     {
223         for (auto& e : scale_data)
224         {
225             for (auto& toplevel : e.first->enumerate_views(false))
226             {
227                 pop_transformer(toplevel);
228             }
229 
230             if (e.second.visibility == view_scale_data::view_visibility_t::HIDDEN)
231             {
232                 e.first->set_visible(true);
233             }
234 
235             e.second.visibility = view_scale_data::view_visibility_t::VISIBLE;
236         }
237     }
238 
239     /* Check whether views exist on other workspaces */
all_same_as_current_workspace_views()240     bool all_same_as_current_workspace_views()
241     {
242         return get_all_workspace_views().size() ==
243                get_current_workspace_views().size();
244     }
245 
246     /* Activate scale, switch activator modes and deactivate */
handle_toggle(bool want_all_workspaces)247     bool handle_toggle(bool want_all_workspaces)
248     {
249         if (active && (all_same_as_current_workspace_views() ||
250                        (want_all_workspaces == this->all_workspaces)))
251         {
252             deactivate();
253 
254             return true;
255         }
256 
257         this->all_workspaces = want_all_workspaces;
258         if (active)
259         {
260             switch_scale_modes();
261 
262             return true;
263         } else
264         {
265             return activate();
266         }
267     }
268 
269     /* Activate scale for views on the current workspace */
270     wf::activator_callback toggle_cb = [=] (auto)
__anondca609510502(auto) 271     {
272         if (handle_toggle(false))
273         {
274             output->render->schedule_redraw();
275 
276             return true;
277         }
278 
279         return false;
280     };
281 
282     /* Activate scale for views on all workspaces */
283     wf::activator_callback toggle_all_cb = [=] (auto)
__anondca609510602(auto) 284     {
285         if (handle_toggle(true))
286         {
287             output->render->schedule_redraw();
288 
289             return true;
290         }
291 
292         return false;
293     };
294 
295     wf::signal_connection_t update_cb{[=] (wf::signal_data_t*)
__anondca609510702() 296         {
297             if (active)
298             {
299                 layout_slots(get_views());
300                 output->render->schedule_redraw();
301             }
302         }
303     };
304 
305     /* Connect button signal */
connect_button_signal()306     void connect_button_signal()
307     {
308         disconnect_button_signal();
309         wf::get_core().connect_signal("pointer_button_post", &on_button_event);
310         wf::get_core().connect_signal("touch_down_post", &on_touch_down_event);
311         // connect to the signal before touching up, so that the touch point
312         // is still active.
313         wf::get_core().connect_signal("touch_up", &on_touch_up_event);
314     }
315 
316     /* Disconnect button signal */
disconnect_button_signal()317     void disconnect_button_signal()
318     {
319         on_button_event.disconnect();
320         on_touch_down_event.disconnect();
321         on_touch_up_event.disconnect();
322     }
323 
324     /* For button processing without grabbing */
325     wf::signal_connection_t on_button_event = [=] (wf::signal_data_t *data)
__anondca609510802(wf::signal_data_t *data) 326     {
327         auto ev = static_cast<
328             wf::input_event_signal<wlr_event_pointer_button>*>(data);
329 
330         process_input(ev->event->button, ev->event->state,
331             wf::get_core().get_cursor_position());
332     };
333 
334     wf::signal_connection_t on_touch_down_event = [=] (wf::signal_data_t *data)
__anondca609510902(wf::signal_data_t *data) 335     {
336         auto ev = static_cast<
337             wf::input_event_signal<wlr_event_touch_down>*>(data);
338         if (ev->event->touch_id == 0)
339         {
340             process_input(BTN_LEFT, WLR_BUTTON_PRESSED,
341                 wf::get_core().get_touch_position(0));
342         }
343     };
344 
345     wf::signal_connection_t on_touch_up_event = [=] (wf::signal_data_t *data)
__anondca609510a02(wf::signal_data_t *data) 346     {
347         auto ev = static_cast<
348             wf::input_event_signal<wlr_event_touch_up>*>(data);
349         if (ev->event->touch_id == 0)
350         {
351             process_input(BTN_LEFT, WLR_BUTTON_RELEASED,
352                 wf::get_core().get_touch_position(0));
353         }
354     };
355 
356     /** Return the topmost parent */
get_top_parent(wayfire_view view)357     wayfire_view get_top_parent(wayfire_view view)
358     {
359         while (view && view->parent)
360         {
361             view = view->parent;
362         }
363 
364         return view;
365     }
366 
367     /* Fade all views' alpha to inactive alpha except the
368      * view argument */
fade_out_all_except(wayfire_view view)369     void fade_out_all_except(wayfire_view view)
370     {
371         for (auto& e : scale_data)
372         {
373             auto v = e.first;
374             if (get_top_parent(v) == get_top_parent(view))
375             {
376                 continue;
377             }
378 
379             if (e.second.visibility != view_scale_data::view_visibility_t::VISIBLE)
380             {
381                 continue;
382             }
383 
384             fade_out(v);
385         }
386     }
387 
388     /* Fade in view alpha */
fade_in(wayfire_view view)389     void fade_in(wayfire_view view)
390     {
391         if (!view || !scale_data.count(view))
392         {
393             return;
394         }
395 
396         set_hook();
397         auto alpha = scale_data[view].transformer->alpha;
398         scale_data[view].fade_animation.animate(alpha, 1);
399         if (view->children.size())
400         {
401             fade_in(view->children.front());
402         }
403     }
404 
405     /* Fade out view alpha */
fade_out(wayfire_view view)406     void fade_out(wayfire_view view)
407     {
408         if (!view)
409         {
410             return;
411         }
412 
413         set_hook();
414         for (auto v : view->enumerate_views(false))
415         {
416             // Could happen if we have a never-mapped child view
417             if (!scale_data.count(v))
418             {
419                 continue;
420             }
421 
422             auto alpha = scale_data[v].transformer->alpha;
423             scale_data[v].fade_animation.animate(alpha, (double)inactive_alpha);
424         }
425     }
426 
427     /* Switch to the workspace for the untransformed view geometry */
select_view(wayfire_view view)428     void select_view(wayfire_view view)
429     {
430         if (!view)
431         {
432             return;
433         }
434 
435         auto ws = get_view_main_workspace(view);
436         output->workspace->request_workspace(ws);
437     }
438 
439     /* Updates current and initial view focus variables accordingly */
check_focus_view(wayfire_view view)440     void check_focus_view(wayfire_view view)
441     {
442         if (view == current_focus_view)
443         {
444             current_focus_view = output->get_active_view();
445         }
446 
447         if (view == initial_focus_view)
448         {
449             initial_focus_view = nullptr;
450         }
451     }
452 
453     /* Remove transformer from view and remove view from the scale_data map */
remove_view(wayfire_view view)454     void remove_view(wayfire_view view)
455     {
456         if (!view)
457         {
458             return;
459         }
460 
461         for (auto v : view->enumerate_views(false))
462         {
463             check_focus_view(v);
464             pop_transformer(v);
465             scale_data.erase(v);
466         }
467     }
468 
469     /* Process button event */
process_input(uint32_t button,uint32_t state,wf::pointf_t input_position)470     void process_input(uint32_t button, uint32_t state,
471         wf::pointf_t input_position)
472     {
473         if (!active)
474         {
475             return;
476         }
477 
478         if (state == WLR_BUTTON_PRESSED)
479         {
480             auto view = wf::get_core().get_view_at(input_position);
481             if (view && should_scale_view(view))
482             {
483                 // Mark the view as the target of the next input release operation
484                 last_selected_view = view;
485             } else
486             {
487                 last_selected_view = nullptr;
488             }
489 
490             return;
491         }
492 
493         if (drag_helper->view)
494         {
495             drag_helper->handle_input_released();
496         }
497 
498         auto view = wf::get_core().get_view_at(input_position);
499         if (!view || (last_selected_view != view))
500         {
501             last_selected_view = nullptr;
502             // Operation was cancelled, for ex. dragged outside of the view
503             return;
504         }
505 
506         // Reset last_selected_view, because it is no longer held
507         last_selected_view = nullptr;
508         switch (button)
509         {
510           case BTN_LEFT:
511             // Focus the view under the mouse
512             current_focus_view = view;
513             output->focus_view(view, false);
514             fade_out_all_except(view);
515             fade_in(get_top_parent(view));
516             if (!interact)
517             {
518                 // End scale
519                 initial_focus_view = nullptr;
520                 deactivate();
521                 select_view(view);
522             }
523 
524             break;
525 
526           case BTN_MIDDLE:
527             // Check kill the view
528             if (middle_click_close)
529             {
530                 view->close();
531             }
532 
533             break;
534 
535           default:
536             break;
537         }
538     }
539 
process_motion(wf::point_t to)540     void process_motion(wf::point_t to)
541     {
542         if (last_selected_view)
543         {
544             wf::move_drag::drag_options_t opts;
545             opts.join_views = true;
546             opts.enable_snap_off    = true;
547             opts.snap_off_threshold = 200;
548 
549             drag_helper->start_drag(last_selected_view, to, opts);
550             last_selected_view = nullptr;
551         } else if (drag_helper->view)
552         {
553             drag_helper->handle_motion(to);
554         }
555     }
556 
557     /* Get the workspace for the center point of the untransformed view geometry */
get_view_main_workspace(wayfire_view view)558     wf::point_t get_view_main_workspace(wayfire_view view)
559     {
560         while (view->parent)
561         {
562             view = view->parent;
563         }
564 
565         auto ws = output->workspace->get_current_workspace();
566         auto og = output->get_layout_geometry();
567         auto vg = scale_data.count(view) > 0 ?
568             view->get_bounding_box(scale_data[view].transformer) :
569             view->get_bounding_box();
570         auto center = wf::point_t{vg.x + vg.width / 2, vg.y + vg.height / 2};
571 
572         return wf::point_t{
573             ws.x + (int)std::floor((double)center.x / og.width),
574             ws.y + (int)std::floor((double)center.y / og.height)};
575     }
576 
577     /* Given row and column, return a view at this position in the scale grid,
578      * or the first scaled view if none is found */
find_view_in_grid(int row,int col)579     wayfire_view find_view_in_grid(int row, int col)
580     {
581         for (auto& view : scale_data)
582         {
583             if ((view.first->parent == nullptr) &&
584                 (view.second.visibility ==
585                  view_scale_data::view_visibility_t::VISIBLE) &&
586                 ((view.second.row == row) &&
587                  (view.second.col == col)))
588             {
589                 return view.first;
590             }
591         }
592 
593         return get_views().front();
594     }
595 
596     /* Process key event */
process_key(uint32_t key,uint32_t state)597     void process_key(uint32_t key, uint32_t state)
598     {
599         auto view = output->get_active_view();
600         if (!view)
601         {
602             view = current_focus_view;
603             if (view)
604             {
605                 fade_out_all_except(view);
606                 fade_in(view);
607                 output->focus_view(view, true);
608 
609                 return;
610             }
611         } else if (!scale_data.count(view))
612         {
613             return;
614         }
615 
616         int cur_row  = view ? scale_data[view].row : 0;
617         int cur_col  = view ? scale_data[view].col : 0;
618         int next_row = cur_row;
619         int next_col = cur_col;
620 
621         if ((state != WLR_KEY_PRESSED) ||
622             wf::get_core().get_keyboard_modifiers())
623         {
624             return;
625         }
626 
627         switch (key)
628         {
629           case KEY_UP:
630             next_row--;
631             break;
632 
633           case KEY_DOWN:
634             next_row++;
635             break;
636 
637           case KEY_LEFT:
638             next_col--;
639             break;
640 
641           case KEY_RIGHT:
642             next_col++;
643             break;
644 
645           case KEY_ENTER:
646             deactivate();
647             select_view(current_focus_view);
648 
649             return;
650 
651           case KEY_ESC:
652             deactivate();
653             output->focus_view(initial_focus_view, true);
654             initial_focus_view = nullptr;
655             output->workspace->request_workspace(initial_workspace);
656 
657             return;
658 
659           default:
660             return;
661         }
662 
663         if (!view)
664         {
665             return;
666         }
667 
668         if (!current_row_sizes.empty())
669         {
670             next_row = (next_row + current_row_sizes.size()) %
671                 current_row_sizes.size();
672 
673             if (cur_row != next_row)
674             {
675                 /* when moving to and from the last row, the number of columns
676                  * may be different, so this bit figures out which view we
677                  * should switch focus to */
678                 float p = 1.0 * cur_col / current_row_sizes[cur_row];
679                 next_col = p * current_row_sizes[next_row];
680             } else
681             {
682                 next_col = (next_col + current_row_sizes[cur_row]) %
683                     current_row_sizes[cur_row];
684             }
685         } else
686         {
687             next_row = cur_row;
688             next_col = cur_col;
689         }
690 
691         view = find_view_in_grid(next_row, next_col);
692         if (view && (current_focus_view != view))
693         {
694             // view_focused handler will update the view state
695             output->focus_view(view, false);
696         }
697     }
698 
699     /* Assign the transformer values to the view transformers */
transform_views()700     void transform_views()
701     {
702         for (auto& e : scale_data)
703         {
704             auto view = e.first;
705             auto& view_data = e.second;
706             if (!view || !view_data.transformer)
707             {
708                 continue;
709             }
710 
711             bool needs_damage = false;
712 
713             if (view_data.fade_animation.running() ||
714                 view_data.animation.scale_animation.running())
715             {
716                 view->damage();
717                 view_data.transformer->scale_x =
718                     view_data.animation.scale_animation.scale_x;
719                 view_data.transformer->scale_y =
720                     view_data.animation.scale_animation.scale_y;
721                 view_data.transformer->translation_x =
722                     view_data.animation.scale_animation.translation_x;
723                 view_data.transformer->translation_y =
724                     view_data.animation.scale_animation.translation_y;
725                 view_data.transformer->alpha = view_data.fade_animation;
726                 needs_damage = true;
727 
728                 if ((view_data.visibility ==
729                      view_scale_data::view_visibility_t::HIDING) &&
730                     !view_data.fade_animation.running())
731                 {
732                     view_data.visibility =
733                         view_scale_data::view_visibility_t::HIDDEN;
734                     view->set_visible(false);
735                 }
736             }
737 
738             view_data.transformer->call_pre_hooks(needs_damage);
739         }
740     }
741 
742     /* Returns a list of views for all workspaces */
get_all_workspace_views()743     std::vector<wayfire_view> get_all_workspace_views()
744     {
745         std::vector<wayfire_view> views;
746 
747         for (auto& view :
748              output->workspace->get_views_in_layer(wf::LAYER_WORKSPACE))
749         {
750             if ((view->role != wf::VIEW_ROLE_TOPLEVEL) || !view->is_mapped())
751             {
752                 continue;
753             }
754 
755             views.push_back(view);
756         }
757 
758         return views;
759     }
760 
761     /* Returns a list of views for the current workspace */
get_current_workspace_views()762     std::vector<wayfire_view> get_current_workspace_views()
763     {
764         std::vector<wayfire_view> views;
765 
766         for (auto& view :
767              output->workspace->get_views_in_layer(wf::LAYER_WORKSPACE))
768         {
769             if ((view->role != wf::VIEW_ROLE_TOPLEVEL) || !view->is_mapped())
770             {
771                 continue;
772             }
773 
774             auto vg = view->get_wm_geometry();
775             auto og = output->get_relative_geometry();
776             wf::region_t wr{og};
777             wf::point_t center{vg.x + vg.width / 2, vg.y + vg.height / 2};
778 
779             if (wr.contains_point(center))
780             {
781                 views.push_back(view);
782             }
783         }
784 
785         return views;
786     }
787 
788     /* Returns a list of views to be scaled */
get_views()789     std::vector<wayfire_view> get_views()
790     {
791         std::vector<wayfire_view> views;
792 
793         if (all_workspaces)
794         {
795             views = get_all_workspace_views();
796         } else
797         {
798             views = get_current_workspace_views();
799         }
800 
801         return views;
802     }
803 
804     /**
805      * @return true if the view is to be scaled.
806      */
should_scale_view(wayfire_view view)807     bool should_scale_view(wayfire_view view)
808     {
809         auto views = get_views();
810 
811         return std::find(
812             views.begin(), views.end(), get_top_parent(view)) != views.end();
813     }
814 
815     /* Convenience assignment function */
setup_view_transform(view_scale_data & view_data,double scale_x,double scale_y,double translation_x,double translation_y,double target_alpha)816     void setup_view_transform(view_scale_data& view_data,
817         double scale_x,
818         double scale_y,
819         double translation_x,
820         double translation_y,
821         double target_alpha)
822     {
823         view_data.animation.scale_animation.scale_x.set(
824             view_data.transformer->scale_x, scale_x);
825         view_data.animation.scale_animation.scale_y.set(
826             view_data.transformer->scale_y, scale_y);
827         view_data.animation.scale_animation.translation_x.set(
828             view_data.transformer->translation_x, translation_x);
829         view_data.animation.scale_animation.translation_y.set(
830             view_data.transformer->translation_y, translation_y);
831         view_data.animation.scale_animation.start();
832         view_data.fade_animation = wf::animation::simple_animation_t(
833             wf::option_wrapper_t<int>{"scale/duration"});
834         view_data.fade_animation.animate(view_data.transformer->alpha,
835             target_alpha);
836     }
837 
view_compare_x(const wayfire_view & a,const wayfire_view & b)838     static bool view_compare_x(const wayfire_view& a, const wayfire_view& b)
839     {
840         auto vg_a = a->get_wm_geometry();
841         std::vector<int> a_coords = {vg_a.x, vg_a.width, vg_a.y, vg_a.height};
842         auto vg_b = b->get_wm_geometry();
843         std::vector<int> b_coords = {vg_b.x, vg_b.width, vg_b.y, vg_b.height};
844         return a_coords < b_coords;
845     }
846 
view_compare_y(const wayfire_view & a,const wayfire_view & b)847     static bool view_compare_y(const wayfire_view& a, const wayfire_view& b)
848     {
849         auto vg_a = a->get_wm_geometry();
850         std::vector<int> a_coords = {vg_a.y, vg_a.height, vg_a.x, vg_a.width};
851         auto vg_b = b->get_wm_geometry();
852         std::vector<int> b_coords = {vg_b.y, vg_b.height, vg_b.x, vg_b.width};
853         return a_coords < b_coords;
854     }
855 
view_sort(std::vector<wayfire_view> & views)856     std::vector<std::vector<wayfire_view>> view_sort(
857         std::vector<wayfire_view>& views)
858     {
859         std::vector<std::vector<wayfire_view>> view_grid;
860         std::sort(views.begin(), views.end(), view_compare_y);
861 
862         int rows = sqrt(views.size() + 1);
863         int views_per_row = (int)std::ceil((double)views.size() / rows);
864         size_t n = views.size();
865         for (size_t i = 0; i < n; i += views_per_row)
866         {
867             size_t j = std::min(i + views_per_row, n);
868             view_grid.emplace_back(views.begin() + i, views.begin() + j);
869             std::sort(view_grid.back().begin(), view_grid.back().end(),
870                 view_compare_x);
871         }
872 
873         return view_grid;
874     }
875 
876     /* Filter the views to be arranged by layout_slots() */
filter_views(std::vector<wayfire_view> & views)877     void filter_views(std::vector<wayfire_view>& views)
878     {
879         std::vector<wayfire_view> filtered_views;
880         scale_filter_signal signal(views, filtered_views);
881         output->emit_signal("scale-filter", &signal);
882 
883         /* update hidden views -- ensure that they and their children have a
884          * transformer and are in scale_data */
885         for (auto view : filtered_views)
886         {
887             for (auto v : view->enumerate_views(false))
888             {
889                 add_transformer(v);
890                 auto& view_data = scale_data[v];
891                 if (view_data.visibility ==
892                     view_scale_data::view_visibility_t::VISIBLE)
893                 {
894                     view_data.visibility =
895                         view_scale_data::view_visibility_t::HIDING;
896                     setup_view_transform(view_data, 1, 1, 0, 0, 0);
897                 }
898 
899                 if (v == current_focus_view)
900                 {
901                     current_focus_view = nullptr;
902                 }
903             }
904         }
905 
906         if (!current_focus_view)
907         {
908             current_focus_view = views.empty() ? nullptr : views.front();
909             output->focus_view(current_focus_view, true);
910         }
911     }
912 
913     /* Compute target scale layout geometry for all the view transformers
914      * and start animating. Initial code borrowed from the compiz scale
915      * plugin algorithm */
layout_slots(std::vector<wayfire_view> views)916     void layout_slots(std::vector<wayfire_view> views)
917     {
918         if (!views.size())
919         {
920             if (!all_workspaces && active)
921             {
922                 deactivate();
923             }
924 
925             return;
926         }
927 
928         filter_views(views);
929 
930         auto workarea = output->workspace->get_workarea();
931 
932         auto sorted_rows = view_sort(views);
933         size_t cnt_rows  = sorted_rows.size();
934 
935         const double scaled_height = std::max((double)
936             (workarea.height - (cnt_rows + 1) * spacing) / cnt_rows, 1.0);
937         current_row_sizes.clear();
938 
939         for (size_t i = 0; i < cnt_rows; i++)
940         {
941             size_t cnt_cols = sorted_rows[i].size();
942             current_row_sizes.push_back(cnt_cols);
943             const double scaled_width = std::max((double)
944                 (workarea.width - (cnt_cols + 1) * spacing) / cnt_cols, 1.0);
945 
946             for (size_t j = 0; j < cnt_cols; j++)
947             {
948                 double x = workarea.x + spacing + (spacing + scaled_width) * j;
949                 double y = workarea.y + spacing + (spacing + scaled_height) * i;
950 
951                 auto view = sorted_rows[i][j];
952 
953                 // Calculate current transformation of the view, in order to
954                 // ensure that new views in the view tree start directly at the
955                 // correct position
956                 double main_view_dx    = 0;
957                 double main_view_dy    = 0;
958                 double main_view_scale = 1.0;
959                 if (scale_data.count(view))
960                 {
961                     main_view_dx    = scale_data[view].transformer->translation_x;
962                     main_view_dy    = scale_data[view].transformer->translation_y;
963                     main_view_scale = scale_data[view].transformer->scale_x;
964                 }
965 
966                 // Calculate target alpha for this view and its children
967                 double target_alpha =
968                     (view == current_focus_view) ? 1 : (double)inactive_alpha;
969 
970                 // Helper function to calculate the desired scale for a view
971                 const auto& calculate_scale = [=] (wf::dimensions_t vg,
972                                                    const wf::scale_transformer_t::
973                                                    padding_t& pad)
974                 {
975                     double w = std::max(1.0, scaled_width - pad.left - pad.right);
976                     double h = std::max(1.0, scaled_height - pad.top - pad.bottom);
977 
978                     const double scale = std::min(w / vg.width, h / vg.height);
979                     if (!allow_scale_zoom)
980                     {
981                         return std::min(scale, max_scale_factor);
982                     }
983 
984                     return scale;
985                 };
986 
987                 add_transformer(view);
988                 auto geom = view->transform_region(view->get_wm_geometry(),
989                     scale_data[view].transformer);
990                 double view_scale = calculate_scale({geom.width, geom.height},
991                     scale_data[view].transformer->get_scale_padding());
992                 for (auto& child : view->enumerate_views(false))
993                 {
994                     // Ensure a transformer for the view, and make sure that
995                     // new views in the view tree start off with the correct
996                     // attributes set.
997                     auto new_child   = add_transformer(child);
998                     auto& child_data = scale_data[child];
999                     if (new_child)
1000                     {
1001                         child_data.transformer->translation_x = main_view_dx;
1002                         child_data.transformer->translation_y = main_view_dy;
1003                         child_data.transformer->scale_x = main_view_scale;
1004                         child_data.transformer->scale_y = main_view_scale;
1005                     }
1006 
1007                     if (child_data.visibility ==
1008                         view_scale_data::view_visibility_t::HIDDEN)
1009                     {
1010                         child->set_visible(true);
1011                     }
1012 
1013                     child_data.visibility =
1014                         view_scale_data::view_visibility_t::VISIBLE;
1015 
1016                     child_data.row = i;
1017                     child_data.col = j;
1018 
1019                     if (!active)
1020                     {
1021                         // On exit, we just animate towards normal state
1022                         setup_view_transform(child_data, 1, 1, 0, 0, 1);
1023                         continue;
1024                     }
1025 
1026                     auto vg = child->transform_region(child->get_wm_geometry(),
1027                         child_data.transformer);
1028                     wf::pointf_t center = {vg.x + vg.width / 2.0,
1029                         vg.y + vg.height / 2.0};
1030 
1031                     // Take padding into account
1032                     auto pad     = child_data.transformer->get_scale_padding();
1033                     double scale = calculate_scale({vg.width, vg.height}, pad);
1034                     // Ensure child is not scaled more than parent
1035                     if (!allow_scale_zoom &&
1036                         (child != view) &&
1037                         (max_scale_child > 0.0))
1038                     {
1039                         scale = std::min(max_scale_child * view_scale, scale);
1040                     }
1041 
1042                     // Target geometry is centered around the center slot
1043                     const double dx = x + pad.left - center.x + scaled_width / 2.0;
1044                     const double dy = y + pad.top - center.y + scaled_height / 2.0;
1045                     setup_view_transform(child_data, scale, scale,
1046                         dx, dy, target_alpha);
1047                 }
1048             }
1049         }
1050 
1051         set_hook();
1052         transform_views();
1053     }
1054 
1055     /* Handle interact option changed */
1056     wf::config::option_base_t::updated_callback_t interact_option_changed = [=] ()
__anondca609510c02() 1057     {
1058         if (!output->is_plugin_active(grab_interface->name))
1059         {
1060             return;
1061         }
1062 
1063         if (interact)
1064         {
1065             grab_interface->ungrab();
1066         } else
1067         {
1068             grab_interface->grab();
1069         }
1070     };
1071 
1072     /* Called when adding or removing a group of views to be scaled,
1073      * in this case between views on all workspaces and views on the
1074      * current workspace */
switch_scale_modes()1075     void switch_scale_modes()
1076     {
1077         if (!output->is_plugin_active(grab_interface->name))
1078         {
1079             return;
1080         }
1081 
1082         if (all_workspaces)
1083         {
1084             layout_slots(get_views());
1085 
1086             return;
1087         }
1088 
1089         bool rearrange = false;
1090         for (auto& e : scale_data)
1091         {
1092             if (!should_scale_view(e.first))
1093             {
1094                 setup_view_transform(e.second, 1, 1, 0, 0, 1);
1095                 rearrange = true;
1096             }
1097         }
1098 
1099         if (rearrange)
1100         {
1101             layout_slots(get_views());
1102         }
1103     }
1104 
1105     /* Toggle between restricting maximum scale to 100% or allowing it
1106      * to become the greater. This is particularly noticeable when
1107      * scaling a single view or a view with child views. */
1108     wf::config::option_base_t::updated_callback_t allow_scale_zoom_option_changed =
1109         [=] ()
__anondca609510d02() 1110     {
1111         if (!output->is_plugin_active(grab_interface->name))
1112         {
1113             return;
1114         }
1115 
1116         layout_slots(get_views());
1117     };
1118 
1119     /* New view or view moved to output with scale active */
1120     wf::signal_connection_t view_attached = [this] (wf::signal_data_t *data)
__anondca609510e02(wf::signal_data_t *data) 1121     {
1122         if (!should_scale_view(get_signaled_view(data)))
1123         {
1124             return;
1125         }
1126 
1127         layout_slots(get_views());
1128     };
1129 
handle_view_disappeared(wayfire_view view)1130     void handle_view_disappeared(wayfire_view view)
1131     {
1132         if (scale_data.count(get_top_parent(view)) != 0)
1133         {
1134             remove_view(view);
1135             if (scale_data.empty())
1136             {
1137                 finalize();
1138             }
1139 
1140             if (!view->parent)
1141             {
1142                 layout_slots(get_views());
1143             }
1144         }
1145     }
1146 
1147     /* Destroyed view or view moved to another output */
1148     wf::signal_connection_t view_detached = [this] (wf::signal_data_t *data)
__anondca609510f02(wf::signal_data_t *data) 1149     {
1150         handle_view_disappeared(get_signaled_view(data));
1151     };
1152 
1153     /* Workspace changed */
1154     wf::signal_connection_t workspace_changed{[this] (wf::signal_data_t *data)
__anondca609511002() 1155         {
1156             if (current_focus_view)
1157             {
1158                 output->focus_view(current_focus_view, true);
1159             }
1160 
1161             layout_slots(get_views());
1162         }
1163     };
1164 
1165     /* View geometry changed. Also called when workspace changes */
1166     wf::signal_connection_t view_geometry_changed{[this] (wf::signal_data_t *data)
__anondca609511102() 1167         {
1168             auto views = get_views();
1169             if (!views.size())
1170             {
1171                 deactivate();
1172 
1173                 return;
1174             }
1175 
1176             layout_slots(std::move(views));
1177         }
1178     };
1179 
1180     /* View minimized */
1181     wf::signal_connection_t view_minimized = [this] (wf::signal_data_t *data)
__anondca609511202(wf::signal_data_t *data) 1182     {
1183         auto ev = static_cast<wf::view_minimized_signal*>(data);
1184 
1185         if (ev->state)
1186         {
1187             handle_view_disappeared(ev->view);
1188         } else if (should_scale_view(ev->view))
1189         {
1190             layout_slots(get_views());
1191         }
1192     };
1193 
1194     /* View unmapped */
1195     wf::signal_connection_t view_unmapped{[this] (wf::signal_data_t *data)
__anondca609511302() 1196         {
1197             auto view = get_signaled_view(data);
1198 
1199             check_focus_view(view);
1200         }
1201     };
1202 
1203     /* View focused. This handler makes sure our view remains focused */
1204     wf::signal_connection_t view_focused = [this] (wf::signal_data_t *data)
__anondca609511402(wf::signal_data_t *data) 1205     {
1206         auto view = get_signaled_view(data);
1207         fade_out_all_except(view);
1208         fade_in(view);
1209         current_focus_view = view;
1210     };
1211 
1212     /* Our own refocus that uses untransformed coordinates */
refocus()1213     void refocus()
1214     {
1215         if (current_focus_view)
1216         {
1217             output->focus_view(current_focus_view, true);
1218             select_view(current_focus_view);
1219 
1220             return;
1221         }
1222 
1223         wayfire_view next_focus = nullptr;
1224         auto views = get_current_workspace_views();
1225 
1226         for (auto v : views)
1227         {
1228             if (v->is_mapped() &&
1229                 v->get_keyboard_focus_surface())
1230             {
1231                 next_focus = v;
1232                 break;
1233             }
1234         }
1235 
1236         output->focus_view(next_focus, true);
1237     }
1238 
1239     /* Returns true if any scale animation is running */
animation_running()1240     bool animation_running()
1241     {
1242         for (auto& e : scale_data)
1243         {
1244             if (e.second.fade_animation.running() ||
1245                 e.second.animation.scale_animation.running())
1246             {
1247                 return true;
1248             }
1249         }
1250 
1251         return false;
1252     }
1253 
1254     /* Assign transform values to the actual transformer */
1255     wf::effect_hook_t pre_hook = [=] ()
__anondca609511502() 1256     {
1257         transform_views();
1258     };
1259 
1260     /* Keep rendering until all animation has finished */
1261     wf::effect_hook_t post_hook = [=] ()
__anondca609511602() 1262     {
1263         bool running = animation_running();
1264 
1265         if (running)
1266         {
1267             output->render->schedule_redraw();
1268         }
1269 
1270         if (active || running)
1271         {
1272             return;
1273         }
1274 
1275         finalize();
1276     };
1277 
can_handle_drag()1278     bool can_handle_drag()
1279     {
1280         return output->is_plugin_active(this->grab_interface->name);
1281     }
1282 
1283     wf::signal_connection_t on_drag_output_focus = [=] (auto data)
__anondca609511702(auto data) 1284     {
1285         auto ev = static_cast<wf::move_drag::drag_focus_output_signal*>(data);
1286         if ((ev->focus_output == output) && can_handle_drag())
1287         {
1288             drag_helper->set_scale(1.0);
1289         }
1290     };
1291 
1292     wf::signal_connection_t on_drag_done = [=] (auto data)
__anondca609511802(auto data) 1293     {
1294         auto ev = static_cast<wf::move_drag::drag_done_signal*>(data);
1295         if ((ev->focused_output == output) && can_handle_drag())
1296         {
1297             if (ev->main_view->get_output() == ev->focused_output)
1298             {
1299                 // View left on the same output, don't do anything
1300                 for (auto& v : ev->all_views)
1301                 {
1302                     set_tiled_wobbly(v.view, true);
1303                 }
1304 
1305                 layout_slots(get_views());
1306                 return;
1307             }
1308 
1309             wf::move_drag::adjust_view_on_output(ev);
1310         }
1311     };
1312 
1313     /* Activate and start scale animation */
activate()1314     bool activate()
1315     {
1316         if (active)
1317         {
1318             return false;
1319         }
1320 
1321         if (!output->activate_plugin(grab_interface))
1322         {
1323             return false;
1324         }
1325 
1326         auto views = get_views();
1327         if (views.empty())
1328         {
1329             output->deactivate_plugin(grab_interface);
1330 
1331             return false;
1332         }
1333 
1334         initial_workspace  = output->workspace->get_current_workspace();
1335         initial_focus_view = output->get_active_view();
1336         current_focus_view = initial_focus_view ?: views.front();
1337         // Make sure no leftover events from the activation binding
1338         // trigger an action in scale
1339         last_selected_view = nullptr;
1340 
1341         if (!interact)
1342         {
1343             if (!grab_interface->grab())
1344             {
1345                 deactivate();
1346 
1347                 return false;
1348             }
1349         }
1350 
1351         if (current_focus_view != output->get_active_view())
1352         {
1353             output->focus_view(current_focus_view, true);
1354         }
1355 
1356         active = true;
1357 
1358         layout_slots(get_views());
1359 
1360         connect_button_signal();
1361         output->connect_signal("view-layer-attached", &view_attached);
1362         output->connect_signal("view-mapped", &view_attached);
1363         output->connect_signal("workspace-changed", &workspace_changed);
1364         output->connect_signal("view-layer-detached", &view_detached);
1365         output->connect_signal("view-minimized", &view_minimized);
1366         output->connect_signal("view-unmapped", &view_unmapped);
1367         output->connect_signal("view-focused", &view_focused);
1368 
1369         fade_out_all_except(current_focus_view);
1370         fade_in(current_focus_view);
1371 
1372         return true;
1373     }
1374 
1375     /* Deactivate and start unscale animation */
deactivate()1376     void deactivate()
1377     {
1378         active = false;
1379 
1380         set_hook();
1381         view_focused.disconnect();
1382         view_unmapped.disconnect();
1383         view_attached.disconnect();
1384         view_minimized.disconnect();
1385         workspace_changed.disconnect();
1386         view_geometry_changed.disconnect();
1387 
1388         grab_interface->ungrab();
1389         output->deactivate_plugin(grab_interface);
1390 
1391         for (auto& e : scale_data)
1392         {
1393             fade_in(e.first);
1394             setup_view_transform(e.second, 1, 1, 0, 0, 1);
1395             if (e.second.visibility == view_scale_data::view_visibility_t::HIDDEN)
1396             {
1397                 e.first->set_visible(true);
1398             }
1399 
1400             e.second.visibility = view_scale_data::view_visibility_t::VISIBLE;
1401         }
1402 
1403         refocus();
1404         output->emit_signal("scale-end", nullptr);
1405     }
1406 
1407     /* Completely end scale, including animation */
finalize()1408     void finalize()
1409     {
1410         if (active)
1411         {
1412             /* only emit the signal if deactivate() was not called before */
1413             output->emit_signal("scale-end", nullptr);
1414 
1415             if (drag_helper->view)
1416             {
1417                 drag_helper->handle_input_released();
1418             }
1419         }
1420 
1421         active = false;
1422 
1423         unset_hook();
1424         remove_transformers();
1425         scale_data.clear();
1426         grab_interface->ungrab();
1427         disconnect_button_signal();
1428         view_focused.disconnect();
1429         view_unmapped.disconnect();
1430         view_attached.disconnect();
1431         view_detached.disconnect();
1432         view_minimized.disconnect();
1433         workspace_changed.disconnect();
1434         view_geometry_changed.disconnect();
1435         output->deactivate_plugin(grab_interface);
1436     }
1437 
1438     /* Utility hook setter */
set_hook()1439     void set_hook()
1440     {
1441         if (hook_set)
1442         {
1443             return;
1444         }
1445 
1446         output->render->add_effect(&post_hook, wf::OUTPUT_EFFECT_POST);
1447         output->render->add_effect(&pre_hook, wf::OUTPUT_EFFECT_PRE);
1448         output->render->schedule_redraw();
1449         hook_set = true;
1450     }
1451 
1452     /* Utility hook unsetter */
unset_hook()1453     void unset_hook()
1454     {
1455         if (!hook_set)
1456         {
1457             return;
1458         }
1459 
1460         output->render->rem_effect(&post_hook);
1461         output->render->rem_effect(&pre_hook);
1462         hook_set = false;
1463     }
1464 
fini()1465     void fini() override
1466     {
1467         finalize();
1468         output->rem_binding(&toggle_cb);
1469         output->rem_binding(&toggle_all_cb);
1470         show_title.fini();
1471     }
1472 };
1473 
1474 DECLARE_WAYFIRE_PLUGIN(wayfire_scale);
1475