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