--- a/plugins/cube/cube.cpp +++ b/plugins/cube/cube.cpp @@ -330,12 +330,21 @@ cube_render_node_t(wayfire_cube *cube) : node_t(false) { this->cube = cube; - auto w = cube->output->wset()->get_workspace_grid_size().width; - auto y = cube->output->wset()->get_current_workspace().y; - for (int i = 0; i < w; i++) - { + auto grid = cube->output->wset()->get_workspace_grid_size(); + auto current = cube->output->wset()->get_current_workspace(); + int count = cube->vertical_transition ? grid.height : grid.width; + for (int i = 0; i < count; i++) + { + wf::point_t workspace = cube->vertical_transition ? + wf::point_t{current.x, i} : wf::point_t{i, current.y}; + if (cube->vertical_transition && cube->workspace_animation_anchor && + (i == cube->workspace_animation_anchor->y)) + { + workspace.x = cube->workspace_animation_anchor->x; + } + auto node = std::make_shared( - cube->output, wf::point_t{i, y}); + cube->output, workspace); node->background = wf::color_t{0.0, 0.0, 0.0, 0.0}; node->include_desktop_environment = false; workspaces.push_back(node); @@ -392,6 +401,8 @@ * for the given FOV */ float identity_z_offset = 0.0f; int geometry_faces = 0; + int geometry_rows = 0; + bool vertical_transition = false; OpenGL::program_t program; @@ -399,6 +410,9 @@ std::make_shared>( "cube-runtime-animation", wf::animation_description_t{}); wf_cube_animation_attribs animation{runtime_duration}; + // Keep added state local: resident Cube libraries export the original + // animation attributes, whose layout must remain compatible on reload. + timed_transition_t row_rotation{animation.cube_animation}; void start_animation(int duration_limit_ms = -1) { @@ -455,15 +469,27 @@ return output->wset()->get_workspace_grid_size().width; } + static float row_side_angle(int rows) + { + // Rows span at most half a turn, like latitudes from pole to pole. + return M_PI / std::max(2, rows - 1); + } + + static float row_z_offset(int rows) + { + return 0.5 / std::tan(row_side_angle(rows) / 2); + } + bool refresh_cube_geometry() { - int faces = get_num_faces(); - if (faces == geometry_faces) + auto grid = output->wset()->get_workspace_grid_size(); + int faces = grid.width; + if ((faces == geometry_faces) && (grid.height == geometry_rows)) { return false; } - float old_offset = identity_z_offset; + float old_offset = vertical_transition ? row_z_offset(geometry_rows) : identity_z_offset; animation.side_angle = 2 * M_PI / float(faces); identity_z_offset = faces == 1 ? 0.0f : 0.5 / std::tan(animation.side_angle / 2); @@ -471,13 +497,27 @@ { // Preserve the camera's distance from the cube when a new // workspace count changes its radius. - float offset = animation.cube_animation.offset_z + - identity_z_offset - old_offset; + float new_offset = vertical_transition ? row_z_offset(grid.height) : identity_z_offset; + float offset = animation.cube_animation.offset_z + new_offset - old_offset; animation.cube_animation.offset_z.set(offset, offset); } geometry_faces = faces; + geometry_rows = grid.height; return true; + } + + void end_vertical_transition() + { + if (vertical_transition) + { + vertical_transition = false; + row_rotation.set(0, 0); + animation.cube_animation.offset_z.set(identity_z_offset + Z_OFFSET_NEAR, + identity_z_offset + Z_OFFSET_NEAR); + rebuild_cube_render_node(); + output->render->damage_whole(); + } } wf::plugin_activation_data_t grab_interface{ @@ -609,6 +649,7 @@ return; } + end_vertical_transition(); float offset_z = identity_z_offset + Z_OFFSET_NEAR; animation.cube_animation.rotation.set(angle, angle); @@ -636,6 +677,13 @@ { fixed_view = nullptr; } + + const float previous_pitch = vertical_transition ? + float(row_rotation) : 0.0f; + vertical_transition = delta.y != 0; + const int rows = output->wset()->get_workspace_grid_size().height; + row_rotation.set( + vertical_transition ? previous_pitch + delta.y * row_side_angle(rows) : 0.0f, 0.0f); workspace_animation_anchor = anchor_workspace; fixed_workspace_view = fixed_view; @@ -644,13 +692,12 @@ animation.in_exit = false; animation.cube_animation.zoom.set(1.0, 1.0); - animation.cube_animation.offset_z.set( - identity_z_offset + Z_OFFSET_NEAR, - identity_z_offset + Z_OFFSET_NEAR); + const float radius = vertical_transition ? row_z_offset(rows) : identity_z_offset; + animation.cube_animation.offset_z.set(radius + Z_OFFSET_NEAR, radius + Z_OFFSET_NEAR); animation.cube_animation.offset_y.set(0, 0); animation.cube_animation.ease_deformation.set(0, 0); - if (delta.x != 0) + if (!vertical_transition && (delta.x != 0)) { animation.cube_animation.rotation.set( delta.x * animation.side_angle, @@ -978,6 +1025,8 @@ fixed_workspace_view = nullptr; workspace_animation_anchor.reset(); + vertical_transition = false; + row_rotation.set(0, 0); clear_fixed_workspace_overlay(); clear_unused_fixed_top_node(); release_cube_input(); @@ -1010,6 +1059,7 @@ return false; } + end_vertical_transition(); /* After the rotation is done, we want to exit cube and focus the target * workspace */ animation.in_exit = false; @@ -1063,6 +1113,7 @@ return false; } + end_vertical_transition(); /* Rotations, offset_y and zoom stay as they are now, as they have been * grabbed. * offset_z changes to the default one. @@ -1208,11 +1259,20 @@ static const GLuint indexData[] = {0, 1, 2, 0, 2, 3}; auto cws = output->wset()->get_current_workspace(); - for (int i = 0; i < get_num_faces(); i++) - { - int index = (cws.x + i) % get_num_faces(); + const int count = vertical_transition ? (int)buffers.size() : get_num_faces(); + for (int i = 0; i < count; i++) + { + int index = vertical_transition ? i : (cws.x + i) % get_num_faces(); GL_CALL(glBindTexture(GL_TEXTURE_2D, wf::gles_texture_t::from_aux(buffers[index]).tex_id)); auto model = calculate_model_matrix(i); + if (vertical_transition) + { + const int rows = output->wset()->get_workspace_grid_size().height; + const float angle = -((i - cws.y) * row_side_angle(rows) + + row_rotation); + model = glm::rotate(glm::mat4(1.0), angle, glm::vec3(1, 0, 0)) * + glm::translate(glm::mat4(1.0), glm::vec3(0, 0, row_z_offset(rows))); + } program.uniformMatrix4f("model", model); if (tessellation_support) @@ -1327,6 +1387,7 @@ fixed_workspace_view = nullptr; workspace_animation_anchor.reset(); clear_fixed_workspace_overlay(); + end_vertical_transition(); rebuild_cube_render_node(); } } --- a/plugins/vswitch/vswitch.cpp +++ b/plugins/vswitch/vswitch.cpp @@ -353,6 +353,15 @@ {} protected: + bool handle_dir(wf::point_t direction, wayfire_toplevel_view view, bool window_only, + binding_callback_t callback) override + { + auto current = output->wset()->get_current_workspace(); + auto grid = output->wset()->get_workspace_grid_size(); + direction.y = wf::clamp(current.y + direction.y, 0, grid.height - 1) - current.y; + return control_bindings_t::handle_dir(direction, view, window_only, callback); + } + wayfire_toplevel_view get_target_view() override { return target(); @@ -632,7 +641,12 @@ auto grid = output->wset()->get_workspace_grid_size(); auto target_ws = cws + delta; target_ws.x = (target_ws.x % grid.width + grid.width) % grid.width; - target_ws.y = (target_ws.y % grid.height + grid.height) % grid.height; + // Columns wrap around the globe. Rows stop at its poles. + target_ws.y = wf::clamp(target_ws.y, 0, grid.height - 1); + if (target_ws == cws) + { + return true; + } auto animation_delta = target_ws - cws; if (animation_delta.x > grid.width / 2) { @@ -640,14 +654,6 @@ } else if (animation_delta.x < -grid.width / 2) { animation_delta.x += grid.width; - } - - if (animation_delta.y > grid.height / 2) - { - animation_delta.y -= grid.height; - } else if (animation_delta.y < -grid.height / 2) - { - animation_delta.y += grid.height; } wayfire_toplevel_view fixed_view = nullptr;