--- a/plugins/cube/cube.cpp +++ b/plugins/cube/cube.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -335,10 +336,11 @@ // A vertical move translates complete Cube rows, including their // side and rear faces. Keep textures for every row so fast steps // and reversal preserve the visible position. - int count = cube->vertical_transition ? grid.height * grid.width : grid.width; + bool all_rows = cube->vertical_transition || cube->mouse_overview_active; + int count = all_rows ? grid.height * grid.width : grid.width; for (int i = 0; i < count; i++) { - wf::point_t workspace = cube->vertical_transition ? + wf::point_t workspace = all_rows ? wf::point_t{i % grid.width, i / grid.width} : wf::point_t{i, current.y}; auto node = std::make_shared( @@ -389,6 +391,7 @@ wf::option_wrapper_t zoom_opt{"cube/zoom"}; wf::option_wrapper_t face_dim{"cube/dim"}; wf::option_wrapper_t resting_yaw{"cube/resting_yaw"}; + wf::option_wrapper_t mouse_overview; wf::option_wrapper_t rotate_layer_shell{"cube/rotate_layer_shell"}; wf::option_wrapper_t window_move_alpha{"vswitch/window_move_alpha"}; bool input_is_grabbed = false; @@ -401,6 +404,7 @@ int geometry_faces = 0; int geometry_rows = 0; bool vertical_transition = false; + bool mouse_overview_active = false; OpenGL::program_t program; @@ -411,6 +415,8 @@ // Keep added state local: resident Cube libraries export the original // animation attributes, whose layout must remain compatible on reload. timed_transition_t row_translation{animation.cube_animation}; + timed_transition_t overview_row{animation.cube_animation}; + timed_transition_t overview_mix{animation.cube_animation}; void start_animation(int duration_limit_ms = -1) { @@ -506,6 +512,23 @@ } } + int selected_overview_row() + { + return std::clamp(int(std::floor(float(overview_row) + 0.5f)), 0, + output->wset()->get_workspace_grid_size().height - 1); + } + + void end_mouse_overview() + { + if (mouse_overview_active) + { + mouse_overview_active = false; + overview_mix.set(0, 0); + rebuild_cube_render_node(); + output->render->damage_whole(); + } + } + wf::plugin_activation_data_t grab_interface{ .name = "cube", .capabilities = 0, @@ -525,6 +548,16 @@ void init() override { + // Promote ini values read with older metadata when hot-loading Cube. + auto section = wf::get_core().config->get_section("cube"); + auto existing = section->get_option_or("mouse_overview"); + if (!std::dynamic_pointer_cast>(existing)) + { + auto option = std::make_shared>("mouse_overview", false); + if (existing) option->set_value_str(existing->get_value_str()); + section->register_new_option(option); + } + mouse_overview.load_option("cube/mouse_overview"); input_grab = std::make_unique("cube", output, nullptr, this, nullptr); input_grab->set_wants_raw_input(true); @@ -636,6 +669,7 @@ } end_vertical_transition(); + end_mouse_overview(); float offset_z = identity_z_offset + Z_OFFSET_NEAR; animation.cube_animation.rotation.set(angle, angle); @@ -659,6 +693,7 @@ return; } + end_mouse_overview(); if (fixed_view && (fixed_view->get_output() != output)) { fixed_view = nullptr; @@ -1007,13 +1042,16 @@ int dvx = calculate_viewport_dx_from_rotation(); auto cws = output->wset()->get_current_workspace(); int nvx = (cws.x + (dvx % size) + size) % size; - output->wset()->set_workspace({nvx, cws.y}); + int nvy = mouse_overview_active ? selected_overview_row() : cws.y; + output->wset()->set_workspace({nvx, nvy}); } fixed_workspace_view = nullptr; workspace_animation_anchor.reset(); vertical_transition = false; row_translation.set(0, 0); + mouse_overview_active = false; + overview_mix.set(0, 0); clear_fixed_workspace_overlay(); clear_unused_fixed_top_node(); release_cube_input(); @@ -1047,6 +1085,7 @@ } end_vertical_transition(); + end_mouse_overview(); /* After the rotation is done, we want to exit cube and focus the target * workspace */ animation.in_exit = false; @@ -1101,6 +1140,19 @@ } end_vertical_transition(); + end_mouse_overview(); + if (mouse_overview && output->wset()->get_workspace_grid_size().height > 1) + { + workspace_animation_anchor.reset(); + fixed_workspace_view = nullptr; + clear_fixed_workspace_overlay(); + mouse_overview_active = true; + auto row = output->wset()->get_current_workspace().y; + overview_row.set(row, row); + overview_mix.set(0, 1); + animation.cube_animation.offset_y.set(0, 0); + rebuild_cube_render_node(); + } /* Rotations, offset_y and zoom stay as they are now, as they have been * grabbed. * offset_z changes to the default one. @@ -1147,12 +1199,13 @@ int dvx = calculate_viewport_dx_from_rotation(); auto cws = output->wset()->get_current_workspace(); int nvx = (cws.x + (dvx % size) + size) % size; - output->wset()->set_workspace({nvx, cws.y}); + int nvy = mouse_overview_active ? selected_overview_row() : cws.y; + output->wset()->set_workspace({nvx, nvy}); wf::get_core().seat->focus_output(output); auto views = output->wset()->get_views( wf::WSET_MAPPED_ONLY | wf::WSET_EXCLUDE_MINIMIZED | - wf::WSET_SORT_STACKING, wf::point_t{nvx, cws.y}); + wf::WSET_SORT_STACKING, wf::point_t{nvx, nvy}); for (auto it = views.rbegin(); it != views.rend(); ++it) { if ((*it)->is_focusable() && (*it)->get_keyboard_focus_surface()) @@ -1160,6 +1213,22 @@ wf::get_core().default_wm->focus_raise_view(*it, false); break; } + } + + if (mouse_overview_active) + { + // Keep all rows while zooming into the selected face. The anchor + // prevents cancellation from applying the horizontal step twice. + workspace_animation_anchor = wf::point_t{nvx, nvy}; + animation.cube_animation.rotation.set(current_rotation + dvx * animation.side_angle, 0); + overview_row.restart_with_end(nvy); + overview_mix.restart_with_end(0); + reset_attribs(); + animation.in_exit = true; + start_animation(); + update_view_matrix(); + output->render->schedule_redraw(); + return; } if (render_node) @@ -1218,6 +1287,18 @@ // the same speed as the front, including rotated/scaled outputs. auto row_translation = glm::translate(glm::mat4(1.0), glm::vec3(0, 2 * row_offset, 0)); + if (mouse_overview_active) + { + // Fit every row while keeping the selected row centered. Scale + // the complete projected stack uniformly, preserving face shape. + float center = overview_row; + float last = output->wset()->get_workspace_grid_size().height - 1; + float extent = 2 * std::max(center, last - center) + 1; + float fit = 0.8f / std::max(1.0f, extent); + float scale = 1 + float(overview_mix) * (fit - 1); + auto frame = glm::scale(glm::mat4(1.0), glm::vec3(scale, scale, 1)); + return output_transform(dest) * frame * row_translation * animation.projection * animation.view * scale_matrix; + } return output_transform(dest) * row_translation * animation.projection * animation.view * scale_matrix; } @@ -1253,13 +1334,13 @@ auto cws = output->wset()->get_current_workspace(); const int faces = get_num_faces(); - const int rows = vertical_transition ? (int)buffers.size() / faces : 1; + const int rows = (vertical_transition || mouse_overview_active) ? (int)buffers.size() / faces : 1; for (int row = 0; row < rows; row++) { // Move every face of a row together without changing its yaw, // orientation, or perspective, including through the final frame. - const float offset = vertical_transition ? - row - cws.y + float(row_translation) : 0.0f; + const float offset = mouse_overview_active ? row - float(overview_row) : + vertical_transition ? row - cws.y + float(row_translation) : 0.0f; program.uniformMatrix4f("VP", calculate_vp_matrix(target, offset)); for (int i = 0; i < faces; i++) { @@ -1351,6 +1432,12 @@ { if (refresh_cube_geometry()) { + if (mouse_overview_active) + { + float last = output->wset()->get_workspace_grid_size().height - 1; + overview_row.set(std::clamp(float(overview_row), 0.0f, last), + std::clamp(float(overview_row.end), 0.0f, last)); + } rebuild_cube_render_node(); output->render->damage_whole(); } @@ -1407,15 +1494,23 @@ animation.cube_animation.zoom.restart_with_end( animation.cube_animation.zoom.end); - double current_off_y = animation.cube_animation.offset_y; - double off_y = current_off_y + ydiff * YVelocity; - - off_y = wf::clamp(off_y, -1.5, 1.5); - animation.cube_animation.offset_y.set(current_off_y, off_y); + if (mouse_overview_active) + { + float last = output->wset()->get_workspace_grid_size().height - 1; + overview_row.restart_with_end(std::clamp(float(overview_row.end + ydiff * YVelocity), 0.0f, last)); + overview_mix.restart_with_end(1); + animation.cube_animation.offset_y.set(0, 0); + } else + { + double current_off_y = animation.cube_animation.offset_y; + double off_y = wf::clamp(current_off_y + ydiff * YVelocity, -1.5, 1.5); + animation.cube_animation.offset_y.set(current_off_y, off_y); + } animation.cube_animation.offset_z.restart_with_end( animation.cube_animation.offset_z.end); - float current_rotation = animation.cube_animation.rotation; + float current_rotation = mouse_overview_active ? animation.cube_animation.rotation.end : + float(animation.cube_animation.rotation); animation.cube_animation.rotation.restart_with_end( current_rotation + xdiff * XVelocity); --- a/metadata/cube.xml +++ b/metadata/cube.xml @@ -2,7 +2,7 @@ <_short>Cube - <_long>A plugin to show the current workspace row as a cube. + <_long>Show workspaces as a cube, with an optional overview of all workspace rows during mouse rotation. Desktop @@ -63,6 +63,14 @@ 3000 1 30000 + + + + <_short>Mouse overview +